Merge branch 'master' of https://github.com/highfidelity/hifi into metavoxels

This commit is contained in:
Andrzej Kapolka 2014-08-04 18:31:08 -07:00
commit 173e8af126
12 changed files with 489 additions and 59 deletions

View file

@ -1,3 +1,4 @@
</div>
<script src='/js/jquery-2.0.3.min.js'></script>
<script src='/js/bootstrap.min.js'></script>
<script src='/js/bootstrap.min.js'></script>
<script src='/js/domain-server.js'></script>

View file

@ -8,4 +8,33 @@
<link href="/css/style.css" rel="stylesheet" media="screen">
</head>
<body>
<nav class="navbar navbar-default" role="navigation">
<div class="container-fluid">
<!-- Brand and toggle get grouped for better mobile display -->
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="/">domain-server</a>
</div>
<!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav">
<li><a href="/">Nodes</a></li>
<li><a href="/settings/">Settings</a></li>
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Assignments <span class="caret"></span></a>
<ul class="dropdown-menu" role="menu">
<li><a href="/assignment">New Assignment</a></li>
</ul>
</li>
</ul>
</div>
</div><!-- /.container-fluid -->
</nav>
<div class="container">

View file

@ -0,0 +1,10 @@
$(document).ready(function(){
var url = window.location;
// Will only work if string in href matches with location
$('ul.nav a[href="'+ url +'"]').parent().addClass('active');
// Will also work for relative and absolute hrefs
$('ul.nav a').filter(function() {
return this.href == url;
}).parent().addClass('active');
});

View file

@ -28,33 +28,5 @@
"default": ""
}
}
},
"voxels": {
"label": "Voxels",
"assignment-types": [2,3],
"settings": {
"voxel-wallet": {
"label": "Destination Wallet ID",
"help": "Wallet to be paid for voxel changes",
"placeholder": "00000000-0000-0000-0000-000000000000",
"default": ""
},
"per-voxel-credits": {
"type": "double",
"label": "Per Voxel Cost",
"help": "Credit cost to change each voxel",
"placeholder": "0.0",
"default": "0.0",
"input_addon": "₵"
},
"per-meter-cubed-credits": {
"type": "double",
"label": "Per Meter Cubed Cost",
"help": "Credit cost to change each cubed meter of voxel space",
"placeholder": "0.0",
"default": "0.0",
"input_addon": "₵"
}
}
}
}

View file

