mirror of
https://github.com/overte-org/overte.git
synced 2025-04-20 11:45:36 +02:00
Merge branch 'master' into 20852
This commit is contained in:
commit
df870d6e82
74 changed files with 3149 additions and 857 deletions
|
@ -12,19 +12,21 @@
|
|||
|
||||
#include "AssetServer.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QCryptographicHash>
|
||||
#include <QDateTime>
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include <QString>
|
||||
#include <QtCore/QCoreApplication>
|
||||
#include <QtCore/QCryptographicHash>
|
||||
#include <QtCore/QDateTime>
|
||||
#include <QtCore/QDir>
|
||||
#include <QtCore/QFile>
|
||||
#include <QtCore/QFileInfo>
|
||||
#include <QtCore/QJsonDocument>
|
||||
#include <QtCore/QString>
|
||||
|
||||
#include <ServerPathUtils.h>
|
||||
|
||||
#include "NetworkLogging.h"
|
||||
#include "NodeType.h"
|
||||
#include "SendAssetTask.h"
|
||||
#include "UploadAssetTask.h"
|
||||
#include <ServerPathUtils.h>
|
||||
|
||||
const QString ASSET_SERVER_LOGGING_TARGET_NAME = "asset-server";
|
||||
|
||||
|
@ -42,6 +44,7 @@ AssetServer::AssetServer(ReceivedMessage& message) :
|
|||
packetReceiver.registerListener(PacketType::AssetGet, this, "handleAssetGet");
|
||||
packetReceiver.registerListener(PacketType::AssetGetInfo, this, "handleAssetGetInfo");
|
||||
packetReceiver.registerListener(PacketType::AssetUpload, this, "handleAssetUpload");
|
||||
packetReceiver.registerListener(PacketType::AssetMappingOperation, this, "handleAssetMappingOperation");
|
||||
}
|
||||
|
||||
void AssetServer::run() {
|
||||
|
@ -56,6 +59,8 @@ void AssetServer::run() {
|
|||
ThreadedAssignment::commonInit(ASSET_SERVER_LOGGING_TARGET_NAME, NodeType::AssetServer);
|
||||
}
|
||||
|
||||
static const QString ASSET_FILES_SUBDIR = "files";
|
||||
|
||||
void AssetServer::completeSetup() {
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
||||
|
@ -96,85 +101,208 @@ void AssetServer::completeSetup() {
|
|||
|
||||
qDebug() << "Creating resources directory";
|
||||
_resourcesDirectory.mkpath(".");
|
||||
_filesDirectory = _resourcesDirectory;
|
||||
|
||||
bool noExistingAssets = !_resourcesDirectory.exists() || _resourcesDirectory.entryList(QDir::Files).size() == 0;
|
||||
|
||||
if (noExistingAssets) {
|
||||
qDebug() << "Asset resources directory empty, searching for existing asset resources to migrate";
|
||||
QString oldDataDirectory = QCoreApplication::applicationDirPath();
|
||||
|
||||
const QString OLD_RESOURCES_PATH = "assets";
|
||||
|
||||
auto oldResourcesDirectory = QDir(oldDataDirectory).filePath("resources/" + OLD_RESOURCES_PATH);
|
||||
|
||||
|
||||
if (QDir(oldResourcesDirectory).exists()) {
|
||||
qDebug() << "Existing assets found in " << oldResourcesDirectory << ", copying to " << _resourcesDirectory;
|
||||
|
||||
|
||||
QDir resourcesParentDirectory = _resourcesDirectory.filePath("..");
|
||||
if (!resourcesParentDirectory.exists()) {
|
||||
qDebug() << "Creating data directory " << resourcesParentDirectory.absolutePath();
|
||||
resourcesParentDirectory.mkpath(".");
|
||||
}
|
||||
|
||||
auto files = QDir(oldResourcesDirectory).entryList(QDir::Files);
|
||||
|
||||
for (auto& file : files) {
|
||||
auto from = oldResourcesDirectory + QDir::separator() + file;
|
||||
auto to = _resourcesDirectory.absoluteFilePath(file);
|
||||
qDebug() << "\tCopying from " << from << " to " << to;
|
||||
QFile::copy(from, to);
|
||||
}
|
||||
|
||||
}
|
||||
if (!_resourcesDirectory.mkpath(ASSET_FILES_SUBDIR) || !_filesDirectory.cd(ASSET_FILES_SUBDIR)) {
|
||||
qCritical() << "Unable to create file directory for asset-server files. Stopping assignment.";
|
||||
setFinished(true);
|
||||
return;
|
||||
}
|
||||
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(SHA256_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;
|
||||
}
|
||||
// load whatever mappings we currently have from the local file
|
||||
loadMappingsFromFile();
|
||||
|
||||
// Read file
|
||||
QFile file { fileInfo.absoluteFilePath() };
|
||||
file.open(QFile::ReadOnly);
|
||||
QByteArray data = file.readAll();
|
||||
qInfo() << "Serving files from: " << _filesDirectory.path();
|
||||
|
||||
auto hash = hashData(data);
|
||||
auto hexHash = hash.toHex();
|
||||
// Check the asset directory to output some information about what we have
|
||||
auto files = _filesDirectory.entryList(QDir::Files);
|
||||
|
||||
qDebug() << "\tMoving " << filename << " to " << hexHash;
|
||||
|
||||
file.rename(_resourcesDirectory.absoluteFilePath(hexHash) + "." + fileInfo.suffix());
|
||||
}
|
||||
}
|
||||
QRegExp hashFileRegex { ASSET_HASH_REGEX_STRING };
|
||||
auto hashedFiles = files.filter(hashFileRegex);
|
||||
|
||||
qInfo() << "There are" << hashedFiles.size() << "asset files in the asset directory.";
|
||||
|
||||
performMappingMigration();
|
||||
|
||||
nodeList->addNodeTypeToInterestSet(NodeType::Agent);
|
||||
}
|
||||
|
||||
void AssetServer::performMappingMigration() {
|
||||
QRegExp hashFileRegex { "^[a-f0-9]{" + QString::number(SHA256_HASH_HEX_LENGTH) + "}(\\.[\\w]+)+$" };
|
||||
|
||||
auto files = _resourcesDirectory.entryInfoList(QDir::Files);
|
||||
|
||||
for (const auto& fileInfo : files) {
|
||||
if (hashFileRegex.exactMatch(fileInfo.fileName())) {
|
||||
// we have a pre-mapping file that we should migrate to the new mapping system
|
||||
qDebug() << "Migrating pre-mapping file" << fileInfo.fileName();
|
||||
|
||||
// rename the file to the same name with no extension
|
||||
QFile oldFile { fileInfo.absoluteFilePath() };
|
||||
|
||||
auto oldAbsolutePath = fileInfo.absoluteFilePath();
|
||||
auto oldFilename = fileInfo.fileName();
|
||||
auto hash = oldFilename.left(SHA256_HASH_HEX_LENGTH);
|
||||
auto fullExtension = oldFilename.mid(oldFilename.indexOf('.'));
|
||||
|
||||
qDebug() << "\tMoving" << oldAbsolutePath << "to" << oldAbsolutePath.replace(fullExtension, "");
|
||||
|
||||
bool renamed = oldFile.copy(_filesDirectory.filePath(hash));
|
||||
if (!renamed) {
|
||||
qWarning() << "\tCould not migrate pre-mapping file" << fileInfo.fileName();
|
||||
} else {
|
||||
qDebug() << "\tRenamed pre-mapping file" << fileInfo.fileName();
|
||||
|
||||
// add a new mapping with the old extension and a truncated version of the hash
|
||||
const int TRUNCATED_HASH_NUM_CHAR = 16;
|
||||
auto fakeFileName = "/" + hash.left(TRUNCATED_HASH_NUM_CHAR) + fullExtension;
|
||||
|
||||
qDebug() << "\tAdding a migration mapping from" << fakeFileName << "to" << hash;
|
||||
|
||||
auto it = _fileMappings.find(fakeFileName);
|
||||
if (it == _fileMappings.end()) {
|
||||
_fileMappings[fakeFileName] = hash;
|
||||
|
||||
if (writeMappingsToFile()) {
|
||||
// mapping added and persisted, we can remove the migrated file
|
||||
oldFile.remove();
|
||||
qDebug() << "\tMigration completed for" << oldFilename;
|
||||
}
|
||||
} else {
|
||||
qDebug() << "\tCould not add migration mapping for" << hash << "since a mapping for" << fakeFileName
|
||||
<< "already exists.";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AssetServer::handleAssetMappingOperation(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
|
||||
MessageID messageID;
|
||||
message->readPrimitive(&messageID);
|
||||
|
||||
AssetMappingOperationType operationType;
|
||||
message->readPrimitive(&operationType);
|
||||
|
||||
auto replyPacket = NLPacketList::create(PacketType::AssetMappingOperationReply, QByteArray(), true, true);
|
||||
replyPacket->writePrimitive(messageID);
|
||||
|
||||
switch (operationType) {
|
||||
case AssetMappingOperationType::Get: {
|
||||
handleGetMappingOperation(*message, senderNode, *replyPacket);
|
||||
break;
|
||||
}
|
||||
case AssetMappingOperationType::GetAll: {
|
||||
handleGetAllMappingOperation(*message, senderNode, *replyPacket);
|
||||
break;
|
||||
}
|
||||
case AssetMappingOperationType::Set: {
|
||||
handleSetMappingOperation(*message, senderNode, *replyPacket);
|
||||
break;
|
||||
}
|
||||
case AssetMappingOperationType::Delete: {
|
||||
handleDeleteMappingsOperation(*message, senderNode, *replyPacket);
|
||||
break;
|
||||
}
|
||||
case AssetMappingOperationType::Rename: {
|
||||
handleRenameMappingOperation(*message, senderNode, *replyPacket);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
nodeList->sendPacketList(std::move(replyPacket), *senderNode);
|
||||
}
|
||||
|
||||
void AssetServer::handleGetMappingOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket) {
|
||||
QString assetPath = message.readString();
|
||||
|
||||
auto it = _fileMappings.find(assetPath);
|
||||
if (it != _fileMappings.end()) {
|
||||
auto assetHash = it->toString();
|
||||
replyPacket.writePrimitive(AssetServerError::NoError);
|
||||
replyPacket.write(QByteArray::fromHex(assetHash.toUtf8()));
|
||||
} else {
|
||||
replyPacket.writePrimitive(AssetServerError::AssetNotFound);
|
||||
}
|
||||
}
|
||||
|
||||
void AssetServer::handleGetAllMappingOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket) {
|
||||
replyPacket.writePrimitive(AssetServerError::NoError);
|
||||
|
||||
auto count = _fileMappings.size();
|
||||
|
||||
replyPacket.writePrimitive(count);
|
||||
|
||||
for (auto it = _fileMappings.cbegin(); it != _fileMappings.cend(); ++ it) {
|
||||
replyPacket.writeString(it.key());
|
||||
replyPacket.write(QByteArray::fromHex(it.value().toString().toUtf8()));
|
||||
}
|
||||
}
|
||||
|
||||
void AssetServer::handleSetMappingOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket) {
|
||||
if (senderNode->getCanRez()) {
|
||||
QString assetPath = message.readString();
|
||||
|
||||
auto assetHash = message.read(SHA256_HASH_LENGTH).toHex();
|
||||
|
||||
if (setMapping(assetPath, assetHash)) {
|
||||
replyPacket.writePrimitive(AssetServerError::NoError);
|
||||
} else {
|
||||
replyPacket.writePrimitive(AssetServerError::MappingOperationFailed);
|
||||
}
|
||||
} else {
|
||||
replyPacket.writePrimitive(AssetServerError::PermissionDenied);
|
||||
}
|
||||
}
|
||||
|
||||
void AssetServer::handleDeleteMappingsOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket) {
|
||||
if (senderNode->getCanRez()) {
|
||||
int numberOfDeletedMappings { 0 };
|
||||
message.readPrimitive(&numberOfDeletedMappings);
|
||||
|
||||
QStringList mappingsToDelete;
|
||||
|
||||
for (int i = 0; i < numberOfDeletedMappings; ++i) {
|
||||
mappingsToDelete << message.readString();
|
||||
}
|
||||
|
||||
if (deleteMappings(mappingsToDelete)) {
|
||||
replyPacket.writePrimitive(AssetServerError::NoError);
|
||||
} else {
|
||||
replyPacket.writePrimitive(AssetServerError::MappingOperationFailed);
|
||||
}
|
||||
} else {
|
||||
replyPacket.writePrimitive(AssetServerError::PermissionDenied);
|
||||
}
|
||||
}
|
||||
|
||||
void AssetServer::handleRenameMappingOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket) {
|
||||
if (senderNode->getCanRez()) {
|
||||
QString oldPath = message.readString();
|
||||
QString newPath = message.readString();
|
||||
|
||||
if (renameMapping(oldPath, newPath)) {
|
||||
replyPacket.writePrimitive(AssetServerError::NoError);
|
||||
} else {
|
||||
replyPacket.writePrimitive(AssetServerError::MappingOperationFailed);
|
||||
}
|
||||
} else {
|
||||
replyPacket.writePrimitive(AssetServerError::PermissionDenied);
|
||||
}
|
||||
}
|
||||
|
||||
void AssetServer::handleAssetGetInfo(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
|
||||
QByteArray assetHash;
|
||||
MessageID messageID;
|
||||
uint8_t extensionLength;
|
||||
|
||||
if (message->getSize() < qint64(SHA256_HASH_LENGTH + sizeof(messageID) + sizeof(extensionLength))) {
|
||||
if (message->getSize() < qint64(SHA256_HASH_LENGTH + sizeof(messageID))) {
|
||||
qDebug() << "ERROR bad file request";
|
||||
return;
|
||||
}
|
||||
|
||||
message->readPrimitive(&messageID);
|
||||
assetHash = message->readWithoutCopy(SHA256_HASH_LENGTH);
|
||||
message->readPrimitive(&extensionLength);
|
||||
QByteArray extension = message->read(extensionLength);
|
||||
|
||||
auto replyPacket = NLPacket::create(PacketType::AssetGetInfoReply);
|
||||
|
||||
|
@ -183,8 +311,8 @@ void AssetServer::handleAssetGetInfo(QSharedPointer<ReceivedMessage> message, Sh
|
|||
replyPacket->writePrimitive(messageID);
|
||||
replyPacket->write(assetHash);
|
||||
|
||||
QString fileName = QString(hexHash) + "." + extension;
|
||||
QFileInfo fileInfo { _resourcesDirectory.filePath(fileName) };
|
||||
QString fileName = QString(hexHash);
|
||||
QFileInfo fileInfo { _filesDirectory.filePath(fileName) };
|
||||
|
||||
if (fileInfo.exists() && fileInfo.isReadable()) {
|
||||
qDebug() << "Opening file: " << fileInfo.filePath();
|
||||
|
@ -201,39 +329,39 @@ void AssetServer::handleAssetGetInfo(QSharedPointer<ReceivedMessage> message, Sh
|
|||
|
||||
void AssetServer::handleAssetGet(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
|
||||
|
||||
auto minSize = qint64(sizeof(MessageID) + SHA256_HASH_LENGTH + sizeof(uint8_t) + sizeof(DataOffset) + sizeof(DataOffset));
|
||||
|
||||
auto minSize = qint64(sizeof(MessageID) + SHA256_HASH_LENGTH + sizeof(DataOffset) + sizeof(DataOffset));
|
||||
|
||||
if (message->getSize() < minSize) {
|
||||
qDebug() << "ERROR bad file request";
|
||||
return;
|
||||
}
|
||||
|
||||
// Queue task
|
||||
auto task = new SendAssetTask(message, senderNode, _resourcesDirectory);
|
||||
auto task = new SendAssetTask(message, senderNode, _filesDirectory);
|
||||
_taskPool.start(task);
|
||||
}
|
||||
|
||||
void AssetServer::handleAssetUpload(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
|
||||
|
||||
|
||||
if (senderNode->getCanRez()) {
|
||||
qDebug() << "Starting an UploadAssetTask for upload from" << uuidStringWithoutCurlyBraces(senderNode->getUUID());
|
||||
|
||||
auto task = new UploadAssetTask(message, senderNode, _resourcesDirectory);
|
||||
|
||||
auto task = new UploadAssetTask(message, senderNode, _filesDirectory);
|
||||
_taskPool.start(task);
|
||||
} else {
|
||||
// this is a node the domain told us is not allowed to rez entities
|
||||
// for now this also means it isn't allowed to add assets
|
||||
// so return a packet with error that indicates that
|
||||
|
||||
|
||||
auto permissionErrorPacket = NLPacket::create(PacketType::AssetUploadReply, sizeof(MessageID) + sizeof(AssetServerError));
|
||||
|
||||
|
||||
MessageID messageID;
|
||||
message->readPrimitive(&messageID);
|
||||
|
||||
|
||||
// write the message ID and a permission denied error
|
||||
permissionErrorPacket->writePrimitive(messageID);
|
||||
permissionErrorPacket->writePrimitive(AssetServerError::PermissionDenied);
|
||||
|
||||
|
||||
// send off the packet
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
nodeList->sendPacket(std::move(permissionErrorPacket), *senderNode);
|
||||
|
@ -242,19 +370,19 @@ void AssetServer::handleAssetUpload(QSharedPointer<ReceivedMessage> message, Sha
|
|||
|
||||
void AssetServer::sendStatsPacket() {
|
||||
QJsonObject serverStats;
|
||||
|
||||
|
||||
auto stats = DependencyManager::get<NodeList>()->sampleStatsForAllConnections();
|
||||
|
||||
|
||||
for (const auto& stat : stats) {
|
||||
QJsonObject nodeStats;
|
||||
auto endTimeMs = std::chrono::duration_cast<std::chrono::milliseconds>(stat.second.endTime);
|
||||
QDateTime date = QDateTime::fromMSecsSinceEpoch(endTimeMs.count());
|
||||
|
||||
|
||||
static const float USEC_PER_SEC = 1000000.0f;
|
||||
static const float MEGABITS_PER_BYTE = 8.0f / 1000000.0f; // Bytes => Mbits
|
||||
float elapsed = (float)(stat.second.endTime - stat.second.startTime).count() / USEC_PER_SEC; // sec
|
||||
float megabitsPerSecPerByte = MEGABITS_PER_BYTE / elapsed; // Bytes => Mb/s
|
||||
|
||||
|
||||
QJsonObject connectionStats;
|
||||
connectionStats["1. Last Heard"] = date.toString();
|
||||
connectionStats["2. Est. Max (P/s)"] = stat.second.estimatedBandwith;
|
||||
|
@ -264,10 +392,10 @@ void AssetServer::sendStatsPacket() {
|
|||
connectionStats["6. Up (Mb/s)"] = stat.second.sentBytes * megabitsPerSecPerByte;
|
||||
connectionStats["7. Down (Mb/s)"] = stat.second.receivedBytes * megabitsPerSecPerByte;
|
||||
nodeStats["Connection Stats"] = connectionStats;
|
||||
|
||||
|
||||
using Events = udt::ConnectionStats::Stats::Event;
|
||||
const auto& events = stat.second.events;
|
||||
|
||||
|
||||
QJsonObject upstreamStats;
|
||||
upstreamStats["1. Sent (P/s)"] = stat.second.sendRate;
|
||||
upstreamStats["2. Sent Packets"] = stat.second.sentPackets;
|
||||
|
@ -279,7 +407,7 @@ void AssetServer::sendStatsPacket() {
|
|||
upstreamStats["8. Sent ACK2"] = events[Events::SentACK2];
|
||||
upstreamStats["9. Retransmitted"] = events[Events::Retransmission];
|
||||
nodeStats["Upstream Stats"] = upstreamStats;
|
||||
|
||||
|
||||
QJsonObject downstreamStats;
|
||||
downstreamStats["1. Recvd (P/s)"] = stat.second.receiveRate;
|
||||
downstreamStats["2. Recvd Packets"] = stat.second.receivedPackets;
|
||||
|
@ -290,7 +418,7 @@ void AssetServer::sendStatsPacket() {
|
|||
downstreamStats["7. Recvd ACK2"] = events[Events::ReceivedACK2];
|
||||
downstreamStats["8. Duplicates"] = events[Events::Duplicate];
|
||||
nodeStats["Downstream Stats"] = downstreamStats;
|
||||
|
||||
|
||||
QString uuid;
|
||||
auto nodelist = DependencyManager::get<NodeList>();
|
||||
if (stat.first == nodelist->getDomainHandler().getSockAddr()) {
|
||||
|
@ -301,10 +429,274 @@ void AssetServer::sendStatsPacket() {
|
|||
uuid = uuidStringWithoutCurlyBraces(node ? node->getUUID() : QUuid());
|
||||
nodeStats[USERNAME_UUID_REPLACEMENT_STATS_KEY] = uuid;
|
||||
}
|
||||
|
||||
|
||||
serverStats[uuid] = nodeStats;
|
||||
}
|
||||
|
||||
|
||||
// send off the stats packets
|
||||
ThreadedAssignment::addPacketStatsAndSendStatsPacket(serverStats);
|
||||
}
|
||||
|
||||
static const QString MAP_FILE_NAME = "map.json";
|
||||
|
||||
void AssetServer::loadMappingsFromFile() {
|
||||
|
||||
auto mapFilePath = _resourcesDirectory.absoluteFilePath(MAP_FILE_NAME);
|
||||
|
||||
QFile mapFile { mapFilePath };
|
||||
if (mapFile.exists()) {
|
||||
if (mapFile.open(QIODevice::ReadOnly)) {
|
||||
QJsonParseError error;
|
||||
|
||||
auto jsonDocument = QJsonDocument::fromJson(mapFile.readAll(), &error);
|
||||
|
||||
if (error.error == QJsonParseError::NoError) {
|
||||
_fileMappings = jsonDocument.object().toVariantHash();
|
||||
|
||||
// remove any mappings that don't match the expected format
|
||||
auto it = _fileMappings.begin();
|
||||
while (it != _fileMappings.end()) {
|
||||
bool shouldDrop = false;
|
||||
|
||||
if (!isValidPath(it.key())) {
|
||||
qWarning() << "Will not keep mapping for" << it.key() << "since it is not a valid path.";
|
||||
shouldDrop = true;
|
||||
}
|
||||
|
||||
if (!isValidHash(it.value().toString())) {
|
||||
qWarning() << "Will not keep mapping for" << it.key() << "since it does not have a valid hash.";
|
||||
shouldDrop = true;
|
||||
}
|
||||
|
||||
if (shouldDrop) {
|
||||
it = _fileMappings.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
qInfo() << "Loaded" << _fileMappings.count() << "mappings from map file at" << mapFilePath;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
qCritical() << "Failed to read mapping file at" << mapFilePath << "- assignment will not continue.";
|
||||
setFinished(true);
|
||||
} else {
|
||||
qInfo() << "No existing mappings loaded from file since no file was found at" << mapFilePath;
|
||||
}
|
||||
}
|
||||
|
||||
bool AssetServer::writeMappingsToFile() {
|
||||
auto mapFilePath = _resourcesDirectory.absoluteFilePath(MAP_FILE_NAME);
|
||||
|
||||
QFile mapFile { mapFilePath };
|
||||
if (mapFile.open(QIODevice::WriteOnly)) {
|
||||
auto jsonObject = QJsonObject::fromVariantHash(_fileMappings);
|
||||
QJsonDocument jsonDocument { jsonObject };
|
||||
|
||||
if (mapFile.write(jsonDocument.toJson()) != -1) {
|
||||
qDebug() << "Wrote JSON mappings to file at" << mapFilePath;
|
||||
return true;
|
||||
} else {
|
||||
qWarning() << "Failed to write JSON mappings to file at" << mapFilePath;
|
||||
}
|
||||
} else {
|
||||
qWarning() << "Failed to open map file at" << mapFilePath;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool AssetServer::setMapping(AssetPath path, AssetHash hash) {
|
||||
path = path.trimmed();
|
||||
|
||||
if (!isValidPath(path)) {
|
||||
qWarning() << "Cannot set a mapping for invalid path:" << path << "=>" << hash;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isValidHash(hash)) {
|
||||
qWarning() << "Cannot set a mapping for invalid hash" << path << "=>" << hash;
|
||||
return false;
|
||||
}
|
||||
|
||||
// remember what the old mapping was in case persistence fails
|
||||
auto oldMapping = _fileMappings.value(path).toString();
|
||||
|
||||
// update the in memory QHash
|
||||
_fileMappings[path] = hash;
|
||||
|
||||
// attempt to write to file
|
||||
if (writeMappingsToFile()) {
|
||||
// persistence succeeded, we are good to go
|
||||
qDebug() << "Set mapping:" << path << "=>" << hash;
|
||||
return true;
|
||||
} else {
|
||||
// failed to persist this mapping to file - put back the old one in our in-memory representation
|
||||
if (oldMapping.isEmpty()) {
|
||||
_fileMappings.remove(path);
|
||||
} else {
|
||||
_fileMappings[path] = oldMapping;
|
||||
}
|
||||
|
||||
qWarning() << "Failed to persist mapping:" << path << "=>" << hash;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool pathIsFolder(const AssetPath& path) {
|
||||
return path.endsWith('/');
|
||||
}
|
||||
|
||||
bool AssetServer::deleteMappings(AssetPathList& paths) {
|
||||
// take a copy of the current mappings in case persistence of these deletes fails
|
||||
auto oldMappings = _fileMappings;
|
||||
|
||||
// enumerate the paths to delete and remove them all
|
||||
for (auto& path : paths) {
|
||||
|
||||
path = path.trimmed();
|
||||
|
||||
// figure out if this path will delete a file or folder
|
||||
if (pathIsFolder(path)) {
|
||||
// enumerate the in memory file mappings and remove anything that matches
|
||||
auto it = _fileMappings.begin();
|
||||
auto sizeBefore = _fileMappings.size();
|
||||
|
||||
while (it != _fileMappings.end()) {
|
||||
if (it.key().startsWith(path)) {
|
||||
it = _fileMappings.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
auto sizeNow = _fileMappings.size();
|
||||
if (sizeBefore != sizeNow) {
|
||||
qDebug() << "Deleted" << sizeBefore - sizeNow << "mappings in folder: " << path;
|
||||
} else {
|
||||
qDebug() << "Did not find any mappings to delete in folder:" << path;
|
||||
}
|
||||
|
||||
} else {
|
||||
auto oldMapping = _fileMappings.take(path);
|
||||
if (!oldMapping.isNull()) {
|
||||
qDebug() << "Deleted a mapping:" << path << "=>" << oldMapping.toString();
|
||||
} else {
|
||||
qDebug() << "Unable to delete a mapping that was not found:" << path;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// deleted the old mappings, attempt to persist to file
|
||||
if (writeMappingsToFile()) {
|
||||
// persistence succeeded we are good to go
|
||||
return true;
|
||||
} else {
|
||||
qWarning() << "Failed to persist deleted mappings, rolling back";
|
||||
|
||||
// we didn't delete the previous mapping, put it back in our in-memory representation
|
||||
_fileMappings = oldMappings;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool AssetServer::renameMapping(AssetPath oldPath, AssetPath newPath) {
|
||||
oldPath = oldPath.trimmed();
|
||||
newPath = newPath.trimmed();
|
||||
|
||||
if (!isValidPath(oldPath) || !isValidPath(newPath)) {
|
||||
qWarning() << "Cannot perform rename with invalid paths - both should have leading forward slashes:"
|
||||
<< oldPath << "=>" << newPath;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// figure out if this rename is for a file or folder
|
||||
if (pathIsFolder(oldPath)) {
|
||||
if (!pathIsFolder(newPath)) {
|
||||
// we were asked to rename a path to a folder to a path that isn't a folder, this is a fail
|
||||
qWarning() << "Cannot rename mapping from folder path" << oldPath << "to file path" << newPath;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// take a copy of the old mappings
|
||||
auto oldMappings = _fileMappings;
|
||||
|
||||
// iterate the current mappings and adjust any that matches the renamed folder
|
||||
auto it = oldMappings.begin();
|
||||
|
||||
while (it != oldMappings.end()) {
|
||||
if (it.key().startsWith(oldPath)) {
|
||||
auto newKey = it.key();
|
||||
newKey.replace(0, oldPath.size(), newPath);
|
||||
|
||||
// remove the old version from the in memory file mappings
|
||||
_fileMappings.remove(it.key());
|
||||
_fileMappings.insert(newKey, it.value());
|
||||
}
|
||||
|
||||
++it;
|
||||
}
|
||||
|
||||
if (writeMappingsToFile()) {
|
||||
// persisted the changed mappings, return success
|
||||
qDebug() << "Renamed folder mapping:" << oldPath << "=>" << newPath;
|
||||
|
||||
return true;
|
||||
} else {
|
||||
// couldn't persist the renamed paths, rollback and return failure
|
||||
_fileMappings = oldMappings;
|
||||
|
||||
qWarning() << "Failed to persist renamed folder mapping:" << oldPath << "=>" << newPath;
|
||||
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (pathIsFolder(newPath)) {
|
||||
// we were asked to rename a path to a file to a path that is a folder, this is a fail
|
||||
qWarning() << "Cannot rename mapping from file path" << oldPath << "to folder path" << newPath;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// take the old hash to remove the old mapping
|
||||
auto oldSourceMapping = _fileMappings.take(oldPath).toString();
|
||||
|
||||
// in case we're overwriting, keep the current destination mapping for potential rollback
|
||||
auto oldDestinationMapping = _fileMappings.value(newPath);
|
||||
|
||||
if (!oldSourceMapping.isEmpty()) {
|
||||
_fileMappings[newPath] = oldSourceMapping;
|
||||
|
||||
if (writeMappingsToFile()) {
|
||||
// persisted the renamed mapping, return success
|
||||
qDebug() << "Renamed mapping:" << oldPath << "=>" << newPath;
|
||||
|
||||
return true;
|
||||
} else {
|
||||
// we couldn't persist the renamed mapping, rollback and return failure
|
||||
_fileMappings[oldPath] = oldSourceMapping;
|
||||
|
||||
if (!oldDestinationMapping.isNull()) {
|
||||
// put back the overwritten mapping for the destination path
|
||||
_fileMappings[newPath] = oldDestinationMapping.toString();
|
||||
} else {
|
||||
// clear the new mapping
|
||||
_fileMappings.remove(newPath);
|
||||
}
|
||||
|
||||
qDebug() << "Failed to persist renamed mapping:" << oldPath << "=>" << newPath;
|
||||
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// failed to find a mapping that was to be renamed, return failure
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,10 +12,10 @@
|
|||
#ifndef hifi_AssetServer_h
|
||||
#define hifi_AssetServer_h
|
||||
|
||||
#include <QDir>
|
||||
#include <QtCore/QDir>
|
||||
#include <QtCore/QThreadPool>
|
||||
|
||||
#include <ThreadedAssignment.h>
|
||||
#include <QThreadPool>
|
||||
|
||||
#include "AssetUtils.h"
|
||||
#include "ReceivedMessage.h"
|
||||
|
@ -34,17 +34,39 @@ private slots:
|
|||
void handleAssetGetInfo(QSharedPointer<ReceivedMessage> packet, SharedNodePointer senderNode);
|
||||
void handleAssetGet(QSharedPointer<ReceivedMessage> packet, SharedNodePointer senderNode);
|
||||
void handleAssetUpload(QSharedPointer<ReceivedMessage> packetList, SharedNodePointer senderNode);
|
||||
void handleAssetMappingOperation(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
|
||||
|
||||
void sendStatsPacket();
|
||||
|
||||
private:
|
||||
static void writeError(NLPacketList* packetList, AssetServerError error);
|
||||
using Mappings = QVariantHash;
|
||||
|
||||
void handleGetMappingOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket);
|
||||
void handleGetAllMappingOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket);
|
||||
void handleSetMappingOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket);
|
||||
void handleDeleteMappingsOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket);
|
||||
void handleRenameMappingOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket);
|
||||
|
||||
// Mapping file operations must be called from main assignment thread only
|
||||
void loadMappingsFromFile();
|
||||
bool writeMappingsToFile();
|
||||
|
||||
/// Set the mapping for path to hash
|
||||
bool setMapping(AssetPath path, AssetHash hash);
|
||||
|
||||
/// Delete mapping `path`. Returns `true` if deletion of mappings succeeds, else `false`.
|
||||
bool deleteMappings(AssetPathList& paths);
|
||||
|
||||
/// Rename mapping from `oldPath` to `newPath`. Returns true if successful
|
||||
bool renameMapping(AssetPath oldPath, AssetPath newPath);
|
||||
|
||||
void performMappingMigration();
|
||||
|
||||
Mappings _fileMappings;
|
||||
|
||||
QDir _resourcesDirectory;
|
||||
QDir _filesDirectory;
|
||||
QThreadPool _taskPool;
|
||||
};
|
||||
|
||||
inline void writeError(NLPacketList* packetList, AssetServerError error) {
|
||||
packetList->writePrimitive(error);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -33,13 +33,10 @@ SendAssetTask::SendAssetTask(QSharedPointer<ReceivedMessage> message, const Shar
|
|||
|
||||
void SendAssetTask::run() {
|
||||
MessageID messageID;
|
||||
uint8_t extensionLength;
|
||||
DataOffset start, end;
|
||||
|
||||
_message->readPrimitive(&messageID);
|
||||
QByteArray assetHash = _message->read(SHA256_HASH_LENGTH);
|
||||
_message->readPrimitive(&extensionLength);
|
||||
QByteArray extension = _message->read(extensionLength);
|
||||
|
||||
// `start` and `end` indicate the range of data to retrieve for the asset identified by `assetHash`.
|
||||
// `start` is inclusive, `end` is exclusive. Requesting `start` = 1, `end` = 10 will retrieve 9 bytes of data,
|
||||
|
@ -59,15 +56,15 @@ void SendAssetTask::run() {
|
|||
replyPacketList->writePrimitive(messageID);
|
||||
|
||||
if (end <= start) {
|
||||
writeError(replyPacketList.get(), AssetServerError::InvalidByteRange);
|
||||
replyPacketList->writePrimitive(AssetServerError::InvalidByteRange);
|
||||
} else {
|
||||
QString filePath = _resourcesDir.filePath(QString(hexHash) + "." + QString(extension));
|
||||
QString filePath = _resourcesDir.filePath(QString(hexHash));
|
||||
|
||||
QFile file { filePath };
|
||||
|
||||
if (file.open(QIODevice::ReadOnly)) {
|
||||
if (file.size() < end) {
|
||||
writeError(replyPacketList.get(), AssetServerError::InvalidByteRange);
|
||||
replyPacketList->writePrimitive(AssetServerError::InvalidByteRange);
|
||||
qCDebug(networking) << "Bad byte range: " << hexHash << " " << start << ":" << end;
|
||||
} else {
|
||||
auto size = end - start;
|
||||
|
@ -80,7 +77,7 @@ void SendAssetTask::run() {
|
|||
file.close();
|
||||
} else {
|
||||
qCDebug(networking) << "Asset not found: " << filePath << "(" << hexHash << ")";
|
||||
writeError(replyPacketList.get(), AssetServerError::AssetNotFound);
|
||||
replyPacketList->writePrimitive(AssetServerError::AssetNotFound);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -37,15 +37,10 @@ void UploadAssetTask::run() {
|
|||
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);
|
||||
|
||||
uint64_t fileSize;
|
||||
buffer.read(reinterpret_cast<char*>(&fileSize), sizeof(fileSize));
|
||||
|
||||
qDebug() << "UploadAssetTask reading a file of " << fileSize << "bytes and extension" << extension << "from"
|
||||
qDebug() << "UploadAssetTask reading a file of " << fileSize << "bytes from"
|
||||
<< uuidStringWithoutCurlyBraces(_senderNode->getUUID());
|
||||
|
||||
auto replyPacket = NLPacket::create(PacketType::AssetUploadReply);
|
||||
|
@ -62,17 +57,47 @@ void UploadAssetTask::run() {
|
|||
qDebug() << "Hash for uploaded file from" << uuidStringWithoutCurlyBraces(_senderNode->getUUID())
|
||||
<< "is: (" << hexHash << ") ";
|
||||
|
||||
QFile file { _resourcesDir.filePath(QString(hexHash)) + "." + QString(extension) };
|
||||
QFile file { _resourcesDir.filePath(QString(hexHash)) };
|
||||
|
||||
bool existingCorrectFile = false;
|
||||
|
||||
if (file.exists()) {
|
||||
qDebug() << "[WARNING] This file already exists: " << hexHash;
|
||||
} else {
|
||||
file.open(QIODevice::WriteOnly);
|
||||
file.write(fileData);
|
||||
file.close();
|
||||
// check if the local file has the correct contents, otherwise we overwrite
|
||||
if (file.open(QIODevice::ReadOnly) && hashData(file.readAll()) == hash) {
|
||||
qDebug() << "Not overwriting existing verified file: " << hexHash;
|
||||
|
||||
existingCorrectFile = true;
|
||||
|
||||
replyPacket->writePrimitive(AssetServerError::NoError);
|
||||
replyPacket->write(hash);
|
||||
} else {
|
||||
qDebug() << "Overwriting an existing file whose contents did not match the expected hash: " << hexHash;
|
||||
file.close();
|
||||
}
|
||||
}
|
||||
replyPacket->writePrimitive(AssetServerError::NoError);
|
||||
replyPacket->write(hash);
|
||||
|
||||
if (!existingCorrectFile) {
|
||||
if (file.open(QIODevice::WriteOnly) && file.write(fileData) == qint64(fileSize)) {
|
||||
qDebug() << "Wrote file" << hexHash << "to disk. Upload complete";
|
||||
file.close();
|
||||
|
||||
replyPacket->writePrimitive(AssetServerError::NoError);
|
||||
replyPacket->write(hash);
|
||||
} else {
|
||||
qWarning() << "Failed to upload or write to file" << hexHash << " - upload failed.";
|
||||
|
||||
// upload has failed - remove the file and return an error
|
||||
auto removed = file.remove();
|
||||
|
||||
if (!removed) {
|
||||
qWarning() << "Removal of failed upload file" << hexHash << "failed.";
|
||||
}
|
||||
|
||||
replyPacket->writePrimitive(AssetServerError::FileOperationFailed);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
|
22
examples/utilities/tools/render/BG.qml
Normal file
22
examples/utilities/tools/render/BG.qml
Normal file
|
@ -0,0 +1,22 @@
|
|||
//
|
||||
// BG.qml
|
||||
// examples/utilities/tools/render
|
||||
//
|
||||
// Created by Zach Pomerantz on 2/8/2016
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.4
|
||||
|
||||
Item {
|
||||
Timer {
|
||||
running: true; repeat: true
|
||||
onTriggered: time.text = Render.getConfig("DrawBackgroundDeferred").gpuTime
|
||||
}
|
||||
|
||||
Text { id: time; font.pointSize: 20 }
|
||||
}
|
||||
|
21
examples/utilities/tools/render/debugBG.js
Normal file
21
examples/utilities/tools/render/debugBG.js
Normal file
|
@ -0,0 +1,21 @@
|
|||
//
|
||||
// debugBG.js
|
||||
// examples/utilities/tools/render
|
||||
//
|
||||
// Zach Pomerantz, created on 1/27/2016.
|
||||
// Copyright 2016 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
|
||||
//
|
||||
|
||||
// Set up the qml ui
|
||||
var qml = Script.resolvePath('BG.qml');
|
||||
var window = new OverlayWindow({
|
||||
title: 'Background Timer',
|
||||
source: qml,
|
||||
width: 300
|
||||
});
|
||||
window.setPosition(25, 50);
|
||||
window.closed.connect(function() { Script.stop(); });
|
||||
|
520
interface/resources/qml/AssetServer.qml
Normal file
520
interface/resources/qml/AssetServer.qml
Normal file
|
@ -0,0 +1,520 @@
|
|||
//
|
||||
// AssetServer.qml
|
||||
//
|
||||
// Created by Clement on 3/1/16
|
||||
// Copyright 2016 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
|
||||
//
|
||||
|
||||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Dialogs 1.2 as OriginalDialogs
|
||||
import Qt.labs.settings 1.0
|
||||
|
||||
import "styles-uit"
|
||||
import "controls-uit" as HifiControls
|
||||
import "windows-uit"
|
||||
import "dialogs"
|
||||
|
||||
Window {
|
||||
id: root
|
||||
objectName: "AssetServer"
|
||||
title: "My Asset Server"
|
||||
resizable: true
|
||||
destroyOnInvisible: true
|
||||
x: 40; y: 40
|
||||
implicitWidth: 384; implicitHeight: 640
|
||||
minSize: Qt.vector2d(200, 300)
|
||||
|
||||
property int colorScheme: hifi.colorSchemes.dark
|
||||
|
||||
HifiConstants { id: hifi }
|
||||
|
||||
property var scripts: ScriptDiscoveryService;
|
||||
property var assetProxyModel: Assets.proxyModel;
|
||||
property var assetMappingsModel: Assets.mappingModel;
|
||||
property var currentDirectory;
|
||||
|
||||
Settings {
|
||||
category: "Overlay.AssetServer"
|
||||
property alias x: root.x
|
||||
property alias y: root.y
|
||||
property alias directory: root.currentDirectory
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
ApplicationInterface.uploadRequest.connect(uploadClicked);
|
||||
assetMappingsModel.errorGettingMappings.connect(handleGetMappingsError);
|
||||
reload();
|
||||
}
|
||||
|
||||
function doDeleteFile(path) {
|
||||
console.log("Deleting " + path);
|
||||
|
||||
Assets.deleteMappings(path, function(err) {
|
||||
if (err) {
|
||||
console.log("Asset browser - error deleting path: ", path, err);
|
||||
|
||||
box = errorMessageBox("There was an error deleting:\n" + path + "\n" + err);
|
||||
box.selected.connect(reload);
|
||||
} else {
|
||||
console.log("Asset browser - finished deleting path: ", path);
|
||||
reload();
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
function doRenameFile(oldPath, newPath) {
|
||||
|
||||
if (newPath[0] != "/") {
|
||||
newPath = "/" + newPath;
|
||||
}
|
||||
|
||||
if (oldPath[oldPath.length - 1] == '/' && newPath[newPath.length - 1] != '/') {
|
||||
// this is a folder rename but the user neglected to add a trailing slash when providing a new path
|
||||
newPath = newPath + "/";
|
||||
}
|
||||
|
||||
if (Assets.isKnownFolder(newPath)) {
|
||||
box = errorMessageBox("Cannot overwrite existing directory.");
|
||||
box.selected.connect(reload);
|
||||
}
|
||||
|
||||
console.log("Asset browser - renaming " + oldPath + " to " + newPath);
|
||||
|
||||
Assets.renameMapping(oldPath, newPath, function(err) {
|
||||
if (err) {
|
||||
console.log("Asset browser - error renaming: ", oldPath, "=>", newPath, " - error ", err);
|
||||
box = errorMessageBox("There was an error renaming:\n" + oldPath + " to " + newPath + "\n" + err);
|
||||
box.selected.connect(reload);
|
||||
} else {
|
||||
console.log("Asset browser - finished rename: ", oldPath, "=>", newPath);
|
||||
}
|
||||
|
||||
reload();
|
||||
});
|
||||
}
|
||||
|
||||
function fileExists(path) {
|
||||
return Assets.isKnownMapping(path);
|
||||
}
|
||||
|
||||
function askForOverwrite(path, callback) {
|
||||
var object = desktop.messageBox({
|
||||
icon: hifi.icons.question,
|
||||
buttons: OriginalDialogs.StandardButton.Yes | OriginalDialogs.StandardButton.No,
|
||||
defaultButton: OriginalDialogs.StandardButton.No,
|
||||
title: "Overwrite File",
|
||||
text: path + "\n" + "This file already exists. Do you want to overwrite it?"
|
||||
});
|
||||
object.selected.connect(function(button) {
|
||||
if (button === OriginalDialogs.StandardButton.Yes) {
|
||||
callback();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function canAddToWorld(path) {
|
||||
var supportedExtensions = [/\.fbx\b/i, /\.obj\b/i];
|
||||
|
||||
return supportedExtensions.reduce(function(total, current) {
|
||||
return total | new RegExp(current).test(path);
|
||||
}, false);
|
||||
}
|
||||
|
||||
function reload() {
|
||||
Assets.mappingModel.refresh();
|
||||
treeView.selection.clear();
|
||||
}
|
||||
|
||||
function handleGetMappingsError(errorString) {
|
||||
errorMessageBox(
|
||||
"There was a problem retreiving the list of assets from your Asset Server.\n"
|
||||
+ errorString
|
||||
);
|
||||
}
|
||||
|
||||
function addToWorld() {
|
||||
var url = assetProxyModel.data(treeView.selection.currentIndex, 0x103);
|
||||
|
||||
if (!url || !canAddToWorld(url)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var name = assetProxyModel.data(treeView.selection.currentIndex);
|
||||
|
||||
console.log("Asset browser - adding asset " + url + " (" + name + ") to world.");
|
||||
|
||||
var addPosition = Vec3.sum(MyAvatar.position, Vec3.multiply(2, Quat.getFront(MyAvatar.orientation)));
|
||||
Entities.addModelEntity(name, url, addPosition);
|
||||
}
|
||||
|
||||
function copyURLToClipboard(index) {
|
||||
if (!index) {
|
||||
index = treeView.selection.currentIndex;
|
||||
}
|
||||
|
||||
var url = assetProxyModel.data(treeView.selection.currentIndex, 0x103);
|
||||
if (!url) {
|
||||
return;
|
||||
}
|
||||
Window.copyToClipboard(url);
|
||||
}
|
||||
|
||||
function renameEl(index, data) {
|
||||
if (!index) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var path = assetProxyModel.data(index, 0x100);
|
||||
if (!path) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var destinationPath = path.split('/');
|
||||
destinationPath[destinationPath.length - (path[path.length - 1] === '/' ? 2 : 1)] = data;
|
||||
destinationPath = destinationPath.join('/').trim();
|
||||
|
||||
if (path === destinationPath) {
|
||||
return;
|
||||
}
|
||||
if (!fileExists(destinationPath)) {
|
||||
doRenameFile(path, destinationPath);
|
||||
}
|
||||
}
|
||||
function renameFile(index) {
|
||||
if (!index) {
|
||||
index = treeView.selection.currentIndex;
|
||||
}
|
||||
|
||||
var path = assetProxyModel.data(index, 0x100);
|
||||
if (!path) {
|
||||
return;
|
||||
}
|
||||
|
||||
var object = desktop.inputDialog({
|
||||
label: "Enter new path:",
|
||||
current: path,
|
||||
placeholderText: "Enter path here"
|
||||
});
|
||||
object.selected.connect(function(destinationPath) {
|
||||
destinationPath = destinationPath.trim();
|
||||
|
||||
if (path == destinationPath) {
|
||||
return;
|
||||
}
|
||||
if (fileExists(destinationPath)) {
|
||||
askForOverwrite(destinationPath, function() {
|
||||
doRenameFile(path, destinationPath);
|
||||
});
|
||||
} else {
|
||||
doRenameFile(path, destinationPath);
|
||||
}
|
||||
});
|
||||
}
|
||||
function deleteFile(index) {
|
||||
if (!index) {
|
||||
index = treeView.selection.currentIndex;
|
||||
}
|
||||
var path = assetProxyModel.data(index, 0x100);
|
||||
if (!path) {
|
||||
return;
|
||||
}
|
||||
|
||||
var isFolder = assetProxyModel.data(treeView.selection.currentIndex, 0x101);
|
||||
var typeString = isFolder ? 'folder' : 'file';
|
||||
|
||||
var object = desktop.messageBox({
|
||||
icon: hifi.icons.question,
|
||||
buttons: OriginalDialogs.StandardButton.Yes + OriginalDialogs.StandardButton.No,
|
||||
defaultButton: OriginalDialogs.StandardButton.Yes,
|
||||
title: "Delete",
|
||||
text: "You are about to delete the following " + typeString + ":\n" + path + "\nDo you want to continue?"
|
||||
});
|
||||
object.selected.connect(function(button) {
|
||||
if (button === OriginalDialogs.StandardButton.Yes) {
|
||||
doDeleteFile(path);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
property var uploadOpen: false;
|
||||
Timer {
|
||||
id: timer
|
||||
}
|
||||
function uploadClicked(fileUrl) {
|
||||
if (uploadOpen) {
|
||||
return;
|
||||
}
|
||||
uploadOpen = true;
|
||||
|
||||
function doUpload(url, dropping) {
|
||||
var fileUrl = fileDialogHelper.urlToPath(url);
|
||||
|
||||
var path = assetProxyModel.data(treeView.selection.currentIndex, 0x100);
|
||||
var directory = path ? path.slice(0, path.lastIndexOf('/') + 1) : "/";
|
||||
var filename = fileUrl.slice(fileUrl.lastIndexOf('/') + 1);
|
||||
|
||||
Assets.uploadFile(fileUrl, directory + filename,
|
||||
function() {
|
||||
// Upload started
|
||||
uploadSpinner.visible = true;
|
||||
uploadButton.enabled = false;
|
||||
uploadProgressLabel.text = "In progress...";
|
||||
},
|
||||
function(err, path) {
|
||||
print(err, path);
|
||||
if (!err) {
|
||||
uploadProgressLabel.text = "Upload Complete";
|
||||
timer.interval = 1000;
|
||||
timer.repeat = false;
|
||||
timer.triggered.connect(function() {
|
||||
uploadSpinner.visible = false;
|
||||
uploadButton.enabled = true;
|
||||
uploadOpen = false;
|
||||
});
|
||||
timer.start();
|
||||
console.log("Asset Browser - finished uploading: ", fileUrl);
|
||||
reload();
|
||||
} else {
|
||||
if (err > 0) {
|
||||
console.log("Asset Browser - error uploading: ", fileUrl, " - error ", err);
|
||||
var box = errorMessageBox("There was an error uploading:\n" + fileUrl + "\n" + Assets.getErrorString(err));
|
||||
box.selected.connect(reload);
|
||||
}
|
||||
uploadSpinner.visible = false;
|
||||
uploadButton.enabled = true;
|
||||
uploadOpen = false;
|
||||
}
|
||||
}, dropping);
|
||||
}
|
||||
|
||||
if (fileUrl) {
|
||||
doUpload(fileUrl, true);
|
||||
} else {
|
||||
var browser = desktop.fileDialog({
|
||||
selectDirectory: false,
|
||||
dir: currentDirectory
|
||||
});
|
||||
browser.canceled.connect(function() {
|
||||
uploadOpen = false;
|
||||
});
|
||||
browser.selectedFile.connect(function(url) {
|
||||
currentDirectory = browser.dir;
|
||||
doUpload(url, false);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function errorMessageBox(message) {
|
||||
return desktop.messageBox({
|
||||
icon: hifi.icons.warning,
|
||||
defaultButton: OriginalDialogs.StandardButton.Ok,
|
||||
title: "Error",
|
||||
text: message
|
||||
});
|
||||
}
|
||||
|
||||
Item {
|
||||
width: pane.contentWidth
|
||||
height: pane.height
|
||||
|
||||
HifiControls.ContentSection {
|
||||
id: assetDirectory
|
||||
name: "Asset Directory"
|
||||
spacing: hifi.dimensions.contentSpacing.y
|
||||
isFirst: true
|
||||
|
||||
Row {
|
||||
id: buttonRow
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
spacing: hifi.dimensions.contentSpacing.x
|
||||
|
||||
HifiControls.GlyphButton {
|
||||
glyph: hifi.glyphs.reload
|
||||
color: hifi.buttons.white
|
||||
colorScheme: root.colorScheme
|
||||
height: 26
|
||||
width: 26
|
||||
|
||||
onClicked: root.reload()
|
||||
}
|
||||
|
||||
HifiControls.Button {
|
||||
text: "ADD TO WORLD"
|
||||
color: hifi.buttons.white
|
||||
colorScheme: root.colorScheme
|
||||
height: 26
|
||||
width: 120
|
||||
|
||||
enabled: canAddToWorld(assetProxyModel.data(treeView.selection.currentIndex, 0x100))
|
||||
|
||||
onClicked: root.addToWorld()
|
||||
}
|
||||
|
||||
HifiControls.Button {
|
||||
text: "RENAME"
|
||||
color: hifi.buttons.white
|
||||
colorScheme: root.colorScheme
|
||||
height: 26
|
||||
width: 80
|
||||
|
||||
onClicked: root.renameFile()
|
||||
enabled: treeView.selection.hasSelection
|
||||
}
|
||||
|
||||
HifiControls.Button {
|
||||
id: deleteButton
|
||||
|
||||
text: "DELETE"
|
||||
color: hifi.buttons.red
|
||||
colorScheme: root.colorScheme
|
||||
height: 26
|
||||
width: 80
|
||||
|
||||
onClicked: root.deleteFile()
|
||||
enabled: treeView.selection.hasSelection
|
||||
}
|
||||
}
|
||||
|
||||
Menu {
|
||||
id: contextMenu
|
||||
title: "Edit"
|
||||
property var url: ""
|
||||
property var currentIndex: null
|
||||
|
||||
MenuItem {
|
||||
text: "Copy URL"
|
||||
onTriggered: {
|
||||
copyURLToClipboard(contextMenu.currentIndex);
|
||||
}
|
||||
}
|
||||
|
||||
MenuItem {
|
||||
text: "Rename"
|
||||
onTriggered: {
|
||||
renameFile(contextMenu.currentIndex);
|
||||
}
|
||||
}
|
||||
|
||||
MenuItem {
|
||||
text: "Delete"
|
||||
onTriggered: {
|
||||
deleteFile(contextMenu.currentIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HifiControls.Tree {
|
||||
id: treeView
|
||||
anchors.top: assetDirectory.bottom
|
||||
anchors.bottom: uploadSection.top
|
||||
anchors.margins: 12
|
||||
anchors.bottomMargin: 0
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
|
||||
treeModel: assetProxyModel
|
||||
canEdit: true
|
||||
colorScheme: root.colorScheme
|
||||
|
||||
modifyEl: renameEl
|
||||
|
||||
MouseArea {
|
||||
propagateComposedEvents: true
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.RightButton
|
||||
onClicked: {
|
||||
var index = treeView.indexAt(mouse.x, mouse.y);
|
||||
|
||||
treeView.selection.setCurrentIndex(index, 0x0002);
|
||||
|
||||
contextMenu.currentIndex = index;
|
||||
contextMenu.popup();
|
||||
}
|
||||
}
|
||||
}
|
||||
HifiControls.ContentSection {
|
||||
id: uploadSection
|
||||
name: "Upload A File"
|
||||
spacing: hifi.dimensions.contentSpacing.y
|
||||
anchors.bottom: parent.bottom
|
||||
height: 130
|
||||
|
||||
Item {
|
||||
height: parent.height
|
||||
width: parent.width
|
||||
HifiControls.Button {
|
||||
id: uploadButton
|
||||
anchors.right: parent.right
|
||||
|
||||
text: "Choose File"
|
||||
color: hifi.buttons.blue
|
||||
colorScheme: root.colorScheme
|
||||
height: 30
|
||||
width: 155
|
||||
|
||||
onClicked: uploadClickedTimer.running = true
|
||||
|
||||
// For some reason trigginer an API that enters
|
||||
// an internal event loop directly from the button clicked
|
||||
// trigger below causes the appliction to behave oddly.
|
||||
// Most likely because the button onClicked handling is never
|
||||
// completed until the function returns.
|
||||
// FIXME find a better way of handling the input dialogs that
|
||||
// doesn't trigger this.
|
||||
Timer {
|
||||
id: uploadClickedTimer
|
||||
interval: 5
|
||||
repeat: false
|
||||
running: false
|
||||
onTriggered: uploadClicked();
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: uploadSpinner
|
||||
visible: false
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
width: 40
|
||||
height: 32
|
||||
|
||||
Image {
|
||||
id: image
|
||||
width: 24
|
||||
height: 24
|
||||
source: "../images/Loading-Outer-Ring.png"
|
||||
RotationAnimation on rotation {
|
||||
loops: Animation.Infinite
|
||||
from: 0
|
||||
to: 360
|
||||
duration: 2000
|
||||
}
|
||||
}
|
||||
Image {
|
||||
width: 24
|
||||
height: 24
|
||||
source: "../images/Loading-Inner-H.png"
|
||||
}
|
||||
HifiControls.Label {
|
||||
id: uploadProgressLabel
|
||||
anchors.left: image.right
|
||||
anchors.leftMargin: 10
|
||||
anchors.verticalCenter: image.verticalCenter
|
||||
text: "In progress..."
|
||||
colorScheme: root.colorScheme
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -15,8 +15,9 @@ import QtQuick.Controls.Styles 1.4
|
|||
import "../styles-uit"
|
||||
|
||||
Original.Button {
|
||||
id: button
|
||||
property int color: 0
|
||||
property int colorScheme: hifi.colorSchemes.light
|
||||
|
||||
width: 120
|
||||
height: 28
|
||||
|
||||
|
@ -24,27 +25,43 @@ Original.Button {
|
|||
|
||||
background: Rectangle {
|
||||
radius: hifi.buttons.radius
|
||||
|
||||
gradient: Gradient {
|
||||
GradientStop {
|
||||
position: 0.2
|
||||
color: enabled
|
||||
? (!pressed && button.color != hifi.buttons.black || (!hovered || pressed) && button.color == hifi.buttons.black
|
||||
? hifi.buttons.colorStart[button.color] : hifi.buttons.colorFinish[button.color])
|
||||
: hifi.buttons.colorStart[hifi.buttons.white]
|
||||
color: {
|
||||
if (!control.enabled) {
|
||||
hifi.buttons.disabledColorStart[control.colorScheme]
|
||||
} else if (control.pressed) {
|
||||
hifi.buttons.pressedColor[control.color]
|
||||
} else if (control.hovered) {
|
||||
hifi.buttons.hoveredColor[control.color]
|
||||
} else {
|
||||
hifi.buttons.colorStart[control.color]
|
||||
}
|
||||
}
|
||||
}
|
||||
GradientStop {
|
||||
position: 1.0
|
||||
color: enabled
|
||||
? ((!hovered || pressed) && button.color != hifi.buttons.black || !pressed && button.color == hifi.buttons.black
|
||||
? hifi.buttons.colorFinish[button.color] : hifi.buttons.colorStart[button.color])
|
||||
: hifi.buttons.colorFinish[hifi.buttons.white]
|
||||
color: {
|
||||
if (!control.enabled) {
|
||||
hifi.buttons.disabledColorFinish[control.colorScheme]
|
||||
} else if (control.pressed) {
|
||||
hifi.buttons.pressedColor[control.color]
|
||||
} else if (control.hovered) {
|
||||
hifi.buttons.hoveredColor[control.color]
|
||||
} else {
|
||||
hifi.buttons.colorFinish[control.color]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
label: RalewayBold {
|
||||
font.capitalization: Font.AllUppercase
|
||||
color: enabled ? hifi.buttons.textColor[button.color] : hifi.colors.lightGrayText
|
||||
color: enabled ? hifi.buttons.textColor[control.color]
|
||||
: hifi.buttons.disabledTextColor[control.colorScheme]
|
||||
size: hifi.fontSizes.buttonLabel
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
|
|
|
@ -14,7 +14,7 @@ import QtGraphicalEffects 1.0
|
|||
import "../styles-uit"
|
||||
|
||||
Column {
|
||||
property string name: "Static Section"
|
||||
property string name: "Content Section"
|
||||
property bool isFirst: false
|
||||
property bool isCollapsible: false // Set at creation.
|
||||
property bool isCollapsed: false
|
||||
|
|
71
interface/resources/qml/controls-uit/GlyphButton.qml
Normal file
71
interface/resources/qml/controls-uit/GlyphButton.qml
Normal file
|
@ -0,0 +1,71 @@
|
|||
//
|
||||
// GlyphButton.qml
|
||||
//
|
||||
// Created by Clement on 3/7/16
|
||||
// Copyright 2016 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
|
||||
//
|
||||
|
||||
|
||||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.4 as Original
|
||||
import QtQuick.Controls.Styles 1.4
|
||||
|
||||
import "../styles-uit"
|
||||
|
||||
Original.Button {
|
||||
property int color: 0
|
||||
property int colorScheme: hifi.colorShemes.light
|
||||
property string glyph: ""
|
||||
|
||||
width: 120
|
||||
height: 28
|
||||
|
||||
style: ButtonStyle {
|
||||
|
||||
background: Rectangle {
|
||||
radius: hifi.buttons.radius
|
||||
|
||||
gradient: Gradient {
|
||||
GradientStop {
|
||||
position: 0.2
|
||||
color: {
|
||||
if (!control.enabled) {
|
||||
hifi.buttons.disabledColorStart[control.colorScheme]
|
||||
} else if (control.pressed) {
|
||||
hifi.buttons.pressedColor[control.color]
|
||||
} else if (control.hovered) {
|
||||
hifi.buttons.hoveredColor[control.color]
|
||||
} else {
|
||||
hifi.buttons.colorStart[control.color]
|
||||
}
|
||||
}
|
||||
}
|
||||
GradientStop {
|
||||
position: 1.0
|
||||
color: {
|
||||
if (!control.enabled) {
|
||||
hifi.buttons.disabledColorFinish[control.colorScheme]
|
||||
} else if (control.pressed) {
|
||||
hifi.buttons.pressedColor[control.color]
|
||||
} else if (control.hovered) {
|
||||
hifi.buttons.hoveredColor[control.color]
|
||||
} else {
|
||||
hifi.buttons.colorFinish[control.color]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
label: HiFiGlyphs {
|
||||
color: enabled ? hifi.buttons.textColor[control.color]
|
||||
: hifi.buttons.disabledTextColor[control.colorScheme]
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: control.glyph
|
||||
}
|
||||
}
|
||||
}
|
|
@ -38,7 +38,7 @@ TextField {
|
|||
: (textField.focus ? hifi.colors.white : hifi.colors.lightGrayText)
|
||||
background: Rectangle {
|
||||
color: isLightColorScheme
|
||||
? (textField.focus ? hifi.colors.white : hifi.colors.lightGray)
|
||||
? (textField.focus ? hifi.colors.white : hifi.colors.textFieldLightBackground)
|
||||
: (textField.focus ? hifi.colors.black : hifi.colors.baseGrayShadow)
|
||||
border.color: hifi.colors.primaryHighlight
|
||||
border.width: textField.focus ? 1 : 0
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
import QtQml.Models 2.2
|
||||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Controls.Styles 1.4
|
||||
|
@ -18,10 +19,16 @@ TreeView {
|
|||
id: treeView
|
||||
|
||||
property var treeModel: ListModel { }
|
||||
property var canEdit: false
|
||||
property int colorScheme: hifi.colorSchemes.light
|
||||
readonly property bool isLightColorScheme: colorScheme == hifi.colorSchemes.light
|
||||
|
||||
property var modifyEl: function(index, data) { return false; }
|
||||
|
||||
model: treeModel
|
||||
selection: ItemSelectionModel {
|
||||
model: treeModel
|
||||
}
|
||||
|
||||
TableViewColumn {
|
||||
role: "display";
|
||||
|
@ -120,7 +127,9 @@ TreeView {
|
|||
: (styleData.alternate ? hifi.colors.tableRowDarkEven : hifi.colors.tableRowDarkOdd)
|
||||
}
|
||||
|
||||
itemDelegate: FiraSansSemiBold {
|
||||
itemDelegate: Loader {
|
||||
id: itemDelegateLoader
|
||||
|
||||
anchors {
|
||||
left: parent ? parent.left : undefined
|
||||
leftMargin: (2 + styleData.depth) * hifi.dimensions.tablePadding
|
||||
|
@ -129,11 +138,83 @@ TreeView {
|
|||
verticalCenter: parent ? parent.verticalCenter : undefined
|
||||
}
|
||||
|
||||
text: styleData.value
|
||||
size: hifi.fontSizes.tableText
|
||||
color: colorScheme == hifi.colorSchemes.light
|
||||
? (styleData.selected ? hifi.colors.black : hifi.colors.baseGrayHighlight)
|
||||
: (styleData.selected ? hifi.colors.black : hifi.colors.lightGrayText)
|
||||
function getComponent() {
|
||||
if (treeView.canEdit && styleData.selected) {
|
||||
return textFieldComponent;
|
||||
} else {
|
||||
return labelComponent;
|
||||
}
|
||||
|
||||
}
|
||||
sourceComponent: getComponent()
|
||||
|
||||
Component {
|
||||
id: labelComponent
|
||||
FiraSansSemiBold {
|
||||
|
||||
text: styleData.value
|
||||
size: hifi.fontSizes.tableText
|
||||
color: colorScheme == hifi.colorSchemes.light
|
||||
? (styleData.selected ? hifi.colors.black : hifi.colors.baseGrayHighlight)
|
||||
: (styleData.selected ? hifi.colors.black : hifi.colors.lightGrayText)
|
||||
}
|
||||
}
|
||||
Component {
|
||||
id: textFieldComponent
|
||||
|
||||
TextField {
|
||||
id: textField
|
||||
readOnly: !activeFocus
|
||||
|
||||
text: styleData.value
|
||||
|
||||
FontLoader { id: firaSansSemiBold; source: "../../fonts/FiraSans-SemiBold.ttf"; }
|
||||
font.family: firaSansSemiBold.name
|
||||
font.pixelSize: hifi.fontSizes.textFieldInput
|
||||
height: hifi.dimensions.tableRowHeight
|
||||
|
||||
style: TextFieldStyle {
|
||||
textColor: readOnly
|
||||
? hifi.colors.black
|
||||
: (treeView.isLightColorScheme ? hifi.colors.black : hifi.colors.white)
|
||||
background: Rectangle {
|
||||
visible: !readOnly
|
||||
color: treeView.isLightColorScheme ? hifi.colors.white : hifi.colors.black
|
||||
border.color: hifi.colors.primaryHighlight
|
||||
border.width: 1
|
||||
}
|
||||
selectedTextColor: hifi.colors.black
|
||||
selectionColor: hifi.colors.primaryHighlight
|
||||
padding.left: readOnly ? 0 : hifi.dimensions.textPadding
|
||||
padding.right: readOnly ? 0 : hifi.dimensions.textPadding
|
||||
}
|
||||
|
||||
validator: RegExpValidator {
|
||||
regExp: /[^/]+/
|
||||
}
|
||||
|
||||
Keys.onPressed: {
|
||||
if (event.key == Qt.Key_Escape) {
|
||||
text = styleData.value;
|
||||
unfocusHelper.forceActiveFocus();
|
||||
event.accepted = true;
|
||||
}
|
||||
}
|
||||
onAccepted: {
|
||||
if (acceptableInput && styleData.selected) {
|
||||
if (!modifyEl(selection.currentIndex, text)) {
|
||||
text = styleData.value;
|
||||
}
|
||||
unfocusHelper.forceActiveFocus();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: unfocusHelper
|
||||
visible: false
|
||||
}
|
||||
|
||||
onDoubleClicked: isExpanded(index) ? collapse(index) : expand(index)
|
||||
|
|
|
@ -72,7 +72,7 @@ ModalWindow {
|
|||
readonly property int maxHeight: 720
|
||||
|
||||
function resize() {
|
||||
var targetWidth = mainTextContainer.width
|
||||
var targetWidth = mainTextContainer.contentWidth
|
||||
var targetHeight = mainTextContainer.height + 3 * hifi.dimensions.contentSpacing.y
|
||||
+ (informativeTextContainer.text != "" ? informativeTextContainer.contentHeight + 3 * hifi.dimensions.contentSpacing.y : 0)
|
||||
+ buttons.height
|
||||
|
@ -96,6 +96,7 @@ ModalWindow {
|
|||
}
|
||||
lineHeight: 2
|
||||
lineHeightMode: Text.ProportionalHeight
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
|
||||
RalewaySemiBold {
|
||||
|
|
|
@ -34,8 +34,7 @@ ModalWindow {
|
|||
property var items;
|
||||
property string label
|
||||
property var result;
|
||||
// FIXME not current honored
|
||||
property var current;
|
||||
property alias current: textResult.text
|
||||
|
||||
// For text boxes
|
||||
property alias placeholderText: textResult.placeholderText
|
||||
|
|
|
@ -10,6 +10,7 @@ QtObject {
|
|||
readonly property string animDebugDrawPosition: "Debug Draw Position";
|
||||
readonly property string antialiasing: "Antialiasing";
|
||||
readonly property string assetMigration: "ATP Asset Migration";
|
||||
readonly property string assetServer = "Asset Server";
|
||||
readonly property string atmosphere: "Atmosphere";
|
||||
readonly property string attachments: "Attachments...";
|
||||
readonly property string audioNetworkStats: "Audio Network Stats";
|
||||
|
@ -154,7 +155,6 @@ QtObject {
|
|||
readonly property string toolWindow: "Tool Window";
|
||||
readonly property string transmitterDrive: "Transmitter Drive";
|
||||
readonly property string turnWithHead: "Turn using Head";
|
||||
readonly property string uploadAsset: "Upload File to Asset Server";
|
||||
readonly property string useAudioForMouth: "Use Audio for Mouth";
|
||||
readonly property string useCamera: "Use Camera";
|
||||
readonly property string velocityFilter: "Velocity Filter";
|
||||
|
|
|
@ -35,7 +35,7 @@ Item {
|
|||
glyph = hifi.glyphs.alert;
|
||||
break;
|
||||
case hifi.icons.critical:
|
||||
glyph = hifi.glyphs.critical;
|
||||
glyph = hifi.glyphs.error;
|
||||
break;
|
||||
case hifi.icons.placemark:
|
||||
glyph = hifi.glyphs.placemark;
|
||||
|
@ -113,6 +113,7 @@ Item {
|
|||
readonly property color dropDownLightFinish: "#afafaf"
|
||||
readonly property color dropDownDarkStart: "#7d7d7d"
|
||||
readonly property color dropDownDarkFinish: "#6b6a6b"
|
||||
readonly property color textFieldLightBackground: "#d4d4d4"
|
||||
}
|
||||
|
||||
Item {
|
||||
|
@ -164,32 +165,6 @@ Item {
|
|||
readonly property real disclosureButton: dimensions.largeScreen ? 20 : 15
|
||||
}
|
||||
|
||||
Item {
|
||||
id: glyphs
|
||||
readonly property string alert: "+"
|
||||
readonly property string backward: "E"
|
||||
readonly property string caratDn: "5"
|
||||
readonly property string caratR: "3"
|
||||
readonly property string caratUp: "6"
|
||||
readonly property string close: "w"
|
||||
readonly property string closeInverted: "x"
|
||||
readonly property string closeSmall: "C"
|
||||
readonly property string critical: "="
|
||||
readonly property string disclosureButtonCollapse: "M"
|
||||
readonly property string disclosureButtonExpand: "L"
|
||||
readonly property string disclosureCollapse: "Z"
|
||||
readonly property string disclosureExpand: "B"
|
||||
readonly property string forward: "D"
|
||||
readonly property string info: "["
|
||||
readonly property string noIcon: ""
|
||||
readonly property string pin: "y"
|
||||
readonly property string pinInverted: "z"
|
||||
readonly property string placemark: "U"
|
||||
readonly property string question: "]"
|
||||
readonly property string reloadSmall: "a"
|
||||
readonly property string resizeHandle: "A"
|
||||
}
|
||||
|
||||
Item {
|
||||
id: icons
|
||||
// Values per OffscreenUi::Icon
|
||||
|
@ -208,8 +183,13 @@ Item {
|
|||
readonly property int red: 2
|
||||
readonly property int black: 3
|
||||
readonly property var textColor: [ colors.darkGray, colors.white, colors.white, colors.white ]
|
||||
readonly property var colorStart: [ "#ffffff", "#00b4ef", "#d42043", "#343434" ]
|
||||
readonly property var colorFinish: [ "#afafaf", "#1080b8", "#94132e", "#000000" ]
|
||||
readonly property var colorStart: [ colors.white, colors.primaryHighlight, "#d42043", "#343434" ]
|
||||
readonly property var colorFinish: [ colors.lightGrayText, colors.blueAccent, "#94132e", colors.black ]
|
||||
readonly property var hoveredColor: [ colorStart[white], colorStart[blue], colorStart[red], colorFinish[black] ]
|
||||
readonly property var pressedColor: [ colorFinish[white], colorFinish[blue], colorFinish[red], colorStart[black] ]
|
||||
readonly property var disabledColorStart: [ colorStart[white], colors.baseGrayHighlight]
|
||||
readonly property var disabledColorFinish: [ colorFinish[white], colors.baseGrayShadow]
|
||||
readonly property var disabledTextColor: [ colors.lightGrayText, colors.baseGrayShadow]
|
||||
readonly property int radius: 5
|
||||
}
|
||||
|
||||
|
@ -217,4 +197,103 @@ Item {
|
|||
id: effects
|
||||
readonly property int fadeInDuration: 300
|
||||
}
|
||||
Item {
|
||||
id: glyphs
|
||||
readonly property string noIcon: ""
|
||||
readonly property string hmd: "b"
|
||||
readonly property string screen: "c"
|
||||
readonly property string keyboard: "d"
|
||||
readonly property string handControllers: "e"
|
||||
readonly property string headphonesMic: "f"
|
||||
readonly property string gamepad: "g"
|
||||
readonly property string headphones: "h"
|
||||
readonly property string mic: "i"
|
||||
readonly property string upload: "j"
|
||||
readonly property string script: "k"
|
||||
readonly property string text: "l"
|
||||
readonly property string cube: "m"
|
||||
readonly property string sphere: "n"
|
||||
readonly property string zone: "o"
|
||||
readonly property string light: "p"
|
||||
readonly property string web: "q"
|
||||
readonly property string web2: "r"
|
||||
readonly property string edit: "s"
|
||||
readonly property string market: "t"
|
||||
readonly property string directory: "u"
|
||||
readonly property string menu: "v"
|
||||
readonly property string close: "w"
|
||||
readonly property string closeInverted: "x"
|
||||
readonly property string pin: "y"
|
||||
readonly property string pinInverted: "z"
|
||||
readonly property string resizeHandle: "A"
|
||||
readonly property string disclosureExpand: "B"
|
||||
readonly property string reloadSmall: "a"
|
||||
readonly property string closeSmall: "C"
|
||||
readonly property string forward: "D"
|
||||
readonly property string backward: "E"
|
||||
readonly property string reload: "F"
|
||||
readonly property string unmuted: "G"
|
||||
readonly property string muted: "H"
|
||||
readonly property string minimize: "I"
|
||||
readonly property string maximize: "J"
|
||||
readonly property string maximizeInverted: "K"
|
||||
readonly property string disclosureButtonExpand: "L"
|
||||
readonly property string disclosureButtonCollapse: "M"
|
||||
readonly property string scriptStop: "N"
|
||||
readonly property string scriptReload: "O"
|
||||
readonly property string scriptRun: "P"
|
||||
readonly property string scriptNew: "Q"
|
||||
readonly property string hifiForum: "2"
|
||||
readonly property string hifiLogoSmall: "S"
|
||||
readonly property string avatar1: "T"
|
||||
readonly property string placemark: "U"
|
||||
readonly property string box: "V"
|
||||
readonly property string community: "0"
|
||||
readonly property string grabHandle: "X"
|
||||
readonly property string search: "Y"
|
||||
readonly property string disclosureCollapse: "Z"
|
||||
readonly property string scriptUpload: "R"
|
||||
readonly property string code: "W"
|
||||
readonly property string avatar: "<"
|
||||
readonly property string arrowsH: ":"
|
||||
readonly property string arrowsV: ";"
|
||||
readonly property string arrows: "`"
|
||||
readonly property string compress: "!"
|
||||
readonly property string expand: "\""
|
||||
readonly property string placemark1: "#"
|
||||
readonly property string circle: "$"
|
||||
readonly property string handPointer: "9"
|
||||
readonly property string plusSquareO: "%"
|
||||
readonly property string sliders: "&"
|
||||
readonly property string square: "'"
|
||||
readonly property string alignCenter: "8"
|
||||
readonly property string alignJustify: ")"
|
||||
readonly property string alignLeft: "*"
|
||||
readonly property string alignRight: "^"
|
||||
readonly property string bars: "7"
|
||||
readonly property string circleSlash: ","
|
||||
readonly property string sync: "()"
|
||||
readonly property string key: "-"
|
||||
readonly property string link: "."
|
||||
readonly property string location: "/"
|
||||
readonly property string caratR: "3"
|
||||
readonly property string caratL: "4"
|
||||
readonly property string caratDn: "5"
|
||||
readonly property string caratUp: "6"
|
||||
readonly property string folderLg: ">"
|
||||
readonly property string folderSm: "?"
|
||||
readonly property string levelUp: "1"
|
||||
readonly property string info: "["
|
||||
readonly property string question: "]"
|
||||
readonly property string alert: "+"
|
||||
readonly property string home: "_"
|
||||
readonly property string error: "="
|
||||
readonly property string settings: "@"
|
||||
readonly property string trash: "{"
|
||||
readonly property string objectGroup: ""
|
||||
readonly property string cm: "}"
|
||||
readonly property string msvg79: "~"
|
||||
readonly property string deg: "\\"
|
||||
readonly property string px: "|"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -141,6 +141,7 @@
|
|||
#include "ModelPackager.h"
|
||||
#include "PluginContainerProxy.h"
|
||||
#include "scripting/AccountScriptingInterface.h"
|
||||
#include "scripting/AssetMappingsScriptingInterface.h"
|
||||
#include "scripting/AudioDeviceScriptingInterface.h"
|
||||
#include "scripting/ClipboardScriptingInterface.h"
|
||||
#include "scripting/DesktopScriptingInterface.h"
|
||||
|
@ -158,7 +159,6 @@
|
|||
#include "Stars.h"
|
||||
#include "ui/AddressBarDialog.h"
|
||||
#include "ui/AvatarInputs.h"
|
||||
#include "ui/AssetUploadDialogFactory.h"
|
||||
#include "ui/DialogsManager.h"
|
||||
#include "ui/LoginDialog.h"
|
||||
#include "ui/overlays/Cube3DOverlay.h"
|
||||
|
@ -1295,6 +1295,7 @@ void Application::initializeUi() {
|
|||
rootContext->setContextProperty("Quat", new Quat());
|
||||
rootContext->setContextProperty("Vec3", new Vec3());
|
||||
rootContext->setContextProperty("Uuid", new ScriptUUID());
|
||||
rootContext->setContextProperty("Assets", new AssetMappingsScriptingInterface());
|
||||
|
||||
rootContext->setContextProperty("AvatarList", DependencyManager::get<AvatarManager>().data());
|
||||
|
||||
|
@ -4018,9 +4019,6 @@ void Application::nodeAdded(SharedNodePointer node) {
|
|||
if (node->getType() == NodeType::AvatarMixer) {
|
||||
// new avatar mixer, send off our identity packet right away
|
||||
getMyAvatar()->sendIdentityPacket();
|
||||
} else if (node->getType() == NodeType::AssetServer) {
|
||||
// the addition of an asset-server always re-enables the upload to asset server menu option
|
||||
Menu::getInstance()->getActionForOption(MenuOption::UploadAsset)->setEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4070,10 +4068,6 @@ void Application::nodeKilled(SharedNodePointer node) {
|
|||
} else if (node->getType() == NodeType::AvatarMixer) {
|
||||
// our avatar mixer has gone away - clear the hash of avatars
|
||||
DependencyManager::get<AvatarManager>()->clearOtherAvatars();
|
||||
} else if (node->getType() == NodeType::AssetServer
|
||||
&& !DependencyManager::get<NodeList>()->soloNodeOfType(NodeType::AssetServer)) {
|
||||
// this was our last asset server - disable the menu option to upload an asset
|
||||
Menu::getInstance()->getActionForOption(MenuOption::UploadAsset)->setEnabled(false);
|
||||
}
|
||||
}
|
||||
void Application::trackIncomingOctreePacket(ReceivedMessage& message, SharedNodePointer sendingNode, bool wasStatsPacket) {
|
||||
|
@ -4299,7 +4293,10 @@ bool Application::acceptURL(const QString& urlString, bool defaultUpload) {
|
|||
}
|
||||
}
|
||||
|
||||
return defaultUpload && askToUploadAsset(urlString);
|
||||
if (defaultUpload) {
|
||||
toggleAssetServerWidget(urlString);
|
||||
}
|
||||
return defaultUpload;
|
||||
}
|
||||
|
||||
void Application::setSessionUUID(const QUuid& sessionUUID) {
|
||||
|
@ -4327,8 +4324,8 @@ bool Application::askToSetAvatarUrl(const QString& url) {
|
|||
|
||||
case FSTReader::HEAD_AND_BODY_MODEL:
|
||||
ok = QMessageBox::Ok == OffscreenUi::question("Set Avatar",
|
||||
"Would you like to use '" + modelName + "' for your avatar?",
|
||||
QMessageBox::Ok | QMessageBox::Cancel, QMessageBox::Ok);
|
||||
"Would you like to use '" + modelName + "' for your avatar?",
|
||||
QMessageBox::Ok | QMessageBox::Cancel, QMessageBox::Ok);
|
||||
break;
|
||||
|
||||
default:
|
||||
|
@ -4361,79 +4358,6 @@ bool Application::askToLoadScript(const QString& scriptFilenameOrURL) {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool Application::askToUploadAsset(const QString& filename) {
|
||||
if (!DependencyManager::get<NodeList>()->getThisNodeCanRez()) {
|
||||
OffscreenUi::warning(_window, "Failed Upload",
|
||||
QString("You don't have upload rights on that domain.\n\n"));
|
||||
return false;
|
||||
}
|
||||
|
||||
QUrl url { filename };
|
||||
if (auto upload = DependencyManager::get<AssetClient>()->createUpload(url.toLocalFile())) {
|
||||
|
||||
QMessageBox messageBox;
|
||||
messageBox.setWindowTitle("Asset upload");
|
||||
messageBox.setText("You are about to upload the following file to the asset server:\n" +
|
||||
url.toDisplayString());
|
||||
messageBox.setInformativeText("Do you want to continue?");
|
||||
messageBox.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel);
|
||||
messageBox.setDefaultButton(QMessageBox::Ok);
|
||||
|
||||
// Option to drop model in world for models
|
||||
if (filename.endsWith(FBX_EXTENSION, Qt::CaseInsensitive) || filename.endsWith(OBJ_EXTENSION, Qt::CaseInsensitive)) {
|
||||
auto checkBox = new QCheckBox(&messageBox);
|
||||
checkBox->setText("Add to scene");
|
||||
messageBox.setCheckBox(checkBox);
|
||||
}
|
||||
|
||||
if (messageBox.exec() != QMessageBox::Ok) {
|
||||
upload->deleteLater();
|
||||
return false;
|
||||
}
|
||||
|
||||
// connect to the finished signal so we know when the AssetUpload is done
|
||||
if (messageBox.checkBox() && (messageBox.checkBox()->checkState() == Qt::Checked)) {
|
||||
// Custom behavior for models
|
||||
QObject::connect(upload, &AssetUpload::finished, this, &Application::modelUploadFinished);
|
||||
} else {
|
||||
QObject::connect(upload, &AssetUpload::finished,
|
||||
&AssetUploadDialogFactory::getInstance(),
|
||||
&AssetUploadDialogFactory::handleUploadFinished);
|
||||
}
|
||||
|
||||
// start the upload now
|
||||
upload->start();
|
||||
return true;
|
||||
}
|
||||
|
||||
// display a message box with the error
|
||||
OffscreenUi::warning(_window, "Failed Upload", QString("Failed to upload %1.\n\n").arg(filename));
|
||||
return false;
|
||||
}
|
||||
|
||||
void Application::modelUploadFinished(AssetUpload* upload, const QString& hash) {
|
||||
auto filename = QFileInfo(upload->getFilename()).fileName();
|
||||
|
||||
if ((upload->getError() == AssetUpload::NoError) &&
|
||||
(FBX_EXTENSION.endsWith(upload->getExtension(), Qt::CaseInsensitive) ||
|
||||
OBJ_EXTENSION.endsWith(upload->getExtension(), Qt::CaseInsensitive))) {
|
||||
|
||||
auto entities = DependencyManager::get<EntityScriptingInterface>();
|
||||
|
||||
EntityItemProperties properties;
|
||||
properties.setType(EntityTypes::Model);
|
||||
properties.setModelURL(QString("%1:%2.%3").arg(URL_SCHEME_ATP).arg(hash).arg(upload->getExtension()));
|
||||
properties.setPosition(_myCamera.getPosition() + _myCamera.getOrientation() * Vectors::FRONT * 2.0f);
|
||||
properties.setName(QUrl(upload->getFilename()).fileName());
|
||||
|
||||
entities->addEntity(properties);
|
||||
|
||||
upload->deleteLater();
|
||||
} else {
|
||||
AssetUploadDialogFactory::getInstance().handleUploadFinished(upload, hash);
|
||||
}
|
||||
}
|
||||
|
||||
bool Application::askToWearAvatarAttachmentUrl(const QString& url) {
|
||||
|
||||
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
|
||||
|
@ -4533,6 +4457,22 @@ void Application::toggleRunningScriptsWidget() {
|
|||
//}
|
||||
}
|
||||
|
||||
void Application::toggleAssetServerWidget(QString filePath) {
|
||||
if (!DependencyManager::get<NodeList>()->getThisNodeCanRez()) {
|
||||
return;
|
||||
}
|
||||
|
||||
static const QUrl url { "AssetServer.qml" };
|
||||
|
||||
auto startUpload = [=](QQmlContext* context, QObject* newObject){
|
||||
if (!filePath.isEmpty()) {
|
||||
emit uploadRequest(filePath);
|
||||
}
|
||||
};
|
||||
DependencyManager::get<OffscreenUi>()->show(url, "AssetServer", startUpload);
|
||||
startUpload(nullptr, nullptr);
|
||||
}
|
||||
|
||||
void Application::packageModel() {
|
||||
ModelPackager::package();
|
||||
}
|
||||
|
|
|
@ -231,6 +231,8 @@ signals:
|
|||
void beforeAboutToQuit();
|
||||
void activeDisplayPluginChanged();
|
||||
|
||||
void uploadRequest(QString path);
|
||||
|
||||
public slots:
|
||||
QVector<EntityItemID> pasteEntities(float x, float y, float z);
|
||||
bool exportEntities(const QString& filename, const QVector<EntityItemID>& entityIDs);
|
||||
|
@ -242,6 +244,7 @@ public slots:
|
|||
Q_INVOKABLE void loadScriptURLDialog();
|
||||
void toggleLogDialog();
|
||||
void toggleRunningScriptsWidget();
|
||||
void toggleAssetServerWidget(QString filePath = "");
|
||||
|
||||
void handleLocalServerConnection();
|
||||
void readArgumentsFromLocalSocket();
|
||||
|
@ -303,8 +306,6 @@ private slots:
|
|||
bool acceptSnapshot(const QString& urlString);
|
||||
bool askToSetAvatarUrl(const QString& url);
|
||||
bool askToLoadScript(const QString& scriptFilenameOrURL);
|
||||
bool askToUploadAsset(const QString& asset);
|
||||
void modelUploadFinished(AssetUpload* upload, const QString& hash);
|
||||
|
||||
bool askToWearAvatarAttachmentUrl(const QString& url);
|
||||
void displayAvatarAttachmentWarning(const QString& message) const;
|
||||
|
|
|
@ -35,7 +35,6 @@
|
|||
#include "MainWindow.h"
|
||||
#include "render/DrawStatus.h"
|
||||
#include "scripting/MenuScriptingInterface.h"
|
||||
#include "ui/AssetUploadDialogFactory.h"
|
||||
#include "ui/DialogsManager.h"
|
||||
#include "ui/StandAloneJSConsole.h"
|
||||
#include "InterfaceLogging.h"
|
||||
|
@ -92,7 +91,7 @@ Menu::Menu() {
|
|||
redoAction->setShortcut(Qt::CTRL | Qt::SHIFT | Qt::Key_Z);
|
||||
addActionToQMenuAndActionHash(editMenu, redoAction);
|
||||
|
||||
// Edit > Running Sccripts
|
||||
// Edit > Running Scripts
|
||||
addActionToQMenuAndActionHash(editMenu, MenuOption::RunningScripts, Qt::CTRL | Qt::Key_J,
|
||||
qApp, SLOT(toggleRunningScriptsWidget()));
|
||||
|
||||
|
@ -128,10 +127,20 @@ Menu::Menu() {
|
|||
SLOT(toggleConsole()),
|
||||
QAction::NoRole, UNSPECIFIED_POSITION, "Advanced");
|
||||
|
||||
editMenu->addSeparator();
|
||||
|
||||
// Edit > My Asset Server
|
||||
auto assetServerAction = addActionToQMenuAndActionHash(editMenu, MenuOption::AssetServer,
|
||||
Qt::CTRL | Qt::SHIFT | Qt::Key_A,
|
||||
qApp, SLOT(toggleAssetServerWidget()));
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
QObject::connect(nodeList.data(), &NodeList::canRezChanged, assetServerAction, &QAction::setEnabled);
|
||||
assetServerAction->setEnabled(nodeList->getThisNodeCanRez());
|
||||
|
||||
// Edit > Reload All Content [advanced]
|
||||
addActionToQMenuAndActionHash(editMenu, MenuOption::ReloadContent, 0, qApp, SLOT(reloadResourceCaches()),
|
||||
QAction::NoRole, UNSPECIFIED_POSITION, "Advanced");
|
||||
|
||||
|
||||
|
||||
// Edit > Package Model... [advanced]
|
||||
addActionToQMenuAndActionHash(editMenu, MenuOption::PackageModel, 0,
|
||||
|
@ -354,17 +363,6 @@ Menu::Menu() {
|
|||
|
||||
// Developer > Assets >>>
|
||||
MenuWrapper* assetDeveloperMenu = developerMenu->addMenu("Assets");
|
||||
auto& assetDialogFactory = AssetUploadDialogFactory::getInstance();
|
||||
assetDialogFactory.setDialogParent(this);
|
||||
QAction* assetUpload = addActionToQMenuAndActionHash(assetDeveloperMenu,
|
||||
MenuOption::UploadAsset,
|
||||
0,
|
||||
&assetDialogFactory,
|
||||
SLOT(showDialog()));
|
||||
|
||||
// disable the asset upload action by default - it gets enabled only if asset server becomes present
|
||||
assetUpload->setEnabled(false);
|
||||
|
||||
auto& atpMigrator = ATPAssetMigrator::getInstance();
|
||||
atpMigrator.setDialogParent(this);
|
||||
|
||||
|
|
|
@ -34,6 +34,7 @@ namespace MenuOption {
|
|||
const QString AnimDebugDrawDefaultPose = "Debug Draw Default Pose";
|
||||
const QString AnimDebugDrawPosition= "Debug Draw Position";
|
||||
const QString AssetMigration = "ATP Asset Migration";
|
||||
const QString AssetServer = "My Asset Server";
|
||||
const QString Attachments = "Attachments...";
|
||||
const QString AudioNetworkStats = "Audio Network Stats";
|
||||
const QString AudioNoiseReduction = "Audio Noise Reduction";
|
||||
|
@ -168,7 +169,6 @@ namespace MenuOption {
|
|||
const QString ToolWindow = "Tool Window";
|
||||
const QString TransmitterDrive = "Transmitter Drive";
|
||||
const QString TurnWithHead = "Turn using Head";
|
||||
const QString UploadAsset = "Upload File to Asset Server";
|
||||
const QString UseAudioForMouth = "Use Audio for Mouth";
|
||||
const QString UseCamera = "Use Camera";
|
||||
const QString UseAnimPreAndPostRotations = "Use Anim Pre and Post Rotations";
|
||||
|
|
|
@ -24,9 +24,9 @@
|
|||
#include <AssetClient.h>
|
||||
#include <AssetUpload.h>
|
||||
#include <ResourceManager.h>
|
||||
#include <MappingRequest.h>
|
||||
|
||||
#include "OffscreenUi.h"
|
||||
#include "../ui/AssetUploadDialogFactory.h"
|
||||
|
||||
Q_DECLARE_LOGGING_CATEGORY(asset_migrator);
|
||||
Q_LOGGING_CATEGORY(asset_migrator, "hf.asset_migrator");
|
||||
|
@ -38,18 +38,18 @@ ATPAssetMigrator& ATPAssetMigrator::getInstance() {
|
|||
|
||||
static const QString ENTITIES_OBJECT_KEY = "Entities";
|
||||
static const QString MODEL_URL_KEY = "modelURL";
|
||||
static const QString COMPOUND_SHAPE_URL_KEY = "compoundShapeURL";
|
||||
static const QString MESSAGE_BOX_TITLE = "ATP Asset Migration";
|
||||
|
||||
void ATPAssetMigrator::loadEntityServerFile() {
|
||||
auto filename = QFileDialog::getOpenFileName(_dialogParent, "Select an entity-server content file to migrate",
|
||||
QString(), QString("Entity-Server Content (*.gz)"));
|
||||
auto filename = OffscreenUi::getOpenFileName(_dialogParent, tr("Select an entity-server content file to migrate"), QString(), tr("Entity-Server Content (*.gz)"));
|
||||
|
||||
if (!filename.isEmpty()) {
|
||||
qCDebug(asset_migrator) << "Selected filename for ATP asset migration: " << filename;
|
||||
|
||||
static const QString MIGRATION_CONFIRMATION_TEXT {
|
||||
"The ATP Asset Migration process will scan the selected entity-server file, upload discovered resources to the"\
|
||||
" current asset-server and then save a new entity-server file with the ATP URLs.\n\nAre you ready to"\
|
||||
"The ATP Asset Migration process will scan the selected entity-server file,\nupload discovered resources to the"\
|
||||
" current asset-server\nand then save a new entity-server file with the ATP URLs.\n\nAre you ready to"\
|
||||
" continue?\n\nMake sure you are connected to the right domain."
|
||||
};
|
||||
|
||||
|
@ -77,57 +77,75 @@ void ATPAssetMigrator::loadEntityServerFile() {
|
|||
for (auto jsonValue : _entitiesArray) {
|
||||
QJsonObject entityObject = jsonValue.toObject();
|
||||
QString modelURLString = entityObject.value(MODEL_URL_KEY).toString();
|
||||
|
||||
if (!modelURLString.isEmpty()) {
|
||||
QUrl modelURL = QUrl(modelURLString);
|
||||
|
||||
if (!_ignoredUrls.contains(modelURL)
|
||||
&& (modelURL.scheme() == URL_SCHEME_HTTP || modelURL.scheme() == URL_SCHEME_HTTPS
|
||||
|| modelURL.scheme() == URL_SCHEME_FILE || modelURL.scheme() == URL_SCHEME_FTP)) {
|
||||
|
||||
if (_pendingReplacements.contains(modelURL)) {
|
||||
// we already have a request out for this asset, just store the QJsonValueRef
|
||||
// so we can do the hash replacement when the request comes back
|
||||
_pendingReplacements.insert(modelURL, jsonValue);
|
||||
} else if (_uploadedAssets.contains(modelURL)) {
|
||||
// we already have a hash for this asset
|
||||
// so just do the replacement immediately
|
||||
entityObject[MODEL_URL_KEY] = _uploadedAssets.value(modelURL).toString();
|
||||
jsonValue = entityObject;
|
||||
} else if (wantsToMigrateResource(modelURL)) {
|
||||
auto request = ResourceManager::createResourceRequest(this, modelURL);
|
||||
|
||||
if (request) {
|
||||
qCDebug(asset_migrator) << "Requesting" << modelURL << "for ATP asset migration";
|
||||
|
||||
// add this combination of QUrl and QJsonValueRef to our multi hash so we can change the URL
|
||||
// to an ATP one once ready
|
||||
_pendingReplacements.insert(modelURL, jsonValue);
|
||||
|
||||
connect(request, &ResourceRequest::finished, this, [=]() {
|
||||
if (request->getResult() == ResourceRequest::Success) {
|
||||
migrateResource(request);
|
||||
QString compoundURLString = entityObject.value(COMPOUND_SHAPE_URL_KEY).toString();
|
||||
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
bool isModelURL = (i == 0);
|
||||
quint8 replacementType = i;
|
||||
auto migrationURLString = (isModelURL) ? modelURLString : compoundURLString;
|
||||
|
||||
if (!migrationURLString.isEmpty()) {
|
||||
QUrl migrationURL = QUrl(migrationURLString);
|
||||
|
||||
if (!_ignoredUrls.contains(migrationURL)
|
||||
&& (migrationURL.scheme() == URL_SCHEME_HTTP || migrationURL.scheme() == URL_SCHEME_HTTPS
|
||||
|| migrationURL.scheme() == URL_SCHEME_FILE || migrationURL.scheme() == URL_SCHEME_FTP)) {
|
||||
|
||||
if (_pendingReplacements.contains(migrationURL)) {
|
||||
// we already have a request out for this asset, just store the QJsonValueRef
|
||||
// so we can do the hash replacement when the request comes back
|
||||
_pendingReplacements.insert(migrationURL, { jsonValue, replacementType });
|
||||
} else if (_uploadedAssets.contains(migrationURL)) {
|
||||
// we already have a hash for this asset
|
||||
// so just do the replacement immediately
|
||||
if (isModelURL) {
|
||||
entityObject[MODEL_URL_KEY] = _uploadedAssets.value(migrationURL).toString();
|
||||
} else {
|
||||
OffscreenUi::warning(_dialogParent, "Error",
|
||||
QString("Could not retrieve asset at %1").arg(modelURL.toString()));
|
||||
entityObject[COMPOUND_SHAPE_URL_KEY] = _uploadedAssets.value(migrationURL).toString();
|
||||
}
|
||||
request->deleteLater();
|
||||
});
|
||||
|
||||
request->send();
|
||||
} else {
|
||||
OffscreenUi::warning(_dialogParent, "Error",
|
||||
QString("Could not create request for asset at %1").arg(modelURL.toString()));
|
||||
|
||||
jsonValue = entityObject;
|
||||
} else if (wantsToMigrateResource(migrationURL)) {
|
||||
auto request = ResourceManager::createResourceRequest(this, migrationURL);
|
||||
|
||||
if (request) {
|
||||
qCDebug(asset_migrator) << "Requesting" << migrationURL << "for ATP asset migration";
|
||||
|
||||
// add this combination of QUrl and QJsonValueRef to our multi hash so we can change the URL
|
||||
// to an ATP one once ready
|
||||
_pendingReplacements.insert(migrationURL, { jsonValue, (isModelURL ? 0 : 1)});
|
||||
|
||||
connect(request, &ResourceRequest::finished, this, [=]() {
|
||||
if (request->getResult() == ResourceRequest::Success) {
|
||||
migrateResource(request);
|
||||
} else {
|
||||
++_errorCount;
|
||||
_pendingReplacements.remove(migrationURL);
|
||||
qWarning() << "Could not retrieve asset at" << migrationURL.toString();
|
||||
|
||||
checkIfFinished();
|
||||
}
|
||||
request->deleteLater();
|
||||
});
|
||||
|
||||
request->send();
|
||||
} else {
|
||||
++_errorCount;
|
||||
qWarning() << "Count not create request for asset at" << migrationURL.toString();
|
||||
}
|
||||
|
||||
} else {
|
||||
_ignoredUrls.insert(migrationURL);
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
_ignoredUrls.insert(modelURL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
_doneReading = true;
|
||||
|
||||
checkIfFinished();
|
||||
|
||||
} else {
|
||||
OffscreenUi::warning(_dialogParent, "Error",
|
||||
|
@ -140,76 +158,109 @@ void ATPAssetMigrator::migrateResource(ResourceRequest* request) {
|
|||
// use an asset client to upload the asset
|
||||
auto assetClient = DependencyManager::get<AssetClient>();
|
||||
|
||||
QFileInfo assetInfo { request->getUrl().fileName() };
|
||||
|
||||
auto upload = assetClient->createUpload(request->getData(), assetInfo.completeSuffix());
|
||||
|
||||
if (upload) {
|
||||
// add this URL to our hash of AssetUpload to original URL
|
||||
_originalURLs.insert(upload, request->getUrl());
|
||||
|
||||
qCDebug(asset_migrator) << "Starting upload of asset from" << request->getUrl();
|
||||
|
||||
// connect to the finished signal so we know when the AssetUpload is done
|
||||
QObject::connect(upload, &AssetUpload::finished, this, &ATPAssetMigrator::assetUploadFinished);
|
||||
|
||||
// start the upload now
|
||||
upload->start();
|
||||
} else {
|
||||
// show a QMessageBox to say that there is no local asset server
|
||||
QString messageBoxText = QString("Could not upload \n\n%1\n\nbecause you are currently not connected" \
|
||||
" to a local asset-server.").arg(assetInfo.fileName());
|
||||
|
||||
QMessageBox::information(_dialogParent, "Failed to Upload", messageBoxText);
|
||||
}
|
||||
auto upload = assetClient->createUpload(request->getData());
|
||||
|
||||
// add this URL to our hash of AssetUpload to original URL
|
||||
_originalURLs.insert(upload, request->getUrl());
|
||||
|
||||
qCDebug(asset_migrator) << "Starting upload of asset from" << request->getUrl();
|
||||
|
||||
// connect to the finished signal so we know when the AssetUpload is done
|
||||
QObject::connect(upload, &AssetUpload::finished, this, &ATPAssetMigrator::assetUploadFinished);
|
||||
|
||||
// start the upload now
|
||||
upload->start();
|
||||
}
|
||||
|
||||
void ATPAssetMigrator::assetUploadFinished(AssetUpload *upload, const QString& hash) {
|
||||
// remove this migrationURL from the key for the AssetUpload pointer
|
||||
auto migrationURL = _originalURLs.take(upload);
|
||||
|
||||
if (upload->getError() == AssetUpload::NoError) {
|
||||
|
||||
const auto& modelURL = _originalURLs[upload];
|
||||
|
||||
// successfully uploaded asset - make any required replacements found in the pending replacements
|
||||
auto values = _pendingReplacements.values(modelURL);
|
||||
|
||||
|
||||
QString atpURL = getATPUrl(hash, upload->getExtension()).toString();
|
||||
|
||||
for (auto value : values) {
|
||||
// replace the modelURL in this QJsonValueRef with the hash
|
||||
QJsonObject valueObject = value.toObject();
|
||||
valueObject[MODEL_URL_KEY] = atpURL;
|
||||
value = valueObject;
|
||||
}
|
||||
|
||||
// add this URL to our list of uploaded assets
|
||||
_uploadedAssets.insert(modelURL, atpURL);
|
||||
|
||||
// pull the replaced models from _pendingReplacements
|
||||
_pendingReplacements.remove(modelURL);
|
||||
|
||||
// are we out of pending replacements? if so it is time to save the entity-server file
|
||||
if (_doneReading && _pendingReplacements.empty()) {
|
||||
saveEntityServerFile();
|
||||
|
||||
// reset after the attempted save, success or fail
|
||||
reset();
|
||||
}
|
||||
// use the path of the migrationURL to add a mapping in the Asset Server
|
||||
auto assetClient = DependencyManager::get<AssetClient>();
|
||||
auto setMappingRequest = assetClient->createSetMappingRequest(migrationURL.path(), hash);
|
||||
|
||||
connect(setMappingRequest, &SetMappingRequest::finished, this, &ATPAssetMigrator::setMappingFinished);
|
||||
|
||||
// add this migrationURL with the key for the SetMappingRequest pointer
|
||||
_originalURLs[setMappingRequest] = migrationURL;
|
||||
|
||||
setMappingRequest->start();
|
||||
} else {
|
||||
AssetUploadDialogFactory::showErrorDialog(upload, _dialogParent);
|
||||
// this is a fail for this modelURL, remove it from pending replacements
|
||||
_pendingReplacements.remove(migrationURL);
|
||||
|
||||
++_errorCount;
|
||||
qWarning() << "Failed to upload" << migrationURL << "- error was" << upload->getErrorString();
|
||||
}
|
||||
|
||||
checkIfFinished();
|
||||
|
||||
upload->deleteLater();
|
||||
}
|
||||
|
||||
void ATPAssetMigrator::setMappingFinished(SetMappingRequest* request) {
|
||||
// take the migrationURL for this SetMappingRequest
|
||||
auto migrationURL = _originalURLs.take(request);
|
||||
|
||||
if (request->getError() == MappingRequest::NoError) {
|
||||
|
||||
// successfully uploaded asset - make any required replacements found in the pending replacements
|
||||
auto values = _pendingReplacements.values(migrationURL);
|
||||
|
||||
QString atpURL = QString("atp:%1").arg(request->getPath());
|
||||
|
||||
for (auto value : values) {
|
||||
// replace the modelURL in this QJsonValueRef with the hash
|
||||
QJsonObject valueObject = value.first.toObject();
|
||||
|
||||
if (value.second == 0) {
|
||||
valueObject[MODEL_URL_KEY] = atpURL;
|
||||
} else {
|
||||
valueObject[COMPOUND_SHAPE_URL_KEY] = atpURL;
|
||||
}
|
||||
|
||||
value.first = valueObject;
|
||||
}
|
||||
|
||||
// add this URL to our list of uploaded assets
|
||||
_uploadedAssets.insert(migrationURL, atpURL);
|
||||
|
||||
// pull the replaced urls from _pendingReplacements
|
||||
_pendingReplacements.remove(migrationURL);
|
||||
} else {
|
||||
// this is a fail for this migrationURL, remove it from pending replacements
|
||||
_pendingReplacements.remove(migrationURL);
|
||||
|
||||
++_errorCount;
|
||||
qWarning() << "Error setting mapping for" << migrationURL << "- error was " << request->getErrorString();
|
||||
}
|
||||
|
||||
checkIfFinished();
|
||||
|
||||
request->deleteLater();
|
||||
}
|
||||
|
||||
void ATPAssetMigrator::checkIfFinished() {
|
||||
// are we out of pending replacements? if so it is time to save the entity-server file
|
||||
if (_doneReading && _pendingReplacements.empty()) {
|
||||
saveEntityServerFile();
|
||||
|
||||
// reset after the attempted save, success or fail
|
||||
reset();
|
||||
}
|
||||
}
|
||||
|
||||
bool ATPAssetMigrator::wantsToMigrateResource(const QUrl& url) {
|
||||
static bool hasAskedForCompleteMigration { false };
|
||||
static bool wantsCompleteMigration { false };
|
||||
|
||||
if (!hasAskedForCompleteMigration) {
|
||||
// this is the first resource migration - ask the user if they just want to migrate everything
|
||||
static const QString COMPLETE_MIGRATION_TEXT { "Do you want to migrate all assets found in this entity-server file?\n\n"\
|
||||
"Select \"Yes\" to upload all discovered assets to the current asset-server immediately.\n\n"\
|
||||
static const QString COMPLETE_MIGRATION_TEXT { "Do you want to migrate all assets found in this entity-server file?\n"\
|
||||
"Select \"Yes\" to upload all discovered assets to the current asset-server immediately.\n"\
|
||||
"Select \"No\" to be prompted for each discovered asset."
|
||||
};
|
||||
|
||||
|
@ -236,7 +287,7 @@ bool ATPAssetMigrator::wantsToMigrateResource(const QUrl& url) {
|
|||
|
||||
void ATPAssetMigrator::saveEntityServerFile() {
|
||||
// show a dialog to ask the user where they want to save the file
|
||||
QString saveName = QFileDialog::getSaveFileName(_dialogParent, "Save Migrated Entities File");
|
||||
QString saveName = OffscreenUi::getSaveFileName(_dialogParent, "Save Migrated Entities File");
|
||||
|
||||
QFile saveFile { saveName };
|
||||
|
||||
|
@ -251,9 +302,16 @@ void ATPAssetMigrator::saveEntityServerFile() {
|
|||
|
||||
saveFile.write(jsonDataForFile);
|
||||
saveFile.close();
|
||||
|
||||
QMessageBox::information(_dialogParent, "Success",
|
||||
QString("Your new entities file has been saved at %1").arg(saveName));
|
||||
|
||||
QString infoMessage = QString("Your new entities file has been saved at\n%1.").arg(saveName);
|
||||
|
||||
if (_errorCount > 0) {
|
||||
infoMessage += QString("\nThere were %1 models that could not be migrated.\n").arg(_errorCount);
|
||||
infoMessage += "Check the warnings in your log for details.\n";
|
||||
infoMessage += "You can re-attempt migration on those models\nby restarting this process with the newly saved file.";
|
||||
}
|
||||
|
||||
OffscreenUi::information(_dialogParent, "Success", infoMessage);
|
||||
} else {
|
||||
OffscreenUi::warning(_dialogParent, "Error", "Could not gzip JSON data for new entities file.");
|
||||
}
|
||||
|
@ -271,4 +329,5 @@ void ATPAssetMigrator::reset() {
|
|||
_uploadedAssets.clear();
|
||||
_originalURLs.clear();
|
||||
_ignoredUrls.clear();
|
||||
_errorCount = 0;
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
|
||||
class AssetUpload;
|
||||
class ResourceRequest;
|
||||
class SetMappingRequest;
|
||||
|
||||
class ATPAssetMigrator : public QObject {
|
||||
Q_OBJECT
|
||||
|
@ -32,8 +33,11 @@ public slots:
|
|||
void loadEntityServerFile();
|
||||
private slots:
|
||||
void assetUploadFinished(AssetUpload* upload, const QString& hash);
|
||||
void setMappingFinished(SetMappingRequest* request);
|
||||
private:
|
||||
void migrateResource(ResourceRequest* request);
|
||||
|
||||
void checkIfFinished();
|
||||
void saveEntityServerFile();
|
||||
|
||||
void reset();
|
||||
|
@ -44,11 +48,14 @@ private:
|
|||
QJsonArray _entitiesArray;
|
||||
|
||||
bool _doneReading { false };
|
||||
|
||||
QMultiHash<QUrl, QJsonValueRef> _pendingReplacements;
|
||||
|
||||
using JSONTypePair = std::pair<QJsonValueRef, quint8>;
|
||||
|
||||
QMultiHash<QUrl, JSONTypePair> _pendingReplacements;
|
||||
QHash<QUrl, QUrl> _uploadedAssets;
|
||||
QHash<AssetUpload*, QUrl> _originalURLs;
|
||||
QHash<QObject*, QUrl> _originalURLs;
|
||||
QSet<QUrl> _ignoredUrls;
|
||||
int _errorCount { 0 };
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -39,7 +39,7 @@ int main(int argc, const char* argv[]) {
|
|||
static const DWORD BUG_SPLAT_FLAGS = MDSF_PREVENTHIJACKING | MDSF_USEGUARDMEMORY;
|
||||
static const char* BUG_SPLAT_DATABASE = "interface_alpha";
|
||||
static const char* BUG_SPLAT_APPLICATION_NAME = "Interface";
|
||||
MiniDmpSender mpSender { BUG_SPLAT_DATABASE, BUG_SPLAT_APPLICATION_NAME, BuildInfo::VERSION.toLatin1().constData(),
|
||||
MiniDmpSender mpSender { BUG_SPLAT_DATABASE, BUG_SPLAT_APPLICATION_NAME, qPrintable(BuildInfo::VERSION),
|
||||
nullptr, BUG_SPLAT_FLAGS };
|
||||
#endif
|
||||
|
||||
|
@ -143,6 +143,14 @@ int main(int argc, const char* argv[]) {
|
|||
|
||||
QObject::connect(&server, &QLocalServer::newConnection, &app, &Application::handleLocalServerConnection);
|
||||
|
||||
#ifdef HAS_BUGSPLAT
|
||||
AccountManager& accountManager = AccountManager::getInstance();
|
||||
mpSender.setDefaultUserName(qPrintable(accountManager.getAccountInfo().getUsername()));
|
||||
QObject::connect(&accountManager, &AccountManager::usernameChanged, &app, [&mpSender](const QString& newUsername) {
|
||||
mpSender.setDefaultUserName(qPrintable(newUsername));
|
||||
});
|
||||
#endif
|
||||
|
||||
QTranslator translator;
|
||||
translator.load("i18n/interface_en");
|
||||
app.installTranslator(&translator);
|
||||
|
|
284
interface/src/scripting/AssetMappingsScriptingInterface.cpp
Normal file
284
interface/src/scripting/AssetMappingsScriptingInterface.cpp
Normal file
|
@ -0,0 +1,284 @@
|
|||
//
|
||||
// AssetMappingsScriptingInterface.cpp
|
||||
// libraries/script-engine/src
|
||||
//
|
||||
// Created by Ryan Huffman on 2016-03-09.
|
||||
// Copyright 2016 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 "AssetMappingsScriptingInterface.h"
|
||||
|
||||
#include <QtScript/QScriptEngine>
|
||||
#include <QtCore/QFile>
|
||||
|
||||
#include <AssetRequest.h>
|
||||
#include <AssetUpload.h>
|
||||
#include <MappingRequest.h>
|
||||
#include <NetworkLogging.h>
|
||||
#include <OffscreenUi.h>
|
||||
|
||||
AssetMappingsScriptingInterface::AssetMappingsScriptingInterface() {
|
||||
_proxyModel.setSourceModel(&_assetMappingModel);
|
||||
_proxyModel.setSortRole(Qt::DisplayRole);
|
||||
_proxyModel.setDynamicSortFilter(true);
|
||||
_proxyModel.sort(0);
|
||||
}
|
||||
|
||||
void AssetMappingsScriptingInterface::setMapping(QString path, QString hash, QJSValue callback) {
|
||||
auto assetClient = DependencyManager::get<AssetClient>();
|
||||
auto request = assetClient->createSetMappingRequest(path, hash);
|
||||
|
||||
connect(request, &SetMappingRequest::finished, this, [this, callback](SetMappingRequest* request) mutable {
|
||||
if (callback.isCallable()) {
|
||||
QJSValueList args { request->getErrorString(), request->getPath() };
|
||||
callback.call(args);
|
||||
}
|
||||
|
||||
request->deleteLater();
|
||||
});
|
||||
|
||||
request->start();
|
||||
}
|
||||
|
||||
void AssetMappingsScriptingInterface::getMapping(QString path, QJSValue callback) {
|
||||
auto assetClient = DependencyManager::get<AssetClient>();
|
||||
auto request = assetClient->createGetMappingRequest(path);
|
||||
|
||||
connect(request, &GetMappingRequest::finished, this, [this, callback](GetMappingRequest* request) mutable {
|
||||
if (callback.isCallable()) {
|
||||
QJSValueList args { request->getErrorString() };
|
||||
callback.call(args);
|
||||
}
|
||||
|
||||
request->deleteLater();
|
||||
});
|
||||
|
||||
request->start();
|
||||
}
|
||||
|
||||
void AssetMappingsScriptingInterface::uploadFile(QString path, QString mapping, QJSValue startedCallback, QJSValue completedCallback, bool dropEvent) {
|
||||
static const QString helpText =
|
||||
"Upload your asset to a specific folder by entering the full path. Specifying\n"
|
||||
"a new folder name will automatically create that folder for you.";
|
||||
static const QString dropHelpText =
|
||||
"This file will be added to your Asset Server.\n"
|
||||
"Use the field below to place your file in a specific folder or to rename it.\n"
|
||||
"Specifying a new folder name will automatically create that folder for you.";
|
||||
|
||||
auto offscreenUi = DependencyManager::get<OffscreenUi>();
|
||||
auto result = offscreenUi->inputDialog(OffscreenUi::ICON_INFORMATION, "Specify Asset Path",
|
||||
dropEvent ? dropHelpText : helpText, mapping);
|
||||
|
||||
if (!result.isValid()) {
|
||||
completedCallback.call({ -1 });
|
||||
return;
|
||||
}
|
||||
mapping = result.toString();
|
||||
mapping = mapping.trimmed();
|
||||
|
||||
if (mapping[0] != '/') {
|
||||
mapping = "/" + mapping;
|
||||
}
|
||||
|
||||
// Check for override
|
||||
if (isKnownMapping(mapping)) {
|
||||
auto message = mapping + "\n" + "This file already exists. Do you want to overwrite it?";
|
||||
auto button = offscreenUi->messageBox(OffscreenUi::ICON_QUESTION, "Overwrite File", message,
|
||||
QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
|
||||
if (button == QMessageBox::No) {
|
||||
completedCallback.call({ -1 });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
startedCallback.call();
|
||||
|
||||
auto upload = DependencyManager::get<AssetClient>()->createUpload(path);
|
||||
QObject::connect(upload, &AssetUpload::finished, this, [=](AssetUpload* upload, const QString& hash) mutable {
|
||||
if (upload->getError() != AssetUpload::NoError) {
|
||||
if (completedCallback.isCallable()) {
|
||||
QJSValueList args { upload->getErrorString() };
|
||||
completedCallback.call(args);
|
||||
}
|
||||
} else {
|
||||
setMapping(mapping, hash, completedCallback);
|
||||
}
|
||||
|
||||
upload->deleteLater();
|
||||
});
|
||||
|
||||
upload->start();
|
||||
}
|
||||
|
||||
void AssetMappingsScriptingInterface::deleteMappings(QStringList paths, QJSValue callback) {
|
||||
auto assetClient = DependencyManager::get<AssetClient>();
|
||||
auto request = assetClient->createDeleteMappingsRequest(paths);
|
||||
|
||||
connect(request, &DeleteMappingsRequest::finished, this, [this, callback](DeleteMappingsRequest* request) mutable {
|
||||
if (callback.isCallable()) {
|
||||
QJSValueList args { request->getErrorString() };
|
||||
callback.call(args);
|
||||
}
|
||||
|
||||
request->deleteLater();
|
||||
});
|
||||
|
||||
request->start();
|
||||
}
|
||||
|
||||
void AssetMappingsScriptingInterface::getAllMappings(QJSValue callback) {
|
||||
auto assetClient = DependencyManager::get<AssetClient>();
|
||||
auto request = assetClient->createGetAllMappingsRequest();
|
||||
|
||||
connect(request, &GetAllMappingsRequest::finished, this, [this, callback](GetAllMappingsRequest* request) mutable {
|
||||
auto mappings = request->getMappings();
|
||||
auto map = callback.engine()->newObject();
|
||||
|
||||
for (auto& kv : mappings ) {
|
||||
map.setProperty(kv.first, kv.second);
|
||||
}
|
||||
|
||||
if (callback.isCallable()) {
|
||||
QJSValueList args { request->getErrorString(), map };
|
||||
callback.call(args);
|
||||
}
|
||||
|
||||
request->deleteLater();
|
||||
});
|
||||
|
||||
request->start();
|
||||
}
|
||||
|
||||
void AssetMappingsScriptingInterface::renameMapping(QString oldPath, QString newPath, QJSValue callback) {
|
||||
auto assetClient = DependencyManager::get<AssetClient>();
|
||||
auto request = assetClient->createRenameMappingRequest(oldPath, newPath);
|
||||
|
||||
connect(request, &RenameMappingRequest::finished, this, [this, callback](RenameMappingRequest* request) mutable {
|
||||
if (callback.isCallable()) {
|
||||
QJSValueList args { request->getErrorString() };
|
||||
callback.call(args);
|
||||
}
|
||||
|
||||
request->deleteLater();
|
||||
});
|
||||
|
||||
request->start();
|
||||
}
|
||||
|
||||
bool AssetMappingModel::isKnownFolder(QString path) const {
|
||||
if (!path.endsWith("/")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto existingPaths = _pathToItemMap.keys();
|
||||
for (auto& entry : existingPaths) {
|
||||
if (entry.startsWith(path)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static int assetMappingModelMetatypeId = qRegisterMetaType<AssetMappingModel*>("AssetMappingModel*");
|
||||
|
||||
void AssetMappingModel::refresh() {
|
||||
qDebug() << "Refreshing asset mapping model";
|
||||
auto assetClient = DependencyManager::get<AssetClient>();
|
||||
auto request = assetClient->createGetAllMappingsRequest();
|
||||
|
||||
connect(request, &GetAllMappingsRequest::finished, this, [this](GetAllMappingsRequest* request) mutable {
|
||||
if (request->getError() == MappingRequest::NoError) {
|
||||
auto mappings = request->getMappings();
|
||||
auto existingPaths = _pathToItemMap.keys();
|
||||
for (auto& mapping : mappings) {
|
||||
auto& path = mapping.first;
|
||||
auto parts = path.split("/");
|
||||
auto length = parts.length();
|
||||
|
||||
existingPaths.removeOne(mapping.first);
|
||||
|
||||
QString fullPath = "/";
|
||||
|
||||
QStandardItem* lastItem = nullptr;
|
||||
|
||||
// start index at 1 to avoid empty string from leading slash
|
||||
for (int i = 1; i < length; ++i) {
|
||||
fullPath += (i == 1 ? "" : "/") + parts[i];
|
||||
|
||||
auto it = _pathToItemMap.find(fullPath);
|
||||
if (it == _pathToItemMap.end()) {
|
||||
auto item = new QStandardItem(parts[i]);
|
||||
bool isFolder = i < length - 1;
|
||||
item->setData(isFolder ? fullPath + "/" : fullPath, Qt::UserRole);
|
||||
item->setData(isFolder, Qt::UserRole + 1);
|
||||
item->setData(parts[i], Qt::UserRole + 2);
|
||||
item->setData("atp:" + fullPath, Qt::UserRole + 3);
|
||||
item->setData(fullPath, Qt::UserRole + 4);
|
||||
if (lastItem) {
|
||||
lastItem->setChild(lastItem->rowCount(), 0, item);
|
||||
} else {
|
||||
appendRow(item);
|
||||
}
|
||||
|
||||
lastItem = item;
|
||||
_pathToItemMap[fullPath] = lastItem;
|
||||
} else {
|
||||
lastItem = it.value();
|
||||
}
|
||||
}
|
||||
|
||||
Q_ASSERT(fullPath == path);
|
||||
}
|
||||
|
||||
// Remove folders from list
|
||||
auto it = existingPaths.begin();
|
||||
while (it != existingPaths.end()) {
|
||||
Q_ASSERT(_pathToItemMap.contains(*it));
|
||||
auto item = _pathToItemMap[*it];
|
||||
if (item && item->data(Qt::UserRole + 1).toBool()) {
|
||||
it = existingPaths.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& path : existingPaths) {
|
||||
Q_ASSERT(_pathToItemMap.contains(path));
|
||||
|
||||
auto item = _pathToItemMap[path];
|
||||
|
||||
while (item) {
|
||||
// During each iteration, delete item
|
||||
QStandardItem* nextItem = nullptr;
|
||||
|
||||
auto fullPath = item->data(Qt::UserRole + 4).toString();
|
||||
auto parent = item->parent();
|
||||
if (parent) {
|
||||
parent->removeRow(item->row());
|
||||
if (parent->rowCount() > 0) {
|
||||
// The parent still contains children, set the nextItem to null so we stop processing
|
||||
nextItem = nullptr;
|
||||
} else {
|
||||
nextItem = parent;
|
||||
}
|
||||
} else {
|
||||
auto removed = removeRow(item->row());
|
||||
Q_ASSERT(removed);
|
||||
}
|
||||
|
||||
Q_ASSERT(_pathToItemMap.contains(fullPath));
|
||||
_pathToItemMap.remove(fullPath);
|
||||
|
||||
item = nextItem;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
emit errorGettingMappings(request->getErrorString());
|
||||
}
|
||||
});
|
||||
|
||||
request->start();
|
||||
}
|
70
interface/src/scripting/AssetMappingsScriptingInterface.h
Normal file
70
interface/src/scripting/AssetMappingsScriptingInterface.h
Normal file
|
@ -0,0 +1,70 @@
|
|||
//
|
||||
// AssetMappingsScriptingInterface.h
|
||||
// libraries/script-engine/src
|
||||
//
|
||||
// Created by Ryan Huffman on 2016-03-09.
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef hifi_AssetMappingsScriptingInterface_h
|
||||
#define hifi_AssetMappingsScriptingInterface_h
|
||||
|
||||
#include <QtCore/QObject>
|
||||
#include <QtScript/QScriptValue>
|
||||
|
||||
#include <AssetClient.h>
|
||||
#include <QSortFilterProxyModel>
|
||||
|
||||
|
||||
|
||||
class AssetMappingModel : public QStandardItemModel {
|
||||
Q_OBJECT
|
||||
public:
|
||||
Q_INVOKABLE void refresh();
|
||||
|
||||
bool isKnownMapping(QString path) const { return _pathToItemMap.contains(path); }
|
||||
bool isKnownFolder(QString path) const;
|
||||
|
||||
signals:
|
||||
void errorGettingMappings(QString errorString);
|
||||
|
||||
private:
|
||||
QHash<QString, QStandardItem*> _pathToItemMap;
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(AssetMappingModel*);
|
||||
|
||||
class AssetMappingsScriptingInterface : public QObject {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(AssetMappingModel* mappingModel READ getAssetMappingModel CONSTANT)
|
||||
Q_PROPERTY(QAbstractProxyModel* proxyModel READ getProxyModel CONSTANT)
|
||||
public:
|
||||
AssetMappingsScriptingInterface();
|
||||
|
||||
Q_INVOKABLE AssetMappingModel* getAssetMappingModel() { return &_assetMappingModel; }
|
||||
Q_INVOKABLE QAbstractProxyModel* getProxyModel() { return &_proxyModel; }
|
||||
|
||||
Q_INVOKABLE bool isKnownMapping(QString path) const { return _assetMappingModel.isKnownMapping(path); }
|
||||
Q_INVOKABLE bool isKnownFolder(QString path) const { return _assetMappingModel.isKnownFolder(path); }
|
||||
|
||||
Q_INVOKABLE void setMapping(QString path, QString hash, QJSValue callback = QJSValue());
|
||||
Q_INVOKABLE void getMapping(QString path, QJSValue callback = QJSValue());
|
||||
Q_INVOKABLE void uploadFile(QString path, QString mapping, QJSValue startedCallback = QJSValue(), QJSValue completedCallback = QJSValue(), bool dropEvent = false);
|
||||
Q_INVOKABLE void deleteMappings(QStringList paths, QJSValue callback);
|
||||
Q_INVOKABLE void deleteMapping(QString path, QJSValue callback) { deleteMappings(QStringList(path), callback = QJSValue()); }
|
||||
Q_INVOKABLE void getAllMappings(QJSValue callback = QJSValue());
|
||||
Q_INVOKABLE void renameMapping(QString oldPath, QString newPath, QJSValue callback = QJSValue());
|
||||
|
||||
protected:
|
||||
QSet<AssetRequest*> _pendingRequests;
|
||||
AssetMappingModel _assetMappingModel;
|
||||
QSortFilterProxyModel _proxyModel;
|
||||
};
|
||||
|
||||
|
||||
#endif // hifi_AssetMappingsScriptingInterface_h
|
|
@ -9,6 +9,7 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <QClipboard>
|
||||
#include <QtCore/QDir>
|
||||
#include <QMessageBox>
|
||||
#include <QScriptValue>
|
||||
|
@ -139,3 +140,8 @@ int WindowScriptingInterface::getX() {
|
|||
int WindowScriptingInterface::getY() {
|
||||
return qApp->getWindow()->y();
|
||||
}
|
||||
|
||||
void WindowScriptingInterface::copyToClipboard(const QString& text) {
|
||||
qDebug() << "Copying";
|
||||
QApplication::clipboard()->setText(text);
|
||||
}
|
||||
|
|
|
@ -40,6 +40,7 @@ public slots:
|
|||
QScriptValue prompt(const QString& message = "", const QString& defaultText = "");
|
||||
QScriptValue browse(const QString& title = "", const QString& directory = "", const QString& nameFilter = "");
|
||||
QScriptValue save(const QString& title = "", const QString& directory = "", const QString& nameFilter = "");
|
||||
void copyToClipboard(const QString& text);
|
||||
|
||||
signals:
|
||||
void domainChanged(const QString& domainHostname);
|
||||
|
|
|
@ -1,151 +0,0 @@
|
|||
//
|
||||
// AssetUploadDialogFactory.cpp
|
||||
// interface/src/ui
|
||||
//
|
||||
// Created by Stephen Birarda on 2015-08-26.
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "AssetUploadDialogFactory.h"
|
||||
|
||||
#include <QtCore/QDebug>
|
||||
#include <QtWidgets/QDialogButtonBox>
|
||||
#include <QtWidgets/QFileDialog>
|
||||
#include <QtWidgets/QLabel>
|
||||
#include <QtWidgets/QMessageBox>
|
||||
#include <QtWidgets/QLineEdit>
|
||||
#include <QtWidgets/QVBoxLayout>
|
||||
|
||||
#include <AssetClient.h>
|
||||
#include <AssetUpload.h>
|
||||
#include <AssetUtils.h>
|
||||
#include <NodeList.h>
|
||||
#include <OffscreenUi.h>
|
||||
#include <ResourceManager.h>
|
||||
|
||||
AssetUploadDialogFactory& AssetUploadDialogFactory::getInstance() {
|
||||
static AssetUploadDialogFactory staticInstance;
|
||||
return staticInstance;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void AssetUploadDialogFactory::showDialog() {
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
||||
if (nodeList->getThisNodeCanRez()) {
|
||||
auto filename = QFileDialog::getOpenFileName(_dialogParent, "Select a file to upload");
|
||||
|
||||
if (!filename.isEmpty()) {
|
||||
qDebug() << "Selected filename for upload to asset-server: " << filename;
|
||||
|
||||
auto assetClient = DependencyManager::get<AssetClient>();
|
||||
auto upload = assetClient->createUpload(filename);
|
||||
|
||||
if (upload) {
|
||||
// connect to the finished signal so we know when the AssetUpload is done
|
||||
QObject::connect(upload, &AssetUpload::finished, this, &AssetUploadDialogFactory::handleUploadFinished);
|
||||
|
||||
// start the upload now
|
||||
upload->start();
|
||||
} else {
|
||||
// show a QMessageBox to say that there is no local asset server
|
||||
QString messageBoxText = QString("Could not upload \n\n%1\n\nbecause you are currently not connected" \
|
||||
" to a local asset-server.").arg(QFileInfo(filename).fileName());
|
||||
|
||||
QMessageBox::information(_dialogParent, "Failed to Upload", messageBoxText);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// we don't have permission to upload to asset server in this domain - show the permission denied error
|
||||
showErrorDialog(nullptr, _dialogParent, AssetUpload::PERMISSION_DENIED_ERROR);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void AssetUploadDialogFactory::handleUploadFinished(AssetUpload* upload, const QString& hash) {
|
||||
if (upload->getError() == AssetUpload::NoError) {
|
||||
// show message box for successful upload, with copiable text for ATP hash
|
||||
QDialog* hashCopyDialog = new QDialog(_dialogParent);
|
||||
|
||||
// delete the dialog on close
|
||||
hashCopyDialog->setAttribute(Qt::WA_DeleteOnClose);
|
||||
|
||||
// set the window title
|
||||
hashCopyDialog->setWindowTitle(tr("Successful Asset Upload"));
|
||||
|
||||
// setup a layout for the contents of the dialog
|
||||
QVBoxLayout* boxLayout = new QVBoxLayout;
|
||||
|
||||
// set the label text (this shows above the text box)
|
||||
QLabel* lineEditLabel = new QLabel;
|
||||
lineEditLabel->setText(QString("ATP URL for %1").arg(QFileInfo(upload->getFilename()).fileName()));
|
||||
|
||||
// setup the line edit to hold the copiable text
|
||||
QLineEdit* lineEdit = new QLineEdit;
|
||||
|
||||
QString atpURL = QString("%1:%2.%3").arg(URL_SCHEME_ATP).arg(hash).arg(upload->getExtension());
|
||||
|
||||
// set the ATP URL as the text value so it's copiable
|
||||
lineEdit->insert(atpURL);
|
||||
|
||||
// figure out what size this line edit should be using font metrics
|
||||
QFontMetrics textMetrics { lineEdit->font() };
|
||||
|
||||
// set the fixed width on the line edit
|
||||
// pad it by 10 to cover the border and some extra space on the right side (for clicking)
|
||||
static const int LINE_EDIT_RIGHT_PADDING { 10 };
|
||||
|
||||
lineEdit->setFixedWidth(textMetrics.width(atpURL) + LINE_EDIT_RIGHT_PADDING );
|
||||
|
||||
// left align the ATP URL line edit
|
||||
lineEdit->home(true);
|
||||
|
||||
// add the label and line edit to the dialog
|
||||
boxLayout->addWidget(lineEditLabel);
|
||||
boxLayout->addWidget(lineEdit);
|
||||
|
||||
// setup an OK button to close the dialog
|
||||
QDialogButtonBox* buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok);
|
||||
connect(buttonBox, &QDialogButtonBox::accepted, hashCopyDialog, &QDialog::close);
|
||||
boxLayout->addWidget(buttonBox);
|
||||
|
||||
// set the new layout on the dialog
|
||||
hashCopyDialog->setLayout(boxLayout);
|
||||
|
||||
// show the new dialog
|
||||
hashCopyDialog->show();
|
||||
} else {
|
||||
// display a message box with the error
|
||||
showErrorDialog(upload, _dialogParent);
|
||||
}
|
||||
|
||||
upload->deleteLater();
|
||||
}
|
||||
|
||||
void AssetUploadDialogFactory::showErrorDialog(AssetUpload* upload, QWidget* dialogParent, const QString& overrideMessage) {
|
||||
QString filename;
|
||||
|
||||
if (upload) {
|
||||
filename = QFileInfo { upload->getFilename() }.fileName();
|
||||
}
|
||||
|
||||
QString errorMessage = overrideMessage;
|
||||
|
||||
if (errorMessage.isEmpty() && upload) {
|
||||
errorMessage = upload->getErrorString();
|
||||
}
|
||||
|
||||
QString dialogMessage;
|
||||
|
||||
if (upload) {
|
||||
dialogMessage += QString("Failed to upload %1.\n\n").arg(filename);
|
||||
}
|
||||
|
||||
dialogMessage += errorMessage;
|
||||
|
||||
OffscreenUi::warning(dialogParent, "Failed Upload", dialogMessage);
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
//
|
||||
// AssetUploadDialogFactory.h
|
||||
// interface/src/ui
|
||||
//
|
||||
// Created by Stephen Birarda on 2015-08-26.
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef hifi_AssetUploadDialogFactory_h
|
||||
#define hifi_AssetUploadDialogFactory_h
|
||||
|
||||
#include <QtCore/QObject>
|
||||
|
||||
class AssetUpload;
|
||||
|
||||
class AssetUploadDialogFactory : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
AssetUploadDialogFactory(const AssetUploadDialogFactory& other) = delete;
|
||||
AssetUploadDialogFactory& operator=(const AssetUploadDialogFactory& rhs) = delete;
|
||||
|
||||
static AssetUploadDialogFactory& getInstance();
|
||||
static void showErrorDialog(AssetUpload* upload, QWidget* dialogParent, const QString& overrideMessage = QString());
|
||||
|
||||
void setDialogParent(QWidget* dialogParent) { _dialogParent = dialogParent; }
|
||||
|
||||
public slots:
|
||||
void showDialog();
|
||||
void handleUploadFinished(AssetUpload* upload, const QString& hash);
|
||||
|
||||
private:
|
||||
AssetUploadDialogFactory() = default;
|
||||
|
||||
|
||||
|
||||
QWidget* _dialogParent { nullptr };
|
||||
};
|
||||
|
||||
#endif // hifi_AssetUploadDialogFactory_h
|
|
@ -48,6 +48,13 @@ RenderableModelEntityItem::~RenderableModelEntityItem() {
|
|||
|
||||
void RenderableModelEntityItem::setModelURL(const QString& url) {
|
||||
auto& currentURL = getParsedModelURL();
|
||||
if (_model && (currentURL != url)) {
|
||||
// The machinery for updateModelBounds will give existing models the opportunity to fix their translation/rotation/scale/registration.
|
||||
// The first two are straightforward, but the latter two have guards to make sure they don't happen after they've already been set.
|
||||
// Here we reset those guards. This doesn't cause the entity values to change -- it just allows the model to match once it comes in.
|
||||
_model->setScaleToFit(false, getDimensions());
|
||||
_model->setSnapModelToRegistrationPoint(false, getRegistrationPoint());
|
||||
}
|
||||
ModelEntityItem::setModelURL(url);
|
||||
|
||||
if (currentURL != getParsedModelURL() || !_model) {
|
||||
|
|
|
@ -125,7 +125,8 @@ void main(void) {
|
|||
vec4 quadPos = radius * UNIT_QUAD[twoTriID];
|
||||
|
||||
vec4 anchorPoint;
|
||||
<$transformModelToEyePos(cam, obj, inPosition, anchorPoint)$>
|
||||
vec4 _inPosition = vec4(inPosition, 1.0);
|
||||
<$transformModelToEyePos(cam, obj, _inPosition, anchorPoint)$>
|
||||
|
||||
vec4 eyePos = anchorPoint + quadPos;
|
||||
<$transformEyeToClipPos(cam, eyePos, gl_Position)$>
|
||||
|
|
|
@ -182,6 +182,15 @@ QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties
|
|||
return id;
|
||||
}
|
||||
|
||||
QUuid EntityScriptingInterface::addModelEntity(const QString& name, const QString& modelUrl, const glm::vec3& position) {
|
||||
EntityItemProperties properties;
|
||||
properties.setType(EntityTypes::Model);
|
||||
properties.setName(name);
|
||||
properties.setModelURL(modelUrl);
|
||||
properties.setPosition(position);
|
||||
return addEntity(properties);
|
||||
}
|
||||
|
||||
EntityItemProperties EntityScriptingInterface::getEntityProperties(QUuid identity) {
|
||||
EntityPropertyFlags noSpecificProperties;
|
||||
return getEntityProperties(identity, noSpecificProperties);
|
||||
|
|
|
@ -84,6 +84,9 @@ public slots:
|
|||
/// adds a model with the specific properties
|
||||
Q_INVOKABLE QUuid addEntity(const EntityItemProperties& properties);
|
||||
|
||||
/// temporary method until addEntity can be used from QJSEngine
|
||||
Q_INVOKABLE QUuid addModelEntity(const QString& name, const QString& modelUrl, const glm::vec3& position);
|
||||
|
||||
/// gets the current model properties for a specific model
|
||||
/// this function will not find return results in script engine contexts which don't have access to models
|
||||
Q_INVOKABLE EntityItemProperties getEntityProperties(QUuid entityID);
|
||||
|
|
|
@ -74,9 +74,14 @@ void Context::downloadFramebuffer(const FramebufferPointer& srcFramebuffer, cons
|
|||
_backend->downloadFramebuffer(srcFramebuffer, region, destImage);
|
||||
}
|
||||
|
||||
const Backend::TransformCamera& Backend::TransformCamera::recomputeDerived() const {
|
||||
const Backend::TransformCamera& Backend::TransformCamera::recomputeDerived(const Transform& xformView) const {
|
||||
_projectionInverse = glm::inverse(_projection);
|
||||
_viewInverse = glm::inverse(_view);
|
||||
|
||||
// Get the viewEyeToWorld matrix form the transformView as passed to the gpu::Batch
|
||||
// this is the "_viewInverse" fed to the shader
|
||||
// Genetrate the "_view" matrix as well from the xform
|
||||
xformView.getMatrix(_viewInverse);
|
||||
_view = glm::inverse(_viewInverse);
|
||||
|
||||
Mat4 viewUntranslated = _view;
|
||||
viewUntranslated[3] = Vec4(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
|
@ -84,16 +89,16 @@ const Backend::TransformCamera& Backend::TransformCamera::recomputeDerived() con
|
|||
return *this;
|
||||
}
|
||||
|
||||
Backend::TransformCamera Backend::TransformCamera::getEyeCamera(int eye, const StereoState& _stereo) const {
|
||||
Backend::TransformCamera Backend::TransformCamera::getEyeCamera(int eye, const StereoState& _stereo, const Transform& xformView) const {
|
||||
TransformCamera result = *this;
|
||||
Transform offsetTransform = xformView;
|
||||
if (!_stereo._skybox) {
|
||||
result._view = _stereo._eyeViews[eye] * result._view;
|
||||
offsetTransform.postTranslate(-Vec3(_stereo._eyeViews[eye][3]));
|
||||
} else {
|
||||
glm::mat4 skyboxView = _stereo._eyeViews[eye];
|
||||
skyboxView[3] = vec4(0, 0, 0, 1);
|
||||
result._view = skyboxView * result._view;
|
||||
// FIXME: If "skybox" the ipd is set to 0 for now, let s try to propose a better solution for this in the future
|
||||
}
|
||||
result._projection = _stereo._eyeProjections[eye];
|
||||
result.recomputeDerived();
|
||||
result.recomputeDerived(offsetTransform);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -79,15 +79,15 @@ public:
|
|||
// UBO class... layout MUST match the layout in TransformCamera.slh
|
||||
class TransformCamera {
|
||||
public:
|
||||
Mat4 _view;
|
||||
mutable Mat4 _view;
|
||||
mutable Mat4 _viewInverse;
|
||||
mutable Mat4 _projectionViewUntranslated;
|
||||
Mat4 _projection;
|
||||
mutable Mat4 _projectionInverse;
|
||||
Vec4 _viewport; // Public value is int but float in the shader to stay in floats for all the transform computations.
|
||||
|
||||
const Backend::TransformCamera& recomputeDerived() const;
|
||||
TransformCamera getEyeCamera(int eye, const StereoState& stereo) const;
|
||||
const Backend::TransformCamera& recomputeDerived(const Transform& xformView) const;
|
||||
TransformCamera getEyeCamera(int eye, const StereoState& stereo, const Transform& xformView) const;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -105,6 +105,7 @@ void GLBackend::TransformStageState::preUpdate(size_t commandIndex, const Stereo
|
|||
}
|
||||
|
||||
if (_invalidView) {
|
||||
// This is when the _view matrix gets assigned
|
||||
_view.getInverseMatrix(_camera._view);
|
||||
}
|
||||
|
||||
|
@ -113,11 +114,11 @@ void GLBackend::TransformStageState::preUpdate(size_t commandIndex, const Stereo
|
|||
if (stereo._enable) {
|
||||
_cameraOffsets.push_back(TransformStageState::Pair(commandIndex, offset));
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
_cameras.push_back(_camera.getEyeCamera(i, stereo));
|
||||
_cameras.push_back(_camera.getEyeCamera(i, stereo, _view));
|
||||
}
|
||||
} else {
|
||||
_cameraOffsets.push_back(TransformStageState::Pair(commandIndex, offset));
|
||||
_cameras.push_back(_camera.recomputeDerived());
|
||||
_cameras.push_back(_camera.recomputeDerived(_view));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -76,24 +76,39 @@ TransformObject getTransformObject() {
|
|||
<$viewport$> = <$cameraTransform$>._viewport;
|
||||
<@endfunc@>
|
||||
|
||||
<@func transformModelToClipPos(cameraTransform, objectTransform, modelPos, clipPos)@>
|
||||
<!// Equivalent to the following but hoppefully a tad more accurate
|
||||
//return camera._projection * camera._view * object._model * pos; !>
|
||||
{ // transformModelToClipPos
|
||||
vec4 _eyepos = (<$objectTransform$>._model * <$modelPos$>) + vec4(-<$modelPos$>.w * <$cameraTransform$>._viewInverse[3].xyz, 0.0);
|
||||
<$clipPos$> = <$cameraTransform$>._projectionViewUntranslated * _eyepos;
|
||||
<@func transformModelToEyeWorldAlignedPos(cameraTransform, objectTransform, modelPos, eyeWorldAlignedPos)@>
|
||||
<!// Bring the model pos in the world aligned space centered on the eye axis !>
|
||||
{ // _transformModelToEyeWorldAlignedPos
|
||||
highp mat4 _mv = <$objectTransform$>._model;
|
||||
_mv[3].xyz -= <$cameraTransform$>._viewInverse[3].xyz;
|
||||
highp vec4 _eyeWApos = (_mv * <$modelPos$>);
|
||||
<$eyeWorldAlignedPos$> = _eyeWApos;
|
||||
}
|
||||
<@endfunc@>
|
||||
|
||||
<@func $transformModelToEyeAndClipPos(cameraTransform, objectTransform, modelPos, eyePos, clipPos)@>
|
||||
<!// Equivalent to the following but hoppefully a tad more accurate
|
||||
//return camera._projection * camera._view * object._model * pos; !>
|
||||
<@func transformModelToClipPos(cameraTransform, objectTransform, modelPos, clipPos)@>
|
||||
{ // transformModelToClipPos
|
||||
vec4 _worldpos = (<$objectTransform$>._model * <$modelPos$>);
|
||||
<$eyePos$> = (<$cameraTransform$>._view * _worldpos);
|
||||
vec4 _eyepos =(<$objectTransform$>._model * <$modelPos$>) + vec4(-<$modelPos$>.w * <$cameraTransform$>._viewInverse[3].xyz, 0.0);
|
||||
<$clipPos$> = <$cameraTransform$>._projectionViewUntranslated * _eyepos;
|
||||
// <$eyePos$> = (<$cameraTransform$>._projectionInverse * <$clipPos$>);
|
||||
vec4 eyeWAPos;
|
||||
<$transformModelToEyeWorldAlignedPos($cameraTransform$, $objectTransform$, $modelPos$, eyeWAPos)$>
|
||||
|
||||
<$clipPos$> = <$cameraTransform$>._projectionViewUntranslated * eyeWAPos;
|
||||
}
|
||||
<@endfunc@>
|
||||
|
||||
<@func transformModelToEyeAndClipPos(cameraTransform, objectTransform, modelPos, eyePos, clipPos)@>
|
||||
{ // transformModelToEyeAndClipPos
|
||||
vec4 eyeWAPos;
|
||||
<$transformModelToEyeWorldAlignedPos($cameraTransform$, $objectTransform$, $modelPos$, eyeWAPos)$>
|
||||
<$clipPos$> = <$cameraTransform$>._projectionViewUntranslated * eyeWAPos;
|
||||
<$eyePos$> = vec4((<$cameraTransform$>._view * vec4(eyeWAPos.xyz, 0.0)).xyz, 1.0);
|
||||
}
|
||||
<@endfunc@>
|
||||
|
||||
<@func transformModelToEyePos(cameraTransform, objectTransform, modelPos, eyePos)@>
|
||||
{ // transformModelToEyePos
|
||||
vec4 eyeWAPos;
|
||||
<$transformModelToEyeWorldAlignedPos($cameraTransform$, $objectTransform$, $modelPos$, eyeWAPos)$>
|
||||
<$eyePos$> = vec4((<$cameraTransform$>._view * vec4(eyeWAPos.xyz, 0.0)).xyz, 1.0);
|
||||
}
|
||||
<@endfunc@>
|
||||
|
||||
|
@ -103,6 +118,7 @@ TransformObject getTransformObject() {
|
|||
}
|
||||
<@endfunc@>
|
||||
|
||||
|
||||
<@func transformModelToEyeDir(cameraTransform, objectTransform, modelDir, eyeDir)@>
|
||||
{ // transformModelToEyeDir
|
||||
vec3 mr0 = vec3(<$objectTransform$>._modelInverse[0].x, <$objectTransform$>._modelInverse[1].x, <$objectTransform$>._modelInverse[2].x);
|
||||
|
@ -129,15 +145,6 @@ TransformObject getTransformObject() {
|
|||
}
|
||||
<@endfunc@>
|
||||
|
||||
<@func $transformModelToEyePos(cameraTransform, objectTransform, modelPos, eyePos)@>
|
||||
<!// Equivalent to the following but hoppefully a tad more accurate
|
||||
//return camera._view * object._model * pos; !>
|
||||
{ // transformModelToEyePos
|
||||
vec4 _worldpos = (<$objectTransform$>._model * vec4(<$modelPos$>.xyz, 1.0));
|
||||
<$eyePos$> = (<$cameraTransform$>._view * _worldpos);
|
||||
}
|
||||
<@endfunc@>
|
||||
|
||||
<@func transformEyeToClipPos(cameraTransform, eyePos, clipPos)@>
|
||||
{ // transformEyeToClipPos
|
||||
<$clipPos$> = <$cameraTransform$>._projection * vec4(<$eyePos$>.xyz, 1.0);
|
||||
|
|
|
@ -135,6 +135,8 @@ bool NetworkGeometry::isLoadedWithTextures() const {
|
|||
}
|
||||
|
||||
if (!_isLoadedWithTextures) {
|
||||
_hasTransparentTextures = true;
|
||||
|
||||
for (auto&& material : _materials) {
|
||||
if ((material->albedoTexture && !material->albedoTexture->isLoaded()) ||
|
||||
(material->normalTexture && !material->normalTexture->isLoaded()) ||
|
||||
|
@ -145,7 +147,16 @@ bool NetworkGeometry::isLoadedWithTextures() const {
|
|||
(material->lightmapTexture && !material->lightmapTexture->isLoaded())) {
|
||||
return false;
|
||||
}
|
||||
if (material->albedoTexture && material->albedoTexture->getGPUTexture()) {
|
||||
// Reset the materialKey transparentTexture key only, as it is albedoTexture-dependent
|
||||
const auto& usage = material->albedoTexture->getGPUTexture()->getUsage();
|
||||
bool isTransparentTexture = usage.isAlpha() && !usage.isAlphaMask();
|
||||
material->_material->setTransparentTexture(isTransparentTexture);
|
||||
// FIXME: Materials with *some* transparent textures seem to give all *other* textures alphas of 0.
|
||||
_hasTransparentTextures = isTransparentTexture && _hasTransparentTextures;
|
||||
}
|
||||
}
|
||||
|
||||
_isLoadedWithTextures = true;
|
||||
}
|
||||
return true;
|
||||
|
|
|
@ -75,6 +75,10 @@ public:
|
|||
// true when the requested geometry and its textures are loaded.
|
||||
bool isLoadedWithTextures() const;
|
||||
|
||||
// true if the albedo texture has a non-masking alpha channel.
|
||||
// This can only be known after isLoadedWithTextures().
|
||||
bool hasTransparentTextures() const { return _hasTransparentTextures; }
|
||||
|
||||
// WARNING: only valid when isLoaded returns true.
|
||||
const FBXGeometry& getFBXGeometry() const { return *_geometry; }
|
||||
const std::vector<std::unique_ptr<NetworkMesh>>& getMeshes() const { return _meshes; }
|
||||
|
@ -151,6 +155,7 @@ protected:
|
|||
|
||||
// cache for isLoadedWithTextures()
|
||||
mutable bool _isLoadedWithTextures = false;
|
||||
mutable bool _hasTransparentTextures = false;
|
||||
};
|
||||
|
||||
/// Reads geometry in a worker thread.
|
||||
|
|
|
@ -80,6 +80,10 @@ void Material::setMetallic(float metallic) {
|
|||
_schemaBuffer.edit<Schema>()._metallic = metallic;
|
||||
}
|
||||
|
||||
void Material::setTransparentTexture(bool isTransparent) {
|
||||
_key.setTransparentTexture(isTransparent);
|
||||
_schemaBuffer.edit<Schema>()._key = (uint32)_key._flags.to_ulong();
|
||||
}
|
||||
|
||||
void Material::setTextureMap(MapChannel channel, const TextureMapPointer& textureMap) {
|
||||
if (textureMap) {
|
||||
|
@ -92,6 +96,3 @@ void Material::setTextureMap(MapChannel channel, const TextureMapPointer& textur
|
|||
_textureMaps.erase(channel);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -32,6 +32,7 @@ public:
|
|||
METALLIC_VAL_BIT,
|
||||
GLOSSY_VAL_BIT,
|
||||
TRANSPARENT_VAL_BIT,
|
||||
TRANSPARENT_TEX_VAL_BIT,
|
||||
|
||||
EMISSIVE_MAP_BIT,
|
||||
ALBEDO_MAP_BIT,
|
||||
|
@ -101,6 +102,9 @@ public:
|
|||
void setAlbedo(bool value) { _flags.set(ALBEDO_VAL_BIT, value); }
|
||||
bool isAlbedo() const { return _flags[ALBEDO_VAL_BIT]; }
|
||||
|
||||
void setTransparentTexture(bool value) { _flags.set(TRANSPARENT_TEX_VAL_BIT, value); }
|
||||
bool isTransparentTexture() const { return _flags[TRANSPARENT_TEX_VAL_BIT]; }
|
||||
|
||||
void setAlbedoMap(bool value) { _flags.set(ALBEDO_MAP_BIT, value); }
|
||||
bool isAlbedoMap() const { return _flags[ALBEDO_MAP_BIT]; }
|
||||
|
||||
|
@ -164,6 +168,9 @@ public:
|
|||
Builder& withoutAlbedo() { _value.reset(MaterialKey::ALBEDO_VAL_BIT); _mask.set(MaterialKey::ALBEDO_VAL_BIT); return (*this); }
|
||||
Builder& withAlbedo() { _value.set(MaterialKey::ALBEDO_VAL_BIT); _mask.set(MaterialKey::ALBEDO_VAL_BIT); return (*this); }
|
||||
|
||||
Builder& withoutTransparentTexture() { _value.reset(MaterialKey::TRANSPARENT_TEX_VAL_BIT); _mask.set(MaterialKey::TRANSPARENT_TEX_VAL_BIT); return (*this); }
|
||||
Builder& withTransparentTexture() { _value.set(MaterialKey::TRANSPARENT_TEX_VAL_BIT); _mask.set(MaterialKey::TRANSPARENT_TEX_VAL_BIT); return (*this); }
|
||||
|
||||
Builder& withoutAlbedoMap() { _value.reset(MaterialKey::ALBEDO_MAP_BIT); _mask.set(MaterialKey::ALBEDO_MAP_BIT); return (*this); }
|
||||
Builder& withAlbedoMap() { _value.set(MaterialKey::ALBEDO_MAP_BIT); _mask.set(MaterialKey::ALBEDO_MAP_BIT); return (*this); }
|
||||
|
||||
|
@ -248,6 +255,8 @@ public:
|
|||
void setRoughness(float roughness);
|
||||
float getRoughness() const { return _schemaBuffer.get<Schema>()._roughness; }
|
||||
|
||||
void setTransparentTexture(bool isTransparent);
|
||||
|
||||
// Schema to access the attribute values of the material
|
||||
class Schema {
|
||||
public:
|
||||
|
@ -260,8 +269,7 @@ public:
|
|||
glm::vec3 _fresnel{ 0.03f }; // Fresnel value for a default non metallic
|
||||
float _metallic{ 0.0f }; // Not Metallic
|
||||
|
||||
|
||||
glm::vec3 _spare0{ 0.0f };
|
||||
glm::vec3 _spare{ 0.0f };
|
||||
|
||||
uint32_t _key{ 0 }; // a copy of the materialKey
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ struct Material {
|
|||
vec4 _emissiveOpacity;
|
||||
vec4 _albedoRoughness;
|
||||
vec4 _fresnelMetallic;
|
||||
vec4 _spare;
|
||||
vec4 _spareKey;
|
||||
};
|
||||
|
||||
uniform materialBuffer {
|
||||
|
@ -37,25 +37,23 @@ float getMaterialMetallic(Material m) { return m._fresnelMetallic.a; }
|
|||
|
||||
float getMaterialShininess(Material m) { return 1.0 - getMaterialRoughness(m); }
|
||||
|
||||
int getMaterialKey(Material m) { return floatBitsToInt(m._spare.w); }
|
||||
int getMaterialKey(Material m) { return floatBitsToInt(m._spareKey.w); }
|
||||
|
||||
const int EMISSIVE_VAL_BIT = 0x00000001;
|
||||
const int ALBEDO_VAL_BIT = 0x00000002;
|
||||
const int METALLIC_VAL_BIT = 0x00000004;
|
||||
const int GLOSSY_VAL_BIT = 0x00000008;
|
||||
const int TRANSPARENT_VAL_BIT = 0x00000010;
|
||||
const int TRANSPARENT_TEX_VAL_BIT = 0x00000020;
|
||||
|
||||
const int EMISSIVE_MAP_BIT = 0x00000040;
|
||||
const int ALBEDO_MAP_BIT = 0x00000080;
|
||||
const int METALLIC_MAP_BIT = 0x00000100;
|
||||
const int ROUGHNESS_MAP_BIT = 0x00000200;
|
||||
const int TRANSPARENT_MAP_BIT = 0x00000400;
|
||||
const int NORMAL_MAP_BIT = 0x00000800;
|
||||
const int OCCLUSION_MAP_BIT = 0x00001000;
|
||||
|
||||
const int EMISSIVE_MAP_BIT = 0x00000020;
|
||||
const int ALBEDO_MAP_BIT = 0x00000040;
|
||||
const int METALLIC_MAP_BIT = 0x00000080;
|
||||
const int ROUGHNESS_MAP_BIT = 0x00000100;
|
||||
const int TRANSPARENT_MAP_BIT = 0x00000200;
|
||||
const int NORMAL_MAP_BIT = 0x00000400;
|
||||
const int OCCLUSION_MAP_BIT = 0x00000800;
|
||||
|
||||
const int LIGHTMAP_MAP_BIT = 0x00001000;
|
||||
|
||||
|
||||
const int LIGHTMAP_MAP_BIT = 0x00002000;
|
||||
|
||||
<@endif@>
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
#include "AssetRequest.h"
|
||||
#include "AssetUpload.h"
|
||||
#include "AssetUtils.h"
|
||||
#include "MappingRequest.h"
|
||||
#include "NetworkAccessManager.h"
|
||||
#include "NetworkLogging.h"
|
||||
#include "NodeList.h"
|
||||
|
@ -30,7 +31,6 @@
|
|||
|
||||
MessageID AssetClient::_currentID = 0;
|
||||
|
||||
|
||||
AssetClient::AssetClient() {
|
||||
|
||||
setCustomDeleter([](Dependency* dependency){
|
||||
|
@ -39,6 +39,8 @@ AssetClient::AssetClient() {
|
|||
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
auto& packetReceiver = nodeList->getPacketReceiver();
|
||||
|
||||
packetReceiver.registerListener(PacketType::AssetMappingOperationReply, this, "handleAssetMappingOperationReply");
|
||||
packetReceiver.registerListener(PacketType::AssetGetInfoReply, this, "handleAssetGetInfoReply");
|
||||
packetReceiver.registerListener(PacketType::AssetGetReply, this, "handleAssetGetReply", true);
|
||||
packetReceiver.registerListener(PacketType::AssetUploadReply, this, "handleAssetUploadReply");
|
||||
|
@ -97,6 +99,33 @@ void AssetClient::clearCache() {
|
|||
}
|
||||
}
|
||||
|
||||
void AssetClient::handleAssetMappingOperationReply(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
|
||||
MessageID messageID;
|
||||
message->readPrimitive(&messageID);
|
||||
|
||||
AssetServerError error;
|
||||
message->readPrimitive(&error);
|
||||
|
||||
// Check if we have any pending requests for this node
|
||||
auto messageMapIt = _pendingMappingRequests.find(senderNode);
|
||||
if (messageMapIt != _pendingMappingRequests.end()) {
|
||||
|
||||
// Found the node, get the MessageID -> Callback map
|
||||
auto& messageCallbackMap = messageMapIt->second;
|
||||
|
||||
// Check if we have this pending request
|
||||
auto requestIt = messageCallbackMap.find(messageID);
|
||||
if (requestIt != messageCallbackMap.end()) {
|
||||
auto callback = requestIt->second;
|
||||
callback(true, error, message);
|
||||
messageCallbackMap.erase(requestIt);
|
||||
}
|
||||
|
||||
// Although the messageCallbackMap may now be empty, we won't delete the node until we have disconnected from
|
||||
// it to avoid constantly creating/deleting the map on subsequent requests.
|
||||
}
|
||||
}
|
||||
|
||||
bool haveAssetServer() {
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
SharedNodePointer assetServer = nodeList->soloNodeOfType(NodeType::AssetServer);
|
||||
|
@ -110,52 +139,68 @@ bool haveAssetServer() {
|
|||
return true;
|
||||
}
|
||||
|
||||
AssetRequest* AssetClient::createRequest(const QString& hash, const QString& extension) {
|
||||
if (hash.length() != SHA256_HASH_HEX_LENGTH) {
|
||||
qCWarning(asset_client) << "Invalid hash size";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (haveAssetServer()) {
|
||||
auto request = new AssetRequest(hash, extension);
|
||||
|
||||
// Move to the AssetClient thread in case we are not currently on that thread (which will usually be the case)
|
||||
request->moveToThread(thread());
|
||||
|
||||
return request;
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
GetMappingRequest* AssetClient::createGetMappingRequest(const AssetPath& path) {
|
||||
return new GetMappingRequest(path);
|
||||
}
|
||||
|
||||
GetAllMappingsRequest* AssetClient::createGetAllMappingsRequest() {
|
||||
auto request = new GetAllMappingsRequest();
|
||||
|
||||
request->moveToThread(thread());
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
DeleteMappingsRequest* AssetClient::createDeleteMappingsRequest(const AssetPathList& paths) {
|
||||
auto request = new DeleteMappingsRequest(paths);
|
||||
|
||||
request->moveToThread(thread());
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
SetMappingRequest* AssetClient::createSetMappingRequest(const AssetPath& path, const AssetHash& hash) {
|
||||
auto request = new SetMappingRequest(path, hash);
|
||||
|
||||
request->moveToThread(thread());
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
RenameMappingRequest* AssetClient::createRenameMappingRequest(const AssetPath& oldPath, const AssetPath& newPath) {
|
||||
auto request = new RenameMappingRequest(oldPath, newPath);
|
||||
|
||||
request->moveToThread(thread());
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
AssetRequest* AssetClient::createRequest(const AssetHash& hash) {
|
||||
auto request = new AssetRequest(hash);
|
||||
|
||||
// Move to the AssetClient thread in case we are not currently on that thread (which will usually be the case)
|
||||
request->moveToThread(thread());
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
AssetUpload* AssetClient::createUpload(const QString& filename) {
|
||||
|
||||
if (haveAssetServer()) {
|
||||
auto upload = new AssetUpload(filename);
|
||||
|
||||
upload->moveToThread(thread());
|
||||
|
||||
return upload;
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
auto upload = new AssetUpload(filename);
|
||||
|
||||
upload->moveToThread(thread());
|
||||
|
||||
return upload;
|
||||
}
|
||||
|
||||
AssetUpload* AssetClient::createUpload(const QByteArray& data, const QString& extension) {
|
||||
if (haveAssetServer()) {
|
||||
auto upload = new AssetUpload(data, extension);
|
||||
|
||||
upload->moveToThread(thread());
|
||||
|
||||
return upload;
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
AssetUpload* AssetClient::createUpload(const QByteArray& data) {
|
||||
auto upload = new AssetUpload(data);
|
||||
|
||||
upload->moveToThread(thread());
|
||||
|
||||
return upload;
|
||||
}
|
||||
|
||||
bool AssetClient::getAsset(const QString& hash, const QString& extension, DataOffset start, DataOffset end,
|
||||
bool AssetClient::getAsset(const QString& hash, DataOffset start, DataOffset end,
|
||||
ReceivedAssetCallback callback, ProgressCallback progressCallback) {
|
||||
if (hash.length() != SHA256_HASH_HEX_LENGTH) {
|
||||
qCWarning(asset_client) << "Invalid hash size";
|
||||
|
@ -169,8 +214,7 @@ bool AssetClient::getAsset(const QString& hash, const QString& extension, DataOf
|
|||
|
||||
auto messageID = ++_currentID;
|
||||
|
||||
auto payloadSize = sizeof(messageID) + SHA256_HASH_LENGTH + sizeof(uint8_t) + extension.length()
|
||||
+ sizeof(start) + sizeof(end);
|
||||
auto payloadSize = sizeof(messageID) + SHA256_HASH_LENGTH + sizeof(start) + sizeof(end);
|
||||
auto packet = NLPacket::create(PacketType::AssetGet, payloadSize, true);
|
||||
|
||||
qCDebug(asset_client) << "Requesting data from" << start << "to" << end << "of" << hash << "from asset-server.";
|
||||
|
@ -179,9 +223,6 @@ bool AssetClient::getAsset(const QString& hash, const QString& extension, DataOf
|
|||
|
||||
packet->write(QByteArray::fromHex(hash.toLatin1()));
|
||||
|
||||
packet->writePrimitive(uint8_t(extension.length()));
|
||||
packet->write(extension.toLatin1());
|
||||
|
||||
packet->writePrimitive(start);
|
||||
packet->writePrimitive(end);
|
||||
|
||||
|
@ -190,34 +231,36 @@ bool AssetClient::getAsset(const QString& hash, const QString& extension, DataOf
|
|||
_pendingRequests[assetServer][messageID] = { callback, progressCallback };
|
||||
|
||||
return true;
|
||||
} else {
|
||||
callback(false, AssetServerError::NoError, QByteArray());
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
bool AssetClient::getAssetInfo(const QString& hash, const QString& extension, GetInfoCallback callback) {
|
||||
bool AssetClient::getAssetInfo(const QString& hash, GetInfoCallback callback) {
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
SharedNodePointer assetServer = nodeList->soloNodeOfType(NodeType::AssetServer);
|
||||
|
||||
if (assetServer) {
|
||||
auto messageID = ++_currentID;
|
||||
|
||||
auto payloadSize = sizeof(messageID) + SHA256_HASH_LENGTH + sizeof(uint8_t) + extension.length();
|
||||
auto payloadSize = sizeof(messageID) + SHA256_HASH_LENGTH;
|
||||
auto packet = NLPacket::create(PacketType::AssetGetInfo, payloadSize, true);
|
||||
|
||||
packet->writePrimitive(messageID);
|
||||
packet->write(QByteArray::fromHex(hash.toLatin1()));
|
||||
packet->writePrimitive(uint8_t(extension.length()));
|
||||
packet->write(extension.toLatin1());
|
||||
|
||||
nodeList->sendPacket(std::move(packet), *assetServer);
|
||||
|
||||
_pendingInfoRequests[assetServer][messageID] = callback;
|
||||
|
||||
return true;
|
||||
} else {
|
||||
callback(false, AssetServerError::NoError, { "", 0 });
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void AssetClient::handleAssetGetInfoReply(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
|
||||
|
@ -305,7 +348,137 @@ void AssetClient::handleAssetGetReply(QSharedPointer<ReceivedMessage> message, S
|
|||
}
|
||||
}
|
||||
|
||||
bool AssetClient::uploadAsset(const QByteArray& data, const QString& extension, UploadResultCallback callback) {
|
||||
bool AssetClient::getAssetMapping(const AssetPath& path, MappingOperationCallback callback) {
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
SharedNodePointer assetServer = nodeList->soloNodeOfType(NodeType::AssetServer);
|
||||
|
||||
if (assetServer) {
|
||||
auto packetList = NLPacketList::create(PacketType::AssetMappingOperation, QByteArray(), true, true);
|
||||
|
||||
auto messageID = ++_currentID;
|
||||
packetList->writePrimitive(messageID);
|
||||
|
||||
packetList->writePrimitive(AssetMappingOperationType::Get);
|
||||
|
||||
packetList->writeString(path);
|
||||
|
||||
nodeList->sendPacketList(std::move(packetList), *assetServer);
|
||||
|
||||
_pendingMappingRequests[assetServer][messageID] = callback;
|
||||
|
||||
return true;
|
||||
} else {
|
||||
callback(false, AssetServerError::NoError, QSharedPointer<ReceivedMessage>());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool AssetClient::getAllAssetMappings(MappingOperationCallback callback) {
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
SharedNodePointer assetServer = nodeList->soloNodeOfType(NodeType::AssetServer);
|
||||
|
||||
if (assetServer) {
|
||||
auto packetList = NLPacketList::create(PacketType::AssetMappingOperation, QByteArray(), true, true);
|
||||
|
||||
auto messageID = ++_currentID;
|
||||
packetList->writePrimitive(messageID);
|
||||
|
||||
packetList->writePrimitive(AssetMappingOperationType::GetAll);
|
||||
|
||||
nodeList->sendPacketList(std::move(packetList), *assetServer);
|
||||
|
||||
_pendingMappingRequests[assetServer][messageID] = callback;
|
||||
|
||||
return true;
|
||||
} else {
|
||||
callback(false, AssetServerError::NoError, QSharedPointer<ReceivedMessage>());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool AssetClient::deleteAssetMappings(const AssetPathList& paths, MappingOperationCallback callback) {
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
SharedNodePointer assetServer = nodeList->soloNodeOfType(NodeType::AssetServer);
|
||||
|
||||
if (assetServer) {
|
||||
auto packetList = NLPacketList::create(PacketType::AssetMappingOperation, QByteArray(), true, true);
|
||||
|
||||
auto messageID = ++_currentID;
|
||||
packetList->writePrimitive(messageID);
|
||||
|
||||
packetList->writePrimitive(AssetMappingOperationType::Delete);
|
||||
|
||||
packetList->writePrimitive(int(paths.size()));
|
||||
|
||||
for (auto& path: paths) {
|
||||
packetList->writeString(path);
|
||||
}
|
||||
|
||||
nodeList->sendPacketList(std::move(packetList), *assetServer);
|
||||
|
||||
_pendingMappingRequests[assetServer][messageID] = callback;
|
||||
|
||||
return true;
|
||||
} else {
|
||||
callback(false, AssetServerError::NoError, QSharedPointer<ReceivedMessage>());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool AssetClient::setAssetMapping(const QString& path, const AssetHash& hash, MappingOperationCallback callback) {
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
SharedNodePointer assetServer = nodeList->soloNodeOfType(NodeType::AssetServer);
|
||||
|
||||
if (assetServer) {
|
||||
auto packetList = NLPacketList::create(PacketType::AssetMappingOperation, QByteArray(), true, true);
|
||||
|
||||
auto messageID = ++_currentID;
|
||||
packetList->writePrimitive(messageID);
|
||||
|
||||
packetList->writePrimitive(AssetMappingOperationType::Set);
|
||||
|
||||
packetList->writeString(path);
|
||||
packetList->write(QByteArray::fromHex(hash.toUtf8()));
|
||||
|
||||
nodeList->sendPacketList(std::move(packetList), *assetServer);
|
||||
|
||||
_pendingMappingRequests[assetServer][messageID] = callback;
|
||||
|
||||
return true;
|
||||
} else {
|
||||
callback(false, AssetServerError::NoError, QSharedPointer<ReceivedMessage>());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool AssetClient::renameAssetMapping(const AssetPath& oldPath, const AssetPath& newPath, MappingOperationCallback callback) {
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
SharedNodePointer assetServer = nodeList->soloNodeOfType(NodeType::AssetServer);
|
||||
|
||||
if (assetServer) {
|
||||
auto packetList = NLPacketList::create(PacketType::AssetMappingOperation, QByteArray(), true, true);
|
||||
|
||||
auto messageID = ++_currentID;
|
||||
packetList->writePrimitive(messageID);
|
||||
|
||||
packetList->writePrimitive(AssetMappingOperationType::Rename);
|
||||
|
||||
packetList->writeString(oldPath);
|
||||
packetList->writeString(newPath);
|
||||
|
||||
nodeList->sendPacketList(std::move(packetList), *assetServer);
|
||||
|
||||
_pendingMappingRequests[assetServer][messageID] = callback;
|
||||
|
||||
return true;
|
||||
|
||||
} else {
|
||||
callback(false, AssetServerError::NoError, QSharedPointer<ReceivedMessage>());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool AssetClient::uploadAsset(const QByteArray& data, UploadResultCallback callback) {
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
SharedNodePointer assetServer = nodeList->soloNodeOfType(NodeType::AssetServer);
|
||||
|
||||
|
@ -315,9 +488,6 @@ bool AssetClient::uploadAsset(const QByteArray& data, const QString& extension,
|
|||
auto messageID = ++_currentID;
|
||||
packetList->writePrimitive(messageID);
|
||||
|
||||
packetList->writePrimitive(static_cast<uint8_t>(extension.length()));
|
||||
packetList->write(extension.toLatin1().constData(), extension.length());
|
||||
|
||||
uint64_t size = data.length();
|
||||
packetList->writePrimitive(size);
|
||||
packetList->write(data.constData(), size);
|
||||
|
@ -327,8 +497,10 @@ bool AssetClient::uploadAsset(const QByteArray& data, const QString& extension,
|
|||
_pendingUploads[assetServer][messageID] = callback;
|
||||
|
||||
return true;
|
||||
} else {
|
||||
callback(false, AssetServerError::NoError, QString());
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void AssetClient::handleAssetUploadReply(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
|
||||
|
@ -404,71 +576,14 @@ void AssetClient::handleNodeKilled(SharedNodePointer node) {
|
|||
messageMapIt->second.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AssetScriptingInterface::uploadData(QString data, QString extension, QScriptValue callback) {
|
||||
QByteArray dataByteArray = data.toUtf8();
|
||||
auto upload = DependencyManager::get<AssetClient>()->createUpload(dataByteArray, extension);
|
||||
if (!upload) {
|
||||
qCWarning(asset_client) << "Error uploading file to asset server";
|
||||
return;
|
||||
}
|
||||
|
||||
QObject::connect(upload, &AssetUpload::finished, this, [this, callback, extension](AssetUpload* upload, const QString& hash) mutable {
|
||||
if (callback.isFunction()) {
|
||||
QString url = "atp://" + hash + "." + extension;
|
||||
QScriptValueList args { url };
|
||||
callback.call(_engine->currentContext()->thisObject(), args);
|
||||
}
|
||||
});
|
||||
upload->start();
|
||||
}
|
||||
|
||||
AssetScriptingInterface::AssetScriptingInterface(QScriptEngine* engine) :
|
||||
_engine(engine)
|
||||
{
|
||||
}
|
||||
|
||||
void AssetScriptingInterface::downloadData(QString urlString, QScriptValue callback) {
|
||||
const QString ATP_SCHEME { "atp://" };
|
||||
|
||||
if (!urlString.startsWith(ATP_SCHEME)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Make request to atp
|
||||
auto path = urlString.right(urlString.length() - ATP_SCHEME.length());
|
||||
auto parts = path.split(".", QString::SkipEmptyParts);
|
||||
auto hash = parts.length() > 0 ? parts[0] : "";
|
||||
auto extension = parts.length() > 1 ? parts[1] : "";
|
||||
|
||||
if (hash.length() != SHA256_HASH_HEX_LENGTH) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto assetClient = DependencyManager::get<AssetClient>();
|
||||
auto assetRequest = assetClient->createRequest(hash, extension);
|
||||
|
||||
if (!assetRequest) {
|
||||
return;
|
||||
}
|
||||
|
||||
_pendingRequests << assetRequest;
|
||||
|
||||
connect(assetRequest, &AssetRequest::finished, this, [this, callback](AssetRequest* request) mutable {
|
||||
Q_ASSERT(request->getState() == AssetRequest::Finished);
|
||||
|
||||
if (request->getError() == AssetRequest::Error::NoError) {
|
||||
if (callback.isFunction()) {
|
||||
QString data = QString::fromUtf8(request->getData());
|
||||
QScriptValueList args { data };
|
||||
callback.call(_engine->currentContext()->thisObject(), args);
|
||||
{
|
||||
auto messageMapIt = _pendingMappingRequests.find(node);
|
||||
if (messageMapIt != _pendingMappingRequests.end()) {
|
||||
for (const auto& value : messageMapIt->second) {
|
||||
value.second(false, AssetServerError::NoError, QSharedPointer<ReceivedMessage>());
|
||||
}
|
||||
messageMapIt->second.clear();
|
||||
}
|
||||
|
||||
request->deleteLater();
|
||||
_pendingRequests.remove(request);
|
||||
});
|
||||
|
||||
assetRequest->start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,12 +9,14 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
|
||||
#ifndef hifi_AssetClient_h
|
||||
#define hifi_AssetClient_h
|
||||
|
||||
#include <QStandardItemModel>
|
||||
#include <QtQml/QJSEngine>
|
||||
#include <QString>
|
||||
#include <QScriptValue>
|
||||
|
||||
#include <map>
|
||||
|
||||
#include <DependencyManager.h>
|
||||
|
||||
|
@ -25,6 +27,11 @@
|
|||
#include "ReceivedMessage.h"
|
||||
#include "ResourceCache.h"
|
||||
|
||||
class GetMappingRequest;
|
||||
class SetMappingRequest;
|
||||
class GetAllMappingsRequest;
|
||||
class DeleteMappingsRequest;
|
||||
class RenameMappingRequest;
|
||||
class AssetRequest;
|
||||
class AssetUpload;
|
||||
|
||||
|
@ -33,20 +40,25 @@ struct AssetInfo {
|
|||
int64_t size;
|
||||
};
|
||||
|
||||
using MappingOperationCallback = std::function<void(bool responseReceived, AssetServerError serverError, QSharedPointer<ReceivedMessage> message)>;
|
||||
using ReceivedAssetCallback = std::function<void(bool responseReceived, AssetServerError serverError, const QByteArray& data)>;
|
||||
using GetInfoCallback = std::function<void(bool responseReceived, AssetServerError serverError, AssetInfo info)>;
|
||||
using UploadResultCallback = std::function<void(bool responseReceived, AssetServerError serverError, const QString& hash)>;
|
||||
using ProgressCallback = std::function<void(qint64 totalReceived, qint64 total)>;
|
||||
|
||||
|
||||
class AssetClient : public QObject, public Dependency {
|
||||
Q_OBJECT
|
||||
public:
|
||||
AssetClient();
|
||||
|
||||
Q_INVOKABLE AssetRequest* createRequest(const QString& hash, const QString& extension);
|
||||
Q_INVOKABLE GetMappingRequest* createGetMappingRequest(const AssetPath& path);
|
||||
Q_INVOKABLE GetAllMappingsRequest* createGetAllMappingsRequest();
|
||||
Q_INVOKABLE DeleteMappingsRequest* createDeleteMappingsRequest(const AssetPathList& paths);
|
||||
Q_INVOKABLE SetMappingRequest* createSetMappingRequest(const AssetPath& path, const AssetHash& hash);
|
||||
Q_INVOKABLE RenameMappingRequest* createRenameMappingRequest(const AssetPath& oldPath, const AssetPath& newPath);
|
||||
Q_INVOKABLE AssetRequest* createRequest(const AssetHash& hash);
|
||||
Q_INVOKABLE AssetUpload* createUpload(const QString& filename);
|
||||
Q_INVOKABLE AssetUpload* createUpload(const QByteArray& data, const QString& extension);
|
||||
Q_INVOKABLE AssetUpload* createUpload(const QByteArray& data);
|
||||
|
||||
public slots:
|
||||
void init();
|
||||
|
@ -55,6 +67,7 @@ public slots:
|
|||
void clearCache();
|
||||
|
||||
private slots:
|
||||
void handleAssetMappingOperationReply(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
|
||||
void handleAssetGetInfoReply(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
|
||||
void handleAssetGetReply(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
|
||||
void handleAssetUploadReply(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
|
||||
|
@ -62,10 +75,16 @@ private slots:
|
|||
void handleNodeKilled(SharedNodePointer node);
|
||||
|
||||
private:
|
||||
bool getAssetInfo(const QString& hash, const QString& extension, GetInfoCallback callback);
|
||||
bool getAsset(const QString& hash, const QString& extension, DataOffset start, DataOffset end,
|
||||
bool getAssetMapping(const AssetHash& hash, MappingOperationCallback callback);
|
||||
bool getAllAssetMappings(MappingOperationCallback callback);
|
||||
bool setAssetMapping(const QString& path, const AssetHash& hash, MappingOperationCallback callback);
|
||||
bool deleteAssetMappings(const AssetPathList& paths, MappingOperationCallback callback);
|
||||
bool renameAssetMapping(const AssetPath& oldPath, const AssetPath& newPath, MappingOperationCallback callback);
|
||||
|
||||
bool getAssetInfo(const QString& hash, GetInfoCallback callback);
|
||||
bool getAsset(const QString& hash, DataOffset start, DataOffset end,
|
||||
ReceivedAssetCallback callback, ProgressCallback progressCallback);
|
||||
bool uploadAsset(const QByteArray& data, const QString& extension, UploadResultCallback callback);
|
||||
bool uploadAsset(const QByteArray& data, UploadResultCallback callback);
|
||||
|
||||
struct GetAssetCallbacks {
|
||||
ReceivedAssetCallback completeCallback;
|
||||
|
@ -73,26 +92,18 @@ private:
|
|||
};
|
||||
|
||||
static MessageID _currentID;
|
||||
std::unordered_map<SharedNodePointer, std::unordered_map<MessageID, MappingOperationCallback>> _pendingMappingRequests;
|
||||
std::unordered_map<SharedNodePointer, std::unordered_map<MessageID, GetAssetCallbacks>> _pendingRequests;
|
||||
std::unordered_map<SharedNodePointer, std::unordered_map<MessageID, GetInfoCallback>> _pendingInfoRequests;
|
||||
std::unordered_map<SharedNodePointer, std::unordered_map<MessageID, UploadResultCallback>> _pendingUploads;
|
||||
|
||||
|
||||
friend class AssetRequest;
|
||||
friend class AssetUpload;
|
||||
friend class GetMappingRequest;
|
||||
friend class GetAllMappingsRequest;
|
||||
friend class SetMappingRequest;
|
||||
friend class DeleteMappingsRequest;
|
||||
friend class RenameMappingRequest;
|
||||
};
|
||||
|
||||
|
||||
class AssetScriptingInterface : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
AssetScriptingInterface(QScriptEngine* engine);
|
||||
|
||||
Q_INVOKABLE void uploadData(QString data, QString extension, QScriptValue callback);
|
||||
Q_INVOKABLE void downloadData(QString url, QScriptValue downloadComplete);
|
||||
protected:
|
||||
QSet<AssetRequest*> _pendingRequests;
|
||||
QScriptEngine* _engine;
|
||||
};
|
||||
|
||||
|
||||
#endif
|
||||
|
|
|
@ -20,10 +20,8 @@
|
|||
#include "NodeList.h"
|
||||
#include "ResourceCache.h"
|
||||
|
||||
AssetRequest::AssetRequest(const QString& hash, const QString& extension) :
|
||||
QObject(),
|
||||
_hash(hash),
|
||||
_extension(extension)
|
||||
AssetRequest::AssetRequest(const QString& hash) :
|
||||
_hash(hash)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -37,6 +35,15 @@ void AssetRequest::start() {
|
|||
qCWarning(asset_client) << "AssetRequest already started.";
|
||||
return;
|
||||
}
|
||||
|
||||
// in case we haven't parsed a valid hash, return an error now
|
||||
if (!isValidHash(_hash)) {
|
||||
_error = InvalidHash;
|
||||
_state = Finished;
|
||||
|
||||
emit finished(this);
|
||||
return;
|
||||
}
|
||||
|
||||
// Try to load from cache
|
||||
_data = loadFromCache(getUrl());
|
||||
|
@ -53,9 +60,9 @@ void AssetRequest::start() {
|
|||
_state = WaitingForInfo;
|
||||
|
||||
auto assetClient = DependencyManager::get<AssetClient>();
|
||||
assetClient->getAssetInfo(_hash, _extension, [this](bool responseReceived, AssetServerError serverError, AssetInfo info) {
|
||||
assetClient->getAssetInfo(_hash, [this](bool responseReceived, AssetServerError serverError, AssetInfo info) {
|
||||
_info = info;
|
||||
|
||||
|
||||
if (!responseReceived) {
|
||||
_error = NetworkError;
|
||||
} else if (serverError != AssetServerError::NoError) {
|
||||
|
@ -85,7 +92,7 @@ void AssetRequest::start() {
|
|||
int start = 0, end = _info.size;
|
||||
|
||||
auto assetClient = DependencyManager::get<AssetClient>();
|
||||
assetClient->getAsset(_hash, _extension, start, end, [this, start, end](bool responseReceived, AssetServerError serverError,
|
||||
assetClient->getAsset(_hash, start, end, [this, start, end](bool responseReceived, AssetServerError serverError,
|
||||
const QByteArray& data) {
|
||||
if (!responseReceived) {
|
||||
_error = NetworkError;
|
||||
|
|
|
@ -34,19 +34,20 @@ public:
|
|||
NoError,
|
||||
NotFound,
|
||||
InvalidByteRange,
|
||||
InvalidHash,
|
||||
HashVerificationFailed,
|
||||
NetworkError,
|
||||
UnknownError
|
||||
};
|
||||
|
||||
AssetRequest(const QString& hash, const QString& extension);
|
||||
AssetRequest(const QString& hash);
|
||||
|
||||
Q_INVOKABLE void start();
|
||||
|
||||
const QByteArray& getData() const { return _data; }
|
||||
const State& getState() const { return _state; }
|
||||
const Error& getError() const { return _error; }
|
||||
QUrl getUrl() const { return ::getATPUrl(_hash, _extension); }
|
||||
QUrl getUrl() const { return ::getATPUrl(_hash); }
|
||||
|
||||
signals:
|
||||
void finished(AssetRequest* thisRequest);
|
||||
|
@ -58,7 +59,6 @@ private:
|
|||
AssetInfo _info;
|
||||
uint64_t _totalReceived { 0 };
|
||||
QString _hash;
|
||||
QString _extension;
|
||||
QByteArray _data;
|
||||
int _numPendingRequests { 0 };
|
||||
};
|
||||
|
|
|
@ -13,37 +13,99 @@
|
|||
|
||||
#include "AssetClient.h"
|
||||
#include "AssetUtils.h"
|
||||
#include "MappingRequest.h"
|
||||
|
||||
AssetResourceRequest::~AssetResourceRequest() {
|
||||
if (_assetMappingRequest) {
|
||||
_assetMappingRequest->deleteLater();
|
||||
}
|
||||
|
||||
if (_assetRequest) {
|
||||
_assetRequest->deleteLater();
|
||||
}
|
||||
}
|
||||
|
||||
bool AssetResourceRequest::urlIsAssetHash() const {
|
||||
static const QString ATP_HASH_REGEX_STRING { "^atp:([A-Fa-f0-9]{64})(\\.[\\w]+)?$" };
|
||||
|
||||
QRegExp hashRegex { ATP_HASH_REGEX_STRING };
|
||||
return hashRegex.exactMatch(_url.toString());
|
||||
}
|
||||
|
||||
void AssetResourceRequest::doSend() {
|
||||
// Make request to atp
|
||||
auto assetClient = DependencyManager::get<AssetClient>();
|
||||
auto parts = _url.path().split(".", QString::SkipEmptyParts);
|
||||
auto hash = parts.length() > 0 ? parts[0] : "";
|
||||
auto extension = parts.length() > 1 ? parts[1] : "";
|
||||
|
||||
if (hash.length() != SHA256_HASH_HEX_LENGTH) {
|
||||
_result = InvalidURL;
|
||||
_state = Finished;
|
||||
// We'll either have a hash or an ATP path to a file (that maps to a hash)
|
||||
|
||||
emit finished();
|
||||
return;
|
||||
if (urlIsAssetHash()) {
|
||||
// We've detected that this is a hash - simply use AssetClient to request that asset
|
||||
auto parts = _url.path().split(".", QString::SkipEmptyParts);
|
||||
auto hash = parts.length() > 0 ? parts[0] : "";
|
||||
|
||||
requestHash(hash);
|
||||
} else {
|
||||
// This is an ATP path, we'll need to figure out what the mapping is.
|
||||
// This may incur a roundtrip to the asset-server, or it may return immediately from the cache in AssetClient.
|
||||
|
||||
auto path = _url.path();
|
||||
requestMappingForPath(path);
|
||||
}
|
||||
}
|
||||
|
||||
_assetRequest = assetClient->createRequest(hash, extension);
|
||||
void AssetResourceRequest::requestMappingForPath(const AssetPath& path) {
|
||||
auto assetClient = DependencyManager::get<AssetClient>();
|
||||
_assetMappingRequest = assetClient->createGetMappingRequest(path);
|
||||
|
||||
if (!_assetRequest) {
|
||||
_result = ServerUnavailable;
|
||||
_state = Finished;
|
||||
// make sure we'll hear about the result of the get mapping request
|
||||
connect(_assetMappingRequest, &GetMappingRequest::finished, this, [this, path](GetMappingRequest* request){
|
||||
Q_ASSERT(_state == InProgress);
|
||||
Q_ASSERT(request == _assetMappingRequest);
|
||||
|
||||
emit finished();
|
||||
return;
|
||||
}
|
||||
switch (request->getError()) {
|
||||
case MappingRequest::NoError:
|
||||
// we have no error, we should have a resulting hash - use that to send of a request for that asset
|
||||
qDebug() << "Got mapping for:" << path << "=>" << request->getHash();
|
||||
|
||||
requestHash(request->getHash());
|
||||
|
||||
break;
|
||||
default: {
|
||||
switch (request->getError()) {
|
||||
case MappingRequest::NotFound:
|
||||
// no result for the mapping request, set error to not found
|
||||
_result = NotFound;
|
||||
break;
|
||||
case MappingRequest::NetworkError:
|
||||
// didn't hear back from the server, mark it unavailable
|
||||
_result = ServerUnavailable;
|
||||
break;
|
||||
default:
|
||||
_result = Error;
|
||||
break;
|
||||
}
|
||||
|
||||
// since we've failed we know we are finished
|
||||
_state = Finished;
|
||||
emit finished();
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_assetMappingRequest->deleteLater();
|
||||
_assetMappingRequest = nullptr;
|
||||
});
|
||||
|
||||
_assetMappingRequest->start();
|
||||
}
|
||||
|
||||
void AssetResourceRequest::requestHash(const AssetHash& hash) {
|
||||
|
||||
// Make request to atp
|
||||
auto assetClient = DependencyManager::get<AssetClient>();
|
||||
_assetRequest = assetClient->createRequest(hash);
|
||||
|
||||
connect(_assetRequest, &AssetRequest::progress, this, &AssetResourceRequest::progress);
|
||||
connect(_assetRequest, &AssetRequest::finished, this, [this](AssetRequest* req) {
|
||||
|
@ -56,6 +118,9 @@ void AssetResourceRequest::doSend() {
|
|||
_data = req->getData();
|
||||
_result = Success;
|
||||
break;
|
||||
case AssetRequest::InvalidHash:
|
||||
_result = InvalidURL;
|
||||
break;
|
||||
case AssetRequest::Error::NotFound:
|
||||
_result = NotFound;
|
||||
break;
|
||||
|
|
|
@ -30,6 +30,12 @@ private slots:
|
|||
void onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal);
|
||||
|
||||
private:
|
||||
bool urlIsAssetHash() const;
|
||||
|
||||
void requestMappingForPath(const AssetPath& path);
|
||||
void requestHash(const AssetHash& hash);
|
||||
|
||||
GetMappingRequest* _assetMappingRequest { nullptr };
|
||||
AssetRequest* _assetRequest { nullptr };
|
||||
};
|
||||
|
||||
|
|
|
@ -19,9 +19,8 @@
|
|||
|
||||
const QString AssetUpload::PERMISSION_DENIED_ERROR = "You do not have permission to upload content to this asset-server.";
|
||||
|
||||
AssetUpload::AssetUpload(const QByteArray& data, const QString& extension) :
|
||||
_data(data),
|
||||
_extension(extension)
|
||||
AssetUpload::AssetUpload(const QByteArray& data) :
|
||||
_data(data)
|
||||
{
|
||||
|
||||
}
|
||||
|
@ -35,6 +34,8 @@ AssetUpload::AssetUpload(const QString& filename) :
|
|||
QString AssetUpload::getErrorString() const {
|
||||
// figure out the right error message for error
|
||||
switch (_error) {
|
||||
case AssetUpload::NoError:
|
||||
return QString();
|
||||
case AssetUpload::PermissionDenied:
|
||||
return PERMISSION_DENIED_ERROR;
|
||||
case AssetUpload::TooLarge:
|
||||
|
@ -42,10 +43,11 @@ QString AssetUpload::getErrorString() const {
|
|||
case AssetUpload::FileOpenError:
|
||||
return "The file could not be opened. Please check your permissions and try again.";
|
||||
case AssetUpload::NetworkError:
|
||||
return "The file could not be opened. Please check your network connectivity.";
|
||||
return "There was a problem reaching your Asset Server. Please check your network connectivity.";
|
||||
case AssetUpload::ServerFileError:
|
||||
return "The Asset Server failed to store the asset. Please try again.";
|
||||
default:
|
||||
// not handled, do not show a message box
|
||||
return QString();
|
||||
return QString("Unknown error with code %1").arg(_error);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -59,12 +61,7 @@ void AssetUpload::start() {
|
|||
// try to open the file at the given filename
|
||||
QFile file { _filename };
|
||||
|
||||
if (file.open(QIODevice::ReadOnly)) {
|
||||
|
||||
// file opened, read the data and grab the extension
|
||||
_extension = QFileInfo(_filename).suffix();
|
||||
_extension = _extension.toLower();
|
||||
|
||||
if (file.open(QIODevice::ReadOnly)) {
|
||||
_data = file.readAll();
|
||||
} else {
|
||||
// we couldn't open the file - set the error result
|
||||
|
@ -72,6 +69,8 @@ void AssetUpload::start() {
|
|||
|
||||
// emit that we are done
|
||||
emit finished(this, QString());
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -82,7 +81,7 @@ void AssetUpload::start() {
|
|||
qCDebug(asset_client) << "Attempting to upload" << _filename << "to asset-server.";
|
||||
}
|
||||
|
||||
assetClient->uploadAsset(_data, _extension, [this](bool responseReceived, AssetServerError error, const QString& hash){
|
||||
assetClient->uploadAsset(_data, [this](bool responseReceived, AssetServerError error, const QString& hash){
|
||||
if (!responseReceived) {
|
||||
_error = NetworkError;
|
||||
} else {
|
||||
|
@ -96,6 +95,9 @@ void AssetUpload::start() {
|
|||
case AssetServerError::PermissionDenied:
|
||||
_error = PermissionDenied;
|
||||
break;
|
||||
case AssetServerError::FileOperationFailed:
|
||||
_error = ServerFileError;
|
||||
break;
|
||||
default:
|
||||
_error = FileOpenError;
|
||||
break;
|
||||
|
@ -103,7 +105,7 @@ void AssetUpload::start() {
|
|||
}
|
||||
|
||||
if (_error == NoError && hash == hashData(_data).toHex()) {
|
||||
saveToCache(getATPUrl(hash, _extension), _data);
|
||||
saveToCache(getATPUrl(hash), _data);
|
||||
}
|
||||
|
||||
emit finished(this, hash);
|
||||
|
|
|
@ -32,18 +32,18 @@ public:
|
|||
Timeout,
|
||||
TooLarge,
|
||||
PermissionDenied,
|
||||
FileOpenError
|
||||
FileOpenError,
|
||||
ServerFileError
|
||||
};
|
||||
|
||||
static const QString PERMISSION_DENIED_ERROR;
|
||||
|
||||
AssetUpload(const QString& filename);
|
||||
AssetUpload(const QByteArray& data, const QString& extension);
|
||||
AssetUpload(const QByteArray& data);
|
||||
|
||||
Q_INVOKABLE void start();
|
||||
|
||||
const QString& getFilename() const { return _filename; }
|
||||
const QString& getExtension() const { return _extension; }
|
||||
const Error& getError() const { return _error; }
|
||||
QString getErrorString() const;
|
||||
|
||||
|
@ -54,7 +54,6 @@ signals:
|
|||
private:
|
||||
QString _filename;
|
||||
QByteArray _data;
|
||||
QString _extension;
|
||||
Error _error;
|
||||
};
|
||||
|
||||
|
|
|
@ -19,12 +19,8 @@
|
|||
|
||||
#include "ResourceManager.h"
|
||||
|
||||
QUrl getATPUrl(const QString& hash, const QString& extension) {
|
||||
if (!extension.isEmpty()) {
|
||||
return QUrl(QString("%1:%2.%3").arg(URL_SCHEME_ATP, hash, extension));
|
||||
} else {
|
||||
return QUrl(QString("%1:%2").arg(URL_SCHEME_ATP, hash));
|
||||
}
|
||||
QUrl getATPUrl(const QString& hash) {
|
||||
return QUrl(QString("%1:%2").arg(URL_SCHEME_ATP, hash));
|
||||
}
|
||||
|
||||
QByteArray hashData(const QByteArray& data) {
|
||||
|
@ -67,3 +63,13 @@ bool saveToCache(const QUrl& url, const QByteArray& file) {
|
|||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool isValidPath(const AssetPath& path) {
|
||||
QRegExp pathRegex { ASSET_PATH_REGEX_STRING };
|
||||
return pathRegex.exactMatch(path);
|
||||
}
|
||||
|
||||
bool isValidHash(const AssetHash& hash) {
|
||||
QRegExp hashRegex { ASSET_HASH_REGEX_STRING };
|
||||
return hashRegex.exactMatch(hash);
|
||||
}
|
||||
|
|
|
@ -14,29 +14,52 @@
|
|||
|
||||
#include <cstdint>
|
||||
|
||||
#include <map>
|
||||
|
||||
#include <QtCore/QByteArray>
|
||||
#include <QtCore/QUrl>
|
||||
|
||||
using MessageID = uint32_t;
|
||||
using DataOffset = int64_t;
|
||||
|
||||
using AssetPath = QString;
|
||||
using AssetHash = QString;
|
||||
using AssetMapping = std::map<AssetPath, AssetHash>;
|
||||
using AssetPathList = QStringList;
|
||||
|
||||
const size_t SHA256_HASH_LENGTH = 32;
|
||||
const size_t SHA256_HASH_HEX_LENGTH = 64;
|
||||
const uint64_t MAX_UPLOAD_SIZE = 1000 * 1000 * 1000; // 1GB
|
||||
|
||||
const QString ASSET_PATH_REGEX_STRING = "^\\/(?!\\/)(?:[^\\/]|\\/(?!\\/))*$";
|
||||
const QString ASSET_HASH_REGEX_STRING = QString("^[a-fA-F0-9]{%1}$").arg(SHA256_HASH_HEX_LENGTH);
|
||||
|
||||
enum AssetServerError : uint8_t {
|
||||
NoError = 0,
|
||||
AssetNotFound,
|
||||
InvalidByteRange,
|
||||
AssetTooLarge,
|
||||
PermissionDenied
|
||||
PermissionDenied,
|
||||
MappingOperationFailed,
|
||||
FileOperationFailed
|
||||
};
|
||||
|
||||
QUrl getATPUrl(const QString& hash, const QString& extension = QString());
|
||||
enum AssetMappingOperationType : uint8_t {
|
||||
Get = 0,
|
||||
GetAll,
|
||||
Set,
|
||||
Delete,
|
||||
Rename
|
||||
};
|
||||
|
||||
QUrl getATPUrl(const QString& hash);
|
||||
|
||||
QByteArray hashData(const QByteArray& data);
|
||||
|
||||
QByteArray loadFromCache(const QUrl& url);
|
||||
bool saveToCache(const QUrl& url, const QByteArray& file);
|
||||
|
||||
bool isValidPath(const AssetPath& path);
|
||||
bool isValidHash(const QString& hashString);
|
||||
|
||||
#endif
|
||||
|
|
240
libraries/networking/src/MappingRequest.cpp
Normal file
240
libraries/networking/src/MappingRequest.cpp
Normal file
|
@ -0,0 +1,240 @@
|
|||
//
|
||||
// MappingRequest.cpp
|
||||
// libraries/networking/src
|
||||
//
|
||||
// Created by Stephen Birarda on 2016-03-08.
|
||||
// Copyright 2016 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 "MappingRequest.h"
|
||||
|
||||
#include <QtCore/QThread>
|
||||
|
||||
#include <DependencyManager.h>
|
||||
|
||||
#include "AssetClient.h"
|
||||
|
||||
void MappingRequest::start() {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "start", Qt::AutoConnection);
|
||||
return;
|
||||
}
|
||||
doStart();
|
||||
};
|
||||
|
||||
QString MappingRequest::getErrorString() const {
|
||||
switch (_error) {
|
||||
case MappingRequest::NoError:
|
||||
return QString();
|
||||
case MappingRequest::NotFound:
|
||||
return "Asset not found";
|
||||
case MappingRequest::NetworkError:
|
||||
return "Unable to communicate with Asset Server";
|
||||
case MappingRequest::PermissionDenied:
|
||||
return "Permission denied";
|
||||
case MappingRequest::InvalidPath:
|
||||
return "Path is invalid";
|
||||
case MappingRequest::InvalidHash:
|
||||
return "Hash is invalid";
|
||||
case MappingRequest::UnknownError:
|
||||
return "Asset Server internal error";
|
||||
default:
|
||||
return QString("Unknown error with code %1").arg(_error);
|
||||
}
|
||||
}
|
||||
|
||||
GetMappingRequest::GetMappingRequest(const AssetPath& path) : _path(path.trimmed()) {
|
||||
};
|
||||
|
||||
void GetMappingRequest::doStart() {
|
||||
|
||||
// short circuit the request if the path is invalid
|
||||
if (!isValidPath(_path)) {
|
||||
_error = MappingRequest::InvalidPath;
|
||||
emit finished(this);
|
||||
return;
|
||||
}
|
||||
|
||||
auto assetClient = DependencyManager::get<AssetClient>();
|
||||
|
||||
assetClient->getAssetMapping(_path, [this, assetClient](bool responseReceived, AssetServerError error, QSharedPointer<ReceivedMessage> message) {
|
||||
if (!responseReceived) {
|
||||
_error = NetworkError;
|
||||
} else {
|
||||
switch (error) {
|
||||
case AssetServerError::NoError:
|
||||
_error = NoError;
|
||||
break;
|
||||
case AssetServerError::AssetNotFound:
|
||||
_error = NotFound;
|
||||
break;
|
||||
default:
|
||||
_error = UnknownError;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!_error) {
|
||||
_hash = message->read(SHA256_HASH_LENGTH).toHex();
|
||||
}
|
||||
emit finished(this);
|
||||
});
|
||||
};
|
||||
|
||||
GetAllMappingsRequest::GetAllMappingsRequest() {
|
||||
};
|
||||
|
||||
void GetAllMappingsRequest::doStart() {
|
||||
auto assetClient = DependencyManager::get<AssetClient>();
|
||||
assetClient->getAllAssetMappings([this, assetClient](bool responseReceived, AssetServerError error, QSharedPointer<ReceivedMessage> message) {
|
||||
if (!responseReceived) {
|
||||
_error = NetworkError;
|
||||
} else {
|
||||
switch (error) {
|
||||
case AssetServerError::NoError:
|
||||
_error = NoError;
|
||||
break;
|
||||
default:
|
||||
_error = UnknownError;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (!_error) {
|
||||
int numberOfMappings;
|
||||
message->readPrimitive(&numberOfMappings);
|
||||
for (auto i = 0; i < numberOfMappings; ++i) {
|
||||
auto path = message->readString();
|
||||
auto hash = message->read(SHA256_HASH_LENGTH).toHex();
|
||||
_mappings[path] = hash;
|
||||
}
|
||||
}
|
||||
emit finished(this);
|
||||
});
|
||||
};
|
||||
|
||||
SetMappingRequest::SetMappingRequest(const AssetPath& path, const AssetHash& hash) :
|
||||
_path(path.trimmed()),
|
||||
_hash(hash)
|
||||
{
|
||||
|
||||
};
|
||||
|
||||
void SetMappingRequest::doStart() {
|
||||
|
||||
// short circuit the request if the hash or path are invalid
|
||||
auto validPath = isValidPath(_path);
|
||||
auto validHash = isValidHash(_hash);
|
||||
if (!validPath || !validHash) {
|
||||
_error = !validPath ? MappingRequest::InvalidPath : MappingRequest::InvalidHash;
|
||||
emit finished(this);
|
||||
return;
|
||||
}
|
||||
|
||||
auto assetClient = DependencyManager::get<AssetClient>();
|
||||
|
||||
assetClient->setAssetMapping(_path, _hash, [this, assetClient](bool responseReceived, AssetServerError error, QSharedPointer<ReceivedMessage> message) {
|
||||
if (!responseReceived) {
|
||||
_error = NetworkError;
|
||||
} else {
|
||||
switch (error) {
|
||||
case AssetServerError::NoError:
|
||||
_error = NoError;
|
||||
break;
|
||||
case AssetServerError::PermissionDenied:
|
||||
_error = PermissionDenied;
|
||||
break;
|
||||
default:
|
||||
_error = UnknownError;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
emit finished(this);
|
||||
});
|
||||
};
|
||||
|
||||
DeleteMappingsRequest::DeleteMappingsRequest(const AssetPathList& paths) : _paths(paths) {
|
||||
for (auto& path : _paths) {
|
||||
path = path.trimmed();
|
||||
}
|
||||
};
|
||||
|
||||
void DeleteMappingsRequest::doStart() {
|
||||
|
||||
// short circuit the request if any of the paths are invalid
|
||||
for (auto& path : _paths) {
|
||||
if (!isValidPath(path)) {
|
||||
_error = MappingRequest::InvalidPath;
|
||||
emit finished(this);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
auto assetClient = DependencyManager::get<AssetClient>();
|
||||
|
||||
assetClient->deleteAssetMappings(_paths, [this, assetClient](bool responseReceived, AssetServerError error, QSharedPointer<ReceivedMessage> message) {
|
||||
if (!responseReceived) {
|
||||
_error = NetworkError;
|
||||
} else {
|
||||
switch (error) {
|
||||
case AssetServerError::NoError:
|
||||
_error = NoError;
|
||||
break;
|
||||
case AssetServerError::PermissionDenied:
|
||||
_error = PermissionDenied;
|
||||
break;
|
||||
default:
|
||||
_error = UnknownError;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
emit finished(this);
|
||||
});
|
||||
};
|
||||
|
||||
RenameMappingRequest::RenameMappingRequest(const AssetPath& oldPath, const AssetPath& newPath) :
|
||||
_oldPath(oldPath.trimmed()),
|
||||
_newPath(newPath.trimmed())
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void RenameMappingRequest::doStart() {
|
||||
|
||||
// short circuit the request if either of the paths are invalid
|
||||
if (!isValidPath(_oldPath) || !isValidPath(_newPath)) {
|
||||
_error = InvalidPath;
|
||||
emit finished(this);
|
||||
return;
|
||||
}
|
||||
|
||||
auto assetClient = DependencyManager::get<AssetClient>();
|
||||
|
||||
assetClient->renameAssetMapping(_oldPath, _newPath, [this, assetClient](bool responseReceived,
|
||||
AssetServerError error,
|
||||
QSharedPointer<ReceivedMessage> message) {
|
||||
if (!responseReceived) {
|
||||
_error = NetworkError;
|
||||
} else {
|
||||
switch (error) {
|
||||
case AssetServerError::NoError:
|
||||
_error = NoError;
|
||||
break;
|
||||
case AssetServerError::PermissionDenied:
|
||||
_error = PermissionDenied;
|
||||
break;
|
||||
default:
|
||||
_error = UnknownError;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
emit finished(this);
|
||||
});
|
||||
}
|
127
libraries/networking/src/MappingRequest.h
Normal file
127
libraries/networking/src/MappingRequest.h
Normal file
|
@ -0,0 +1,127 @@
|
|||
//
|
||||
// MappingRequest.h
|
||||
// libraries/networking/src
|
||||
//
|
||||
// Created by Stephen Birarda on 2016-03-08.
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef hifi_MappingRequest_h
|
||||
#define hifi_MappingRequest_h
|
||||
|
||||
#include <QtCore/QObject>
|
||||
|
||||
#include "AssetUtils.h"
|
||||
|
||||
class MappingRequest : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum Error {
|
||||
NoError,
|
||||
NotFound,
|
||||
NetworkError,
|
||||
PermissionDenied,
|
||||
InvalidPath,
|
||||
InvalidHash,
|
||||
UnknownError
|
||||
};
|
||||
|
||||
Q_INVOKABLE void start();
|
||||
Error getError() const { return _error; }
|
||||
Q_INVOKABLE QString getErrorString() const;
|
||||
|
||||
protected:
|
||||
Error _error { NoError };
|
||||
|
||||
private:
|
||||
virtual void doStart() = 0;
|
||||
};
|
||||
|
||||
|
||||
class GetMappingRequest : public MappingRequest {
|
||||
Q_OBJECT
|
||||
public:
|
||||
GetMappingRequest(const AssetPath& path);
|
||||
|
||||
AssetHash getHash() const { return _hash; }
|
||||
|
||||
signals:
|
||||
void finished(GetMappingRequest* thisRequest);
|
||||
|
||||
private:
|
||||
virtual void doStart() override;
|
||||
|
||||
AssetPath _path;
|
||||
AssetHash _hash;
|
||||
};
|
||||
|
||||
class SetMappingRequest : public MappingRequest {
|
||||
Q_OBJECT
|
||||
public:
|
||||
SetMappingRequest(const AssetPath& path, const AssetHash& hash);
|
||||
|
||||
AssetPath getPath() const { return _path; }
|
||||
AssetHash getHash() const { return _hash; }
|
||||
|
||||
signals:
|
||||
void finished(SetMappingRequest* thisRequest);
|
||||
|
||||
private:
|
||||
virtual void doStart() override;
|
||||
|
||||
AssetPath _path;
|
||||
AssetHash _hash;
|
||||
};
|
||||
|
||||
class DeleteMappingsRequest : public MappingRequest {
|
||||
Q_OBJECT
|
||||
public:
|
||||
DeleteMappingsRequest(const AssetPathList& path);
|
||||
|
||||
signals:
|
||||
void finished(DeleteMappingsRequest* thisRequest);
|
||||
|
||||
private:
|
||||
virtual void doStart() override;
|
||||
|
||||
AssetPathList _paths;
|
||||
};
|
||||
|
||||
class RenameMappingRequest : public MappingRequest {
|
||||
Q_OBJECT
|
||||
public:
|
||||
RenameMappingRequest(const AssetPath& oldPath, const AssetPath& newPath);
|
||||
|
||||
signals:
|
||||
void finished(RenameMappingRequest* thisRequest);
|
||||
|
||||
private:
|
||||
virtual void doStart() override;
|
||||
|
||||
AssetPath _oldPath;
|
||||
AssetPath _newPath;
|
||||
};
|
||||
|
||||
class GetAllMappingsRequest : public MappingRequest {
|
||||
Q_OBJECT
|
||||
public:
|
||||
GetAllMappingsRequest();
|
||||
|
||||
AssetMapping getMappings() const { return _mappings; }
|
||||
|
||||
signals:
|
||||
void finished(GetAllMappingsRequest* thisRequest);
|
||||
|
||||
private:
|
||||
virtual void doStart() override;
|
||||
|
||||
std::map<AssetPath, AssetHash> _mappings;
|
||||
};
|
||||
|
||||
|
||||
#endif // hifi_MappingRequest_h
|
|
@ -107,6 +107,15 @@ QByteArray ReceivedMessage::readAll() {
|
|||
return read(getBytesLeftToRead());
|
||||
}
|
||||
|
||||
QString ReceivedMessage::readString() {
|
||||
uint32_t size;
|
||||
readPrimitive(&size);
|
||||
//Q_ASSERT(size <= _size - _position);
|
||||
auto string = QString::fromUtf8(_data.constData() + _position, size);
|
||||
_position += size;
|
||||
return string;
|
||||
}
|
||||
|
||||
QByteArray ReceivedMessage::readWithoutCopy(qint64 size) {
|
||||
QByteArray data { QByteArray::fromRawData(_data.constData() + _position, size) };
|
||||
_position += size;
|
||||
|
|
|
@ -63,6 +63,8 @@ public:
|
|||
QByteArray read(qint64 size);
|
||||
QByteArray readAll();
|
||||
|
||||
QString readString();
|
||||
|
||||
QByteArray readHead(qint64 size);
|
||||
|
||||
// This will return a QByteArray referencing the underlying data _without_ refcounting that data.
|
||||
|
@ -86,7 +88,6 @@ private:
|
|||
QByteArray _data;
|
||||
QByteArray _headData;
|
||||
|
||||
std::atomic<qint64> _size { true };
|
||||
std::atomic<qint64> _position { 0 };
|
||||
std::atomic<qint64> _numPackets { 0 };
|
||||
|
||||
|
|
|
@ -150,6 +150,20 @@ QByteArray BasePacket::readWithoutCopy(qint64 maxSize) {
|
|||
return data;
|
||||
}
|
||||
|
||||
qint64 BasePacket::writeString(const QString& string) {
|
||||
QByteArray data = string.toUtf8();
|
||||
writePrimitive(static_cast<uint32_t>(data.length()));
|
||||
return writeData(data.constData(), data.length());
|
||||
}
|
||||
|
||||
QString BasePacket::readString() {
|
||||
uint32_t size;
|
||||
readPrimitive(&size);
|
||||
auto string = QString::fromUtf8(getPayload() + pos(), size);
|
||||
seek(pos() + size);
|
||||
return string;
|
||||
}
|
||||
|
||||
bool BasePacket::reset() {
|
||||
if (isWritable()) {
|
||||
_payloadSize = 0;
|
||||
|
|
|
@ -77,7 +77,10 @@ public:
|
|||
using QIODevice::read; // Bring QIODevice::read methods to scope, otherwise they are hidden by folling method
|
||||
QByteArray read(qint64 maxSize);
|
||||
QByteArray readWithoutCopy(qint64 maxSize); // this can only be used if packet will stay in scope
|
||||
|
||||
|
||||
qint64 writeString(const QString& string);
|
||||
QString readString();
|
||||
|
||||
template<typename T> qint64 peekPrimitive(T* data);
|
||||
template<typename T> qint64 readPrimitive(T* data);
|
||||
template<typename T> qint64 writePrimitive(const T& data);
|
||||
|
|
|
@ -53,6 +53,11 @@ PacketVersion versionForPacketType(PacketType packetType) {
|
|||
return static_cast<PacketVersion>(AvatarMixerPacketVersion::SoftAttachmentSupport);
|
||||
case PacketType::ICEServerHeartbeat:
|
||||
return 18; // ICE Server Heartbeat signing
|
||||
case PacketType::AssetGetInfo:
|
||||
case PacketType::AssetGet:
|
||||
case PacketType::AssetUpload:
|
||||
// Removal of extension from Asset requests
|
||||
return 18;
|
||||
default:
|
||||
return 17;
|
||||
}
|
||||
|
|
|
@ -91,7 +91,9 @@ public:
|
|||
MessagesData,
|
||||
MessagesSubscribe,
|
||||
MessagesUnsubscribe,
|
||||
ICEServerHeartbeatDenied
|
||||
ICEServerHeartbeatDenied,
|
||||
AssetMappingOperation,
|
||||
AssetMappingOperationReply
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -158,6 +158,12 @@ void PacketList::preparePackets(MessageNumber messageNumber) {
|
|||
|
||||
const qint64 PACKET_LIST_WRITE_ERROR = -1;
|
||||
|
||||
qint64 PacketList::writeString(const QString& string) {
|
||||
QByteArray data = string.toUtf8();
|
||||
writePrimitive(static_cast<uint32_t>(data.length()));
|
||||
return writeData(data.constData(), data.length());
|
||||
}
|
||||
|
||||
qint64 PacketList::writeData(const char* data, qint64 maxSize) {
|
||||
auto sizeRemaining = maxSize;
|
||||
|
||||
|
|
|
@ -59,6 +59,8 @@ public:
|
|||
|
||||
template<typename T> qint64 readPrimitive(T* data);
|
||||
template<typename T> qint64 writePrimitive(const T& data);
|
||||
|
||||
qint64 writeString(const QString& string);
|
||||
|
||||
protected:
|
||||
PacketList(PacketType packetType, QByteArray extendedHeader = QByteArray(), bool isReliable = false, bool isOrdered = false);
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
#include "Socket.h"
|
||||
|
||||
using namespace udt;
|
||||
using namespace std::chrono;
|
||||
|
||||
template <typename Mutex1, typename Mutex2>
|
||||
class DoubleLock {
|
||||
|
@ -280,11 +281,11 @@ void SendQueue::run() {
|
|||
// 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.
|
||||
}
|
||||
|
||||
|
||||
// Keep an HRC to know when the next packet should have been
|
||||
auto nextPacketTimestamp = p_high_resolution_clock::now();
|
||||
|
||||
while (_state == State::Running) {
|
||||
// Record how long the loop takes to execute
|
||||
const auto loopStartTimestamp = p_high_resolution_clock::now();
|
||||
|
||||
bool sentAPacket = maybeResendPacket();
|
||||
|
||||
// if we didn't find a packet to re-send AND we think we can fit a new packet on the wire
|
||||
|
@ -303,10 +304,13 @@ void SendQueue::run() {
|
|||
if (_state != State::Running || isInactive(sentAPacket)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// push the next packet timestamp forwards by the current packet send period
|
||||
nextPacketTimestamp += std::chrono::microseconds(_packetSendPeriod);
|
||||
|
||||
// sleep as long as we need until next packet send, if we can
|
||||
const auto loopEndTimestamp = p_high_resolution_clock::now();
|
||||
const auto timeToSleep = (loopStartTimestamp + std::chrono::microseconds(_packetSendPeriod)) - loopEndTimestamp;
|
||||
const auto timeToSleep = duration_cast<microseconds>(nextPacketTimestamp - p_high_resolution_clock::now());
|
||||
|
||||
std::this_thread::sleep_for(timeToSleep);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -264,7 +264,9 @@ void DeferredLightingEffect::render(const render::RenderContextPointer& renderCo
|
|||
deferredTransforms[i].projection = projMats[i];
|
||||
|
||||
auto sideViewMat = monoViewMat * glm::inverse(eyeViews[i]);
|
||||
viewTransforms[i].evalFromRawMatrix(sideViewMat);
|
||||
// viewTransforms[i].evalFromRawMatrix(sideViewMat);
|
||||
viewTransforms[i] = monoViewTransform;
|
||||
viewTransforms[i].postTranslate(-glm::vec3((eyeViews[i][3])));// evalFromRawMatrix(sideViewMat);
|
||||
deferredTransforms[i].viewInverse = sideViewMat;
|
||||
|
||||
deferredTransforms[i].stereoSide = (i == 0 ? -1.0f : 1.0f);
|
||||
|
|
|
@ -81,7 +81,7 @@ ItemKey MeshPartPayload::getKey() const {
|
|||
|
||||
if (_drawMaterial) {
|
||||
auto matKey = _drawMaterial->getKey();
|
||||
if (matKey.isTransparent() || matKey.isTransparentMap()) {
|
||||
if (matKey.isTransparent() || matKey.isTransparentTexture() || matKey.isTransparentMap()) {
|
||||
builder.withTransparent();
|
||||
}
|
||||
}
|
||||
|
@ -100,7 +100,7 @@ ShapeKey MeshPartPayload::getShapeKey() const {
|
|||
}
|
||||
|
||||
ShapeKey::Builder builder;
|
||||
if (drawMaterialKey.isTransparent() || drawMaterialKey.isTransparentMap()) {
|
||||
if (drawMaterialKey.isTransparent() || drawMaterialKey.isTransparentTexture() || drawMaterialKey.isTransparentMap()) {
|
||||
builder.withTranslucent();
|
||||
}
|
||||
if (drawMaterialKey.isNormalMap()) {
|
||||
|
@ -365,7 +365,7 @@ ItemKey ModelMeshPartPayload::getKey() const {
|
|||
|
||||
if (_drawMaterial) {
|
||||
auto matKey = _drawMaterial->getKey();
|
||||
if (matKey.isTransparent() || matKey.isTransparentMap()) {
|
||||
if (matKey.isTransparent() || matKey.isTransparentTexture() || matKey.isTransparentMap()) {
|
||||
builder.withTransparent();
|
||||
}
|
||||
}
|
||||
|
@ -412,7 +412,8 @@ ShapeKey ModelMeshPartPayload::getShapeKey() const {
|
|||
drawMaterialKey = _drawMaterial->getKey();
|
||||
}
|
||||
|
||||
bool isTranslucent = drawMaterialKey.isTransparent() || drawMaterialKey.isTransparentMap();
|
||||
bool isTranslucent =
|
||||
drawMaterialKey.isTransparent() || drawMaterialKey.isTransparentTexture() || drawMaterialKey.isTransparentMap();
|
||||
bool hasTangents = drawMaterialKey.isNormalMap() && !mesh.tangents.isEmpty();
|
||||
bool hasSpecular = drawMaterialKey.isMetallicMap();
|
||||
bool hasLightmap = drawMaterialKey.isLightmapMap();
|
||||
|
|
|
@ -74,6 +74,21 @@ Model::~Model() {
|
|||
|
||||
AbstractViewStateInterface* Model::_viewState = NULL;
|
||||
|
||||
bool Model::needsFixupInScene() {
|
||||
if (readyToAddToScene()) {
|
||||
// Once textures are loaded, fixup if they are now transparent
|
||||
if (!_needsReload && _needsUpdateTransparentTextures && _geometry->isLoadedWithTextures()) {
|
||||
_needsUpdateTransparentTextures = false;
|
||||
if (_hasTransparentTextures != _geometry->hasTransparentTextures()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (!_readyWhenAdded) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Model::setTranslation(const glm::vec3& translation) {
|
||||
_translation = translation;
|
||||
|
@ -728,6 +743,8 @@ void Model::setURL(const QUrl& url) {
|
|||
}
|
||||
|
||||
_needsReload = true;
|
||||
_needsUpdateTransparentTextures = true;
|
||||
_hasTransparentTextures = false;
|
||||
_meshGroupsKnown = false;
|
||||
invalidCalculatedMeshBoxes();
|
||||
deleteGeometry();
|
||||
|
|
|
@ -70,7 +70,7 @@ public:
|
|||
|
||||
// new Scene/Engine rendering support
|
||||
void setVisibleInScene(bool newValue, std::shared_ptr<render::Scene> scene);
|
||||
bool needsFixupInScene() { return !_readyWhenAdded && readyToAddToScene(); }
|
||||
bool needsFixupInScene();
|
||||
bool readyToAddToScene(RenderArgs* renderArgs = nullptr) {
|
||||
return !_needsReload && isRenderable() && isActive() && isLoaded();
|
||||
}
|
||||
|
@ -365,10 +365,12 @@ protected:
|
|||
|
||||
QSet<std::shared_ptr<MeshPartPayload>> _renderItemsSet;
|
||||
QMap<render::ItemID, render::PayloadPointer> _renderItems;
|
||||
bool _readyWhenAdded = false;
|
||||
bool _needsReload = true;
|
||||
bool _needsUpdateClusterMatrices = true;
|
||||
bool _showCollisionHull = false;
|
||||
bool _readyWhenAdded { false };
|
||||
bool _needsReload { true };
|
||||
bool _needsUpdateClusterMatrices { true };
|
||||
bool _needsUpdateTransparentTextures { true };
|
||||
bool _hasTransparentTextures { false };
|
||||
bool _showCollisionHull { false };
|
||||
|
||||
friend class ModelMeshPartPayload;
|
||||
RigPointer _rig;
|
||||
|
|
|
@ -291,6 +291,7 @@ void DrawBackgroundDeferred::run(const SceneContextPointer& sceneContext, const
|
|||
RenderArgs* args = renderContext->args;
|
||||
doInBatch(args->_context, [&](gpu::Batch& batch) {
|
||||
args->_batch = &batch;
|
||||
_gpuTimer.begin(batch);
|
||||
|
||||
auto lightingFBO = DependencyManager::get<FramebufferCache>()->getLightingFramebuffer();
|
||||
|
||||
|
@ -310,8 +311,11 @@ void DrawBackgroundDeferred::run(const SceneContextPointer& sceneContext, const
|
|||
batch.setViewTransform(viewMat);
|
||||
|
||||
renderItems(sceneContext, renderContext, inItems);
|
||||
_gpuTimer.end(batch);
|
||||
});
|
||||
args->_batch = nullptr;
|
||||
|
||||
std::static_pointer_cast<Config>(renderContext->jobConfig)->gpuTime = _gpuTimer.getAverage();
|
||||
}
|
||||
|
||||
void Blit::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext) {
|
||||
|
|
|
@ -82,11 +82,27 @@ protected:
|
|||
static gpu::PipelinePointer _opaquePipeline; //lazy evaluation hence mutable
|
||||
};
|
||||
|
||||
class DrawBackgroundDeferredConfig : public render::Job::Config {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(double gpuTime READ getGpuTime)
|
||||
public:
|
||||
double getGpuTime() { return gpuTime; }
|
||||
|
||||
protected:
|
||||
friend class DrawBackgroundDeferred;
|
||||
double gpuTime;
|
||||
};
|
||||
|
||||
class DrawBackgroundDeferred {
|
||||
public:
|
||||
using Config = DrawBackgroundDeferredConfig;
|
||||
using JobModel = render::Job::ModelI<DrawBackgroundDeferred, render::ItemBounds, Config>;
|
||||
|
||||
void configure(const Config& config) {}
|
||||
void run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext, const render::ItemBounds& inItems);
|
||||
|
||||
using JobModel = render::Job::ModelI<DrawBackgroundDeferred, render::ItemBounds>;
|
||||
protected:
|
||||
gpu::RangeTimer _gpuTimer;
|
||||
};
|
||||
|
||||
class DrawOverlay3DConfig : public render::Job::Config {
|
||||
|
|
|
@ -20,7 +20,8 @@
|
|||
<@include gpu/Transform.slh@>
|
||||
<$declareStandardCameraTransform()$>
|
||||
|
||||
uniform sampler2D albedoMap;
|
||||
<@include MaterialTextures.slh@>
|
||||
<$declareMaterialTextures(ALBEDO, ROUGHNESS, _SCRIBE_NULL, _SCRIBE_NULL, EMISSIVE, OCCLUSION)$>
|
||||
|
||||
in vec2 _texCoord0;
|
||||
in vec4 _position;
|
||||
|
@ -31,16 +32,27 @@ in float _alpha;
|
|||
out vec4 _fragColor;
|
||||
|
||||
void main(void) {
|
||||
vec4 albedo = texture(albedoMap, _texCoord0);
|
||||
|
||||
Material mat = getMaterial();
|
||||
int matKey = getMaterialKey(mat);
|
||||
<$fetchMaterialTextures(matKey, _texCoord0, albedoTex, roughnessTex, _SCRIBE_NULL, _SCRIBE_NULL, emissiveTex, occlusionTex)$>
|
||||
|
||||
vec3 albedo = getMaterialAlbedo(mat);
|
||||
<$evalMaterialAlbedo(albedoTex, albedo, matKey, albedo)$>;
|
||||
albedo *= _color;
|
||||
|
||||
float roughness = getMaterialRoughness(mat);
|
||||
<$evalMaterialRoughness(roughnessTex, roughness, matKey, roughness)$>;
|
||||
|
||||
float metallic = getMaterialMetallic(mat);
|
||||
|
||||
vec3 emissive = getMaterialEmissive(mat);
|
||||
<$evalMaterialEmissive(emissiveTex, emissive, matKey, emissive)$>;
|
||||
|
||||
|
||||
vec3 fragPosition = _position.xyz;
|
||||
vec3 fragNormal = normalize(_normal);
|
||||
vec3 fragAlbedo = getMaterialAlbedo(mat) * albedo.rgb * _color;
|
||||
float fragMetallic = getMaterialMetallic(mat);
|
||||
vec3 fragEmissive = getMaterialEmissive(mat);
|
||||
float fragRoughness = getMaterialRoughness(mat);
|
||||
float fragOpacity = getMaterialOpacity(mat) * albedo.a * _alpha;
|
||||
|
||||
float fragOpacity = getMaterialOpacity(mat) * albedoTex.a * _alpha;
|
||||
|
||||
TransformCamera cam = getTransformCamera();
|
||||
|
||||
|
@ -50,9 +62,9 @@ void main(void) {
|
|||
1.0,
|
||||
fragPosition,
|
||||
fragNormal,
|
||||
fragAlbedo,
|
||||
fragMetallic,
|
||||
fragEmissive,
|
||||
fragRoughness),
|
||||
albedo,
|
||||
metallic,
|
||||
emissive,
|
||||
roughness),
|
||||
fragOpacity);
|
||||
}
|
||||
|
|
74
libraries/script-engine/src/AssetScriptingInterface.cpp
Normal file
74
libraries/script-engine/src/AssetScriptingInterface.cpp
Normal file
|
@ -0,0 +1,74 @@
|
|||
//
|
||||
// AssetScriptingInterface.cpp
|
||||
// libraries/script-engine/src
|
||||
//
|
||||
// Created by Stephen Birarda on 2016-03-08.
|
||||
// Copyright 2016 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 "AssetScriptingInterface.h"
|
||||
|
||||
#include <QtScript/QScriptEngine>
|
||||
|
||||
#include <AssetRequest.h>
|
||||
#include <AssetUpload.h>
|
||||
#include <MappingRequest.h>
|
||||
#include <NetworkLogging.h>
|
||||
|
||||
AssetScriptingInterface::AssetScriptingInterface(QScriptEngine* engine) :
|
||||
_engine(engine)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void AssetScriptingInterface::uploadData(QString data, QScriptValue callback) {
|
||||
QByteArray dataByteArray = data.toUtf8();
|
||||
auto upload = DependencyManager::get<AssetClient>()->createUpload(dataByteArray);
|
||||
|
||||
QObject::connect(upload, &AssetUpload::finished, this, [this, callback](AssetUpload* upload, const QString& hash) mutable {
|
||||
if (callback.isFunction()) {
|
||||
QString url = "atp:" + hash;
|
||||
QScriptValueList args { url };
|
||||
callback.call(_engine->currentContext()->thisObject(), args);
|
||||
}
|
||||
});
|
||||
upload->start();
|
||||
}
|
||||
|
||||
void AssetScriptingInterface::downloadData(QString urlString, QScriptValue callback) {
|
||||
const QString ATP_SCHEME { "atp:" };
|
||||
|
||||
if (!urlString.startsWith(ATP_SCHEME)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Make request to atp
|
||||
auto path = urlString.right(urlString.length() - ATP_SCHEME.length());
|
||||
auto parts = path.split(".", QString::SkipEmptyParts);
|
||||
auto hash = parts.length() > 0 ? parts[0] : "";
|
||||
|
||||
auto assetClient = DependencyManager::get<AssetClient>();
|
||||
auto assetRequest = assetClient->createRequest(hash);
|
||||
|
||||
_pendingRequests << assetRequest;
|
||||
|
||||
connect(assetRequest, &AssetRequest::finished, this, [this, callback](AssetRequest* request) mutable {
|
||||
Q_ASSERT(request->getState() == AssetRequest::Finished);
|
||||
|
||||
if (request->getError() == AssetRequest::Error::NoError) {
|
||||
if (callback.isFunction()) {
|
||||
QString data = QString::fromUtf8(request->getData());
|
||||
QScriptValueList args { data };
|
||||
callback.call(_engine->currentContext()->thisObject(), args);
|
||||
}
|
||||
}
|
||||
|
||||
request->deleteLater();
|
||||
_pendingRequests.remove(request);
|
||||
});
|
||||
|
||||
assetRequest->start();
|
||||
}
|
35
libraries/script-engine/src/AssetScriptingInterface.h
Normal file
35
libraries/script-engine/src/AssetScriptingInterface.h
Normal file
|
@ -0,0 +1,35 @@
|
|||
//
|
||||
// AssetScriptingInterface.h
|
||||
// libraries/script-engine/src
|
||||
//
|
||||
// Created by Stephen Birarda on 2016-03-08.
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef hifi_AssetScriptingInterface_h
|
||||
#define hifi_AssetScriptingInterface_h
|
||||
|
||||
#include <QtCore/QObject>
|
||||
#include <QtScript/QScriptValue>
|
||||
|
||||
#include <AssetClient.h>
|
||||
|
||||
class AssetScriptingInterface : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
AssetScriptingInterface(QScriptEngine* engine);
|
||||
|
||||
Q_INVOKABLE void uploadData(QString data, QScriptValue callback);
|
||||
Q_INVOKABLE void downloadData(QString url, QScriptValue downloadComplete);
|
||||
|
||||
protected:
|
||||
QSet<AssetRequest*> _pendingRequests;
|
||||
QScriptEngine* _engine;
|
||||
};
|
||||
|
||||
#endif // hifi_AssetScriptingInterface_h
|
|
@ -198,13 +198,13 @@ bool RecordingScriptingInterface::saveRecordingToAsset(QScriptValue getClipAtpUr
|
|||
return false;
|
||||
}
|
||||
|
||||
if (auto upload = DependencyManager::get<AssetClient>()->createUpload(recording::Clip::toBuffer(_lastClip), HFR_EXTENSION)) {
|
||||
if (auto upload = DependencyManager::get<AssetClient>()->createUpload(recording::Clip::toBuffer(_lastClip))) {
|
||||
QObject::connect(upload, &AssetUpload::finished, this, [=](AssetUpload* upload, const QString& hash) mutable {
|
||||
QString clip_atp_url = "";
|
||||
|
||||
if (upload->getError() == AssetUpload::NoError) {
|
||||
|
||||
clip_atp_url = QString("%1:%2.%3").arg(URL_SCHEME_ATP, hash, upload->getExtension());
|
||||
clip_atp_url = QString("%1:%2").arg(URL_SCHEME_ATP, hash);
|
||||
upload->deleteLater();
|
||||
} else {
|
||||
qCWarning(scriptengine) << "Error during the Asset upload.";
|
||||
|
|
|
@ -23,7 +23,6 @@
|
|||
|
||||
#include <AnimationCache.h>
|
||||
#include <AnimVariant.h>
|
||||
#include <AssetClient.h>
|
||||
#include <AvatarData.h>
|
||||
#include <AvatarHashMap.h>
|
||||
#include <LimitedNodeList.h>
|
||||
|
@ -32,6 +31,7 @@
|
|||
|
||||
#include "MouseEvent.h"
|
||||
#include "ArrayBufferClass.h"
|
||||
#include "AssetScriptingInterface.h"
|
||||
#include "AudioScriptingInterface.h"
|
||||
#include "Quat.h"
|
||||
#include "Mat4.h"
|
||||
|
|
Loading…
Reference in a new issue