From 3783c9897643095c5af7c34b5fab022309911bcd Mon Sep 17 00:00:00 2001 From: Maki Date: Tue, 4 Aug 2020 01:33:19 +0100 Subject: [PATCH] Allow resolving relative urls when importing entities json --- libraries/octree/src/Octree.cpp | 29 +++++--- libraries/octree/src/Octree.h | 4 +- .../octree/src/OctreeEntitiesFileParser.cpp | 70 ++++++++++++++++++- .../octree/src/OctreeEntitiesFileParser.h | 2 + 4 files changed, 93 insertions(+), 12 deletions(-) diff --git a/libraries/octree/src/Octree.cpp b/libraries/octree/src/Octree.cpp index 897ac142bf..96f2383181 100644 --- a/libraries/octree/src/Octree.cpp +++ b/libraries/octree/src/Octree.cpp @@ -685,8 +685,9 @@ bool Octree::readFromFile(const char* fileName) { QDataStream fileInputStream(&file); QFileInfo fileInfo(qFileName); uint64_t fileLength = fileInfo.size(); + QUrl relativeURL = QUrl::fromLocalFile(qFileName).adjusted(QUrl::RemoveFilename); - bool success = readFromStream(fileLength, fileInputStream); + bool success = readFromStream(fileLength, fileInputStream, "", false, relativeURL); file.close(); @@ -708,7 +709,9 @@ bool Octree::readJSONFromGzippedFile(QString qFileName) { } QDataStream jsonStream(jsonData); - return readJSONFromStream(-1, jsonStream); + QUrl relativeURL = QUrl::fromLocalFile(qFileName).adjusted(QUrl::RemoveFilename); + + return readJSONFromStream(-1, jsonStream, "", false, relativeURL); } // hack to get the marketplace id into the entities. We will create a way to get this from a hash of @@ -761,13 +764,15 @@ bool Octree::readFromURL( QByteArray uncompressedJsonData; bool wasCompressed = gunzip(data, uncompressedJsonData); + QUrl relativeURL = QUrl(urlString).adjusted(QUrl::RemoveFilename); + if (wasCompressed) { QDataStream inputStream(uncompressedJsonData); - return readFromStream(uncompressedJsonData.size(), inputStream, marketplaceID); + return readFromStream(uncompressedJsonData.size(), inputStream, marketplaceID, isImport, relativeURL); } QDataStream inputStream(data); - return readFromStream(data.size(), inputStream, marketplaceID, isImport); + return readFromStream(data.size(), inputStream, marketplaceID, isImport, relativeURL); } bool Octree::readFromByteArray( @@ -780,20 +785,23 @@ bool Octree::readFromByteArray( QByteArray uncompressedJsonData; bool wasCompressed = gunzip(data, uncompressedJsonData); + QUrl relativeURL = QUrl(urlString).adjusted(QUrl::RemoveFilename); + if (wasCompressed) { QDataStream inputStream(uncompressedJsonData); - return readFromStream(uncompressedJsonData.size(), inputStream, marketplaceID); + return readFromStream(uncompressedJsonData.size(), inputStream, marketplaceID, false, relativeURL); } QDataStream inputStream(data); - return readFromStream(data.size(), inputStream, marketplaceID); + return readFromStream(data.size(), inputStream, marketplaceID, false, relativeURL); } bool Octree::readFromStream( uint64_t streamLength, QDataStream& inputStream, const QString& marketplaceID, - const bool isImport + const bool isImport, + const QUrl& relativeURL ) { // decide if this is binary SVO or JSON-formatted SVO QIODevice *device = inputStream.device(); @@ -806,7 +814,7 @@ bool Octree::readFromStream( return false; } else { qCDebug(octree) << "Reading from JSON SVO Stream length:" << streamLength; - return readJSONFromStream(streamLength, inputStream, marketplaceID, isImport); + return readJSONFromStream(streamLength, inputStream, marketplaceID, isImport, relativeURL); } } @@ -837,7 +845,8 @@ bool Octree::readJSONFromStream( uint64_t streamLength, QDataStream& inputStream, const QString& marketplaceID, /*=""*/ - const bool isImport + const bool isImport, + const QUrl& relativeURL ) { // if the data is gzipped we may not have a useful bytesAvailable() result, so just keep reading until // we get an eof. Leave streamLength parameter for consistency. @@ -858,7 +867,9 @@ bool Octree::readJSONFromStream( } OctreeEntitiesFileParser octreeParser; + octreeParser.relativeURL = relativeURL; octreeParser.setEntitiesString(jsonBuffer); + QVariantMap asMap; if (!octreeParser.parseEntities(asMap)) { qCritical() << "Couldn't parse Entities JSON:" << octreeParser.getErrorString().c_str(); diff --git a/libraries/octree/src/Octree.h b/libraries/octree/src/Octree.h index a7885801de..cb46d5151b 100644 --- a/libraries/octree/src/Octree.h +++ b/libraries/octree/src/Octree.h @@ -218,8 +218,8 @@ public: bool readFromFile(const char* filename); bool readFromURL(const QString& url, const bool isObservable = true, const qint64 callerId = -1, const bool isImport = false); // will support file urls as well... bool readFromByteArray(const QString& url, const QByteArray& byteArray); - bool readFromStream(uint64_t streamLength, QDataStream& inputStream, const QString& marketplaceID="", const bool isImport = false); - bool readJSONFromStream(uint64_t streamLength, QDataStream& inputStream, const QString& marketplaceID="", const bool isImport = false); + bool readFromStream(uint64_t streamLength, QDataStream& inputStream, const QString& marketplaceID="", const bool isImport = false, const QUrl& urlString = QUrl()); + bool readJSONFromStream(uint64_t streamLength, QDataStream& inputStream, const QString& marketplaceID="", const bool isImport = false, const QUrl& urlString = QUrl()); bool readJSONFromGzippedFile(QString qFileName); virtual bool readFromMap(QVariantMap& entityDescription, const bool isImport = false) = 0; diff --git a/libraries/octree/src/OctreeEntitiesFileParser.cpp b/libraries/octree/src/OctreeEntitiesFileParser.cpp index e82201adfd..72cfd30ef9 100644 --- a/libraries/octree/src/OctreeEntitiesFileParser.cpp +++ b/libraries/octree/src/OctreeEntitiesFileParser.cpp @@ -237,7 +237,75 @@ bool OctreeEntitiesFileParser::readEntitiesArray(QVariantList& entitiesArray) { return false; } - entitiesArray.append(entity.object()); + QJsonObject entityObject = entity.object(); + + // resolve urls starting with ./ or ../ + if (relativeURL.isEmpty() == false) { + bool isDirty = false; + + const QStringList urlKeys { + // model + "modelURL", + "animation.url", + // image + "imageURL", + // web + "sourceUrl", + "scriptURL", + // zone + "ambientLight.ambientURL", + "skybox.url", + // particles + "textures", + // materials + "materialURL", + // ...shared + "href", + "script", + "serverScripts", + "collisionSoundURL", + "compoundShapeURL", + // TODO: deal with materialData and userData + }; + + for (const QString& key : urlKeys) { + if (key.contains('.')) { + // url is inside another object + const QStringList keyPair = key.split('.'); + const QString entityKey = keyPair[0]; + const QString childKey = keyPair[1]; + + if (entityObject.contains(entityKey) && entityObject[entityKey].isObject()) { + QJsonObject childObject = entityObject[entityKey].toObject(); + + if (childObject.contains(childKey) && childObject[childKey].isString()) { + const QString url = childObject[childKey].toString(); + + if (url.startsWith("./") || url.startsWith("../")) { + childObject[childKey] = relativeURL.resolved(url).toString(); + entityObject[entityKey] = childObject; + isDirty = true; + } + } + } + } else { + if (entityObject.contains(key) && entityObject[key].isString()) { + const QString url = entityObject[key].toString(); + + if (url.startsWith("./") || url.startsWith("../")) { + entityObject[key] = relativeURL.resolved(url).toString(); + isDirty = true; + } + } + } + } + + if (isDirty) { + entity.setObject(entityObject); + } + } + + entitiesArray.append(entityObject); _position = matchingBrace; char c = nextToken(); if (c == ']') { diff --git a/libraries/octree/src/OctreeEntitiesFileParser.h b/libraries/octree/src/OctreeEntitiesFileParser.h index bc51896b18..39560710db 100644 --- a/libraries/octree/src/OctreeEntitiesFileParser.h +++ b/libraries/octree/src/OctreeEntitiesFileParser.h @@ -16,12 +16,14 @@ #include #include +#include class OctreeEntitiesFileParser { public: void setEntitiesString(const QByteArray& entitiesContents); bool parseEntities(QVariantMap& parsedEntities); std::string getErrorString() const; + QUrl relativeURL; private: int nextToken();