@ -1280,6 +1280,9 @@ bool DomainServer::isAuthenticatedRequest(HTTPConnection* connection, const QUrl
const QByteArray HTTP_COOKIE_HEADER_KEY = "Cookie";
const QString ADMIN_USERS_CONFIG_KEY = "admin-users";
const QString ADMIN_ROLES_CONFIG_KEY = "admin-roles";
const QString BASIC_AUTH_CONFIG_KEY = "basic-auth";
const QByteArray UNAUTHENTICATED_BODY = "You do not have permission to access this domain-server.";
if (!_oauthProviderURL.isEmpty()
&& (_argumentVariantMap.contains(ADMIN_USERS_CONFIG_KEY) || _argumentVariantMap.contains(ADMIN_ROLES_CONFIG_KEY))) {
@ -1293,6 +1296,11 @@ bool DomainServer::isAuthenticatedRequest(HTTPConnection* connection, const QUrl
cookieUUID = cookieUUIDRegex.cap(1);
}
if (_argumentVariantMap.contains(BASIC_AUTH_CONFIG_KEY)) {
qDebug() << "Config file contains web admin settings for OAuth and basic HTTP authentication."
<< "These cannot be combined - using OAuth for authentication.";
}
if (!cookieUUID.isNull() && _cookieSessionHash.contains(cookieUUID)) {
// pull the QJSONObject for the user with this cookie UUID
DomainServerWebSessionData sessionData = _cookieSessionHash.value(cookieUUID);
@ -1315,8 +1323,7 @@ bool DomainServer::isAuthenticatedRequest(HTTPConnection* connection, const QUrl
}
}
QString unauthenticatedRequest = "You do not have permission to access this domain-server.";
connection->respond(HTTPConnection::StatusCode401, unauthenticatedRequest.toUtf8());
connection->respond(HTTPConnection::StatusCode401, UNAUTHENTICATED_BODY);
// the user does not have allowed username or role, return 401
return false;
@ -1340,6 +1347,59 @@ bool DomainServer::isAuthenticatedRequest(HTTPConnection* connection, const QUrl
// we don't know about this user yet, so they are not yet authenticated
return false;
}
} else if (_argumentVariantMap.contains(BASIC_AUTH_CONFIG_KEY)) {
// config file contains username and password combinations for basic auth
const QByteArray BASIC_AUTH_HEADER_KEY = "Authorization";
// check if a username and password have been provided with the request
QString basicAuthString = connection->requestHeaders().value(BASIC_AUTH_HEADER_KEY);
if (!basicAuthString.isEmpty()) {
QStringList splitAuthString = basicAuthString.split(' ');
QString base64String = splitAuthString.size() == 2 ? splitAuthString[1] : "";
QString credentialString = QByteArray::fromBase64(base64String.toLocal8Bit());
if (!credentialString.isEmpty()) {
QStringList credentialList = credentialString.split(':');
if (credentialList.size() == 2) {
QString username = credentialList[0];
QString password = credentialList[1];
// we've pulled a username and password - now check if there is a match in our basic auth hash
QJsonObject basicAuthObject = _argumentVariantMap.value(BASIC_AUTH_CONFIG_KEY).toJsonValue().toObject();
if (basicAuthObject.contains(username)) {
const QString BASIC_AUTH_USER_PASSWORD_KEY = "password";
QJsonObject userObject = basicAuthObject.value(username).toObject();
if (userObject.contains(BASIC_AUTH_USER_PASSWORD_KEY)
&& userObject.value(BASIC_AUTH_USER_PASSWORD_KEY).toString() == password) {
// this is username / password match - let this user in
return true;
}
}
}
}
}
// basic HTTP auth being used but no username and password are present
// or the username and password are not correct
// send back a 401 and ask for basic auth
const QByteArray HTTP_AUTH_REQUEST_HEADER_KEY = "WWW-Authenticate";
static QString HTTP_AUTH_REALM_STRING = QString("Basic realm='%1 %2'")
.arg(_hostname.isEmpty() ? "localhost" : _hostname)
.arg("domain-server");
Headers basicAuthHeader;
basicAuthHeader.insert(HTTP_AUTH_REQUEST_HEADER_KEY, HTTP_AUTH_REALM_STRING.toUtf8());
connection->respond(HTTPConnection::StatusCode401, UNAUTHENTICATED_BODY,
HTTPConnection::DefaultContentType, basicAuthHeader);
// not authenticated, bubble up false
return false;
} else {
// we don't have an OAuth URL + admin roles/usernames, so all users are authenticated
return true;

View file

@ -1502,10 +1502,11 @@ glm::vec3 Application::getMouseVoxelWorldCoordinates(const VoxelDetail& mouseVox
}
FaceTracker* Application::getActiveFaceTracker() {
return _cara.isActive() ? static_cast<FaceTracker*>(&_cara) :
(_faceshift.isActive() ? static_cast<FaceTracker*>(&_faceshift) :
(_faceplus.isActive() ? static_cast<FaceTracker*>(&_faceplus) :
(_visage.isActive() ? static_cast<FaceTracker*>(&_visage) : NULL)));
return (_dde.isActive() ? static_cast<FaceTracker*>(&_dde) :
(_cara.isActive() ? static_cast<FaceTracker*>(&_cara) :
(_faceshift.isActive() ? static_cast<FaceTracker*>(&_faceshift) :
(_faceplus.isActive() ? static_cast<FaceTracker*>(&_faceplus) :
(_visage.isActive() ? static_cast<FaceTracker*>(&_visage) : NULL)))));
}
struct SendVoxelsOperationArgs {
@ -1937,13 +1938,21 @@ void Application::updateVisage() {
_visage.update();
}
void Application::updateDDE() {
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
PerformanceWarning warn(showWarnings, "Application::updateDDE()");
// Update Cara
_dde.update();
}
void Application::updateCara() {
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
PerformanceWarning warn(showWarnings, "Application::updateCara()");
// Update Cara
_cara.update();
// Copy angular velocity if measured by cara, to the head
if (_cara.isActive()) {
_myAvatar->getHead()->setAngularVelocity(_cara.getHeadAngularVelocity());
@ -3265,6 +3274,7 @@ void Application::resetSensors() {
_faceplus.reset();
_faceshift.reset();
_visage.reset();
_dde.reset();
OculusManager::reset();

View file

@ -64,6 +64,7 @@
#include "devices/SixenseManager.h"
#include "devices/Visage.h"
#include "devices/CaraFaceTracker.h"
#include "devices/DdeFaceTracker.h"
#include "models/ModelTreeRenderer.h"
#include "particles/ParticleTreeRenderer.h"
#include "renderer/AmbientOcclusionEffect.h"
@ -214,6 +215,7 @@ public:
Faceplus* getFaceplus() { return &_faceplus; }
Faceshift* getFaceshift() { return &_faceshift; }
Visage* getVisage() { return &_visage; }
DdeFaceTracker* getDDE() { return &_dde; }
CaraFaceTracker* getCara() { return &_cara; }
FaceTracker* getActiveFaceTracker();
SixenseManager* getSixenseManager() { return &_sixenseManager; }
@ -393,6 +395,7 @@ private:
void updateFaceplus();
void updateFaceshift();
void updateVisage();
void updateDDE();
void updateCara();
void updateMyAvatarLookAtPosition();
void updateThreads(float deltaTime);
@ -493,6 +496,7 @@ private:
Faceshift _faceshift;
Visage _visage;
CaraFaceTracker _cara;
DdeFaceTracker _dde;
SixenseManager _sixenseManager;
PrioVR _prioVR;

View file

@ -68,6 +68,9 @@ void Head::simulate(float deltaTime, bool isMine, bool billboard) {
if ((_isFaceshiftConnected = faceTracker)) {
_blendshapeCoefficients = faceTracker->getBlendshapeCoefficients();
_isFaceshiftConnected = true;
} else if (Application::getInstance()->getDDE()->isActive()) {
faceTracker = Application::getInstance()->getDDE();
_blendshapeCoefficients = faceTracker->getBlendshapeCoefficients();
}
}

View file

@ -9,8 +9,8 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hi_fi_CaraFaceTracker_h
#define hi_fi_CaraFaceTracker_h
#ifndef hifi_CaraFaceTracker_h
#define hifi_CaraFaceTracker_h
#include <QUdpSocket>
@ -121,4 +121,4 @@ private:
int _jawOpenIndex;
};
#endif //endif hi_fi_CaraFaceTracker_h
#endif //endif hifi_CaraFaceTracker_h

View file

@ -0,0 +1,245 @@
//
// DdeFaceTracker.cpp
//
//
// Created by Clement on 8/2/14.
// Copyright 2014 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 <SharedUtil.h>
#include <QJsonDocument>
#include <QJsonArray>
#include <QJsonObject>
#include <QElapsedTimer>
#include "DdeFaceTracker.h"
static const QHostAddress DDE_FEATURE_POINT_SERVER_ADDR("127.0.0.1");
static const quint16 DDE_FEATURE_POINT_SERVER_PORT = 5555;
static const int NUM_EXPRESSION = 46;
static const int MIN_PACKET_SIZE = (8 + NUM_EXPRESSION) * sizeof(float) + sizeof(int);
static const int MAX_NAME_SIZE = 31;
struct Packet{
//roughly in mm
float focal_length[1];
float translation[3];
//quaternion
float rotation[4];
//blendshape coefficients ranging between -0.2 and 1.5
float expressions[NUM_EXPRESSION];
//avatar id selected on the UI
int avatar_id;
//client name, arbitrary length
char name[MAX_NAME_SIZE + 1];
};
DdeFaceTracker::DdeFaceTracker() :
_lastReceiveTimestamp(0),
_reset(false),
_leftBlinkIndex(0), // see http://support.faceshift.com/support/articles/35129-export-of-blendshapes
_rightBlinkIndex(1),
_leftEyeOpenIndex(8),
_rightEyeOpenIndex(9),
_browDownLeftIndex(14),
_browDownRightIndex(15),
_browUpCenterIndex(16),
_browUpLeftIndex(17),
_browUpRightIndex(18),
_mouthSmileLeftIndex(28),
_mouthSmileRightIndex(29),
_jawOpenIndex(21)
{
_blendshapeCoefficients.resize(NUM_EXPRESSION);
connect(&_udpSocket, SIGNAL(readyRead()), SLOT(readPendingDatagrams()));
connect(&_udpSocket, SIGNAL(error(QAbstractSocket::SocketError)), SLOT(socketErrorOccurred(QAbstractSocket::SocketError)));
connect(&_udpSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), SLOT(socketStateChanged(QAbstractSocket::SocketState)));
bindTo(DDE_FEATURE_POINT_SERVER_PORT);
}
DdeFaceTracker::DdeFaceTracker(const QHostAddress& host, quint16 port) :
_lastReceiveTimestamp(0),
_reset(false),
_leftBlinkIndex(0), // see http://support.faceshift.com/support/articles/35129-export-of-blendshapes
_rightBlinkIndex(1),
_leftEyeOpenIndex(8),
_rightEyeOpenIndex(9),
_browDownLeftIndex(14),
_browDownRightIndex(15),
_browUpCenterIndex(16),
_browUpLeftIndex(17),
_browUpRightIndex(18),
_mouthSmileLeftIndex(28),
_mouthSmileRightIndex(29),
_jawOpenIndex(21)
{
_blendshapeCoefficients.resize(NUM_EXPRESSION);
connect(&_udpSocket, SIGNAL(readyRead()), SLOT(readPendingDatagrams()));
connect(&_udpSocket, SIGNAL(error(QAbstractSocket::SocketError)), SLOT(socketErrorOccurred(QAbstractSocket::SocketError)));
connect(&_udpSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), SIGNAL(socketStateChanged(QAbstractSocket::SocketState)));
bindTo(host, port);
}
DdeFaceTracker::~DdeFaceTracker() {
if(_udpSocket.isOpen())
_udpSocket.close();
}
void DdeFaceTracker::init() {
}
void DdeFaceTracker::reset() {
_reset = true;
}
void DdeFaceTracker::update() {
}
void DdeFaceTracker::bindTo(quint16 port) {
bindTo(QHostAddress::Any, port);
}
void DdeFaceTracker::bindTo(const QHostAddress& host, quint16 port) {
if(_udpSocket.isOpen()) {
_udpSocket.close();
}
_udpSocket.bind(host, port);
}
bool DdeFaceTracker::isActive() const {
static const int ACTIVE_TIMEOUT_USECS = 3000000; //3 secs
return (usecTimestampNow() - _lastReceiveTimestamp < ACTIVE_TIMEOUT_USECS);
}
//private slots and methods
void DdeFaceTracker::socketErrorOccurred(QAbstractSocket::SocketError socketError) {
qDebug() << "[Error] DDE Face Tracker Socket Error: " << _udpSocket.errorString();
}
void DdeFaceTracker::socketStateChanged(QAbstractSocket::SocketState socketState) {
QString state;
switch(socketState) {
case QAbstractSocket::BoundState:
state = "Bounded";
break;
case QAbstractSocket::ClosingState:
state = "Closing";
break;
case QAbstractSocket::ConnectedState:
state = "Connected";
break;
case QAbstractSocket::ConnectingState:
state = "Connecting";
break;
case QAbstractSocket::HostLookupState:
state = "Host Lookup";
break;
case QAbstractSocket::ListeningState:
state = "Listening";
break;
case QAbstractSocket::UnconnectedState:
state = "Unconnected";
break;
}
qDebug() << "[Info] DDE Face Tracker Socket: " << socketState;
}
void DdeFaceTracker::readPendingDatagrams() {
QByteArray buffer;
while (_udpSocket.hasPendingDatagrams()) {
buffer.resize(_udpSocket.pendingDatagramSize());
_udpSocket.readDatagram(buffer.data(), buffer.size());
}
decodePacket(buffer);
}
float DdeFaceTracker::getBlendshapeCoefficient(int index) const {
return (index >= 0 && index < (int)_blendshapeCoefficients.size()) ? _blendshapeCoefficients[index] : 0.0f;
}
static const float DDE_MIN_RANGE = -0.2;
static const float DDE_MAX_RANGE = 1.5;
float rescaleCoef(float ddeCoef) {
return (ddeCoef - DDE_MIN_RANGE) / (DDE_MAX_RANGE - DDE_MIN_RANGE);
}
void DdeFaceTracker::decodePacket(const QByteArray& buffer) {
if(buffer.size() > MIN_PACKET_SIZE) {
Packet packet;
int bytesToCopy = glm::min((int)sizeof(packet), buffer.size());
memset(&packet.name, '\n', MAX_NAME_SIZE + 1);
memcpy(&packet, buffer.data(), bytesToCopy);
glm::vec3 translation;
memcpy(&translation, packet.translation, sizeof(packet.translation));
glm::quat rotation;
memcpy(&rotation, &packet.rotation, sizeof(packet.rotation));
if (_reset) {
memcpy(&_referenceTranslation, &translation, sizeof(glm::vec3));
memcpy(&_referenceRotation, &rotation, sizeof(glm::quat));
_reset = false;
}
// Compute relative translation
float LEAN_DAMPING_FACTOR = 40;
translation -= _referenceTranslation;
translation /= LEAN_DAMPING_FACTOR;
translation.x *= -1;
// Compute relative rotation
rotation = glm::inverse(_referenceRotation) * rotation;
// copy values
_headTranslation = translation;
_headRotation = rotation;
// Set blendshapes
float BLINK_MAGNIFIER = 2.0f;
_blendshapeCoefficients[_leftBlinkIndex] = rescaleCoef(packet.expressions[1]) * BLINK_MAGNIFIER;
_blendshapeCoefficients[_rightBlinkIndex] = rescaleCoef(packet.expressions[0]) * BLINK_MAGNIFIER;
float leftBrow = 1.0f - rescaleCoef(packet.expressions[14]);
if (leftBrow < 0.5f) {
_blendshapeCoefficients[_browDownLeftIndex] = 1.0f - 2.0f * leftBrow;
_blendshapeCoefficients[_browUpLeftIndex] = 0.0f;
} else {
_blendshapeCoefficients[_browDownLeftIndex] = 0.0f;
_blendshapeCoefficients[_browUpLeftIndex] = 2.0f * (leftBrow - 0.5f);
}
float rightBrow = 1.0f - rescaleCoef(packet.expressions[15]);
if (rightBrow < 0.5f) {
_blendshapeCoefficients[_browDownRightIndex] = 1.0f - 2.0f * rightBrow;
_blendshapeCoefficients[_browUpRightIndex] = 0.0f;
} else {
_blendshapeCoefficients[_browDownRightIndex] = 0.0f;
_blendshapeCoefficients[_browUpRightIndex] = 2.0f * (rightBrow - 0.5f);
}
float JAW_OPEN_MAGNIFIER = 1.4f;
_blendshapeCoefficients[_jawOpenIndex] = rescaleCoef(packet.expressions[21]) * JAW_OPEN_MAGNIFIER;
_blendshapeCoefficients[_mouthSmileLeftIndex] = rescaleCoef(packet.expressions[24]);
_blendshapeCoefficients[_mouthSmileRightIndex] = rescaleCoef(packet.expressions[23]);
} else {
qDebug() << "[Error] DDE Face Tracker Decode Error";
}
_lastReceiveTimestamp = usecTimestampNow();
}

View file

@ -0,0 +1,89 @@
//
// DdeFaceTracker.h
//
//
// Created by Clement on 8/2/14.
// Copyright 2014 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_DdeFaceTracker_h
#define hifi_DdeFaceTracker_h
#include <QUdpSocket>
#include "FaceTracker.h"
class DdeFaceTracker : public FaceTracker {
Q_OBJECT
public:
DdeFaceTracker();
DdeFaceTracker(const QHostAddress& host, quint16 port);
~DdeFaceTracker();
//initialization
void init();
void reset();
void update();
//sockets
void bindTo(quint16 port);
void bindTo(const QHostAddress& host, quint16 port);
bool isActive() const;
float getLeftBlink() const { return getBlendshapeCoefficient(_leftBlinkIndex); }
float getRightBlink() const { return getBlendshapeCoefficient(_rightBlinkIndex); }
float getLeftEyeOpen() const { return getBlendshapeCoefficient(_leftEyeOpenIndex); }
float getRightEyeOpen() const { return getBlendshapeCoefficient(_rightEyeOpenIndex); }
float getBrowDownLeft() const { return getBlendshapeCoefficient(_browDownLeftIndex); }
float getBrowDownRight() const { return getBlendshapeCoefficient(_browDownRightIndex); }
float getBrowUpCenter() const { return getBlendshapeCoefficient(_browUpCenterIndex); }
float getBrowUpLeft() const { return getBlendshapeCoefficient(_browUpLeftIndex); }
float getBrowUpRight() const { return getBlendshapeCoefficient(_browUpRightIndex); }
float getMouthSize() const { return getBlendshapeCoefficient(_jawOpenIndex); }
float getMouthSmileLeft() const { return getBlendshapeCoefficient(_mouthSmileLeftIndex); }
float getMouthSmileRight() const { return getBlendshapeCoefficient(_mouthSmileRightIndex); }
private slots:
//sockets
void socketErrorOccurred(QAbstractSocket::SocketError socketError);
void readPendingDatagrams();
void socketStateChanged(QAbstractSocket::SocketState socketState);
private:
float getBlendshapeCoefficient(int index) const;
void decodePacket(const QByteArray& buffer);
// sockets
QUdpSocket _udpSocket;
quint64 _lastReceiveTimestamp;
bool _reset;
glm::vec3 _referenceTranslation;
glm::quat _referenceRotation;
int _leftBlinkIndex;
int _rightBlinkIndex;
int _leftEyeOpenIndex;
int _rightEyeOpenIndex;
// Brows
int _browDownLeftIndex;
int _browDownRightIndex;
int _browUpCenterIndex;
int _browUpLeftIndex;
int _browUpRightIndex;
int _mouthSmileLeftIndex;
int _mouthSmileRightIndex;
int _jawOpenIndex;
};
#endif // hifi_DdeFaceTracker_h

View file

@ -9,6 +9,7 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <QtCore/QCoreApplication>
#include <QtCore/QDebug>
#include <QtCore/QFile>
#include <QtCore/QJsonDocument>
@ -68,29 +69,35 @@ QVariantMap HifiConfigVariantMap::mergeCLParametersWithJSONConfig(const QStringL
int configIndex = argumentList.indexOf(CONFIG_FILE_OPTION);
QString configFilePath;
if (configIndex != -1) {
// we have a config file - try and read it
QString configFilePath = argumentList[configIndex + 1];
QFile configFile(configFilePath);
if (configFile.exists()) {
qDebug() << "Reading JSON config file at" << configFilePath;
configFile.open(QIODevice::ReadOnly);
QJsonDocument configDocument = QJsonDocument::fromJson(configFile.readAll());
QJsonObject rootObject = configDocument.object();
// enumerate the keys of the configDocument object
foreach(const QString& key, rootObject.keys()) {
if (!mergedMap.contains(key)) {
// no match in existing list, add it
mergedMap.insert(key, QVariant(rootObject[key]));
}
configFilePath = argumentList[configIndex + 1];
} else {
// no config file - try to read a file at resources/config.json
configFilePath = QCoreApplication::applicationDirPath() + "/resources/config.json";
}
QFile configFile(configFilePath);
if (configFile.exists()) {
qDebug() << "Reading JSON config file at" << configFilePath;
configFile.open(QIODevice::ReadOnly);
QJsonDocument configDocument = QJsonDocument::fromJson(configFile.readAll());
QJsonObject rootObject = configDocument.object();
// enumerate the keys of the configDocument object
foreach(const QString& key, rootObject.keys()) {
if (!mergedMap.contains(key)) {
// no match in existing list, add it
mergedMap.insert(key, QVariant(rootObject[key]));
}
} else {
qDebug() << "Could not find JSON config file at" << configFilePath;
}
} else {
qDebug() << "Could not find JSON config file at" << configFilePath;
}
return mergedMap;