Merge pull request #14479 from sabrina-shanman/hfm_mimetype

(case 20037) Add robust MIME type detection for loading models in ModelCache
This commit is contained in:
Shannon Romano 2018-12-11 18:41:55 +00:00 committed by GitHub
commit a3c870ed90
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 537 additions and 73 deletions

View file

@ -100,6 +100,7 @@
#include <MainWindow.h>
#include <MappingRequest.h>
#include <MessagesClient.h>
#include <hfm/ModelFormatRegistry.h>
#include <model-networking/ModelCacheScriptingInterface.h>
#include <model-networking/TextureCacheScriptingInterface.h>
#include <ModelEntityItem.h>
@ -830,6 +831,7 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) {
DependencyManager::set<NodeList>(NodeType::Agent, listenPort);
DependencyManager::set<recording::ClipCache>();
DependencyManager::set<GeometryCache>();
DependencyManager::set<ModelFormatRegistry>(); // ModelFormatRegistry must be defined before ModelCache. See the ModelCache constructor.
DependencyManager::set<ModelCache>();
DependencyManager::set<ModelCacheScriptingInterface>();
DependencyManager::set<ScriptCache>();
@ -2690,6 +2692,7 @@ Application::~Application() {
DependencyManager::destroy<TextureCache>();
DependencyManager::destroy<ModelCacheScriptingInterface>();
DependencyManager::destroy<ModelCache>();
DependencyManager::destroy<ModelFormatRegistry>();
DependencyManager::destroy<ScriptCache>();
DependencyManager::destroy<SoundCacheScriptingInterface>();
DependencyManager::destroy<SoundCache>();

View file

@ -20,6 +20,7 @@
#include <Profile.h>
#include "AnimationLogging.h"
#include <FBXSerializer.h>
int animationPointerMetaTypeId = qRegisterMetaType<AnimationPointer>();

View file

@ -17,7 +17,7 @@
#include <QtScript/QScriptValue>
#include <DependencyManager.h>
#include <FBXSerializer.h>
#include <hfm/HFM.h>
#include <ResourceCache.h>
class Animation;

View file

@ -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<hfm::Serializer::Factory> FBXSerializer::getFactory() const {
return std::make_unique<hfm::Serializer::SimpleFactory<FBXSerializer>>();
}
HFMModel::Pointer FBXSerializer::read(const QByteArray& data, const QVariantHash& mapping, const QUrl& url) {
QBuffer buffer(const_cast<QByteArray*>(&data));
buffer.open(QIODevice::ReadOnly);

View file

@ -96,6 +96,9 @@ class ExtractedMesh;
class FBXSerializer : public HFMSerializer {
public:
MediaType getMediaType() const override;
std::unique_ptr<hfm::Serializer::Factory> getFactory() const override;
HFMModel* _hfmModel;
/// Reads HFMModel from the supplied model and mapping data.
/// \exception QString if an error occurs in parsing

View file

@ -35,11 +35,6 @@
#include "FBXSerializer.h"
GLTFSerializer::GLTFSerializer() {
}
bool GLTFSerializer::getStringVal(const QJsonObject& object, const QString& fieldname,
QString& value, QMap<QString, bool>& 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<hfm::Serializer::Factory> GLTFSerializer::getFactory() const {
return std::make_unique<hfm::Serializer::SimpleFactory<GLTFSerializer>>();
}
HFMModel::Pointer GLTFSerializer::read(const QByteArray& data, const QVariantHash& mapping, const QUrl& url) {
_url = url;

View file

@ -16,7 +16,6 @@
#include <QtNetwork/QNetworkReply>
#include <hfm/ModelFormatLogging.h>
#include <hfm/HFMSerializer.h>
#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<hfm::Serializer::Factory> getFactory() const override;
HFMModel::Pointer read(const QByteArray& data, const QVariantHash& mapping, const QUrl& url = QUrl()) override;
private:
GLTFFile _file;

View file

@ -651,6 +651,15 @@ done:
return result;
}
MediaType OBJSerializer::getMediaType() const {
MediaType mediaType("obj");
mediaType.extensions.push_back("obj");
return mediaType;
}
std::unique_ptr<hfm::Serializer::Factory> OBJSerializer::getFactory() const {
return std::make_unique<hfm::Serializer::SimpleFactory<OBJSerializer>>();
}
HFMModel::Pointer OBJSerializer::read(const QByteArray& data, const QVariantHash& mapping, const QUrl& url) {
PROFILE_RANGE_EX(resource_parse, __FUNCTION__, 0xffff0000, nullptr);

View file

@ -14,7 +14,6 @@
#include <QtNetwork/QNetworkReply>
#include <hfm/HFMSerializer.h>
#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<hfm::Serializer::Factory> getFactory() const override;
typedef QVector<OBJFace> FaceGroup;
QVector<glm::vec3> vertices;
QVector<glm::vec3> vertexColors;

View file

@ -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<Serializer::Factory> supportedFactory) {
std::lock_guard<std::mutex> lock(_libraryLock);
MediaTypeID id = _mediaTypeLibrary.registerMediaType(mediaType);
_supportedFormats.emplace_back(id, supportedFactory);
return id;
}
void FormatRegistry::unregisterMediaType(const MediaTypeID& mediaTypeID) {
std::lock_guard<std::mutex> 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<Serializer> FormatRegistry::getSerializerForMediaTypeID(FormatRegistry::MediaTypeID mediaTypeID) const {
// TODO: shared_lock in C++14
std::lock_guard<std::mutex> lock(*const_cast<std::mutex*>(&_libraryLock));
for (auto it = _supportedFormats.begin(); it != _supportedFormats.end(); it++) {
if ((*it).mediaTypeID == mediaTypeID) {
return (*it).factory->get();
}
}
return std::shared_ptr<Serializer>();
}
std::shared_ptr<Serializer> 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<std::mutex> lock(*const_cast<std::mutex*>(&_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);
}
};

View file

@ -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 <shared/MediaTypeLibrary.h>
#include <shared/ReadWriteLockable.h>
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<Serializer::Factory> supportedFactory);
void unregisterMediaType(const MediaTypeID& id);
std::shared_ptr<Serializer> getSerializerForMediaType(const hifi::ByteArray& data, const hifi::URL& url, const std::string& webMediaType) const;
protected:
std::shared_ptr<Serializer> getSerializerForMediaTypeID(MediaTypeID id) const;
MediaTypeLibrary _mediaTypeLibrary;
std::mutex _libraryLock;
class SupportedFormat {
public:
SupportedFormat(const MediaTypeID& mediaTypeID, std::unique_ptr<Serializer::Factory>& factory) :
mediaTypeID(mediaTypeID),
factory(std::move(factory)) {
}
MediaTypeID mediaTypeID;
std::unique_ptr<Serializer::Factory> factory;
};
std::vector<SupportedFormat> _supportedFormats;
};
};
#endif // hifi_HFMFormatRegistry_h

View file

@ -15,10 +15,27 @@
#include <shared/HifiTypes.h>
#include "HFM.h"
#include <shared/MediaTypeLibrary.h>
namespace hfm {
class Serializer {
public:
class Factory {
public:
virtual std::shared_ptr<Serializer> get() = 0;
};
template<typename T>
class SimpleFactory : public Factory {
std::shared_ptr<Serializer> get() override {
return std::make_shared<T>();
}
};
virtual MediaType getMediaType() const = 0;
virtual std::unique_ptr<Factory> getFactory() const = 0;
virtual Model::Pointer read(const hifi::ByteArray& data, const hifi::VariantHash& mapping, const hifi::URL& url = hifi::URL()) = 0;
};

View file

@ -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<hfm::Serializer> ModelFormatRegistry::getSerializerForMediaType(const hifi::ByteArray& data, const hifi::URL& url, const std::string& webMediaType) const {
return _hfmFormatRegistry.getSerializerForMediaType(data, url, webMediaType);
}

View file

@ -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 <DependencyManager.h>
class ModelFormatRegistry : public Dependency {
public:
void addFormat(const hfm::Serializer& serializer);
std::shared_ptr<hfm::Serializer> getSerializerForMediaType(const hifi::ByteArray& data, const hifi::URL& url, const std::string& webMediaType) const;
protected:
hfm::FormatRegistry _hfmFormatRegistry;
};
#endif // hifi_ModelFormatRegistry_h

View file

@ -12,9 +12,6 @@
#include "ModelCache.h"
#include <Finally.h>
#include <FSTReader.h>
#include "FBXSerializer.h"
#include "OBJSerializer.h"
#include "GLTFSerializer.h"
#include <gpu/Batch.h>
#include <gpu/Stream.h>
@ -26,6 +23,10 @@
#include "ModelNetworkingLogging.h"
#include <Trace.h>
#include <StatTracker.h>
#include <hfm/ModelFormatRegistry.h>
#include <FBXSerializer.h>
#include <OBJSerializer.h>
#include <GLTFSerializer.h>
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>& 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>& 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<StatTracker>()->incrementStat("PendingProcessing");
}
@ -154,11 +155,13 @@ public:
virtual void run() override;
private:
ModelLoader _modelLoader;
QWeakPointer<Resource> _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>();
modelFormatRegistry->addFormat(FBXSerializer());
modelFormatRegistry->addFormat(OBJSerializer());
modelFormatRegistry->addFormat(GLTFSerializer());
}
QSharedPointer<Resource> ModelCache::createResource(const QUrl& url, const QSharedPointer<Resource>& fallback,
@ -328,7 +328,7 @@ QSharedPointer<Resource> 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, &Resource::deleter);

View file

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

View file

@ -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 <DependencyManager.h>
#include <hfm/ModelFormatRegistry.h>
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<ModelFormatRegistry>()->getSerializerForMediaType(data, url, webMediaType);
if (!serializer) {
return hfm::Model::Pointer();
}
return serializer->read(data, mapping, url);
}

View file

@ -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 <shared/HifiTypes.h>
#include <hfm/HFM.h>
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

View file

@ -94,7 +94,7 @@ void HTTPResourceRequest::onRequestFinished() {
// Content-Range: <unit> <range-start>-<range-end>/*
// Content-Range: <unit> */<size>
//
auto parseContentRangeHeader = [](QString contentRangeHeader) -> std::pair<bool, uint64_t> {
static auto parseContentRangeHeader = [](QString contentRangeHeader) -> std::pair<bool, uint64_t> {
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<bool, QString> {
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;

View file

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

View file

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

View file

@ -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 <vector>
#include <string>
#include <functional>
#include <mutex>
#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<std::string> extensions;
std::vector<std::string> webMediaTypes;
std::vector<FileSignature> 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<Entry> _mediaTypes;
};
#endif // hifi_MeidaTypeLibrary_h