Merge branch 'master' of github.com:highfidelity/hifi into no-id-swap-redux

This commit is contained in:
Seth Alves 2015-05-21 09:20:56 -07:00
commit 52ce26d80d
19 changed files with 897 additions and 775 deletions

View file

@ -43,8 +43,7 @@ int hifiSockAddrMeta = qRegisterMetaType<HifiSockAddr>("HifiSockAddr");
AssignmentClient::AssignmentClient(Assignment::Type requestAssignmentType, QString assignmentPool, AssignmentClient::AssignmentClient(Assignment::Type requestAssignmentType, QString assignmentPool,
QUuid walletUUID, QString assignmentServerHostname, quint16 assignmentServerPort, QUuid walletUUID, QString assignmentServerHostname, quint16 assignmentServerPort,
quint16 assignmentMonitorPort) : quint16 assignmentMonitorPort) :
_assignmentServerHostname(DEFAULT_ASSIGNMENT_SERVER_HOSTNAME), _assignmentServerHostname(DEFAULT_ASSIGNMENT_SERVER_HOSTNAME)
_localASPortSharedMem(NULL)
{ {
LogUtils::init(); LogUtils::init();
@ -181,8 +180,7 @@ void AssignmentClient::sendAssignmentRequest() {
if (_assignmentServerHostname == "localhost") { if (_assignmentServerHostname == "localhost") {
// we want to check again for the local domain-server port in case the DS has restarted // we want to check again for the local domain-server port in case the DS has restarted
quint16 localAssignmentServerPort; quint16 localAssignmentServerPort;
if (nodeList->getLocalServerPortFromSharedMemory(DOMAIN_SERVER_LOCAL_PORT_SMEM_KEY, _localASPortSharedMem, if (nodeList->getLocalServerPortFromSharedMemory(DOMAIN_SERVER_LOCAL_PORT_SMEM_KEY, localAssignmentServerPort)) {
localAssignmentServerPort)) {
if (localAssignmentServerPort != _assignmentServerSocket.getPort()) { if (localAssignmentServerPort != _assignmentServerSocket.getPort()) {
qDebug() << "Port for local assignment server read from shared memory is" qDebug() << "Port for local assignment server read from shared memory is"
<< localAssignmentServerPort; << localAssignmentServerPort;
@ -190,7 +188,11 @@ void AssignmentClient::sendAssignmentRequest() {
_assignmentServerSocket.setPort(localAssignmentServerPort); _assignmentServerSocket.setPort(localAssignmentServerPort);
nodeList->setAssignmentServerSocket(_assignmentServerSocket); nodeList->setAssignmentServerSocket(_assignmentServerSocket);
} }
} else {
qDebug() << "Failed to read local assignment server port from shared memory"
<< "- will send assignment request to previous assignment server socket.";
} }
} }
nodeList->sendAssignment(_requestAssignment); nodeList->sendAssignment(_requestAssignment);

View file

@ -44,7 +44,6 @@ private:
QPointer<ThreadedAssignment> _currentAssignment; QPointer<ThreadedAssignment> _currentAssignment;
QString _assignmentServerHostname; QString _assignmentServerHostname;
HifiSockAddr _assignmentServerSocket; HifiSockAddr _assignmentServerSocket;
QSharedMemory* _localASPortSharedMem; // memory shared with domain server
QTimer _requestTimer; // timer for requesting and assignment QTimer _requestTimer; // timer for requesting and assignment
QTimer _statsTimerACM; // timer for sending stats to assignment client monitor QTimer _statsTimerACM; // timer for sending stats to assignment client monitor

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

View file

@ -109,6 +109,13 @@ table {
width: 100%; width: 100%;
} }
/* styling for bootstrap-switch toggles */
.checkbox-help {
margin-top: 10px;
}
/* CSS only spinner for AJAX requests */
.spinner { .spinner {
margin: 30px auto 0; margin: 30px auto 0;
width: 70px; width: 70px;

View file

@ -7,7 +7,9 @@
<link href="/css/bootstrap.min.css" rel="stylesheet" media="screen"> <link href="/css/bootstrap.min.css" rel="stylesheet" media="screen">
<link href="/css/style.css" rel="stylesheet" media="screen"> <link href="/css/style.css" rel="stylesheet" media="screen">
<link href="/css/sweetalert.css" rel="stylesheet" media="screen"> <link href="/css/sweetalert.css" rel="stylesheet" media="screen">
<link href="/css/bootstrap-switch.min.css" rel="stylesheet" media="screen">
<link href="/stats/css/json.human.css" rel="stylesheet" media="screen"> <link href="/stats/css/json.human.css" rel="stylesheet" media="screen">
</head> </head>
<body> <body>
<nav class="navbar navbar-default" role="navigation"> <nav class="navbar navbar-default" role="navigation">

View file

@ -99,7 +99,8 @@
<script src='/js/underscore-min.js'></script> <script src='/js/underscore-min.js'></script>
<script src='/js/underscore-keypath.min.js'></script> <script src='/js/underscore-keypath.min.js'></script>
<script src='/js/bootbox.min.js'></script> <script src='/js/bootbox.min.js'></script>
<script src='/js/sweetalert.min.js'></script> <script src='js/bootstrap-switch.min.js'></script>
<script src='/js/settings.js'></script> <script src='js/sweetalert.min.js'></script>
<script src='/js/form2js.min.js'></script> <script src='js/settings.js'></script>
<script src='js/form2js.min.js'></script>
<!--#include virtual="page-end.html"--> <!--#include virtual="page-end.html"-->

File diff suppressed because one or more lines are too long

View file

@ -60,10 +60,15 @@ var viewHelpers = {
if (setting.label) { if (setting.label) {
form_group += "<label class='" + label_class + "'>" + setting.label + "</label>" form_group += "<label class='" + label_class + "'>" + setting.label + "</label>"
} }
form_group += "<div class='checkbox" + (isLocked ? " disabled" : "") + "'>"
form_group += "<label for='" + keypath + "'>" form_group += "<div class='toggle-checkbox-container" + (isLocked ? " disabled" : "") + "'>"
form_group += "<input type='checkbox'" + common_attrs() + (setting_value ? "checked" : "") + (isLocked ? " disabled" : "") + "/>" form_group += "<input type='checkbox'" + common_attrs('toggle-checkbox') + (setting_value ? "checked" : "")
form_group += " " + setting.help + "</label>"; form_group += (isLocked ? " disabled" : "") + "/>"
if (setting.help) {
form_group += "<span class='help-block checkbox-help'>" + setting.help + "</span>";
}
form_group += "</div>" form_group += "</div>"
} else { } else {
input_type = _.has(setting, 'type') ? setting.type : "text" input_type = _.has(setting, 'type') ? setting.type : "text"
@ -201,10 +206,17 @@ $(document).ready(function(){
$('#' + Settings.FORM_ID).on('change', '.' + Settings.TRIGGER_CHANGE_CLASS , function(){ $('#' + Settings.FORM_ID).on('change', '.' + Settings.TRIGGER_CHANGE_CLASS , function(){
// this input was changed, add the changed data attribute to it // this input was changed, add the changed data attribute to it
$(this).attr('data-changed', true) $(this).attr('data-changed', true);
badgeSidebarForDifferences($(this)) badgeSidebarForDifferences($(this));
}) });
$('#' + Settings.FORM_ID).on('switchChange.bootstrapSwitch', 'input.toggle-checkbox', function(){
// this checkbox was changed, add the changed data attribute to it
$(this).attr('data-changed', true);
badgeSidebarForDifferences($(this));
});
$('.advanced-toggle').click(function(){ $('.advanced-toggle').click(function(){
Settings.showAdvanced = !Settings.showAdvanced Settings.showAdvanced = !Settings.showAdvanced
@ -735,6 +747,9 @@ function reloadSettings() {
// call our method to setup the place names table // call our method to setup the place names table
setupPlacesTable(); setupPlacesTable();
// setup any bootstrap switches
$('.toggle-checkbox').bootstrapSwitch();
// add tooltip to locked settings // add tooltip to locked settings
$('label.locked').tooltip({ $('label.locked').tooltip({
placement: 'right', placement: 'right',

View file

@ -44,13 +44,10 @@ int const DomainServer::EXIT_CODE_REBOOT = 234923;
const QString ICE_SERVER_DEFAULT_HOSTNAME = "ice.highfidelity.io"; const QString ICE_SERVER_DEFAULT_HOSTNAME = "ice.highfidelity.io";
const QString ALLOWED_USERS_SETTINGS_KEYPATH = "security.allowed_users";
const QString MAXIMUM_USER_CAPACITY = "security.maximum_user_capacity"; const QString MAXIMUM_USER_CAPACITY = "security.maximum_user_capacity";
const QString ALLOWED_EDITORS_SETTINGS_KEYPATH = "security.allowed_editors"; const QString ALLOWED_EDITORS_SETTINGS_KEYPATH = "security.allowed_editors";
const QString EDITORS_ARE_REZZERS_KEYPATH = "security.editors_are_rezzers"; const QString EDITORS_ARE_REZZERS_KEYPATH = "security.editors_are_rezzers";
DomainServer::DomainServer(int argc, char* argv[]) : DomainServer::DomainServer(int argc, char* argv[]) :
QCoreApplication(argc, argv), QCoreApplication(argc, argv),
_httpManager(DOMAIN_SERVER_HTTP_PORT, QString("%1/resources/web/").arg(QCoreApplication::applicationDirPath()), this), _httpManager(DOMAIN_SERVER_HTTP_PORT, QString("%1/resources/web/").arg(QCoreApplication::applicationDirPath()), this),
@ -776,9 +773,8 @@ bool DomainServer::shouldAllowConnectionFromNode(const QString& username,
const HifiSockAddr& senderSockAddr, const HifiSockAddr& senderSockAddr,
QString& reasonReturn) { QString& reasonReturn) {
const QVariant* allowedUsersVariant = valueForKeyPath(_settingsManager.getSettingsMap(), bool isRestrictingAccess =
ALLOWED_USERS_SETTINGS_KEYPATH); _settingsManager.valueOrDefaultValueForKeyPath(RESTRICTED_ACCESS_SETTINGS_KEYPATH).toBool();
QStringList allowedUsers = allowedUsersVariant ? allowedUsersVariant->toStringList() : QStringList();
// we always let in a user who is sending a packet from our local socket or from the localhost address // we always let in a user who is sending a packet from our local socket or from the localhost address
if (senderSockAddr.getAddress() == DependencyManager::get<LimitedNodeList>()->getLocalSockAddr().getAddress() if (senderSockAddr.getAddress() == DependencyManager::get<LimitedNodeList>()->getLocalSockAddr().getAddress()
@ -786,45 +782,50 @@ bool DomainServer::shouldAllowConnectionFromNode(const QString& username,
return true; return true;
} }
if (allowedUsers.count() > 0) { if (isRestrictingAccess) {
QStringList allowedUsers =
_settingsManager.valueOrDefaultValueForKeyPath(ALLOWED_USERS_SETTINGS_KEYPATH).toStringList();
if (allowedUsers.contains(username, Qt::CaseInsensitive)) { if (allowedUsers.contains(username, Qt::CaseInsensitive)) {
if (verifyUsersKey(username, usernameSignature, reasonReturn)) { if (!verifyUsersKey(username, usernameSignature, reasonReturn)) {
return true; return false;
} }
} else { } else {
qDebug() << "Connect request denied for user" << username << "not in allowed users list."; qDebug() << "Connect request denied for user" << username << "not in allowed users list.";
reasonReturn = "User not on whitelist."; reasonReturn = "User not on whitelist.";
}
return false;
} else {
// we have no allowed user list.
// if this user is in the editors list, exempt them from the max-capacity check return false;
const QVariant* allowedEditorsVariant =
valueForKeyPath(_settingsManager.getSettingsMap(), ALLOWED_EDITORS_SETTINGS_KEYPATH);
QStringList allowedEditors = allowedEditorsVariant ? allowedEditorsVariant->toStringList() : QStringList();
if (allowedEditors.contains(username)) {
if (verifyUsersKey(username, usernameSignature, reasonReturn)) {
return true;
}
} }
// if we haven't reached max-capacity, let them in.
const QVariant* maximumUserCapacityVariant = valueForKeyPath(_settingsManager.getSettingsMap(), MAXIMUM_USER_CAPACITY);
unsigned int maximumUserCapacity = maximumUserCapacityVariant ? maximumUserCapacityVariant->toUInt() : 0;
if (maximumUserCapacity > 0) {
unsigned int connectedUsers = countConnectedUsers();
if (connectedUsers >= maximumUserCapacity) {
// too many users, deny the new connection.
qDebug() << connectedUsers << "/" << maximumUserCapacity << "users connected, denying new connection.";
reasonReturn = "Too many connected users.";
return false;
}
qDebug() << connectedUsers << "/" << maximumUserCapacity << "users connected, perhaps allowing new connection.";
}
return true;
} }
// either we aren't restricting users, or this user is in the allowed list
// if this user is in the editors list, exempt them from the max-capacity check
const QVariant* allowedEditorsVariant =
valueForKeyPath(_settingsManager.getSettingsMap(), ALLOWED_EDITORS_SETTINGS_KEYPATH);
QStringList allowedEditors = allowedEditorsVariant ? allowedEditorsVariant->toStringList() : QStringList();
if (allowedEditors.contains(username)) {
if (verifyUsersKey(username, usernameSignature, reasonReturn)) {
return true;
}
}
// if we haven't reached max-capacity, let them in.
const QVariant* maximumUserCapacityVariant = valueForKeyPath(_settingsManager.getSettingsMap(), MAXIMUM_USER_CAPACITY);
unsigned int maximumUserCapacity = maximumUserCapacityVariant ? maximumUserCapacityVariant->toUInt() : 0;
if (maximumUserCapacity > 0) {
unsigned int connectedUsers = countConnectedUsers();
if (connectedUsers >= maximumUserCapacity) {
// too many users, deny the new connection.
qDebug() << connectedUsers << "/" << maximumUserCapacity << "users connected, denying new connection.";
reasonReturn = "Too many connected users.";
return false;
}
qDebug() << connectedUsers << "/" << maximumUserCapacity << "users connected, perhaps allowing new connection.";
}
return true;
} }
void DomainServer::preloadAllowedUserPublicKeys() { void DomainServer::preloadAllowedUserPublicKeys() {
@ -1255,10 +1256,8 @@ void DomainServer::sendHeartbeatToDataServer(const QString& networkAddress) {
// add a flag to indicate if this domain uses restricted access - for now that will exclude it from listings // add a flag to indicate if this domain uses restricted access - for now that will exclude it from listings
const QString RESTRICTED_ACCESS_FLAG = "restricted"; const QString RESTRICTED_ACCESS_FLAG = "restricted";
const QVariant* allowedUsersVariant = valueForKeyPath(_settingsManager.getSettingsMap(), domainObject[RESTRICTED_ACCESS_FLAG] =
ALLOWED_USERS_SETTINGS_KEYPATH); _settingsManager.valueOrDefaultValueForKeyPath(RESTRICTED_ACCESS_SETTINGS_KEYPATH).toBool();
QStringList allowedUsers = allowedUsersVariant ? allowedUsersVariant->toStringList() : QStringList();
domainObject[RESTRICTED_ACCESS_FLAG] = (allowedUsers.size() > 0);
// add the number of currently connected agent users // add the number of currently connected agent users
int numConnectedAuthedUsers = 0; int numConnectedAuthedUsers = 0;

View file

@ -14,6 +14,7 @@
#include <QtCore/QFile> #include <QtCore/QFile>
#include <QtCore/QJsonArray> #include <QtCore/QJsonArray>
#include <QtCore/QJsonObject> #include <QtCore/QJsonObject>
#include <QtCore/QSettings>
#include <QtCore/QStandardPaths> #include <QtCore/QStandardPaths>
#include <QtCore/QUrl> #include <QtCore/QUrl>
#include <QtCore/QUrlQuery> #include <QtCore/QUrlQuery>
@ -42,33 +43,73 @@ DomainServerSettingsManager::DomainServerSettingsManager() :
QFile descriptionFile(QCoreApplication::applicationDirPath() + SETTINGS_DESCRIPTION_RELATIVE_PATH); QFile descriptionFile(QCoreApplication::applicationDirPath() + SETTINGS_DESCRIPTION_RELATIVE_PATH);
descriptionFile.open(QIODevice::ReadOnly); descriptionFile.open(QIODevice::ReadOnly);
_descriptionArray = QJsonDocument::fromJson(descriptionFile.readAll()).array(); QJsonDocument descriptionDocument = QJsonDocument::fromJson(descriptionFile.readAll());
if (descriptionDocument.isObject()) {
QJsonObject descriptionObject = descriptionDocument.object();
const QString DESCRIPTION_VERSION_KEY = "version";
if (descriptionObject.contains(DESCRIPTION_VERSION_KEY)) {
// read the version from the settings description
_descriptionVersion = descriptionObject[DESCRIPTION_VERSION_KEY].toDouble();
if (descriptionObject.contains(DESCRIPTION_SETTINGS_KEY)) {
_descriptionArray = descriptionDocument.object()[DESCRIPTION_SETTINGS_KEY].toArray();
return;
}
}
}
qCritical() << "Did not find settings decription in JSON at" << SETTINGS_DESCRIPTION_RELATIVE_PATH
<< "- Unable to continue. domain-server will quit.";
QMetaObject::invokeMethod(QCoreApplication::instance(), "quit", Qt::QueuedConnection);
} }
void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList) { void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList) {
_configMap.loadMasterAndUserConfig(argumentList); _configMap.loadMasterAndUserConfig(argumentList);
// for now we perform a temporary transition from http-username and http-password to http_username and http_password // What settings version were we before and what are we using now?
const QVariant* oldUsername = valueForKeyPath(_configMap.getUserConfig(), "security.http-username"); // Do we need to do any re-mapping?
const QVariant* oldPassword = valueForKeyPath(_configMap.getUserConfig(), "security.http-password"); QSettings appSettings;
const QString JSON_SETTINGS_VERSION_KEY = "json-settings/version";
double oldVersion = appSettings.value(JSON_SETTINGS_VERSION_KEY, 0.0).toDouble();
if (oldUsername || oldPassword) { if (oldVersion != _descriptionVersion) {
QVariantMap& settingsMap = *reinterpret_cast<QVariantMap*>(_configMap.getUserConfig()["security"].data()); qDebug() << "Previous domain-server settings version was"
<< QString::number(oldVersion, 'g', 8) << "and the new version is"
<< QString::number(_descriptionVersion, 'g', 8) << "- checking if any re-mapping is required";
// remove old keys, move to new format // we have a version mismatch - for now handle custom behaviour here since there are not many remappings
if (oldUsername) { if (oldVersion < 1.0) {
settingsMap["http_username"] = oldUsername->toString(); // This was prior to the introduction of security.restricted_access
settingsMap.remove("http-username"); // If the user has a list of allowed users then set their value for security.restricted_access to true
QVariant* allowedUsers = valueForKeyPath(_configMap.getMergedConfig(), ALLOWED_USERS_SETTINGS_KEYPATH);
if (allowedUsers
&& allowedUsers->canConvert(QMetaType::QVariantList)
&& reinterpret_cast<QVariantList*>(allowedUsers)->size() > 0) {
qDebug() << "Forcing security.restricted_access to TRUE since there was an"
<< "existing list of allowed users.";
// In the pre-toggle system the user had a list of allowed users, so
// we need to set security.restricted_access to true
QVariant* restrictedAccess = valueForKeyPath(_configMap.getUserConfig(),
RESTRICTED_ACCESS_SETTINGS_KEYPATH,
true);
*restrictedAccess = QVariant(true);
// write the new settings to the json file
persistToFile();
}
} }
if (oldPassword) {
settingsMap["http_password"] = oldPassword->toString();
settingsMap.remove("http-password");
}
// save the updated settings
persistToFile();
} }
// write the current description version to our settings
appSettings.setValue(JSON_SETTINGS_VERSION_KEY, _descriptionVersion);
} }
QVariant DomainServerSettingsManager::valueOrDefaultValueForKeyPath(const QString &keyPath) { QVariant DomainServerSettingsManager::valueOrDefaultValueForKeyPath(const QString &keyPath) {

View file

@ -23,6 +23,9 @@ const QString SETTINGS_PATHS_KEY = "paths";
const QString SETTINGS_PATH = "/settings"; const QString SETTINGS_PATH = "/settings";
const QString SETTINGS_PATH_JSON = SETTINGS_PATH + ".json"; const QString SETTINGS_PATH_JSON = SETTINGS_PATH + ".json";
const QString ALLOWED_USERS_SETTINGS_KEYPATH = "security.allowed_users";
const QString RESTRICTED_ACCESS_SETTINGS_KEYPATH = "security.restricted_access";
class DomainServerSettingsManager : public QObject { class DomainServerSettingsManager : public QObject {
Q_OBJECT Q_OBJECT
public: public:
@ -44,6 +47,7 @@ private:
QJsonObject settingDescriptionFromGroup(const QJsonObject& groupObject, const QString& settingName); QJsonObject settingDescriptionFromGroup(const QJsonObject& groupObject, const QString& settingName);
void persistToFile(); void persistToFile();
double _descriptionVersion;
QJsonArray _descriptionArray; QJsonArray _descriptionArray;
HifiConfigVariantMap _configMap; HifiConfigVariantMap _configMap;
}; };

View file

@ -762,22 +762,15 @@ void LimitedNodeList::putLocalPortIntoSharedMemory(const QString key, QObject* p
} }
bool LimitedNodeList::getLocalServerPortFromSharedMemory(const QString key, QSharedMemory*& sharedMem, bool LimitedNodeList::getLocalServerPortFromSharedMemory(const QString key, quint16& localPort) {
quint16& localPort) { QSharedMemory sharedMem(key);
if (!sharedMem) { if (!sharedMem.attach(QSharedMemory::ReadOnly)) {
sharedMem = new QSharedMemory(key, this); qWarning() << "Could not attach to shared memory at key" << key;
return false;
if (!sharedMem->attach(QSharedMemory::ReadOnly)) { } else {
qWarning() << "Could not attach to shared memory at key" << key; sharedMem.lock();
} memcpy(&localPort, sharedMem.data(), sizeof(localPort));
} sharedMem.unlock();
if (sharedMem->isAttached()) {
sharedMem->lock();
memcpy(&localPort, sharedMem->data(), sizeof(localPort));
sharedMem->unlock();
return true; return true;
} }
return false;
} }

View file

@ -200,7 +200,7 @@ public:
} }
void putLocalPortIntoSharedMemory(const QString key, QObject* parent, quint16 localPort); void putLocalPortIntoSharedMemory(const QString key, QObject* parent, quint16 localPort);
bool getLocalServerPortFromSharedMemory(const QString key, QSharedMemory*& sharedMem, quint16& localPort); bool getLocalServerPortFromSharedMemory(const QString key, quint16& localPort);
public slots: public slots:
void reset(); void reset();

View file

@ -335,12 +335,8 @@ void NodeList::sendDomainServerCheckIn() {
if (_domainHandler.getSockAddr().getAddress() == QHostAddress::LocalHost if (_domainHandler.getSockAddr().getAddress() == QHostAddress::LocalHost
|| _domainHandler.getHostname() == "localhost") { || _domainHandler.getHostname() == "localhost") {
static QSharedMemory* localDSPortSharedMem = NULL;
quint16 domainPort = DEFAULT_DOMAIN_SERVER_PORT; quint16 domainPort = DEFAULT_DOMAIN_SERVER_PORT;
getLocalServerPortFromSharedMemory(DOMAIN_SERVER_LOCAL_PORT_SMEM_KEY, getLocalServerPortFromSharedMemory(DOMAIN_SERVER_LOCAL_PORT_SMEM_KEY, domainPort);
localDSPortSharedMem,
domainPort);
qCDebug(networking) << "Local domain-server port read from shared memory (or default) is" << domainPort; qCDebug(networking) << "Local domain-server port read from shared memory (or default) is" << domainPort;
_domainHandler.setPort(domainPort); _domainHandler.setPort(domainPort);
} }

View file

@ -159,16 +159,17 @@ void HifiConfigVariantMap::addMissingValuesToExistingMap(QVariantMap& existingMa
} }
} }
QVariant* valueForKeyPath(QVariantMap& variantMap, const QString& keyPath) { QVariant* valueForKeyPath(QVariantMap& variantMap, const QString& keyPath, bool shouldCreateIfMissing) {
int dotIndex = keyPath.indexOf('.'); int dotIndex = keyPath.indexOf('.');
QString firstKey = (dotIndex == -1) ? keyPath : keyPath.mid(0, dotIndex); QString firstKey = (dotIndex == -1) ? keyPath : keyPath.mid(0, dotIndex);
if (variantMap.contains(firstKey)) { if (shouldCreateIfMissing || variantMap.contains(firstKey)) {
if (dotIndex == -1) { if (dotIndex == -1) {
return &variantMap[firstKey]; return &variantMap[firstKey];
} else if (variantMap[firstKey].canConvert(QMetaType::QVariantMap)) { } else if (variantMap[firstKey].canConvert(QMetaType::QVariantMap)) {
return valueForKeyPath(*static_cast<QVariantMap*>(variantMap[firstKey].data()), keyPath.mid(dotIndex + 1)); return valueForKeyPath(*static_cast<QVariantMap*>(variantMap[firstKey].data()), keyPath.mid(dotIndex + 1),
shouldCreateIfMissing);
} }
} }

View file

@ -37,6 +37,6 @@ private:
void addMissingValuesToExistingMap(QVariantMap& existingMap, const QVariantMap& newMap); void addMissingValuesToExistingMap(QVariantMap& existingMap, const QVariantMap& newMap);
}; };
QVariant* valueForKeyPath(QVariantMap& variantMap, const QString& keyPath); QVariant* valueForKeyPath(QVariantMap& variantMap, const QString& keyPath, bool shouldCreateIfMissing = false);
#endif // hifi_HifiConfigVariantMap_h #endif // hifi_HifiConfigVariantMap_h