diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index fbad66bf55..bf5fcc5b20 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -100,6 +100,7 @@ #include #include #include +#include #include #include #include @@ -830,6 +831,7 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) { DependencyManager::set(NodeType::Agent, listenPort); DependencyManager::set(); DependencyManager::set(); + DependencyManager::set(); // ModelFormatRegistry must be defined before ModelCache. See the ModelCache constructor. DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); @@ -2690,6 +2692,7 @@ Application::~Application() { DependencyManager::destroy(); DependencyManager::destroy(); DependencyManager::destroy(); + DependencyManager::destroy(); DependencyManager::destroy(); DependencyManager::destroy(); DependencyManager::destroy(); diff --git a/libraries/animation/src/AnimationCache.cpp b/libraries/animation/src/AnimationCache.cpp index ec26782d0e..f7a7dd861a 100644 --- a/libraries/animation/src/AnimationCache.cpp +++ b/libraries/animation/src/AnimationCache.cpp @@ -20,6 +20,7 @@ #include #include "AnimationLogging.h" +#include int animationPointerMetaTypeId = qRegisterMetaType(); diff --git a/libraries/animation/src/AnimationCache.h b/libraries/animation/src/AnimationCache.h index d4574d9d3b..2f8168625e 100644 --- a/libraries/animation/src/AnimationCache.h +++ b/libraries/animation/src/AnimationCache.h @@ -17,7 +17,7 @@ #include #include -#include +#include #include class Animation; diff --git a/libraries/fbx/src/FBXSerializer.cpp b/libraries/fbx/src/FBXSerializer.cpp index 67220342b8..b425b6795d 100644 --- a/libraries/fbx/src/FBXSerializer.cpp +++ b/libraries/fbx/src/FBXSerializer.cpp @@ -1854,6 +1854,17 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr return hfmModelPtr; } +MediaType FBXSerializer::getMediaType() const { + MediaType mediaType("fbx"); + mediaType.extensions.push_back("fbx"); + mediaType.fileSignatures.emplace_back("Kaydara FBX Binary \x00", 0); + return mediaType; +} + +std::unique_ptr FBXSerializer::getFactory() const { + return std::make_unique>(); +} + HFMModel::Pointer FBXSerializer::read(const QByteArray& data, const QVariantHash& mapping, const QUrl& url) { QBuffer buffer(const_cast(&data)); buffer.open(QIODevice::ReadOnly); diff --git a/libraries/fbx/src/FBXSerializer.h b/libraries/fbx/src/FBXSerializer.h index c69f75cc5c..a76fb8f9bf 100644 --- a/libraries/fbx/src/FBXSerializer.h +++ b/libraries/fbx/src/FBXSerializer.h @@ -96,6 +96,9 @@ class ExtractedMesh; class FBXSerializer : public HFMSerializer { public: + MediaType getMediaType() const override; + std::unique_ptr getFactory() const override; + HFMModel* _hfmModel; /// Reads HFMModel from the supplied model and mapping data. /// \exception QString if an error occurs in parsing diff --git a/libraries/fbx/src/GLTFSerializer.cpp b/libraries/fbx/src/GLTFSerializer.cpp index 28d377c605..e254a91eb0 100644 --- a/libraries/fbx/src/GLTFSerializer.cpp +++ b/libraries/fbx/src/GLTFSerializer.cpp @@ -35,11 +35,6 @@ #include "FBXSerializer.h" - -GLTFSerializer::GLTFSerializer() { - -} - bool GLTFSerializer::getStringVal(const QJsonObject& object, const QString& fieldname, QString& value, QMap& defined) { bool _defined = (object.contains(fieldname) && object[fieldname].isString()); @@ -910,6 +905,17 @@ bool GLTFSerializer::buildGeometry(HFMModel& hfmModel, const QUrl& url) { return true; } +MediaType GLTFSerializer::getMediaType() const { + MediaType mediaType("gltf"); + mediaType.extensions.push_back("gltf"); + mediaType.webMediaTypes.push_back("model/gltf+json"); + return mediaType; +} + +std::unique_ptr GLTFSerializer::getFactory() const { + return std::make_unique>(); +} + HFMModel::Pointer GLTFSerializer::read(const QByteArray& data, const QVariantHash& mapping, const QUrl& url) { _url = url; diff --git a/libraries/fbx/src/GLTFSerializer.h b/libraries/fbx/src/GLTFSerializer.h index 1ec1183e36..5fca77c4fd 100644 --- a/libraries/fbx/src/GLTFSerializer.h +++ b/libraries/fbx/src/GLTFSerializer.h @@ -16,7 +16,6 @@ #include #include #include -#include "FBXSerializer.h" struct GLTFAsset { @@ -703,7 +702,9 @@ struct GLTFFile { class GLTFSerializer : public QObject, public HFMSerializer { Q_OBJECT public: - GLTFSerializer(); + MediaType getMediaType() const override; + std::unique_ptr getFactory() const override; + HFMModel::Pointer read(const QByteArray& data, const QVariantHash& mapping, const QUrl& url = QUrl()) override; private: GLTFFile _file; diff --git a/libraries/fbx/src/OBJSerializer.cpp b/libraries/fbx/src/OBJSerializer.cpp index af8dfb5562..9c92466565 100644 --- a/libraries/fbx/src/OBJSerializer.cpp +++ b/libraries/fbx/src/OBJSerializer.cpp @@ -651,6 +651,15 @@ done: return result; } +MediaType OBJSerializer::getMediaType() const { + MediaType mediaType("obj"); + mediaType.extensions.push_back("obj"); + return mediaType; +} + +std::unique_ptr OBJSerializer::getFactory() const { + return std::make_unique>(); +} HFMModel::Pointer OBJSerializer::read(const QByteArray& data, const QVariantHash& mapping, const QUrl& url) { PROFILE_RANGE_EX(resource_parse, __FUNCTION__, 0xffff0000, nullptr); diff --git a/libraries/fbx/src/OBJSerializer.h b/libraries/fbx/src/OBJSerializer.h index a6fe3817ca..c4f8025e66 100644 --- a/libraries/fbx/src/OBJSerializer.h +++ b/libraries/fbx/src/OBJSerializer.h @@ -14,7 +14,6 @@ #include #include -#include "FBXSerializer.h" class OBJTokenizer { public: @@ -92,6 +91,9 @@ public: class OBJSerializer: public QObject, public HFMSerializer { // QObject so we can make network requests. Q_OBJECT public: + MediaType getMediaType() const override; + std::unique_ptr getFactory() const override; + typedef QVector FaceGroup; QVector vertices; QVector vertexColors; diff --git a/libraries/hfm/src/hfm/HFMFormatRegistry.cpp b/libraries/hfm/src/hfm/HFMFormatRegistry.cpp new file mode 100644 index 0000000000..1328369cff --- /dev/null +++ b/libraries/hfm/src/hfm/HFMFormatRegistry.cpp @@ -0,0 +1,65 @@ +// +// HFMFormatRegistry.cpp +// libraries/hfm/src/hfm +// +// Created by Sabrina Shanman on 2018/11/29. +// Copyright 2018 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 "HFMFormatRegistry.h" + +namespace hfm { + +FormatRegistry::MediaTypeID FormatRegistry::registerMediaType(const MediaType& mediaType, std::unique_ptr supportedFactory) { + std::lock_guard lock(_libraryLock); + + MediaTypeID id = _mediaTypeLibrary.registerMediaType(mediaType); + _supportedFormats.emplace_back(id, supportedFactory); + return id; +} + +void FormatRegistry::unregisterMediaType(const MediaTypeID& mediaTypeID) { + std::lock_guard lock(_libraryLock); + + for (auto it = _supportedFormats.begin(); it != _supportedFormats.end(); it++) { + if ((*it).mediaTypeID == mediaTypeID) { + _supportedFormats.erase(it); + break; + } + } + _mediaTypeLibrary.unregisterMediaType(mediaTypeID); +} + +std::shared_ptr FormatRegistry::getSerializerForMediaTypeID(FormatRegistry::MediaTypeID mediaTypeID) const { + // TODO: shared_lock in C++14 + std::lock_guard lock(*const_cast(&_libraryLock)); + + for (auto it = _supportedFormats.begin(); it != _supportedFormats.end(); it++) { + if ((*it).mediaTypeID == mediaTypeID) { + return (*it).factory->get(); + } + } + return std::shared_ptr(); +} + +std::shared_ptr FormatRegistry::getSerializerForMediaType(const hifi::ByteArray& data, const hifi::URL& url, const std::string& webMediaType) const { + MediaTypeID id; + { + // TODO: shared_lock in C++14 + std::lock_guard lock(*const_cast(&_libraryLock)); + + id = _mediaTypeLibrary.findMediaTypeForData(data); + if (id == INVALID_MEDIA_TYPE_ID) { + id = _mediaTypeLibrary.findMediaTypeForURL(url); + if (id == INVALID_MEDIA_TYPE_ID) { + id = _mediaTypeLibrary.findMediaTypeForWebID(webMediaType); + } + } + } + return getSerializerForMediaTypeID(id); +} + +}; diff --git a/libraries/hfm/src/hfm/HFMFormatRegistry.h b/libraries/hfm/src/hfm/HFMFormatRegistry.h new file mode 100644 index 0000000000..a437e9ac37 --- /dev/null +++ b/libraries/hfm/src/hfm/HFMFormatRegistry.h @@ -0,0 +1,50 @@ +// +// HFMFormatRegistry.h +// libraries/hfm/src/hfm +// +// Created by Sabrina Shanman on 2018/11/28. +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_HFMFormatRegistry_h +#define hifi_HFMFormatRegistry_h + +#include "HFMSerializer.h" +#include +#include + +namespace hfm { + +class FormatRegistry { +public: + using MediaTypeID = MediaTypeLibrary::ID; + static const MediaTypeID INVALID_MEDIA_TYPE_ID { MediaTypeLibrary::INVALID_ID }; + + MediaTypeID registerMediaType(const MediaType& mediaType, std::unique_ptr supportedFactory); + void unregisterMediaType(const MediaTypeID& id); + + std::shared_ptr getSerializerForMediaType(const hifi::ByteArray& data, const hifi::URL& url, const std::string& webMediaType) const; + +protected: + std::shared_ptr getSerializerForMediaTypeID(MediaTypeID id) const; + + MediaTypeLibrary _mediaTypeLibrary; + std::mutex _libraryLock; + class SupportedFormat { + public: + SupportedFormat(const MediaTypeID& mediaTypeID, std::unique_ptr& factory) : + mediaTypeID(mediaTypeID), + factory(std::move(factory)) { + } + MediaTypeID mediaTypeID; + std::unique_ptr factory; + }; + std::vector _supportedFormats; +}; + +}; + +#endif // hifi_HFMFormatRegistry_h diff --git a/libraries/hfm/src/hfm/HFMSerializer.h b/libraries/hfm/src/hfm/HFMSerializer.h index db18f21e06..868ec3dd45 100644 --- a/libraries/hfm/src/hfm/HFMSerializer.h +++ b/libraries/hfm/src/hfm/HFMSerializer.h @@ -15,10 +15,27 @@ #include #include "HFM.h" +#include namespace hfm { class Serializer { +public: + class Factory { + public: + virtual std::shared_ptr get() = 0; + }; + + template + class SimpleFactory : public Factory { + std::shared_ptr get() override { + return std::make_shared(); + } + }; + + virtual MediaType getMediaType() const = 0; + virtual std::unique_ptr getFactory() const = 0; + virtual Model::Pointer read(const hifi::ByteArray& data, const hifi::VariantHash& mapping, const hifi::URL& url = hifi::URL()) = 0; }; diff --git a/libraries/hfm/src/hfm/ModelFormatRegistry.cpp b/libraries/hfm/src/hfm/ModelFormatRegistry.cpp new file mode 100644 index 0000000000..d95453161a --- /dev/null +++ b/libraries/hfm/src/hfm/ModelFormatRegistry.cpp @@ -0,0 +1,20 @@ +// +// ModelFormatRegistry.cpp +// libraries/model-networking/src/model-networking +// +// Created by Sabrina Shanman on 2018/11/30. +// Copyright 2018 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 "ModelFormatRegistry.h" + +void ModelFormatRegistry::addFormat(const hfm::Serializer& serializer) { + _hfmFormatRegistry.registerMediaType(serializer.getMediaType(), serializer.getFactory()); +} + +std::shared_ptr ModelFormatRegistry::getSerializerForMediaType(const hifi::ByteArray& data, const hifi::URL& url, const std::string& webMediaType) const { + return _hfmFormatRegistry.getSerializerForMediaType(data, url, webMediaType); +} diff --git a/libraries/hfm/src/hfm/ModelFormatRegistry.h b/libraries/hfm/src/hfm/ModelFormatRegistry.h new file mode 100644 index 0000000000..1228465298 --- /dev/null +++ b/libraries/hfm/src/hfm/ModelFormatRegistry.h @@ -0,0 +1,28 @@ +// +// ModelFormatRegistry.h +// libraries/hfm/src/hfm +// +// Created by Sabrina Shanman on 2018/11/30. +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_ModelFormatRegistry_h +#define hifi_ModelFormatRegistry_h + +#include "HFMFormatRegistry.h" + +#include + +class ModelFormatRegistry : public Dependency { +public: + void addFormat(const hfm::Serializer& serializer); + std::shared_ptr getSerializerForMediaType(const hifi::ByteArray& data, const hifi::URL& url, const std::string& webMediaType) const; + +protected: + hfm::FormatRegistry _hfmFormatRegistry; +}; + +#endif // hifi_ModelFormatRegistry_h diff --git a/libraries/model-networking/src/model-networking/ModelCache.cpp b/libraries/model-networking/src/model-networking/ModelCache.cpp index 254f61eba9..dfee4750f5 100644 --- a/libraries/model-networking/src/model-networking/ModelCache.cpp +++ b/libraries/model-networking/src/model-networking/ModelCache.cpp @@ -12,9 +12,6 @@ #include "ModelCache.h" #include #include -#include "FBXSerializer.h" -#include "OBJSerializer.h" -#include "GLTFSerializer.h" #include #include @@ -26,6 +23,10 @@ #include "ModelNetworkingLogging.h" #include #include +#include +#include +#include +#include Q_LOGGING_CATEGORY(trace_resource_parse_geometry, "trace.resource.parse.geometry") @@ -144,9 +145,9 @@ void GeometryMappingResource::onGeometryMappingLoaded(bool success) { class GeometryReader : public QRunnable { public: - GeometryReader(QWeakPointer& resource, const QUrl& url, const QVariantHash& mapping, - const QByteArray& data, bool combineParts) : - _resource(resource), _url(url), _mapping(mapping), _data(data), _combineParts(combineParts) { + GeometryReader(const ModelLoader& modelLoader, QWeakPointer& resource, const QUrl& url, const QVariantHash& mapping, + const QByteArray& data, bool combineParts, const QString& webMediaType) : + _modelLoader(modelLoader), _resource(resource), _url(url), _mapping(mapping), _data(data), _combineParts(combineParts), _webMediaType(webMediaType) { DependencyManager::get()->incrementStat("PendingProcessing"); } @@ -154,11 +155,13 @@ public: virtual void run() override; private: + ModelLoader _modelLoader; QWeakPointer _resource; QUrl _url; QVariantHash _mapping; QByteArray _data; bool _combineParts; + QString _webMediaType; }; void GeometryReader::run() { @@ -183,62 +186,53 @@ void GeometryReader::run() { throw QString("reply is NULL"); } - QString urlname = _url.path().toLower(); - if (!urlname.isEmpty() && !_url.path().isEmpty() && + // Ensure the resource has not been deleted + auto resource = _resource.toStrongRef(); + if (!resource) { + qCWarning(modelnetworking) << "Abandoning load of" << _url << "; could not get strong ref"; + return; + } - (_url.path().toLower().endsWith(".fbx") || - _url.path().toLower().endsWith(".obj") || - _url.path().toLower().endsWith(".obj.gz") || - _url.path().toLower().endsWith(".gltf"))) { - - HFMModel::Pointer hfmModel; - - QVariantHash serializerMapping = _mapping; - serializerMapping["combineParts"] = _combineParts; - - if (_url.path().toLower().endsWith(".fbx")) { - hfmModel = FBXSerializer().read(_data, serializerMapping, _url); - if (hfmModel->meshes.size() == 0 && hfmModel->joints.size() == 0) { - throw QString("empty geometry, possibly due to an unsupported FBX version"); - } - } else if (_url.path().toLower().endsWith(".obj")) { - hfmModel = OBJSerializer().read(_data, serializerMapping, _url); - } else if (_url.path().toLower().endsWith(".obj.gz")) { - QByteArray uncompressedData; - if (gunzip(_data, uncompressedData)){ - hfmModel = OBJSerializer().read(uncompressedData, serializerMapping, _url); - } else { - throw QString("failed to decompress .obj.gz"); - } - - } else if (_url.path().toLower().endsWith(".gltf")) { - hfmModel = GLTFSerializer().read(_data, serializerMapping, _url); - if (hfmModel->meshes.size() == 0 && hfmModel->joints.size() == 0) { - throw QString("empty geometry, possibly due to an unsupported GLTF version"); - } - } else { - throw QString("unsupported format"); - } - - // Add scripts to hfmModel - if (!_mapping.value(SCRIPT_FIELD).isNull()) { - QVariantList scripts = _mapping.values(SCRIPT_FIELD); - for (auto &script : scripts) { - hfmModel->scripts.push_back(script.toString()); - } - } - - // Ensure the resource has not been deleted - auto resource = _resource.toStrongRef(); - if (!resource) { - qCWarning(modelnetworking) << "Abandoning load of" << _url << "; could not get strong ref"; - } else { - QMetaObject::invokeMethod(resource.data(), "setGeometryDefinition", - Q_ARG(HFMModel::Pointer, hfmModel)); - } - } else { + if (_url.path().isEmpty()) { throw QString("url is invalid"); } + + HFMModel::Pointer hfmModel; + QVariantHash serializerMapping = _mapping; + serializerMapping["combineParts"] = _combineParts; + + if (_url.path().toLower().endsWith(".gz")) { + QByteArray uncompressedData; + if (!gunzip(_data, uncompressedData)) { + throw QString("failed to decompress .gz model"); + } + // Strip the compression extension from the path, so the loader can infer the file type from what remains. + // This is okay because we don't expect the serializer to be able to read the contents of a compressed model file. + auto strippedUrl = _url; + strippedUrl.setPath(_url.path().left(_url.path().size() - 3)); + hfmModel = _modelLoader.load(uncompressedData, serializerMapping, strippedUrl, ""); + } else { + hfmModel = _modelLoader.load(_data, serializerMapping, _url, _webMediaType.toStdString()); + } + + if (!hfmModel) { + throw QString("unsupported format"); + } + + if (hfmModel->meshes.empty() || hfmModel->joints.empty()) { + throw QString("empty geometry, possibly due to an unsupported model version"); + } + + // Add scripts to hfmModel + if (!_mapping.value(SCRIPT_FIELD).isNull()) { + QVariantList scripts = _mapping.values(SCRIPT_FIELD); + for (auto &script : scripts) { + hfmModel->scripts.push_back(script.toString()); + } + } + + QMetaObject::invokeMethod(resource.data(), "setGeometryDefinition", + Q_ARG(HFMModel::Pointer, hfmModel)); } catch (const std::exception&) { auto resource = _resource.toStrongRef(); if (resource) { @@ -258,8 +252,8 @@ void GeometryReader::run() { class GeometryDefinitionResource : public GeometryResource { Q_OBJECT public: - GeometryDefinitionResource(const QUrl& url, const QVariantHash& mapping, const QUrl& textureBaseUrl, bool combineParts) : - GeometryResource(url, resolveTextureBaseUrl(url, textureBaseUrl)), _mapping(mapping), _combineParts(combineParts) {} + GeometryDefinitionResource(const ModelLoader& modelLoader, const QUrl& url, const QVariantHash& mapping, const QUrl& textureBaseUrl, bool combineParts) : + GeometryResource(url, resolveTextureBaseUrl(url, textureBaseUrl)), _modelLoader(modelLoader), _mapping(mapping), _combineParts(combineParts) {} QString getType() const override { return "GeometryDefinition"; } @@ -269,6 +263,7 @@ protected: Q_INVOKABLE void setGeometryDefinition(HFMModel::Pointer hfmModel); private: + ModelLoader _modelLoader; QVariantHash _mapping; bool _combineParts; }; @@ -278,7 +273,7 @@ void GeometryDefinitionResource::downloadFinished(const QByteArray& data) { _url = _effectiveBaseURL; _textureBaseUrl = _effectiveBaseURL; } - QThreadPool::globalInstance()->start(new GeometryReader(_self, _effectiveBaseURL, _mapping, data, _combineParts)); + QThreadPool::globalInstance()->start(new GeometryReader(_modelLoader, _self, _effectiveBaseURL, _mapping, data, _combineParts, _request->getWebMediaType())); } void GeometryDefinitionResource::setGeometryDefinition(HFMModel::Pointer hfmModel) { @@ -316,6 +311,11 @@ ModelCache::ModelCache() { const qint64 GEOMETRY_DEFAULT_UNUSED_MAX_SIZE = DEFAULT_UNUSED_MAX_SIZE; setUnusedResourceCacheSize(GEOMETRY_DEFAULT_UNUSED_MAX_SIZE); setObjectName("ModelCache"); + + auto modelFormatRegistry = DependencyManager::get(); + modelFormatRegistry->addFormat(FBXSerializer()); + modelFormatRegistry->addFormat(OBJSerializer()); + modelFormatRegistry->addFormat(GLTFSerializer()); } QSharedPointer ModelCache::createResource(const QUrl& url, const QSharedPointer& fallback, @@ -328,7 +328,7 @@ QSharedPointer ModelCache::createResource(const QUrl& url, const QShar auto mapping = geometryExtra ? geometryExtra->mapping : QVariantHash(); auto textureBaseUrl = geometryExtra ? geometryExtra->textureBaseUrl : QUrl(); bool combineParts = geometryExtra ? geometryExtra->combineParts : true; - resource = new GeometryDefinitionResource(url, mapping, textureBaseUrl, combineParts); + resource = new GeometryDefinitionResource(_modelLoader, url, mapping, textureBaseUrl, combineParts); } return QSharedPointer(resource, &Resource::deleter); diff --git a/libraries/model-networking/src/model-networking/ModelCache.h b/libraries/model-networking/src/model-networking/ModelCache.h index 9d458e7512..1018bdecd5 100644 --- a/libraries/model-networking/src/model-networking/ModelCache.h +++ b/libraries/model-networking/src/model-networking/ModelCache.h @@ -20,6 +20,7 @@ #include "FBXSerializer.h" #include "TextureCache.h" +#include "ModelLoader.h" // Alias instead of derive to avoid copying @@ -158,6 +159,7 @@ protected: private: ModelCache(); virtual ~ModelCache() = default; + ModelLoader _modelLoader; }; class NetworkMaterial : public graphics::Material { diff --git a/libraries/model-networking/src/model-networking/ModelLoader.cpp b/libraries/model-networking/src/model-networking/ModelLoader.cpp new file mode 100644 index 0000000000..65314633c9 --- /dev/null +++ b/libraries/model-networking/src/model-networking/ModelLoader.cpp @@ -0,0 +1,24 @@ +// +// ModelLoader.cpp +// libraries/model-networking/src/model-networking +// +// Created by Sabrina Shanman on 2018/11/14. +// Copyright 2018 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 "ModelLoader.h" + +#include +#include + + +hfm::Model::Pointer ModelLoader::load(const hifi::ByteArray& data, const hifi::VariantHash& mapping, const hifi::URL& url, const std::string& webMediaType) const { + auto serializer = DependencyManager::get()->getSerializerForMediaType(data, url, webMediaType); + if (!serializer) { + return hfm::Model::Pointer(); + } + return serializer->read(data, mapping, url); +} diff --git a/libraries/model-networking/src/model-networking/ModelLoader.h b/libraries/model-networking/src/model-networking/ModelLoader.h new file mode 100644 index 0000000000..5fbab4fb65 --- /dev/null +++ b/libraries/model-networking/src/model-networking/ModelLoader.h @@ -0,0 +1,26 @@ +// +// ModelLoader.h +// libraries/model-networking/src/model-networking +// +// Created by Sabrina Shanman on 2018/11/13. +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_ModelLoader_h +#define hifi_ModelLoader_h + +#include +#include + +class ModelLoader { +public: + // Given the currently stored list of supported file formats, determine how to load a model from the given parameters. + // If successful, return an owned reference to the newly loaded model. + // If failed, return an empty reference. + hfm::Model::Pointer load(const hifi::ByteArray& data, const hifi::VariantHash& mapping, const hifi::URL& url, const std::string& webMediaType) const; +}; + +#endif // hifi_ModelLoader_h diff --git a/libraries/networking/src/HTTPResourceRequest.cpp b/libraries/networking/src/HTTPResourceRequest.cpp index e26f27adcf..50221a136a 100644 --- a/libraries/networking/src/HTTPResourceRequest.cpp +++ b/libraries/networking/src/HTTPResourceRequest.cpp @@ -94,7 +94,7 @@ void HTTPResourceRequest::onRequestFinished() { // Content-Range: -/* // Content-Range: */ // - auto parseContentRangeHeader = [](QString contentRangeHeader) -> std::pair { + static auto parseContentRangeHeader = [](QString contentRangeHeader) -> std::pair { auto unitRangeParts = contentRangeHeader.split(' '); if (unitRangeParts.size() != 2) { return { false, 0 }; @@ -115,6 +115,15 @@ void HTTPResourceRequest::onRequestFinished() { } }; + static auto parseMediaType = [](QString contentTypeHeader) -> std::pair { + auto contentTypeParts = contentTypeHeader.split(';'); + if (contentTypeParts.size() < 1) { + return { false, "" }; + } + + return { true, contentTypeParts[0] }; + }; + switch(_reply->error()) { case QNetworkReply::NoError: _data = _reply->readAll(); @@ -141,6 +150,16 @@ void HTTPResourceRequest::onRequestFinished() { } } + { + auto contentTypeHeader = _reply->rawHeader("Content-Type"); + bool success; + QString mediaType; + std::tie(success, mediaType) = parseMediaType(contentTypeHeader); + if (success) { + _webMediaType = mediaType; + } + } + recordBytesDownloadedInStats(STAT_HTTP_RESOURCE_TOTAL_BYTES, _data.size()); break; diff --git a/libraries/networking/src/ResourceRequest.h b/libraries/networking/src/ResourceRequest.h index eb306ca5be..550294d79b 100644 --- a/libraries/networking/src/ResourceRequest.h +++ b/libraries/networking/src/ResourceRequest.h @@ -84,6 +84,7 @@ public: bool loadedFromCache() const { return _loadedFromCache; } bool getRangeRequestSuccessful() const { return _rangeRequestSuccessful; } bool getTotalSizeOfResource() const { return _totalSizeOfResource; } + QString getWebMediaType() const { return _webMediaType; } void setFailOnRedirect(bool failOnRedirect) { _failOnRedirect = failOnRedirect; } void setCacheEnabled(bool value) { _cacheEnabled = value; } @@ -111,6 +112,7 @@ protected: ByteRange _byteRange; bool _rangeRequestSuccessful { false }; uint64_t _totalSizeOfResource { 0 }; + QString _webMediaType; int64_t _lastRecordedBytesDownloaded { 0 }; bool _isObservable; qint64 _callerId; diff --git a/libraries/shared/src/shared/MediaTypeLibrary.cpp b/libraries/shared/src/shared/MediaTypeLibrary.cpp new file mode 100644 index 0000000000..790897c3e2 --- /dev/null +++ b/libraries/shared/src/shared/MediaTypeLibrary.cpp @@ -0,0 +1,85 @@ +// +// MediaTypeLibrary.cpp +// libraries/shared/src/shared +// +// Created by Sabrina Shanman on 2018/11/29. +// Copyright 2018 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 "MediaTypeLibrary.h" + +MediaType MediaType::NONE = MediaType(""); + +MediaTypeLibrary::ID MediaTypeLibrary::registerMediaType(const MediaType& mediaType) { + ID id = nextID++; + _mediaTypes.emplace_back(id, mediaType); + return id; +} + +void MediaTypeLibrary::unregisterMediaType(const MediaTypeLibrary::ID& id) { + for (auto it = _mediaTypes.begin(); it != _mediaTypes.end(); it++) { + if ((*it).id == id) { + _mediaTypes.erase(it); + break; + } + } +} + +MediaType MediaTypeLibrary::getMediaType(const MediaTypeLibrary::ID& id) const { + for (auto& supportedFormat : _mediaTypes) { + if (supportedFormat.id == id) { + return supportedFormat.mediaType; + } + } + return MediaType::NONE; +} + +MediaTypeLibrary::ID MediaTypeLibrary::findMediaTypeForData(const hifi::ByteArray& data) const { + // Check file contents + for (auto& mediaType : _mediaTypes) { + for (auto& fileSignature : mediaType.mediaType.fileSignatures) { + auto testBytes = data.mid(fileSignature.byteOffset, (int)fileSignature.bytes.size()).toStdString(); + if (testBytes == fileSignature.bytes) { + return mediaType.id; + } + } + } + + return INVALID_ID; +} + +MediaTypeLibrary::ID MediaTypeLibrary::findMediaTypeForURL(const hifi::URL& url) const { + // Check file extension + std::string urlString = url.path().toStdString(); + std::size_t extensionSeparator = urlString.rfind('.'); + if (extensionSeparator != std::string::npos) { + std::string detectedExtension = urlString.substr(extensionSeparator + 1); + for (auto& supportedFormat : _mediaTypes) { + for (auto& extension : supportedFormat.mediaType.extensions) { + if (extension == detectedExtension) { + return supportedFormat.id; + } + } + } + } + + return INVALID_ID; +} + +MediaTypeLibrary::ID MediaTypeLibrary::findMediaTypeForWebID(const std::string& webMediaType) const { + // Check web media type + if (webMediaType != "") { + for (auto& supportedFormat : _mediaTypes) { + for (auto& candidateWebMediaType : supportedFormat.mediaType.webMediaTypes) { + if (candidateWebMediaType == webMediaType) { + return supportedFormat.id; + } + } + } + } + + return INVALID_ID; +} diff --git a/libraries/shared/src/shared/MediaTypeLibrary.h b/libraries/shared/src/shared/MediaTypeLibrary.h new file mode 100644 index 0000000000..c87da01fa1 --- /dev/null +++ b/libraries/shared/src/shared/MediaTypeLibrary.h @@ -0,0 +1,90 @@ +// +// MediaTypeLibrary.h +// libraries/shared/src/shared +// +// Created by Sabrina Shanman on 2018/11/28. +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_MediaTypeLibrary_h +#define hifi_MediaTypeLibrary_h + +#include +#include +#include +#include + +#include "HifiTypes.h" + +// A short sequence of bytes, typically at the beginning of the file, which identifies the file format +class FileSignature { +public: + FileSignature(const std::string& bytes, int byteOffset) : + bytes(bytes), + byteOffset(byteOffset) { + } + FileSignature(const FileSignature& fileSignature) : + bytes(fileSignature.bytes), + byteOffset(fileSignature.byteOffset) { + } + + std::string bytes; + int byteOffset; +}; + +// A named file extension with a list of known ways to positively identify the file type +class MediaType { +public: + MediaType(const std::string& name) : + name(name) { + } + MediaType() {}; + MediaType(const MediaType& mediaType) : + name(mediaType.name), + extensions(mediaType.extensions), + webMediaTypes(mediaType.webMediaTypes), + fileSignatures(mediaType.fileSignatures) { + } + + static MediaType NONE; + + std::string name; + std::vector extensions; + std::vector webMediaTypes; + std::vector fileSignatures; +}; + +class MediaTypeLibrary { +public: + using ID = unsigned int; + static const ID INVALID_ID { 0 }; + + ID registerMediaType(const MediaType& mediaType); + void unregisterMediaType(const ID& id); + + MediaType getMediaType(const ID& id) const; + + ID findMediaTypeForData(const hifi::ByteArray& data) const; + ID findMediaTypeForURL(const hifi::URL& url) const; + ID findMediaTypeForWebID(const std::string& webMediaType) const; + +protected: + ID nextID { 1 }; + + class Entry { + public: + Entry(const ID& id, const MediaType& mediaType) : + id(id), + mediaType(mediaType) { + } + ID id; + MediaType mediaType; + }; + + std::vector _mediaTypes; +}; + +#endif // hifi_MeidaTypeLibrary_h