" // Data
})
-
+
if (!isLocked) {
if (setting.can_order) {
html += "
"
}
-
+
// populate rows in the table from existing values
var row_num = 1
-
+
_.each(setting_value, function(row, indexOrName) {
- html += "
"
-
+ html += "
"
+
if (setting.numbered === true) {
html += "
" + row_num + "
"
}
-
+
if (setting.key) {
html += "
" + indexOrName + "
"
}
-
+
_.each(setting.columns, function(col) {
html += "
"
-
+
if (isArray) {
rowIsObject = setting.columns.length > 1
colValue = rowIsObject ? row[col.name] : row
html += colValue
-
+
// for arrays we add a hidden input to this td so that values can be posted appropriately
- html += ""
} else if (row.hasOwnProperty(col.name)) {
- html += row[col.name]
+ html += row[col.name]
}
-
+
html += "
"
})
-
+
if (!isLocked) {
if (setting.can_order) {
html += "
"
}
-
+
html += "
"
-
+
row_num++
})
-
+
// populate inputs in the table for new values
if (!isLocked) {
html += makeTableInputs(setting)
}
html += "
"
-
+
return html;
}
function makeTableInputs(setting) {
var html = "
"
-
+
if (setting.numbered === true) {
html += "
"
}
-
+
if (setting.key) {
html += "
\
\
"
}
-
+
_.each(setting.columns, function(col) {
html += "
\
\
"
})
-
+
if (setting.can_order) {
html += "
"
}
html += "
"
html += "
"
-
+
return html
}
function badgeSidebarForDifferences(changedElement) {
// figure out which group this input is in
var panelParentID = changedElement.closest('.panel').attr('id')
-
- // get a JSON representation of that section
- var panelJSON = form2js(panelParentID, ".", false, cleanupFormValues, true)[panelParentID]
- var initialPanelJSON = Settings.initialValues[panelParentID]
-
+
+ // if the panel contains non-grouped settings, the initial value is Settings.initialValues
+ var isGrouped = $(panelParentID).hasClass('grouped');
+
+ if (isGrouped) {
+ var initialPanelJSON = Settings.initialValues[panelParentID];
+
+ // get a JSON representation of that section
+ var panelJSON = form2js(panelParentID, ".", false, cleanupFormValues, true)[panelParentID];
+ } else {
+ var initialPanelJSON = Settings.initialValues;
+
+ // get a JSON representation of that section
+ var panelJSON = form2js(panelParentID, ".", false, cleanupFormValues, true);
+ }
+
var badgeValue = 0
-
+
// badge for any settings we have that are not the same or are not present in initialValues
for (var setting in panelJSON) {
- if ((!_.has(initialPanelJSON, setting) && panelJSON[setting] !== "") ||
- (!_.isEqual(panelJSON[setting], initialPanelJSON[setting])
+ if ((!_.has(initialPanelJSON, setting) && panelJSON[setting] !== "") ||
+ (!_.isEqual(panelJSON[setting], initialPanelJSON[setting])
&& (panelJSON[setting] !== "" || _.has(initialPanelJSON, setting)))) {
badgeValue += 1
}
}
-
+
// update the list-group-item badge to have the new value
if (badgeValue == 0) {
badgeValue = ""
}
-
+
$("a[href='#" + panelParentID + "'] .badge").html(badgeValue);
}
function addTableRow(add_glyphicon) {
var row = $(add_glyphicon).closest('tr')
-
+
var table = row.parents('table')
var isArray = table.data('setting-type') === 'array'
-
+
var columns = row.parent().children('.' + Settings.DATA_ROW_CLASS)
-
+
if (!isArray) {
// Check key spaces
var key = row.children(".key").children("input").val()
@@ -433,7 +449,7 @@ function addTableRow(add_glyphicon) {
return
}
}
-
+
// Check empty fields
var empty = false;
_.each(row.children('.' + Settings.DATA_COL_CLASS + ' input'), function(element) {
@@ -442,23 +458,23 @@ function addTableRow(add_glyphicon) {
return
}
})
-
+
if (empty) {
showErrorMessage("Error", "Empty field(s)")
return
}
-
+
var input_clone = row.clone()
-
+
// Change input row to data row
var table = row.parents("table")
- var setting_name = table.attr("name")
+ var setting_name = table.attr("name")
var full_name = setting_name + "." + key
row.addClass(Settings.DATA_ROW_CLASS + " " + Settings.NEW_ROW_CLASS)
row.removeClass("inputs")
-
+
_.each(row.children(), function(element) {
- if ($(element).hasClass("numbered")) {
+ if ($(element).hasClass("numbered")) {
// Index row
var numbers = columns.children(".numbered")
if (numbers.length > 0) {
@@ -478,88 +494,88 @@ function addTableRow(add_glyphicon) {
var input = $(element).children("input")
$(element).html(input.val())
input.remove()
- } else if ($(element).hasClass(Settings.DATA_COL_CLASS)) {
+ } else if ($(element).hasClass(Settings.DATA_COL_CLASS)) {
// Hide inputs
var input = $(element).children("input")
input.attr("type", "hidden")
-
+
if (isArray) {
var row_index = row.siblings('.' + Settings.DATA_ROW_CLASS).length
var key = $(element).attr('name')
-
+
// are there multiple columns or just one?
// with multiple we have an array of Objects, with one we have an array of whatever the value type is
var num_columns = row.children('.' + Settings.DATA_COL_CLASS).length
- input.attr("name", setting_name + "[" + row_index + "]" + (num_columns > 1 ? "." + key : ""))
+ input.attr("name", setting_name + "[" + row_index + "]" + (num_columns > 1 ? "." + key : ""))
} else {
input.attr("name", full_name + "." + $(element).attr("name"))
}
-
+
input.attr("data-changed", "true")
-
+
$(element).append(input.val())
} else {
console.log("Unknown table element")
}
})
-
+
input_clone.find('input').each(function(){
$(this).val($(this).attr('data-default'));
});
-
+
if (isArray) {
updateDataChangedForSiblingRows(row, true)
-
+
// the addition of any table row should remove the empty-array-row
row.siblings('.empty-array-row').remove()
}
-
+
badgeSidebarForDifferences($(table))
-
+
row.parent().append(input_clone)
}
function deleteTableRow(delete_glyphicon) {
var row = $(delete_glyphicon).closest('tr')
-
+
var table = $(row).closest('table')
var isArray = table.data('setting-type') === 'array'
-
+
row.empty();
-
+
if (!isArray) {
- row.html("");
} else {
if (table.find('.' + Settings.DATA_ROW_CLASS).length > 1) {
updateDataChangedForSiblingRows(row)
-
+
// this isn't the last row - we can just remove it
row.remove()
} else {
// this is the last row, we can't remove it completely since we need to post an empty array
-
+
row.removeClass(Settings.DATA_ROW_CLASS).removeClass(Settings.NEW_ROW_CLASS)
row.addClass('empty-array-row')
-
- row.html("");
}
}
-
+
// we need to fire a change event on one of the remaining inputs so that the sidebar badge is updated
badgeSidebarForDifferences($(table))
}
function moveTableRow(move_glyphicon, move_up) {
var row = $(move_glyphicon).closest('tr')
-
+
var table = $(row).closest('table')
var isArray = table.data('setting-type') === 'array'
if (!isArray) {
return;
}
-
+
if (move_up) {
var prev_row = row.prev()
if (prev_row.hasClass(Settings.DATA_ROW_CLASS)) {
@@ -571,7 +587,7 @@ function moveTableRow(move_glyphicon, move_up) {
next_row.after(row)
}
}
-
+
// we need to fire a change event on one of the remaining inputs so that the sidebar badge is updated
badgeSidebarForDifferences($(table))
}
@@ -579,23 +595,23 @@ function moveTableRow(move_glyphicon, move_up) {
function updateDataChangedForSiblingRows(row, forceTrue) {
// anytime a new row is added to an array we need to set data-changed for all sibling row inputs to true
// unless it matches the inital set of values
-
+
if (!forceTrue) {
// figure out which group this row is in
var panelParentID = row.closest('.panel').attr('id')
// get the short name for the setting from the table
var tableShortName = row.closest('table').data('short-name')
-
+
// get a JSON representation of that section
var panelSettingJSON = form2js(panelParentID, ".", false, cleanupFormValues, true)[panelParentID][tableShortName]
var initialPanelSettingJSON = Settings.initialValues[panelParentID][tableShortName]
-
+
// if they are equal, we don't need data-changed
isTrue = !_.isEqual(panelSettingJSON, initialPanelSettingJSON)
} else {
isTrue = true
}
-
+
row.siblings('.' + Settings.DATA_ROW_CLASS).each(function(){
var hiddenInput = $(this).find('td.' + Settings.DATA_COL_CLASS + ' input')
if (isTrue) {
@@ -611,19 +627,19 @@ function showRestartModal() {
backdrop: 'static',
keyboard: false
});
-
+
var secondsElapsed = 0;
var numberOfSecondsToWait = 3;
-
+
var refreshSpan = $('span#refresh-time')
refreshSpan.html(numberOfSecondsToWait + " seconds");
-
+
// call ourselves every 1 second to countdown
var refreshCountdown = setInterval(function(){
secondsElapsed++;
secondsLeft = numberOfSecondsToWait - secondsElapsed
refreshSpan.html(secondsLeft + (secondsLeft == 1 ? " second" : " seconds"))
-
+
if (secondsElapsed == numberOfSecondsToWait) {
location.reload(true);
clearInterval(refreshCountdown);
@@ -646,22 +662,22 @@ function showErrorMessage(title, message) {
function chooseFromHighFidelityDomains(clickedButton) {
// setup the modal to help user pick their domain
if (Settings.initialValues.metaverse.access_token) {
-
+
// add a spinner to the choose button
clickedButton.html("Loading domains...")
clickedButton.attr('disabled', 'disabled')
-
+
// get a list of user domains from data-web
data_web_domains_url = "https://metaverse.highfidelity.com/api/v1/domains?access_token="
$.getJSON(data_web_domains_url + Settings.initialValues.metaverse.access_token, function(data){
-
+
modal_buttons = {
cancel: {
label: 'Cancel',
className: 'btn-default'
}
}
-
+
if (data.data.domains.length) {
// setup a select box for the returned domains
modal_body = "
Choose the High Fidelity domain you want this domain-server to represent. This will set your domain ID on the settings page.
You do not have any domains in your High Fidelity account." +
+ modal_body = "
You do not have any domains in your High Fidelity account." +
"
Go to your domains page to create a new one. Once your domain is created re-open this dialog to select it.
"
}
-
-
+
+
bootbox.dialog({
title: "Choose matching domain",
message: modal_body,
buttons: modal_buttons
})
-
+
// remove the spinner from the choose button
clickedButton.html("Choose from my domains")
clickedButton.removeAttr('disabled')
})
-
+
} else {
bootbox.alert({
- message: "You must have an access token to query your High Fidelity domains.
" +
+ message: "You must have an access token to query your High Fidelity domains.
" +
"Please follow the instructions on the settings page to add an access token.",
title: "Access token required"
})
diff --git a/domain-server/resources/web/stats/js/underscore-keypath.min.js b/domain-server/resources/web/js/underscore-keypath.min.js
similarity index 100%
rename from domain-server/resources/web/stats/js/underscore-keypath.min.js
rename to domain-server/resources/web/js/underscore-keypath.min.js
diff --git a/domain-server/resources/web/settings/index.shtml b/domain-server/resources/web/settings/index.shtml
index 598f137285..9bbf913b1a 100644
--- a/domain-server/resources/web/settings/index.shtml
+++ b/domain-server/resources/web/settings/index.shtml
@@ -5,31 +5,32 @@
-
+
-
+
-
+
-
+
-
+
@@ -83,8 +92,9 @@
+
-
\ No newline at end of file
+
diff --git a/domain-server/resources/web/stats/index.shtml b/domain-server/resources/web/stats/index.shtml
index dc7fe9678f..13967d4e36 100644
--- a/domain-server/resources/web/stats/index.shtml
+++ b/domain-server/resources/web/stats/index.shtml
@@ -9,6 +9,6 @@
-
+
diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp
index b20b100789..0b42f78fe2 100644
--- a/domain-server/src/DomainServer.cpp
+++ b/domain-server/src/DomainServer.cpp
@@ -75,35 +75,35 @@ DomainServer::DomainServer(int argc, char* argv[]) :
setOrganizationDomain("highfidelity.io");
setApplicationName("domain-server");
QSettings::setDefaultFormat(QSettings::IniFormat);
-
+
// make sure we have a fresh AccountManager instance
// (need this since domain-server can restart itself and maintain static variables)
AccountManager::getInstance(true);
-
+
_settingsManager.setupConfigMap(arguments());
-
+
// setup a shutdown event listener to handle SIGTERM or WM_CLOSE for us
#ifdef _WIN32
installNativeEventFilter(&ShutdownEventListener::getInstance());
#else
ShutdownEventListener::getInstance();
#endif
-
+
qRegisterMetaType("DomainServerWebSessionData");
qRegisterMetaTypeStreamOperators("DomainServerWebSessionData");
-
+
if (optionallyReadX509KeyAndCertificate() && optionallySetupOAuth() && optionallySetupAssignmentPayment()) {
// we either read a certificate and private key or were not passed one
// and completed login or did not need to
qDebug() << "Setting up LimitedNodeList and assignments.";
setupNodeListAndAssignments();
-
+
loadExistingSessionsFromSettings();
-
+
// setup automatic networking settings with data server
setupAutomaticNetworking();
-
+
// preload some user public keys so they can connect on first request
preloadAllowedUserPublicKeys();
}
@@ -111,7 +111,7 @@ DomainServer::DomainServer(int argc, char* argv[]) :
void DomainServer::restart() {
qDebug() << "domain-server is restarting.";
-
+
exit(DomainServer::EXIT_CODE_REBOOT);
}
@@ -182,16 +182,16 @@ bool DomainServer::optionallySetupOAuth() {
const QVariantMap& settingsMap = _settingsManager.getSettingsMap();
_oauthProviderURL = QUrl(settingsMap.value(OAUTH_PROVIDER_URL_OPTION).toString());
-
+
// if we don't have an oauth provider URL then we default to the default node auth url
if (_oauthProviderURL.isEmpty()) {
_oauthProviderURL = NetworkingConstants::METAVERSE_SERVER_URL;
}
-
+
AccountManager& accountManager = AccountManager::getInstance();
accountManager.disableSettingsFilePersistence();
accountManager.setAuthURL(_oauthProviderURL);
-
+
_oauthClientID = settingsMap.value(OAUTH_CLIENT_ID_OPTION).toString();
_oauthClientSecret = QProcessEnvironment::systemEnvironment().value(OAUTH_CLIENT_SECRET_ENV);
_hostname = settingsMap.value(REDIRECT_HOSTNAME_OPTION).toString();
@@ -226,7 +226,7 @@ void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) {
QVariant localPortValue = _settingsManager.valueOrDefaultValueForKeyPath(CUSTOM_LOCAL_PORT_OPTION);
unsigned short domainServerPort = (unsigned short) localPortValue.toUInt();
-
+
QVariantMap& settingsMap = _settingsManager.getSettingsMap();
unsigned short domainServerDTLSPort = 0;
@@ -245,12 +245,12 @@ void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) {
parseAssignmentConfigs(parsedTypes);
populateDefaultStaticAssignmentsExcludingTypes(parsedTypes);
-
+
// check for scripts the user wants to persist from their domain-server config
populateStaticScriptedAssignmentsFromSettings();
auto nodeList = DependencyManager::set(domainServerPort, domainServerDTLSPort);
-
+
// no matter the local port, save it to shared mem so that local assignment clients can ask what it is
nodeList->putLocalPortIntoSharedMemory(DOMAIN_SERVER_LOCAL_PORT_SMEM_KEY, this, nodeList->getNodeSocket().localPort());
@@ -260,7 +260,7 @@ void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) {
quint16 localHttpsPort = DOMAIN_SERVER_HTTPS_PORT;
nodeList->putLocalPortIntoSharedMemory(DOMAIN_SERVER_LOCAL_HTTPS_PORT_SMEM_KEY, this, localHttpsPort);
-
+
// set our LimitedNodeList UUID to match the UUID from our config
// nodes will currently use this to add resources to data-web that relate to our domain
const QString METAVERSE_DOMAIN_ID_KEY_PATH = "metaverse.id";
@@ -280,22 +280,22 @@ void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) {
bool DomainServer::didSetupAccountManagerWithAccessToken() {
AccountManager& accountManager = AccountManager::getInstance();
-
+
if (accountManager.hasValidAccessToken()) {
// we already gave the account manager a valid access token
return true;
}
-
+
if (!_oauthProviderURL.isEmpty()) {
// check for an access-token in our settings, can optionally be overidden by env value
const QString ACCESS_TOKEN_KEY_PATH = "metaverse.access_token";
const QString ENV_ACCESS_TOKEN_KEY = "DOMAIN_SERVER_ACCESS_TOKEN";
-
+
QString accessToken = QProcessEnvironment::systemEnvironment().value(ENV_ACCESS_TOKEN_KEY);
-
+
if (accessToken.isEmpty()) {
const QVariant* accessTokenVariant = valueForKeyPath(_settingsManager.getSettingsMap(), ACCESS_TOKEN_KEY_PATH);
-
+
if (accessTokenVariant && accessTokenVariant->canConvert(QMetaType::QString)) {
accessToken = accessTokenVariant->toString();
} else {
@@ -308,17 +308,17 @@ bool DomainServer::didSetupAccountManagerWithAccessToken() {
qDebug() << "Using access token from DOMAIN_SERVER_ACCESS_TOKEN in env. This overrides any access token present"
<< " in the user or master config.";
}
-
+
// give this access token to the AccountManager
accountManager.setAccessTokenForCurrentAuthURL(accessToken);
-
+
return true;
} else {
qDebug() << "Missing OAuth provider URL, but a domain-server feature was required that requires authentication." <<
"domain-server will now quit.";
QMetaObject::invokeMethod(this, "quit", Qt::QueuedConnection);
-
+
return false;
}
}
@@ -326,24 +326,24 @@ bool DomainServer::didSetupAccountManagerWithAccessToken() {
bool DomainServer::optionallySetupAssignmentPayment() {
const QString PAY_FOR_ASSIGNMENTS_OPTION = "pay-for-assignments";
const QVariantMap& settingsMap = _settingsManager.getSettingsMap();
-
+
if (settingsMap.contains(PAY_FOR_ASSIGNMENTS_OPTION) &&
settingsMap.value(PAY_FOR_ASSIGNMENTS_OPTION).toBool() &&
didSetupAccountManagerWithAccessToken()) {
-
+
qDebug() << "Assignments will be paid for via" << qPrintable(_oauthProviderURL.toString());
-
+
// assume that the fact we are authing against HF data server means we will pay for assignments
// setup a timer to send transactions to pay assigned nodes every 30 seconds
QTimer* creditSetupTimer = new QTimer(this);
connect(creditSetupTimer, &QTimer::timeout, this, &DomainServer::setupPendingAssignmentCredits);
-
+
const qint64 CREDIT_CHECK_INTERVAL_MSECS = 5 * 1000;
creditSetupTimer->start(CREDIT_CHECK_INTERVAL_MSECS);
-
+
QTimer* nodePaymentTimer = new QTimer(this);
connect(nodePaymentTimer, &QTimer::timeout, this, &DomainServer::sendPendingTransactionsToServer);
-
+
const qint64 TRANSACTION_SEND_INTERVAL_MSECS = 30 * 1000;
nodePaymentTimer->start(TRANSACTION_SEND_INTERVAL_MSECS);
}
@@ -353,63 +353,63 @@ bool DomainServer::optionallySetupAssignmentPayment() {
void DomainServer::setupAutomaticNetworking() {
auto nodeList = DependencyManager::get();
-
+
const int STUN_REFLEXIVE_KEEPALIVE_INTERVAL_MSECS = 10 * 1000;
const int STUN_IP_ADDRESS_CHECK_INTERVAL_MSECS = 30 * 1000;
// setup our timer to check our IP via stun every X seconds
QTimer* dynamicIPTimer = new QTimer(this);
connect(dynamicIPTimer, &QTimer::timeout, this, &DomainServer::requestCurrentPublicSocketViaSTUN);
-
+
_automaticNetworkingSetting =
_settingsManager.valueOrDefaultValueForKeyPath(METAVERSE_AUTOMATIC_NETWORKING_KEY_PATH).toString();
-
+
if (_automaticNetworkingSetting == FULL_AUTOMATIC_NETWORKING_VALUE) {
dynamicIPTimer->start(STUN_REFLEXIVE_KEEPALIVE_INTERVAL_MSECS);
-
+
// setup a timer to heartbeat with the ice-server every so often
QTimer* iceHeartbeatTimer = new QTimer(this);
connect(iceHeartbeatTimer, &QTimer::timeout, this, &DomainServer::performICEUpdates);
iceHeartbeatTimer->start(ICE_HEARBEAT_INTERVAL_MSECS);
-
+
// call our sendHeartbeatToIceServer immediately anytime a local or public socket changes
connect(nodeList.data(), &LimitedNodeList::localSockAddrChanged,
this, &DomainServer::sendHeartbeatToIceServer);
connect(nodeList.data(), &LimitedNodeList::publicSockAddrChanged,
this, &DomainServer::sendHeartbeatToIceServer);
-
+
// attempt to update our public socket now, this will send a heartbeat once we get public socket
requestCurrentPublicSocketViaSTUN();
-
+
// in case the STUN lookup is still happening we should re-request a public socket once we get that address
connect(&nodeList->getSTUNSockAddr(), &HifiSockAddr::lookupCompleted,
this, &DomainServer::requestCurrentPublicSocketViaSTUN);
-
+
}
-
+
if (!didSetupAccountManagerWithAccessToken()) {
qDebug() << "Cannot send heartbeat to data server without an access token.";
qDebug() << "Add an access token to your config file or via the web interface.";
-
+
return;
}
-
+
if (_automaticNetworkingSetting == IP_ONLY_AUTOMATIC_NETWORKING_VALUE ||
_automaticNetworkingSetting == FULL_AUTOMATIC_NETWORKING_VALUE) {
-
+
const QUuid& domainID = nodeList->getSessionUUID();
-
+
if (!domainID.isNull()) {
qDebug() << "domain-server" << _automaticNetworkingSetting << "automatic networking enabled for ID"
<< uuidStringWithoutCurlyBraces(domainID) << "via" << _oauthProviderURL.toString();
-
+
if (_automaticNetworkingSetting == IP_ONLY_AUTOMATIC_NETWORKING_VALUE) {
dynamicIPTimer->start(STUN_IP_ADDRESS_CHECK_INTERVAL_MSECS);
-
+
// send public socket changes to the data server so nodes can find us at our new IP
connect(nodeList.data(), &LimitedNodeList::publicSockAddrChanged,
this, &DomainServer::performIPAddressUpdate);
-
+
// attempt to update our sockets now
requestCurrentPublicSocketViaSTUN();
} else {
@@ -419,18 +419,18 @@ void DomainServer::setupAutomaticNetworking() {
} else {
qDebug() << "Cannot enable domain-server automatic networking without a domain ID."
<< "Please add an ID to your config file or via the web interface.";
-
+
return;
}
} else {
sendHeartbeatToDataServer();
}
-
+
qDebug() << "Updating automatic networking setting in domain-server to" << _automaticNetworkingSetting;
-
+
// no matter the auto networking settings we should heartbeat to the data-server every 15s
const int DOMAIN_SERVER_DATA_WEB_HEARTBEAT_MSECS = 15 * 1000;
-
+
QTimer* dataHeartbeatTimer = new QTimer(this);
connect(dataHeartbeatTimer, SIGNAL(timeout()), this, SLOT(sendHeartbeatToDataServer()));
dataHeartbeatTimer->start(DOMAIN_SERVER_DATA_WEB_HEARTBEAT_MSECS);
@@ -445,7 +445,7 @@ void DomainServer::parseAssignmentConfigs(QSet& excludedTypes)
// check for configs from the command line, these take precedence
const QString ASSIGNMENT_CONFIG_REGEX_STRING = "config-([\\d]+)";
QRegExp assignmentConfigRegex(ASSIGNMENT_CONFIG_REGEX_STRING);
-
+
const QVariantMap& settingsMap = _settingsManager.getSettingsMap();
// scan for assignment config keys
@@ -480,26 +480,26 @@ void DomainServer::addStaticAssignmentToAssignmentHash(Assignment* newAssignment
void DomainServer::populateStaticScriptedAssignmentsFromSettings() {
const QString PERSISTENT_SCRIPTS_KEY_PATH = "scripts.persistent_scripts";
const QVariant* persistentScriptsVariant = valueForKeyPath(_settingsManager.getSettingsMap(), PERSISTENT_SCRIPTS_KEY_PATH);
-
+
if (persistentScriptsVariant) {
QVariantList persistentScriptsList = persistentScriptsVariant->toList();
foreach(const QVariant& persistentScriptVariant, persistentScriptsList) {
QVariantMap persistentScript = persistentScriptVariant.toMap();
-
+
const QString PERSISTENT_SCRIPT_URL_KEY = "url";
const QString PERSISTENT_SCRIPT_NUM_INSTANCES_KEY = "num_instances";
const QString PERSISTENT_SCRIPT_POOL_KEY = "pool";
-
+
if (persistentScript.contains(PERSISTENT_SCRIPT_URL_KEY)) {
// check how many instances of this script to add
-
+
int numInstances = persistentScript[PERSISTENT_SCRIPT_NUM_INSTANCES_KEY].toInt();
QString scriptURL = persistentScript[PERSISTENT_SCRIPT_URL_KEY].toString();
-
+
QString scriptPool = persistentScript.value(PERSISTENT_SCRIPT_POOL_KEY).toString();
-
+
qDebug() << "Adding" << numInstances << "of persistent script at URL" << scriptURL << "- pool" << scriptPool;
-
+
for (int i = 0; i < numInstances; ++i) {
// add a scripted assignment to the queue for this instance
Assignment* scriptAssignment = new Assignment(Assignment::CreateCommand,
@@ -544,7 +544,7 @@ void DomainServer::createStaticAssignmentsForType(Assignment::Type type, const Q
QString dashes = payloadKey.size() == 1 ? "-" : "--";
payloadStringList << QString("%1%2 %3").arg(dashes).arg(payloadKey).arg(configMap[payloadKey].toString());
}
-
+
configAssignment->setPayload(payloadStringList.join(' ').toUtf8());
addStaticAssignmentToAssignmentHash(configAssignment);
@@ -557,7 +557,7 @@ void DomainServer::populateDefaultStaticAssignmentsExcludingTypes(const QSet(static_cast(defaultedType) + 1)) {
- if (!excludedTypes.contains(defaultedType)
+ if (!excludedTypes.contains(defaultedType)
&& defaultedType != Assignment::UNUSED_0
&& defaultedType != Assignment::UNUSED_1
&& defaultedType != Assignment::UNUSED_2
@@ -577,7 +577,7 @@ void DomainServer::handleConnectRequest(const QByteArray& packet, const HifiSock
NodeType_t nodeType;
HifiSockAddr publicSockAddr, localSockAddr;
-
+
QDataStream packetStream(packet);
packetStream.skipRawData(numBytesForPacketHeader(packet));
@@ -613,11 +613,11 @@ void DomainServer::handleConnectRequest(const QByteArray& packet, const HifiSock
}
}
-
+
QList nodeInterestList;
QString username;
QByteArray usernameSignature;
-
+
packetStream >> nodeInterestList >> username >> usernameSignature;
auto limitedNodeList = DependencyManager::get();
@@ -630,14 +630,14 @@ void DomainServer::handleConnectRequest(const QByteArray& packet, const HifiSock
out << reason;
// tell client it has been refused.
DependencyManager::get()->writeUnverifiedDatagram(connectionDeniedByteArray, senderSockAddr);
-
+
return;
}
if ((!isAssignment && !STATICALLY_ASSIGNED_NODES.contains(nodeType))
|| (isAssignment && matchingQueuedAssignment)) {
// this was either not a static assignment or it was and we had a matching one in the queue
-
+
QUuid nodeUUID;
if (_connectingICEPeers.contains(packetUUID) || _connectedICEPeers.contains(packetUUID)) {
@@ -656,7 +656,7 @@ void DomainServer::handleConnectRequest(const QByteArray& packet, const HifiSock
const QVariant* editorsAreRezzersVariant =
valueForKeyPath(_settingsManager.getSettingsMap(), EDITORS_ARE_REZZERS_KEYPATH);
-
+
bool onlyEditorsAreRezzers = false;
if (editorsAreRezzersVariant) {
onlyEditorsAreRezzers = editorsAreRezzersVariant->toBool();
@@ -691,10 +691,10 @@ void DomainServer::handleConnectRequest(const QByteArray& packet, const HifiSock
nodeData->setUsername(username);
// also add an interpolation to JSONBreakableMarshal so that servers can get username in stats
- JSONBreakableMarshal::addInterpolationForKey(USERNAME_UUID_REPLACEMENT_STATS_KEY,
+ JSONBreakableMarshal::addInterpolationForKey(USERNAME_UUID_REPLACEMENT_STATS_KEY,
uuidStringWithoutCurlyBraces(nodeUUID), username);
}
-
+
nodeData->setSendingSockAddr(senderSockAddr);
// reply back to the user with a PacketTypeDomainList
@@ -719,16 +719,16 @@ bool DomainServer::verifyUsersKey (const QString& username,
const QByteArray& usernameSignature,
QString& reasonReturn) {
// it's possible this user can be allowed to connect, but we need to check their username signature
-
+
QByteArray publicKeyArray = _userPublicKeys.value(username);
if (!publicKeyArray.isEmpty()) {
// if we do have a public key for the user, check for a signature match
-
+
const unsigned char* publicKeyData = reinterpret_cast(publicKeyArray.constData());
-
+
// first load up the public key into an RSA struct
RSA* rsaPublicKey = d2i_RSA_PUBKEY(NULL, &publicKeyData, publicKeyArray.size());
-
+
if (rsaPublicKey) {
QByteArray decryptedArray(RSA_size(rsaPublicKey), 0);
int decryptResult =
@@ -736,14 +736,14 @@ bool DomainServer::verifyUsersKey (const QString& username,
reinterpret_cast(usernameSignature.constData()),
reinterpret_cast(decryptedArray.data()),
rsaPublicKey, RSA_PKCS1_PADDING);
-
+
if (decryptResult != -1) {
if (username.toLower() == decryptedArray) {
qDebug() << "Username signature matches for" << username << "- allowing connection.";
-
+
// free up the public key before we return
RSA_free(rsaPublicKey);
-
+
return true;
} else {
qDebug() << "Username signature did not match for" << username << "- denying connection.";
@@ -753,7 +753,7 @@ bool DomainServer::verifyUsersKey (const QString& username,
qDebug() << "Couldn't decrypt user signature for" << username << "- denying connection.";
reasonReturn = "Couldn't decrypt user signature.";
}
-
+
// free up the public key, we don't need it anymore
RSA_free(rsaPublicKey);
} else {
@@ -776,13 +776,13 @@ bool DomainServer::shouldAllowConnectionFromNode(const QString& username,
const QVariant* allowedUsersVariant = valueForKeyPath(_settingsManager.getSettingsMap(),
ALLOWED_USERS_SETTINGS_KEYPATH);
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
if (senderSockAddr.getAddress() == DependencyManager::get()->getLocalSockAddr().getAddress()
|| senderSockAddr.getAddress() == QHostAddress::LocalHost) {
return true;
}
-
+
if (allowedUsers.count() > 0) {
if (allowedUsers.contains(username, Qt::CaseInsensitive)) {
if (verifyUsersKey(username, usernameSignature, reasonReturn)) {
@@ -827,7 +827,7 @@ bool DomainServer::shouldAllowConnectionFromNode(const QString& username,
void DomainServer::preloadAllowedUserPublicKeys() {
const QVariant* allowedUsersVariant = valueForKeyPath(_settingsManager.getSettingsMap(), ALLOWED_USERS_SETTINGS_KEYPATH);
QStringList allowedUsers = allowedUsersVariant ? allowedUsersVariant->toStringList() : QStringList();
-
+
if (allowedUsers.size() > 0) {
// in the future we may need to limit how many requests here - for now assume that lists of allowed users are not
// going to create > 100 requests
@@ -842,11 +842,11 @@ void DomainServer::requestUserPublicKey(const QString& username) {
JSONCallbackParameters callbackParams;
callbackParams.jsonCallbackReceiver = this;
callbackParams.jsonCallbackMethod = "publicKeyJSONCallback";
-
+
const QString USER_PUBLIC_KEY_PATH = "api/v1/users/%1/public_key";
-
+
qDebug() << "Requesting public key for user" << username;
-
+
AccountManager::getInstance().sendRequest(USER_PUBLIC_KEY_PATH.arg(username),
AccountManagerAuth::None,
QNetworkAccessManager::GetOperation, callbackParams);
@@ -940,7 +940,7 @@ void DomainServer::sendDomainListToNode(const SharedNodePointer& node, const Hif
int numBroadcastPacketLeadBytes = broadcastDataStream.device()->pos();
DomainServerNodeData* nodeData = reinterpret_cast(node->getLinkedData());
-
+
// if we've established a connection via ICE with this peer, use that socket
// otherwise just try to reply back to them on their sending socket (although that may not work)
HifiSockAddr destinationSockAddr = _connectedICEPeers.value(node->getUUID());
@@ -959,40 +959,40 @@ void DomainServer::sendDomainListToNode(const SharedNodePointer& node, const Hif
// reset our nodeByteArray and nodeDataStream
QByteArray nodeByteArray;
QDataStream nodeDataStream(&nodeByteArray, QIODevice::Append);
-
+
if (otherNode->getUUID() != node->getUUID() && nodeInterestList.contains(otherNode->getType())) {
-
+
// don't send avatar nodes to other avatars, that will come from avatar mixer
nodeDataStream << *otherNode.data();
-
+
// pack the secret that these two nodes will use to communicate with each other
QUuid secretUUID = nodeData->getSessionSecretHash().value(otherNode->getUUID());
if (secretUUID.isNull()) {
// generate a new secret UUID these two nodes can use
secretUUID = QUuid::createUuid();
-
+
// set that on the current Node's sessionSecretHash
nodeData->getSessionSecretHash().insert(otherNode->getUUID(), secretUUID);
-
+
// set it on the other Node's sessionSecretHash
reinterpret_cast(otherNode->getLinkedData())
->getSessionSecretHash().insert(node->getUUID(), secretUUID);
-
+
}
-
+
nodeDataStream << secretUUID;
-
+
if (broadcastPacket.size() + nodeByteArray.size() > dataMTU) {
// we need to break here and start a new packet
// so send the current one
-
+
limitedNodeList->writeUnverifiedDatagram(broadcastPacket, node, senderSockAddr);
-
+
// reset the broadcastPacket structure
broadcastPacket.resize(numBroadcastPacketLeadBytes);
broadcastDataStream.device()->seek(numBroadcastPacketLeadBytes);
}
-
+
// append the nodeByteArray to the current state of broadcastDataStream
broadcastPacket.append(nodeByteArray);
}
@@ -1099,12 +1099,12 @@ void DomainServer::setupPendingAssignmentCredits() {
// enumerate the NodeList to find the assigned nodes
DependencyManager::get()->eachNode([&](const SharedNodePointer& node){
DomainServerNodeData* nodeData = reinterpret_cast(node->getLinkedData());
-
+
if (!nodeData->getAssignmentUUID().isNull() && !nodeData->getWalletUUID().isNull()) {
// check if we have a non-finalized transaction for this node to add this amount to
TransactionHash::iterator i = _pendingAssignmentCredits.find(nodeData->getWalletUUID());
WalletTransaction* existingTransaction = NULL;
-
+
while (i != _pendingAssignmentCredits.end() && i.key() == nodeData->getWalletUUID()) {
if (!i.value()->isFinalized()) {
existingTransaction = i.value();
@@ -1113,16 +1113,16 @@ void DomainServer::setupPendingAssignmentCredits() {
++i;
}
}
-
+
qint64 elapsedMsecsSinceLastPayment = nodeData->getPaymentIntervalTimer().elapsed();
nodeData->getPaymentIntervalTimer().restart();
-
+
const float CREDITS_PER_HOUR = 0.10f;
const float CREDITS_PER_MSEC = CREDITS_PER_HOUR / (60 * 60 * 1000);
const int SATOSHIS_PER_MSEC = CREDITS_PER_MSEC * SATOSHIS_PER_CREDIT;
-
+
float pendingCredits = elapsedMsecsSinceLastPayment * SATOSHIS_PER_MSEC;
-
+
if (existingTransaction) {
existingTransaction->incrementAmount(pendingCredits);
} else {
@@ -1164,22 +1164,22 @@ void DomainServer::sendPendingTransactionsToServer() {
void DomainServer::publicKeyJSONCallback(QNetworkReply& requestReply) {
QJsonObject jsonObject = QJsonDocument::fromJson(requestReply.readAll()).object();
-
+
if (jsonObject["status"].toString() == "success") {
// figure out which user this is for
-
+
const QString PUBLIC_KEY_URL_REGEX_STRING = "api\\/v1\\/users\\/([A-Za-z0-9_\\.]+)\\/public_key";
QRegExp usernameRegex(PUBLIC_KEY_URL_REGEX_STRING);
-
+
if (usernameRegex.indexIn(requestReply.url().toString()) != -1) {
QString username = usernameRegex.cap(1);
-
+
qDebug() << "Storing a public key for user" << username;
-
+
// pull the public key as a QByteArray from this response
const QString JSON_DATA_KEY = "data";
const QString JSON_PUBLIC_KEY_KEY = "public_key";
-
+
_userPublicKeys[username] =
QByteArray::fromBase64(jsonObject[JSON_DATA_KEY].toObject()[JSON_PUBLIC_KEY_KEY].toString().toUtf8());
}
@@ -1219,11 +1219,11 @@ void DomainServer::requestCurrentPublicSocketViaSTUN() {
QJsonObject jsonForDomainSocketUpdate(const HifiSockAddr& socket) {
const QString SOCKET_NETWORK_ADDRESS_KEY = "network_address";
const QString SOCKET_PORT_KEY = "port";
-
+
QJsonObject socketObject;
socketObject[SOCKET_NETWORK_ADDRESS_KEY] = socket.getAddress().toString();
socketObject[SOCKET_PORT_KEY] = socket.getPort();
-
+
return socketObject;
}
@@ -1237,44 +1237,44 @@ void DomainServer::sendHeartbeatToDataServer(const QString& networkAddress) {
const QString DOMAIN_UPDATE = "/api/v1/domains/%1";
auto nodeList = DependencyManager::get();
const QUuid& domainID = nodeList->getSessionUUID();
-
+
// setup the domain object to send to the data server
const QString PUBLIC_NETWORK_ADDRESS_KEY = "network_address";
const QString AUTOMATIC_NETWORKING_KEY = "automatic_networking";
-
+
QJsonObject domainObject;
if (!networkAddress.isEmpty()) {
domainObject[PUBLIC_NETWORK_ADDRESS_KEY] = networkAddress;
}
-
+
domainObject[AUTOMATIC_NETWORKING_KEY] = _automaticNetworkingSetting;
-
+
// 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 QVariant* allowedUsersVariant = valueForKeyPath(_settingsManager.getSettingsMap(),
ALLOWED_USERS_SETTINGS_KEYPATH);
QStringList allowedUsers = allowedUsersVariant ? allowedUsersVariant->toStringList() : QStringList();
domainObject[RESTRICTED_ACCESS_FLAG] = (allowedUsers.size() > 0);
-
+
// add the number of currently connected agent users
int numConnectedAuthedUsers = 0;
-
+
nodeList->eachNode([&numConnectedAuthedUsers](const SharedNodePointer& node){
if (node->getLinkedData() && !static_cast(node->getLinkedData())->getUsername().isEmpty()) {
++numConnectedAuthedUsers;
}
});
-
+
const QString DOMAIN_HEARTBEAT_KEY = "heartbeat";
const QString HEARTBEAT_NUM_USERS_KEY = "num_users";
-
+
QJsonObject heartbeatObject;
heartbeatObject[HEARTBEAT_NUM_USERS_KEY] = numConnectedAuthedUsers;
domainObject[DOMAIN_HEARTBEAT_KEY] = heartbeatObject;
-
+
QString domainUpdateJSON = QString("{\"domain\": %1 }").arg(QString(QJsonDocument(domainObject).toJson()));
-
+
AccountManager::getInstance().sendRequest(DOMAIN_UPDATE.arg(uuidStringWithoutCurlyBraces(domainID)),
AccountManagerAuth::Required,
QNetworkAccessManager::PutOperation,
@@ -1295,11 +1295,11 @@ void DomainServer::sendHeartbeatToIceServer() {
void DomainServer::sendICEPingPackets() {
auto nodeList = DependencyManager::get();
-
+
QHash::iterator peer = _connectingICEPeers.begin();
-
+
while (peer != _connectingICEPeers.end()) {
-
+
if (peer->getConnectionAttempts() >= MAX_ICE_CONNECTION_ATTEMPTS) {
// we've already tried to connect to this peer enough times
// remove it from our list - if it wants to re-connect it'll come back through ice-server
@@ -1308,16 +1308,16 @@ void DomainServer::sendICEPingPackets() {
// send ping packets to this peer's interfaces
qDebug() << "Sending ping packets to establish connectivity with ICE peer with ID"
<< peer->getUUID();
-
+
// send the ping packet to the local and public sockets for this node
QByteArray localPingPacket = nodeList->constructPingPacket(PingType::Local, false);
nodeList->writeUnverifiedDatagram(localPingPacket, peer->getLocalSocket());
-
+
QByteArray publicPingPacket = nodeList->constructPingPacket(PingType::Public, false);
nodeList->writeUnverifiedDatagram(publicPingPacket, peer->getPublicSocket());
-
+
peer->incrementConnectionAttempts();
-
+
// go to next peer in hash
++peer;
}
@@ -1329,17 +1329,17 @@ void DomainServer::processICEHeartbeatResponse(const QByteArray& packet) {
// any peer we don't have we add to the hash, otherwise we update
QDataStream iceResponseStream(packet);
iceResponseStream.skipRawData(numBytesForPacketHeader(packet));
-
+
NetworkPeer receivedPeer;
-
+
while (!iceResponseStream.atEnd()) {
iceResponseStream >> receivedPeer;
-
+
if (!_connectedICEPeers.contains(receivedPeer.getUUID())) {
if (!_connectingICEPeers.contains(receivedPeer.getUUID())) {
qDebug() << "New peer requesting connection being added to hash -" << receivedPeer;
}
-
+
_connectingICEPeers[receivedPeer.getUUID()] = receivedPeer;
}
}
@@ -1348,7 +1348,7 @@ void DomainServer::processICEHeartbeatResponse(const QByteArray& packet) {
void DomainServer::processICEPingReply(const QByteArray& packet, const HifiSockAddr& senderSockAddr) {
QUuid nodeUUID = uuidFromPacketHeader(packet);
NetworkPeer sendingPeer = _connectingICEPeers.take(nodeUUID);
-
+
if (!sendingPeer.isNull()) {
// we had this NetworkPeer in our connecting list - add the right sock addr to our connected list
if (senderSockAddr == sendingPeer.getLocalSocket()) {
@@ -1372,31 +1372,36 @@ void DomainServer::processDatagram(const QByteArray& receivedPacket, const HifiS
break;
case PacketTypeDomainListRequest: {
QUuid nodeUUID = uuidFromPacketHeader(receivedPacket);
-
+
if (!nodeUUID.isNull() && nodeList->nodeWithUUID(nodeUUID)) {
NodeType_t throwawayNodeType;
HifiSockAddr nodePublicAddress, nodeLocalAddress;
-
+
QDataStream packetStream(receivedPacket);
packetStream.skipRawData(numBytesForPacketHeader(receivedPacket));
-
+
parseNodeDataFromByteArray(packetStream, throwawayNodeType, nodePublicAddress, nodeLocalAddress,
senderSockAddr);
-
+
SharedNodePointer checkInNode = nodeList->nodeWithUUID(nodeUUID);
checkInNode->setPublicSocket(nodePublicAddress);
checkInNode->setLocalSocket(nodeLocalAddress);
-
+
// update last receive to now
quint64 timeNow = usecTimestampNow();
checkInNode->setLastHeardMicrostamp(timeNow);
-
+
QList nodeInterestList;
packetStream >> nodeInterestList;
-
+
sendDomainListToNode(checkInNode, senderSockAddr, nodeInterestList.toSet());
}
-
+
+ break;
+ }
+ case PacketTypeDomainServerPathQuery: {
+ // have our private method attempt to respond to this path query
+ respondToPathQuery(receivedPacket, senderSockAddr);
break;
}
case PacketTypeNodeJsonStats: {
@@ -1404,7 +1409,7 @@ void DomainServer::processDatagram(const QByteArray& receivedPacket, const HifiS
if (matchingNode) {
reinterpret_cast(matchingNode->getLinkedData())->parseJSONStatsPacket(receivedPacket);
}
-
+
break;
}
case PacketTypeStunResponse:
@@ -1413,7 +1418,7 @@ void DomainServer::processDatagram(const QByteArray& receivedPacket, const HifiS
case PacketTypeUnverifiedPing: {
QByteArray pingReplyPacket = nodeList->constructPingReplyPacket(receivedPacket);
nodeList->writeUnverifiedDatagram(pingReplyPacket, senderSockAddr);
-
+
break;
}
case PacketTypeUnverifiedPingReply: {
@@ -1470,10 +1475,10 @@ QJsonObject DomainServer::jsonObjectForNode(const SharedNodePointer& node) {
// if the node has pool information, add it
DomainServerNodeData* nodeData = reinterpret_cast(node->getLinkedData());
-
+
// add the node username, if it exists
nodeJson[JSON_KEY_USERNAME] = nodeData->getUsername();
-
+
SharedAssignmentPointer matchingAssignment = _allAssignments.value(nodeData->getAssignmentUUID());
if (matchingAssignment) {
nodeJson[JSON_KEY_POOL] = matchingAssignment->getPool();
@@ -1512,64 +1517,64 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
const QString URI_NODES = "/nodes";
const QString UUID_REGEX_STRING = "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}";
-
+
auto nodeList = DependencyManager::get();
-
+
// allow sub-handlers to handle requests that do not require authentication
if (_settingsManager.handlePublicHTTPRequest(connection, url)) {
return true;
}
-
+
// check if this is a request for a scripted assignment (with a temp unique UUID)
const QString ASSIGNMENT_REGEX_STRING = QString("\\%1\\/(%2)\\/?$").arg(URI_ASSIGNMENT).arg(UUID_REGEX_STRING);
QRegExp assignmentRegex(ASSIGNMENT_REGEX_STRING);
-
+
if (connection->requestOperation() == QNetworkAccessManager::GetOperation
&& assignmentRegex.indexIn(url.path()) != -1) {
QUuid matchingUUID = QUuid(assignmentRegex.cap(1));
-
+
SharedAssignmentPointer matchingAssignment = _allAssignments.value(matchingUUID);
if (!matchingAssignment) {
// check if we have a pending assignment that matches this temp UUID, and it is a scripted assignment
PendingAssignedNodeData* pendingData = _pendingAssignedNodes.value(matchingUUID);
if (pendingData) {
matchingAssignment = _allAssignments.value(pendingData->getAssignmentUUID());
-
+
if (matchingAssignment && matchingAssignment->getType() == Assignment::AgentType) {
// we have a matching assignment and it is for the right type, have the HTTP manager handle it
// via correct URL for the script so the client can download
-
+
QUrl scriptURL = url;
scriptURL.setPath(URI_ASSIGNMENT + "/scripts/"
+ uuidStringWithoutCurlyBraces(pendingData->getAssignmentUUID()));
-
+
// have the HTTPManager serve the appropriate script file
return _httpManager.handleHTTPRequest(connection, scriptURL, true);
}
}
}
-
+
// request not handled
return false;
}
-
+
// check if this is a request for our domain ID
const QString URI_ID = "/id";
if (connection->requestOperation() == QNetworkAccessManager::GetOperation
&& url.path() == URI_ID) {
QUuid domainID = nodeList->getSessionUUID();
-
+
connection->respond(HTTPConnection::StatusCode200, uuidStringWithoutCurlyBraces(domainID).toLocal8Bit());
return true;
}
-
+
// all requests below require a cookie to prove authentication so check that first
if (!isAuthenticatedRequest(connection, url)) {
// this is not an authenticated request
// return true from the handler since it was handled with a 401 or re-direct to auth
return true;
}
-
+
if (connection->requestOperation() == QNetworkAccessManager::GetOperation) {
if (url.path() == "/assignments.json") {
// user is asking for json list of assignments
@@ -1581,7 +1586,7 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
// enumerate the NodeList to find the assigned nodes
nodeList->eachNode([this, &assignedNodesJSON](const SharedNodePointer& node){
DomainServerNodeData* nodeData = reinterpret_cast(node->getLinkedData());
-
+
if (!nodeData->getAssignmentUUID().isNull()) {
// add the node using the UUID as the key
QString uuidString = uuidStringWithoutCurlyBraces(nodeData->getAssignmentUUID());
@@ -1725,10 +1730,10 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
QFile scriptFile(newPath);
if (scriptFile.open(QIODevice::WriteOnly)) {
scriptFile.write(formData[0].second);
-
+
qDebug() << qPrintable(QString("Saved a script for assignment at %1%2")
.arg(newPath).arg(assignmentPool == emptyPool ? "" : " - pool is " + assignmentPool));
-
+
// add the script assigment to the assignment queue
SharedAssignmentPointer sharedScriptedAssignment(scriptAssignment);
_unfulfilledAssignments.enqueue(sharedScriptedAssignment);
@@ -1814,40 +1819,40 @@ bool DomainServer::handleHTTPSRequest(HTTPSConnection* connection, const QUrl &u
QNetworkRequest tokenRequest(tokenRequestUrl);
tokenRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT);
tokenRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
-
+
QNetworkReply* tokenReply = NetworkAccessManager::getInstance().post(tokenRequest, tokenPostBody.toLocal8Bit());
-
+
if (_webAuthenticationStateSet.remove(stateUUID)) {
// this is a web user who wants to auth to access web interface
// we hold the response back to them until we get their profile information
// and can decide if they are let in or not
-
+
QEventLoop loop;
connect(tokenReply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
-
+
// start the loop for the token request
loop.exec();
-
+
QNetworkReply* profileReply = profileRequestGivenTokenReply(tokenReply);
-
+
// stop the loop once the profileReply is complete
connect(profileReply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
-
+
// restart the loop for the profile request
loop.exec();
-
+
// call helper method to get cookieHeaders
Headers cookieHeaders = setupCookieHeadersFromProfileReply(profileReply);
-
+
connection->respond(HTTPConnection::StatusCode302, QByteArray(),
HTTPConnection::DefaultContentType, cookieHeaders);
-
+
delete tokenReply;
delete profileReply;
-
+
// we've redirected the user back to our homepage
return true;
-
+
}
}
@@ -1861,47 +1866,47 @@ bool DomainServer::handleHTTPSRequest(HTTPSConnection* connection, const QUrl &u
}
bool DomainServer::isAuthenticatedRequest(HTTPConnection* connection, const QUrl& url) {
-
+
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_USERNAME_KEY_PATH = "security.http_username";
const QString BASIC_AUTH_PASSWORD_KEY_PATH = "security.http_password";
-
+
const QByteArray UNAUTHENTICATED_BODY = "You do not have permission to access this domain-server.";
-
+
QVariantMap& settingsMap = _settingsManager.getSettingsMap();
-
+
if (!_oauthProviderURL.isEmpty()
&& (settingsMap.contains(ADMIN_USERS_CONFIG_KEY) || settingsMap.contains(ADMIN_ROLES_CONFIG_KEY))) {
QString cookieString = connection->requestHeaders().value(HTTP_COOKIE_HEADER_KEY);
-
+
const QString COOKIE_UUID_REGEX_STRING = HIFI_SESSION_COOKIE_KEY + "=([\\d\\w-]+)($|;)";
QRegExp cookieUUIDRegex(COOKIE_UUID_REGEX_STRING);
-
+
QUuid cookieUUID;
if (cookieString.indexOf(cookieUUIDRegex) != -1) {
cookieUUID = cookieUUIDRegex.cap(1);
}
-
+
if (valueForKeyPath(settingsMap, BASIC_AUTH_USERNAME_KEY_PATH)) {
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);
QString profileUsername = sessionData.getUsername();
-
+
if (settingsMap.value(ADMIN_USERS_CONFIG_KEY).toStringList().contains(profileUsername)) {
// this is an authenticated user
return true;
}
-
+
// loop the roles of this user and see if they are in the admin-roles array
QStringList adminRolesArray = settingsMap.value(ADMIN_ROLES_CONFIG_KEY).toStringList();
-
+
if (!adminRolesArray.isEmpty()) {
foreach(const QString& userRole, sessionData.getRoles()) {
if (adminRolesArray.contains(userRole)) {
@@ -1910,79 +1915,79 @@ bool DomainServer::isAuthenticatedRequest(HTTPConnection* connection, const QUrl
}
}
}
-
+
connection->respond(HTTPConnection::StatusCode401, UNAUTHENTICATED_BODY);
-
+
// the user does not have allowed username or role, return 401
return false;
} else {
// re-direct this user to OAuth page
-
+
// generate a random state UUID to use
QUuid stateUUID = QUuid::createUuid();
-
+
// add it to the set so we can handle the callback from the OAuth provider
_webAuthenticationStateSet.insert(stateUUID);
-
+
QUrl oauthRedirectURL = oauthAuthorizationURL(stateUUID);
-
+
Headers redirectHeaders;
redirectHeaders.insert("Location", oauthRedirectURL.toEncoded());
-
+
connection->respond(HTTPConnection::StatusCode302,
QByteArray(), HTTPConnection::DefaultContentType, redirectHeaders);
-
+
// we don't know about this user yet, so they are not yet authenticated
return false;
}
} else if (valueForKeyPath(settingsMap, BASIC_AUTH_USERNAME_KEY_PATH)) {
// 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 headerUsername = credentialList[0];
QString headerPassword = credentialList[1];
-
+
// we've pulled a username and password - now check if there is a match in our basic auth hash
QString settingsUsername = valueForKeyPath(settingsMap, BASIC_AUTH_USERNAME_KEY_PATH)->toString();
const QVariant* settingsPasswordVariant = valueForKeyPath(settingsMap, BASIC_AUTH_PASSWORD_KEY_PATH);
QString settingsPassword = settingsPasswordVariant ? settingsPasswordVariant->toString() : "";
-
+
if (settingsUsername == headerUsername && headerPassword == settingsPassword) {
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;
@@ -1996,12 +2001,12 @@ QNetworkReply* DomainServer::profileRequestGivenTokenReply(QNetworkReply* tokenR
// pull the access token from the returned JSON and store it with the matching session UUID
QJsonDocument returnedJSON = QJsonDocument::fromJson(tokenReply->readAll());
QString accessToken = returnedJSON.object()[OAUTH_JSON_ACCESS_TOKEN_KEY].toString();
-
+
// fire off a request to get this user's identity so we can see if we will let them in
QUrl profileURL = _oauthProviderURL;
profileURL.setPath("/api/v1/user/profile");
profileURL.setQuery(QString("%1=%2").arg(OAUTH_JSON_ACCESS_TOKEN_KEY, accessToken));
-
+
QNetworkRequest profileRequest(profileURL);
profileRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT);
return NetworkAccessManager::getInstance().get(profileRequest);
@@ -2011,34 +2016,34 @@ const QString DS_SETTINGS_SESSIONS_GROUP = "web-sessions";
Headers DomainServer::setupCookieHeadersFromProfileReply(QNetworkReply* profileReply) {
Headers cookieHeaders;
-
+
// create a UUID for this cookie
QUuid cookieUUID = QUuid::createUuid();
-
+
QJsonDocument profileDocument = QJsonDocument::fromJson(profileReply->readAll());
QJsonObject userObject = profileDocument.object()["data"].toObject()["user"].toObject();
-
+
// add the profile to our in-memory data structure so we know who the user is when they send us their cookie
DomainServerWebSessionData sessionData(userObject);
_cookieSessionHash.insert(cookieUUID, sessionData);
-
+
// persist the cookie to settings file so we can get it back on DS relaunch
QStringList path = QStringList() << DS_SETTINGS_SESSIONS_GROUP << cookieUUID.toString();
Setting::Handle(path).set(QVariant::fromValue(sessionData));
-
+
// setup expiry for cookie to 1 month from today
QDateTime cookieExpiry = QDateTime::currentDateTimeUtc().addMonths(1);
-
+
QString cookieString = HIFI_SESSION_COOKIE_KEY + "=" + uuidStringWithoutCurlyBraces(cookieUUID.toString());
cookieString += "; expires=" + cookieExpiry.toString("ddd, dd MMM yyyy HH:mm:ss") + " GMT";
cookieString += "; domain=" + _hostname + "; path=/";
-
+
cookieHeaders.insert("Set-Cookie", cookieString.toUtf8());
-
+
// redirect the user back to the homepage so they can present their cookie and be authenticated
QString redirectString = "http://" + _hostname + ":" + QString::number(_httpManager.serverPort());
cookieHeaders.insert("Location", redirectString.toUtf8());
-
+
return cookieHeaders;
}
@@ -2046,7 +2051,7 @@ void DomainServer::loadExistingSessionsFromSettings() {
// read data for existing web sessions into memory so existing sessions can be leveraged
Settings domainServerSettings;
domainServerSettings.beginGroup(DS_SETTINGS_SESSIONS_GROUP);
-
+
foreach(const QString& uuidKey, domainServerSettings.childKeys()) {
_cookieSessionHash.insert(QUuid(uuidKey),
domainServerSettings.value(uuidKey).value());
@@ -2077,7 +2082,7 @@ void DomainServer::nodeAdded(SharedNodePointer node) {
}
void DomainServer::nodeKilled(SharedNodePointer node) {
-
+
// remove this node from the connecting / connected ICE lists (if they exist)
_connectingICEPeers.remove(node->getUUID());
_connectedICEPeers.remove(node->getUUID());
@@ -2095,7 +2100,7 @@ void DomainServer::nodeKilled(SharedNodePointer node) {
}
// If this node was an Agent ask JSONBreakableMarshal to potentially remove the interpolation we stored
- JSONBreakableMarshal::removeInterpolationForKey(USERNAME_UUID_REPLACEMENT_STATS_KEY,
+ JSONBreakableMarshal::removeInterpolationForKey(USERNAME_UUID_REPLACEMENT_STATS_KEY,
uuidStringWithoutCurlyBraces(node->getUUID()));
// cleanup the connection secrets that we set up for this node (on the other nodes)
@@ -2181,7 +2186,7 @@ void DomainServer::addStaticAssignmentsToQueue() {
QHash::iterator staticAssignment = staticHashCopy.begin();
while (staticAssignment != staticHashCopy.end()) {
// add any of the un-matched static assignments to the queue
-
+
// enumerate the nodes and check if there is one with an attached assignment with matching UUID
if (!DependencyManager::get()->nodeWithUUID(staticAssignment->data()->getUUID())) {
// this assignment has not been fulfilled - reset the UUID and add it to the assignment queue
@@ -2191,3 +2196,75 @@ void DomainServer::addStaticAssignmentsToQueue() {
++staticAssignment;
}
}
+
+void DomainServer::respondToPathQuery(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr) {
+ // this is a query for the viewpoint resulting from a path
+ // first pull the query path from the packet
+
+ int numHeaderBytes = numBytesForPacketHeaderGivenPacketType(PacketTypeDomainServerPathQuery);
+ const char* packetDataStart = receivedPacket.data() + numHeaderBytes;
+
+ // figure out how many bytes the sender said this path is
+ quint16 numPathBytes = *packetDataStart;
+
+ if (numPathBytes <= receivedPacket.size() - numHeaderBytes - sizeof(numPathBytes)) {
+ // the number of path bytes makes sense for the sent packet - pull out the path
+ QString pathQuery = QString::fromUtf8(packetDataStart + sizeof(numPathBytes), numPathBytes);
+
+ // our settings contain paths that start with a leading slash, so make sure this query has that
+ if (!pathQuery.startsWith("/")) {
+ pathQuery.prepend("/");
+ }
+
+ const QString PATHS_SETTINGS_KEYPATH_FORMAT = "%1.%2";
+ const QString PATH_VIEWPOINT_KEY = "viewpoint";
+
+ // check out paths in the _configMap to see if we have a match
+ const QVariant* pathMatch = valueForKeyPath(_settingsManager.getSettingsMap(),
+ QString(PATHS_SETTINGS_KEYPATH_FORMAT).arg(SETTINGS_PATHS_KEY)
+ .arg(pathQuery));
+ if (pathMatch) {
+ // we got a match, respond with the resulting viewpoint
+ auto nodeList = DependencyManager::get();
+
+ QString responseViewpoint = pathMatch->toMap()[PATH_VIEWPOINT_KEY].toString();
+
+ if (!responseViewpoint.isEmpty()) {
+ QByteArray viewpointUTF8 = responseViewpoint.toUtf8();
+
+ // prepare a packet for the response
+ QByteArray pathResponsePacket = nodeList->byteArrayWithPopulatedHeader(PacketTypeDomainServerPathResponse);
+
+ // check the number of bytes the viewpoint is
+ quint16 numViewpointBytes = responseViewpoint.toUtf8().size();
+
+ // are we going to be able to fit this response viewpoint in a packet?
+ if (numPathBytes + numViewpointBytes + pathResponsePacket.size() + sizeof(numViewpointBytes)
+ < MAX_PACKET_SIZE) {
+ // append the number of bytes this path is
+ pathResponsePacket.append(reinterpret_cast(&numPathBytes), sizeof(numPathBytes));
+
+ // append the path itself
+ pathResponsePacket.append(pathQuery.toUtf8());
+
+ // append the number of bytes the resulting viewpoint is
+ pathResponsePacket.append(reinterpret_cast(&numViewpointBytes), sizeof(numViewpointBytes));
+
+ // append the viewpoint itself
+ pathResponsePacket.append(viewpointUTF8);
+
+ qDebug() << "Sending a viewpoint response for path query" << pathQuery << "-" << viewpointUTF8;
+
+ // send off the packet - see if we can associate this outbound data to a particular node
+ // TODO: does this senderSockAddr always work for a punched DS client?
+ nodeList->writeUnverifiedDatagram(pathResponsePacket, senderSockAddr);
+ }
+ }
+
+ } else {
+ // we don't respond if there is no match - this may need to change once this packet
+ // query/response is made reliable
+ qDebug() << "No match for path query" << pathQuery << "- refusing to respond.";
+ }
+ }
+}
diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h
index 79303ef098..3863084191 100644
--- a/domain-server/src/DomainServer.h
+++ b/domain-server/src/DomainServer.h
@@ -39,29 +39,29 @@ class DomainServer : public QCoreApplication, public HTTPSRequestHandler {
Q_OBJECT
public:
DomainServer(int argc, char* argv[]);
-
+
static int const EXIT_CODE_REBOOT;
-
+
bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler = false);
bool handleHTTPSRequest(HTTPSConnection* connection, const QUrl& url, bool skipSubHandler = false);
-
+
public slots:
/// Called by NodeList to inform us a node has been added
void nodeAdded(SharedNodePointer node);
/// Called by NodeList to inform us a node has been killed
void nodeKilled(SharedNodePointer node);
-
+
void publicKeyJSONCallback(QNetworkReply& requestReply);
void transactionJSONCallback(const QJsonObject& data);
-
+
void restart();
-
+
private slots:
void loginFailed();
void readAvailableDatagrams();
void setupPendingAssignmentCredits();
void sendPendingTransactionsToServer();
-
+
void requestCurrentPublicSocketViaSTUN();
void performIPAddressUpdate(const HifiSockAddr& newPublicSockAddr);
void performICEUpdates();
@@ -74,23 +74,23 @@ private:
bool optionallyReadX509KeyAndCertificate();
bool didSetupAccountManagerWithAccessToken();
bool optionallySetupAssignmentPayment();
-
+
void setupAutomaticNetworking();
void sendHeartbeatToDataServer(const QString& networkAddress);
void processICEPingReply(const QByteArray& packet, const HifiSockAddr& senderSockAddr);
void processICEHeartbeatResponse(const QByteArray& packet);
-
+
void processDatagram(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr);
-
+
void handleConnectRequest(const QByteArray& packet, const HifiSockAddr& senderSockAddr);
unsigned int countConnectedUsers();
bool verifyUsersKey (const QString& username, const QByteArray& usernameSignature, QString& reasonReturn);
bool shouldAllowConnectionFromNode(const QString& username, const QByteArray& usernameSignature,
const HifiSockAddr& senderSockAddr, QString& reasonReturn);
-
+
void preloadAllowedUserPublicKeys();
void requestUserPublicKey(const QString& username);
-
+
int parseNodeDataFromByteArray(QDataStream& packetStream,
NodeType_t& nodeType,
HifiSockAddr& publicSockAddr,
@@ -99,61 +99,63 @@ private:
NodeSet nodeInterestListFromPacket(const QByteArray& packet, int numPreceedingBytes);
void sendDomainListToNode(const SharedNodePointer& node, const HifiSockAddr& senderSockAddr,
const NodeSet& nodeInterestList);
-
+
void parseAssignmentConfigs(QSet& excludedTypes);
void addStaticAssignmentToAssignmentHash(Assignment* newAssignment);
void createStaticAssignmentsForType(Assignment::Type type, const QVariantList& configList);
void populateDefaultStaticAssignmentsExcludingTypes(const QSet& excludedTypes);
void populateStaticScriptedAssignmentsFromSettings();
-
+
SharedAssignmentPointer matchingQueuedAssignmentForCheckIn(const QUuid& checkInUUID, NodeType_t nodeType);
SharedAssignmentPointer deployableAssignmentForRequest(const Assignment& requestAssignment);
void removeMatchingAssignmentFromQueue(const SharedAssignmentPointer& removableAssignment);
void refreshStaticAssignmentAndAddToQueue(SharedAssignmentPointer& assignment);
void addStaticAssignmentsToQueue();
-
+
+ void respondToPathQuery(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr);
+
QUrl oauthRedirectURL();
QUrl oauthAuthorizationURL(const QUuid& stateUUID = QUuid::createUuid());
-
+
bool isAuthenticatedRequest(HTTPConnection* connection, const QUrl& url);
void handleTokenRequestFinished();
QNetworkReply* profileRequestGivenTokenReply(QNetworkReply* tokenReply);
void handleProfileRequestFinished();
Headers setupCookieHeadersFromProfileReply(QNetworkReply* profileReply);
-
+
void loadExistingSessionsFromSettings();
-
+
QJsonObject jsonForSocket(const HifiSockAddr& socket);
QJsonObject jsonObjectForNode(const SharedNodePointer& node);
-
+
HTTPManager _httpManager;
HTTPSManager* _httpsManager;
-
+
QHash _allAssignments;
QQueue _unfulfilledAssignments;
QHash _pendingAssignedNodes;
TransactionHash _pendingAssignmentCredits;
-
+
bool _isUsingDTLS;
-
+
QUrl _oauthProviderURL;
QString _oauthClientID;
QString _oauthClientSecret;
QString _hostname;
-
+
QSet _webAuthenticationStateSet;
QHash _cookieSessionHash;
-
+
QHash _userPublicKeys;
-
+
QHash _connectingICEPeers;
QHash _connectedICEPeers;
-
+
QString _automaticNetworkingSetting;
-
+
DomainServerSettingsManager _settingsManager;
-
+
HifiSockAddr _iceServerSocket;
};
diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp
index f29b1f48ae..5539b6b564 100644
--- a/domain-server/src/DomainServerSettingsManager.cpp
+++ b/domain-server/src/DomainServerSettingsManager.cpp
@@ -32,6 +32,8 @@ const QString DESCRIPTION_NAME_KEY = "name";
const QString SETTING_DESCRIPTION_TYPE_KEY = "type";
const QString DESCRIPTION_COLUMNS_KEY = "columns";
+const QString SETTINGS_VIEWPOINT_KEY = "viewpoint";
+
DomainServerSettingsManager::DomainServerSettingsManager() :
_descriptionArray(),
_configMap()
@@ -39,31 +41,31 @@ DomainServerSettingsManager::DomainServerSettingsManager() :
// load the description object from the settings description
QFile descriptionFile(QCoreApplication::applicationDirPath() + SETTINGS_DESCRIPTION_RELATIVE_PATH);
descriptionFile.open(QIODevice::ReadOnly);
-
+
_descriptionArray = QJsonDocument::fromJson(descriptionFile.readAll()).array();
}
void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList) {
_configMap.loadMasterAndUserConfig(argumentList);
-
+
// for now we perform a temporary transition from http-username and http-password to http_username and http_password
const QVariant* oldUsername = valueForKeyPath(_configMap.getUserConfig(), "security.http-username");
const QVariant* oldPassword = valueForKeyPath(_configMap.getUserConfig(), "security.http-password");
-
+
if (oldUsername || oldPassword) {
QVariantMap& settingsMap = *reinterpret_cast(_configMap.getUserConfig()["security"].data());
-
+
// remove old keys, move to new format
if (oldUsername) {
settingsMap["http_username"] = oldUsername->toString();
settingsMap.remove("http-username");
}
-
+
if (oldPassword) {
settingsMap["http_password"] = oldPassword->toString();
settingsMap.remove("http-password");
}
-
+
// save the updated settings
persistToFile();
}
@@ -71,18 +73,18 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList
QVariant DomainServerSettingsManager::valueOrDefaultValueForKeyPath(const QString &keyPath) {
const QVariant* foundValue = valueForKeyPath(_configMap.getMergedConfig(), keyPath);
-
+
if (foundValue) {
return *foundValue;
} else {
int dotIndex = keyPath.indexOf('.');
-
+
QString groupKey = keyPath.mid(0, dotIndex);
QString settingKey = keyPath.mid(dotIndex + 1);
-
+
foreach(const QVariant& group, _descriptionArray.toVariantList()) {
QVariantMap groupMap = group.toMap();
-
+
if (groupMap[DESCRIPTION_NAME_KEY].toString() == groupKey) {
foreach(const QVariant& setting, groupMap[DESCRIPTION_SETTINGS_KEY].toList()) {
QVariantMap settingMap = setting.toMap();
@@ -90,12 +92,12 @@ QVariant DomainServerSettingsManager::valueOrDefaultValueForKeyPath(const QStrin
return settingMap[SETTING_DEFAULT_KEY];
}
}
-
+
return QVariant();
}
}
}
-
+
return QVariant();
}
@@ -104,23 +106,23 @@ const QString SETTINGS_PATH = "/settings.json";
bool DomainServerSettingsManager::handlePublicHTTPRequest(HTTPConnection* connection, const QUrl &url) {
if (connection->requestOperation() == QNetworkAccessManager::GetOperation && url.path() == SETTINGS_PATH) {
// this is a GET operation for our settings
-
+
// check if there is a query parameter for settings affecting a particular type of assignment
const QString SETTINGS_TYPE_QUERY_KEY = "type";
QUrlQuery settingsQuery(url);
QString typeValue = settingsQuery.queryItemValue(SETTINGS_TYPE_QUERY_KEY);
-
+
if (!typeValue.isEmpty()) {
QJsonObject responseObject = responseObjectForType(typeValue);
-
+
connection->respond(HTTPConnection::StatusCode200, QJsonDocument(responseObject).toJson(), "application/json");
-
+
return true;
} else {
return false;
}
}
-
+
return false;
}
@@ -129,130 +131,125 @@ bool DomainServerSettingsManager::handleAuthenticatedHTTPRequest(HTTPConnection
// this is a POST operation to change one or more settings
QJsonDocument postedDocument = QJsonDocument::fromJson(connection->requestContent());
QJsonObject postedObject = postedDocument.object();
-
- qDebug() << "The postedObject is" << postedObject;
+
+ qDebug() << "DomainServerSettingsManager postedObject -" << postedObject;
// we recurse one level deep below each group for the appropriate setting
- recurseJSONObjectAndOverwriteSettings(postedObject, _configMap.getUserConfig(), _descriptionArray);
-
+ recurseJSONObjectAndOverwriteSettings(postedObject, _configMap.getUserConfig());
+
// store whatever the current _settingsMap is to file
persistToFile();
-
+
// return success to the caller
QString jsonSuccess = "{\"status\": \"success\"}";
connection->respond(HTTPConnection::StatusCode200, jsonSuccess.toUtf8(), "application/json");
-
+
// defer a restart to the domain-server, this gives our HTTPConnection enough time to respond
const int DOMAIN_SERVER_RESTART_TIMER_MSECS = 1000;
QTimer::singleShot(DOMAIN_SERVER_RESTART_TIMER_MSECS, qApp, SLOT(restart()));
-
+
return true;
} else if (connection->requestOperation() == QNetworkAccessManager::GetOperation && url.path() == SETTINGS_PATH) {
// setup a JSON Object with descriptions and non-omitted settings
const QString SETTINGS_RESPONSE_DESCRIPTION_KEY = "descriptions";
const QString SETTINGS_RESPONSE_VALUE_KEY = "values";
const QString SETTINGS_RESPONSE_LOCKED_VALUES_KEY = "locked";
-
+
QJsonObject rootObject;
rootObject[SETTINGS_RESPONSE_DESCRIPTION_KEY] = _descriptionArray;
rootObject[SETTINGS_RESPONSE_VALUE_KEY] = responseObjectForType("", true);
rootObject[SETTINGS_RESPONSE_LOCKED_VALUES_KEY] = QJsonDocument::fromVariant(_configMap.getMasterConfig()).object();
-
-
+
+
connection->respond(HTTPConnection::StatusCode200, QJsonDocument(rootObject).toJson(), "application/json");
}
-
+
return false;
}
QJsonObject DomainServerSettingsManager::responseObjectForType(const QString& typeValue, bool isAuthenticated) {
QJsonObject responseObject;
-
+
if (!typeValue.isEmpty() || isAuthenticated) {
// convert the string type value to a QJsonValue
QJsonValue queryType = typeValue.isEmpty() ? QJsonValue() : QJsonValue(typeValue.toInt());
-
+
const QString AFFECTED_TYPES_JSON_KEY = "assignment-types";
-
+
// enumerate the groups in the description object to find which settings to pass
foreach(const QJsonValue& groupValue, _descriptionArray) {
QJsonObject groupObject = groupValue.toObject();
QString groupKey = groupObject[DESCRIPTION_NAME_KEY].toString();
QJsonArray groupSettingsArray = groupObject[DESCRIPTION_SETTINGS_KEY].toArray();
-
+
QJsonObject groupResponseObject;
-
+
foreach(const QJsonValue& settingValue, groupSettingsArray) {
const QString VALUE_HIDDEN_FLAG_KEY = "value-hidden";
-
+
QJsonObject settingObject = settingValue.toObject();
-
+
if (!settingObject[VALUE_HIDDEN_FLAG_KEY].toBool()) {
QJsonArray affectedTypesArray = settingObject[AFFECTED_TYPES_JSON_KEY].toArray();
if (affectedTypesArray.isEmpty()) {
affectedTypesArray = groupObject[AFFECTED_TYPES_JSON_KEY].toArray();
}
-
+
if (affectedTypesArray.contains(queryType) ||
(queryType.isNull() && isAuthenticated)) {
// this is a setting we should include in the responseObject
-
+
QString settingName = settingObject[DESCRIPTION_NAME_KEY].toString();
-
+
// we need to check if the settings map has a value for this setting
QVariant variantValue;
- QVariant settingsMapGroupValue = _configMap.getMergedConfig()
- .value(groupObject[DESCRIPTION_NAME_KEY].toString());
-
- if (!settingsMapGroupValue.isNull()) {
- variantValue = settingsMapGroupValue.toMap().value(settingName);
+
+ if (!groupKey.isEmpty()) {
+ QVariant settingsMapGroupValue = _configMap.getMergedConfig().value(groupKey);
+
+ if (!settingsMapGroupValue.isNull()) {
+ variantValue = settingsMapGroupValue.toMap().value(settingName);
+ }
+ } else {
+ variantValue = _configMap.getMergedConfig().value(settingName);
}
-
+
+ QJsonValue result;
+
if (variantValue.isNull()) {
// no value for this setting, pass the default
if (settingObject.contains(SETTING_DEFAULT_KEY)) {
- groupResponseObject[settingName] = settingObject[SETTING_DEFAULT_KEY];
+ result = settingObject[SETTING_DEFAULT_KEY];
} else {
// users are allowed not to provide a default for string values
// if so we set to the empty string
- groupResponseObject[settingName] = QString("");
+ result = QString("");
}
-
+
} else {
- groupResponseObject[settingName] = QJsonValue::fromVariant(variantValue);
+ result = QJsonValue::fromVariant(variantValue);
+ }
+
+ if (!groupKey.isEmpty()) {
+ // this belongs in the group object
+ groupResponseObject[settingName] = result;
+ } else {
+ // this is a value that should be at the root
+ responseObject[settingName] = result;
}
}
}
}
-
- if (!groupResponseObject.isEmpty()) {
+
+ if (!groupKey.isEmpty() && !groupResponseObject.isEmpty()) {
// set this group's object to the constructed object
responseObject[groupKey] = groupResponseObject;
}
}
}
-
-
- return responseObject;
-}
-bool DomainServerSettingsManager::settingExists(const QString& groupName, const QString& settingName,
- const QJsonArray& descriptionArray, QJsonObject& settingDescription) {
- foreach(const QJsonValue& groupValue, descriptionArray) {
- QJsonObject groupObject = groupValue.toObject();
- if (groupObject[DESCRIPTION_NAME_KEY].toString() == groupName) {
-
- foreach(const QJsonValue& settingValue, groupObject[DESCRIPTION_SETTINGS_KEY].toArray()) {
- QJsonObject settingObject = settingValue.toObject();
- if (settingObject[DESCRIPTION_NAME_KEY].toString() == settingName) {
- settingDescription = settingObject;
- return true;
- }
- }
- }
- }
- settingDescription = QJsonObject();
- return false;
+
+ return responseObject;
}
void DomainServerSettingsManager::updateSetting(const QString& key, const QJsonValue& newValue, QVariantMap& settingMap,
@@ -266,13 +263,21 @@ void DomainServerSettingsManager::updateSetting(const QString& key, const QJsonV
QString settingType = settingDescription[SETTING_DESCRIPTION_TYPE_KEY].toString();
const QString INPUT_DOUBLE_TYPE = "double";
const QString INPUT_INTEGER_TYPE = "int";
-
+
if (settingType == INPUT_DOUBLE_TYPE) {
settingMap[key] = newValue.toString().toDouble();
} else if (settingType == INPUT_INTEGER_TYPE) {
settingMap[key] = newValue.toString().toInt();
} else {
- settingMap[key] = newValue.toString();
+ QString sanitizedValue = newValue.toString();
+
+ // we perform special handling for viewpoints here
+ // we do not want them to be prepended with a slash
+ if (key == SETTINGS_VIEWPOINT_KEY && !sanitizedValue.startsWith('/')) {
+ sanitizedValue.prepend('/');
+ }
+
+ settingMap[key] = sanitizedValue;
}
}
} else if (newValue.isBool()) {
@@ -282,12 +287,12 @@ void DomainServerSettingsManager::updateSetting(const QString& key, const QJsonV
// we don't have a map below this key yet, so set it up now
settingMap[key] = QVariantMap();
}
-
+
QVariantMap& thisMap = *reinterpret_cast(settingMap[key].data());
foreach(const QString childKey, newValue.toObject().keys()) {
QJsonObject childDescriptionObject = settingDescription;
-
+
// is this the key? if so we have the description already
if (key != settingDescription[DESCRIPTION_NAME_KEY].toString()) {
// otherwise find the description object for this childKey under columns
@@ -299,12 +304,20 @@ void DomainServerSettingsManager::updateSetting(const QString& key, const QJsonV
break;
}
}
- }
+ }
}
-
- updateSetting(childKey, newValue.toObject()[childKey], thisMap, childDescriptionObject);
+
+ QString sanitizedKey = childKey;
+
+ if (key == SETTINGS_PATHS_KEY && !sanitizedKey.startsWith('/')) {
+ // We perform special handling for paths here.
+ // If we got sent a path without a leading slash then we add it.
+ sanitizedKey.prepend("/");
+ }
+
+ updateSetting(sanitizedKey, newValue.toObject()[childKey], thisMap, childDescriptionObject);
}
-
+
if (settingMap[key].toMap().isEmpty()) {
// we've cleared all of the settings below this value, so remove this one too
settingMap.remove(key);
@@ -316,47 +329,104 @@ void DomainServerSettingsManager::updateSetting(const QString& key, const QJsonV
}
}
-void DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject,
- QVariantMap& settingsVariant,
- const QJsonArray& descriptionArray) {
- // Iterate on the setting groups
- foreach(const QString& groupKey, postedObject.keys()) {
- QJsonValue groupValue = postedObject[groupKey];
-
- if (!settingsVariant.contains(groupKey)) {
- // we don't have a map below this key yet, so set it up now
- settingsVariant[groupKey] = QVariantMap();
+QJsonObject DomainServerSettingsManager::settingDescriptionFromGroup(const QJsonObject& groupObject, const QString& settingName) {
+ foreach(const QJsonValue& settingValue, groupObject[DESCRIPTION_SETTINGS_KEY].toArray()) {
+ QJsonObject settingObject = settingValue.toObject();
+ if (settingObject[DESCRIPTION_NAME_KEY].toString() == settingName) {
+ return settingObject;
}
-
- // Iterate on the settings
- foreach(const QString& settingKey, groupValue.toObject().keys()) {
- QJsonValue settingValue = groupValue.toObject()[settingKey];
-
- QJsonObject thisDescription;
- if (settingExists(groupKey, settingKey, descriptionArray, thisDescription)) {
- QVariantMap& thisMap = *reinterpret_cast(settingsVariant[groupKey].data());
- updateSetting(settingKey, settingValue, thisMap, thisDescription);
+ }
+
+ return QJsonObject();
+}
+
+void DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject,
+ QVariantMap& settingsVariant) {
+ // Iterate on the setting groups
+ foreach(const QString& rootKey, postedObject.keys()) {
+ QJsonValue rootValue = postedObject[rootKey];
+
+ if (!settingsVariant.contains(rootKey)) {
+ // we don't have a map below this key yet, so set it up now
+ settingsVariant[rootKey] = QVariantMap();
+ }
+
+ QVariantMap& thisMap = settingsVariant;
+
+ QJsonObject groupDescriptionObject;
+
+ // we need to check the description array to see if this is a root setting or a group setting
+ foreach(const QJsonValue& groupValue, _descriptionArray) {
+ if (groupValue.toObject()[DESCRIPTION_NAME_KEY] == rootKey) {
+ // we matched a group - keep this since we'll use it below to update the settings
+ groupDescriptionObject = groupValue.toObject();
+
+ // change the map we will update to be the map for this group
+ thisMap = *reinterpret_cast(settingsVariant[rootKey].data());
+
+ break;
}
}
-
- if (settingsVariant[groupKey].toMap().empty()) {
+
+ if (groupDescriptionObject.isEmpty()) {
+ // this is a root value, so we can call updateSetting for it directly
+ // first we need to find our description value for it
+
+ QJsonObject matchingDescriptionObject;
+
+ foreach(const QJsonValue& groupValue, _descriptionArray) {
+ // find groups with root values (they don't have a group name)
+ QJsonObject groupObject = groupValue.toObject();
+ if (!groupObject.contains(DESCRIPTION_NAME_KEY)) {
+ // this is a group with root values - check if our setting is in here
+ matchingDescriptionObject = settingDescriptionFromGroup(groupObject, rootKey);
+
+ if (!matchingDescriptionObject.isEmpty()) {
+ break;
+ }
+ }
+ }
+
+ if (!matchingDescriptionObject.isEmpty()) {
+ updateSetting(rootKey, rootValue, thisMap, matchingDescriptionObject);
+ } else {
+ qDebug() << "Setting for root key" << rootKey << "does not exist - cannot update setting.";
+ }
+ } else {
+ // this is a group - iterate on the settings in the group
+ foreach(const QString& settingKey, rootValue.toObject().keys()) {
+ // make sure this particular setting exists and we have a description object for it
+ QJsonObject matchingDescriptionObject = settingDescriptionFromGroup(groupDescriptionObject, settingKey);
+
+ // if we matched the setting then update the value
+ if (!matchingDescriptionObject.isEmpty()) {
+ QJsonValue settingValue = rootValue.toObject()[settingKey];
+ updateSetting(settingKey, settingValue, thisMap, matchingDescriptionObject);
+ } else {
+ qDebug() << "Could not find description for setting" << settingKey << "in group" << rootKey <<
+ "- cannot update setting.";
+ }
+ }
+ }
+
+ if (settingsVariant[rootKey].toMap().empty()) {
// we've cleared all of the settings below this value, so remove this one too
- settingsVariant.remove(groupKey);
+ settingsVariant.remove(rootKey);
}
}
}
void DomainServerSettingsManager::persistToFile() {
-
+
// make sure we have the dir the settings file is supposed to live in
QFileInfo settingsFileInfo(_configMap.getUserConfigFilename());
-
+
if (!settingsFileInfo.dir().exists()) {
settingsFileInfo.dir().mkpath(".");
}
-
+
QFile settingsFile(_configMap.getUserConfigFilename());
-
+
if (settingsFile.open(QIODevice::WriteOnly)) {
settingsFile.write(QJsonDocument::fromVariant(_configMap.getUserConfig()).toJson());
} else {
diff --git a/domain-server/src/DomainServerSettingsManager.h b/domain-server/src/DomainServerSettingsManager.h
index 1034838d28..450baf66ae 100644
--- a/domain-server/src/DomainServerSettingsManager.h
+++ b/domain-server/src/DomainServerSettingsManager.h
@@ -18,27 +18,28 @@
#include
#include
+const QString SETTINGS_PATHS_KEY = "paths";
+
class DomainServerSettingsManager : public QObject {
Q_OBJECT
public:
DomainServerSettingsManager();
bool handlePublicHTTPRequest(HTTPConnection* connection, const QUrl& url);
bool handleAuthenticatedHTTPRequest(HTTPConnection* connection, const QUrl& url);
-
+
void setupConfigMap(const QStringList& argumentList);
QVariant valueOrDefaultValueForKeyPath(const QString& keyPath);
-
+
QVariantMap& getSettingsMap() { return _configMap.getMergedConfig(); }
private:
QJsonObject responseObjectForType(const QString& typeValue, bool isAuthenticated = false);
- void recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject, QVariantMap& settingsVariant,
- const QJsonArray& descriptionArray);
- bool settingExists(const QString& groupName, const QString& settingName,
- const QJsonArray& descriptionArray, QJsonObject& settingDescription);
+ void recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject, QVariantMap& settingsVariant);
+
void updateSetting(const QString& key, const QJsonValue& newValue, QVariantMap& settingMap,
const QJsonObject& settingDescription);
+ QJsonObject settingDescriptionFromGroup(const QJsonObject& groupObject, const QString& settingName);
void persistToFile();
-
+
QJsonArray _descriptionArray;
HifiConfigVariantMap _configMap;
};
diff --git a/examples/grab.js b/examples/grab.js
index abffef6cb2..6bdf218ce2 100644
--- a/examples/grab.js
+++ b/examples/grab.js
@@ -260,9 +260,11 @@ function update(deltaTime) {
}
Entities.editEntity(grabbedEntity, {
+ position: currentPosition,
+ rotation: currentRotation,
velocity: newVelocity,
angularVelocity: angularVelocity
- })
+ });
updateDropLine(targetPosition);
}
}
diff --git a/examples/html/entityProperties.html b/examples/html/entityProperties.html
index 1b004bbd3a..4ce787d78a 100644
--- a/examples/html/entityProperties.html
+++ b/examples/html/entityProperties.html
@@ -633,7 +633,7 @@
elColorBlue.addEventListener('change', colorChangeFunction);
$('#property-color').colpick({
colorScheme:'dark',
- layout:'rgbhex',
+ layout:'hex',
color:'000000',
onSubmit: function(hsb, hex, rgb, el) {
$(el).css('background-color', '#'+hex);
@@ -651,7 +651,7 @@
elLightColorBlue.addEventListener('change', lightColorChangeFunction);
$('#property-light-color').colpick({
colorScheme:'dark',
- layout:'rgbhex',
+ layout:'hex',
color:'000000',
onSubmit: function(hsb, hex, rgb, el) {
$(el).css('background-color', '#'+hex);
@@ -686,7 +686,7 @@
elTextTextColorBlue.addEventListener('change', textTextColorChangeFunction);
$('#property-text-text-color').colpick({
colorScheme:'dark',
- layout:'rgbhex',
+ layout:'hex',
color:'000000',
onSubmit: function(hsb, hex, rgb, el) {
$(el).css('background-color', '#'+hex);
@@ -702,7 +702,7 @@
elTextBackgroundColorBlue.addEventListener('change', textBackgroundColorChangeFunction);
$('#property-text-background-color').colpick({
colorScheme:'dark',
- layout:'rgbhex',
+ layout:'hex',
color:'000000',
onSubmit: function(hsb, hex, rgb, el) {
$(el).css('background-color', '#'+hex);
@@ -714,7 +714,7 @@
elZoneStageSunModelEnabled.addEventListener('change', createEmitGroupCheckedPropertyUpdateFunction('stage','sunModelEnabled'));
$('#property-zone-key-light-color').colpick({
colorScheme:'dark',
- layout:'rgbhex',
+ layout:'hex',
color:'000000',
onSubmit: function(hsb, hex, rgb, el) {
$(el).css('background-color', '#'+hex);
@@ -751,7 +751,7 @@
elZoneSkyboxColorBlue.addEventListener('change', zoneSkyboxColorChangeFunction);
$('#property-zone-skybox-color').colpick({
colorScheme:'dark',
- layout:'rgbhex',
+ layout:'hex',
color:'000000',
onSubmit: function(hsb, hex, rgb, el) {
$(el).css('background-color', '#'+hex);
diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp
index 199f91f087..54b4a8bb54 100644
--- a/interface/src/Application.cpp
+++ b/interface/src/Application.cpp
@@ -1366,12 +1366,12 @@ void Application::keyPressEvent(QKeyEvent* event) {
}
-//#define VR_MENU_ONLY_IN_HMD
+#define VR_MENU_ONLY_IN_HMD
void Application::keyReleaseEvent(QKeyEvent* event) {
if (event->key() == Qt::Key_Alt && _altPressed && _window->isActiveWindow()) {
#ifdef VR_MENU_ONLY_IN_HMD
- if (OculusManager::isConnected()) {
+ if (isHMDMode()) {
#endif
VrMenu::toggle();
#ifdef VR_MENU_ONLY_IN_HMD
@@ -1949,6 +1949,7 @@ void Application::setActiveFaceTracker() {
#endif
#ifdef HAVE_DDE
bool isUsingDDE = Menu::getInstance()->isOptionChecked(MenuOption::UseCamera);
+ Menu::getInstance()->getActionForOption(MenuOption::BinaryEyelidControl)->setVisible(isUsingDDE);
Menu::getInstance()->getActionForOption(MenuOption::UseAudioForMouth)->setVisible(isUsingDDE);
Menu::getInstance()->getActionForOption(MenuOption::VelocityFilter)->setVisible(isUsingDDE);
Menu::getInstance()->getActionForOption(MenuOption::CalibrateCamera)->setVisible(isUsingDDE);
diff --git a/interface/src/DatagramProcessor.cpp b/interface/src/DatagramProcessor.cpp
index f477be0718..51adb7ba1a 100644
--- a/interface/src/DatagramProcessor.cpp
+++ b/interface/src/DatagramProcessor.cpp
@@ -25,7 +25,7 @@
DatagramProcessor::DatagramProcessor(QObject* parent) :
QObject(parent)
{
-
+
}
void DatagramProcessor::processDatagrams() {
@@ -35,23 +35,23 @@ void DatagramProcessor::processDatagrams() {
if (_isShuttingDown) {
return; // bail early... we're shutting down.
}
-
+
HifiSockAddr senderSockAddr;
-
+
static QByteArray incomingPacket;
-
+
Application* application = Application::getInstance();
auto nodeList = DependencyManager::get();
-
+
while (DependencyManager::get()->getNodeSocket().hasPendingDatagrams()) {
incomingPacket.resize(nodeList->getNodeSocket().pendingDatagramSize());
nodeList->readDatagram(incomingPacket, senderSockAddr.getAddressPointer(), senderSockAddr.getPortPointer());
-
+
_inPacketCount++;
_inByteCount += incomingPacket.size();
-
+
if (nodeList->packetVersionAndHashMatch(incomingPacket)) {
-
+
PacketType incomingType = packetTypeForPacket(incomingPacket);
// only process this packet if we have a match on the packet version
switch (incomingType) {
@@ -72,14 +72,14 @@ void DatagramProcessor::processDatagrams() {
Qt::QueuedConnection,
Q_ARG(QByteArray, incomingPacket));
}
-
+
// update having heard from the audio-mixer and record the bytes received
SharedNodePointer audioMixer = nodeList->sendingNodeForPacket(incomingPacket);
-
+
if (audioMixer) {
audioMixer->setLastHeardMicrostamp(usecTimestampNow());
}
-
+
break;
}
case PacketTypeEntityAddResponse:
@@ -94,7 +94,7 @@ void DatagramProcessor::processDatagrams() {
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
"Application::networkReceive()... _octreeProcessor.queueReceivedPacket()");
SharedNodePointer matchedNode = DependencyManager::get()->sendingNodeForPacket(incomingPacket);
-
+
if (matchedNode) {
// add this packet to our list of octree packets and process them on the octree data processing
application->_octreeProcessor.queueReceivedPacket(matchedNode, incomingPacket);
@@ -107,10 +107,10 @@ void DatagramProcessor::processDatagrams() {
case PacketTypeAvatarBillboard: {
// update having heard from the avatar-mixer and record the bytes received
SharedNodePointer avatarMixer = nodeList->sendingNodeForPacket(incomingPacket);
-
+
if (avatarMixer) {
avatarMixer->setLastHeardMicrostamp(usecTimestampNow());
-
+
QMetaObject::invokeMethod(DependencyManager::get().data(), "processAvatarMixerDatagram",
Q_ARG(const QByteArray&, incomingPacket),
Q_ARG(const QWeakPointer&, avatarMixer));
@@ -135,20 +135,20 @@ void DatagramProcessor::processDatagrams() {
case PacketTypeNoisyMute:
case PacketTypeMuteEnvironment: {
bool mute = !DependencyManager::get()->isMuted();
-
+
if (incomingType == PacketTypeMuteEnvironment) {
glm::vec3 position;
float radius;
-
+
int headerSize = numBytesForPacketHeaderGivenPacketType(PacketTypeMuteEnvironment);
memcpy(&position, incomingPacket.constData() + headerSize, sizeof(glm::vec3));
memcpy(&radius, incomingPacket.constData() + headerSize + sizeof(glm::vec3), sizeof(float));
float distance = glm::distance(DependencyManager::get()->getMyAvatar()->getPosition(),
position);
-
+
mute = mute && (distance < radius);
}
-
+
if (mute) {
DependencyManager::get()->toggleMute();
if (incomingType == PacketTypeMuteEnvironment) {
diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp
index 61213e7334..e1587684cf 100644
--- a/interface/src/Menu.cpp
+++ b/interface/src/Menu.cpp
@@ -388,6 +388,8 @@ Menu::Menu() {
}
#ifdef HAVE_DDE
faceTrackingMenu->addSeparator();
+ QAction* binaryEyelidControl = addCheckableActionToQMenuAndActionHash(faceTrackingMenu, MenuOption::BinaryEyelidControl, 0, true);
+ binaryEyelidControl->setVisible(false);
QAction* useAudioForMouth = addCheckableActionToQMenuAndActionHash(faceTrackingMenu, MenuOption::UseAudioForMouth, 0, true);
useAudioForMouth->setVisible(false);
QAction* ddeFiltering = addCheckableActionToQMenuAndActionHash(faceTrackingMenu, MenuOption::VelocityFilter, 0, true);
diff --git a/interface/src/Menu.h b/interface/src/Menu.h
index 96c8fedf0d..435df47487 100644
--- a/interface/src/Menu.h
+++ b/interface/src/Menu.h
@@ -149,6 +149,7 @@ namespace MenuOption {
const QString AudioStatsShowInjectedStreams = "Audio Stats Show Injected Streams";
const QString AvatarReceiveStats = "Show Receive Stats";
const QString BandwidthDetails = "Bandwidth Details";
+ const QString BinaryEyelidControl = "Binary Eyelid Control";
const QString BlueSpeechSphere = "Blue Sphere While Speaking";
const QString BookmarkLocation = "Bookmark Location";
const QString Bookmarks = "Bookmarks";
diff --git a/interface/src/devices/DdeFaceTracker.cpp b/interface/src/devices/DdeFaceTracker.cpp
index 14728f021b..e0b888746f 100644
--- a/interface/src/devices/DdeFaceTracker.cpp
+++ b/interface/src/devices/DdeFaceTracker.cpp
@@ -137,7 +137,7 @@ struct Packet {
};
static const float STARTING_DDE_MESSAGE_TIME = 0.033f;
-
+static const float DEFAULT_DDE_EYE_CLOSING_THRESHOLD = 0.8f;
static const int CALIBRATION_SAMPLES = 150;
#ifdef WIN32
@@ -182,6 +182,7 @@ DdeFaceTracker::DdeFaceTracker(const QHostAddress& host, quint16 serverPort, qui
_lastEyeBlinks(),
_filteredEyeBlinks(),
_lastEyeCoefficients(),
+ _eyeClosingThreshold("ddeEyeClosingThreshold", DEFAULT_DDE_EYE_CLOSING_THRESHOLD),
_isCalibrating(false),
_calibrationCount(0),
_calibrationValues(),
@@ -194,8 +195,8 @@ DdeFaceTracker::DdeFaceTracker(const QHostAddress& host, quint16 serverPort, qui
_coefficientAverages.resize(NUM_FACESHIFT_BLENDSHAPES);
_calibrationValues.resize(NUM_FACESHIFT_BLENDSHAPES);
- _eyeStates[0] = EYE_OPEN;
- _eyeStates[1] = EYE_OPEN;
+ _eyeStates[0] = EYE_UNCONTROLLED;
+ _eyeStates[1] = EYE_UNCONTROLLED;
connect(&_udpSocket, SIGNAL(readyRead()), SLOT(readPendingDatagrams()));
connect(&_udpSocket, SIGNAL(error(QAbstractSocket::SocketError)), SLOT(socketErrorOccurred(QAbstractSocket::SocketError)));
@@ -450,57 +451,72 @@ void DdeFaceTracker::decodePacket(const QByteArray& buffer) {
// Finesse EyeBlink values
float eyeCoefficients[2];
- for (int i = 0; i < 2; i++) {
- // Scale EyeBlink values so that they can be used to control both EyeBlink and EyeOpen
- // -ve values control EyeOpen; +ve values control EyeBlink
- static const float EYE_CONTROL_THRESHOLD = 0.5f; // Resting eye value
- eyeCoefficients[i] = (_filteredEyeBlinks[i] - EYE_CONTROL_THRESHOLD) / (1.0f - EYE_CONTROL_THRESHOLD);
-
- // Change to closing or opening states
- const float EYE_CONTROL_HYSTERISIS = 0.25f;
- const float EYE_CLOSING_THRESHOLD = 0.8f;
- const float EYE_OPENING_THRESHOLD = EYE_CONTROL_THRESHOLD - EYE_CONTROL_HYSTERISIS;
- if ((_eyeStates[i] == EYE_OPEN || _eyeStates[i] == EYE_OPENING) && eyeCoefficients[i] > EYE_CLOSING_THRESHOLD) {
- _eyeStates[i] = EYE_CLOSING;
- } else if ((_eyeStates[i] == EYE_CLOSED || _eyeStates[i] == EYE_CLOSING)
- && eyeCoefficients[i] < EYE_OPENING_THRESHOLD) {
- _eyeStates[i] = EYE_OPENING;
+ if (Menu::getInstance()->isOptionChecked(MenuOption::BinaryEyelidControl)) {
+ if (_eyeStates[0] == EYE_UNCONTROLLED) {
+ _eyeStates[0] = EYE_OPEN;
+ _eyeStates[1] = EYE_OPEN;
}
- const float EYELID_MOVEMENT_RATE = 10.0f; // units/second
- const float EYE_OPEN_SCALE = 0.2f;
- if (_eyeStates[i] == EYE_CLOSING) {
- // Close eyelid until it's fully closed
- float closingValue = _lastEyeCoefficients[i] + EYELID_MOVEMENT_RATE * _averageMessageTime;
- if (closingValue >= 1.0) {
- _eyeStates[i] = EYE_CLOSED;
- eyeCoefficients[i] = 1.0;
- } else {
- eyeCoefficients[i] = closingValue;
+ for (int i = 0; i < 2; i++) {
+ // Scale EyeBlink values so that they can be used to control both EyeBlink and EyeOpen
+ // -ve values control EyeOpen; +ve values control EyeBlink
+ static const float EYE_CONTROL_THRESHOLD = 0.5f; // Resting eye value
+ eyeCoefficients[i] = (_filteredEyeBlinks[i] - EYE_CONTROL_THRESHOLD) / (1.0f - EYE_CONTROL_THRESHOLD);
+
+ // Change to closing or opening states
+ const float EYE_CONTROL_HYSTERISIS = 0.25f;
+ float eyeClosingThreshold = getEyeClosingThreshold();
+ float eyeOpeningThreshold = eyeClosingThreshold - EYE_CONTROL_HYSTERISIS;
+ if ((_eyeStates[i] == EYE_OPEN || _eyeStates[i] == EYE_OPENING) && eyeCoefficients[i] > eyeClosingThreshold) {
+ _eyeStates[i] = EYE_CLOSING;
+ } else if ((_eyeStates[i] == EYE_CLOSED || _eyeStates[i] == EYE_CLOSING)
+ && eyeCoefficients[i] < eyeOpeningThreshold) {
+ _eyeStates[i] = EYE_OPENING;
}
- } else if (_eyeStates[i] == EYE_OPENING) {
- // Open eyelid until it meets the current adjusted value
- float openingValue = _lastEyeCoefficients[i] - EYELID_MOVEMENT_RATE * _averageMessageTime;
- if (openingValue < eyeCoefficients[i] * EYE_OPEN_SCALE) {
- _eyeStates[i] = EYE_OPEN;
+
+ const float EYELID_MOVEMENT_RATE = 10.0f; // units/second
+ const float EYE_OPEN_SCALE = 0.2f;
+ if (_eyeStates[i] == EYE_CLOSING) {
+ // Close eyelid until it's fully closed
+ float closingValue = _lastEyeCoefficients[i] + EYELID_MOVEMENT_RATE * _averageMessageTime;
+ if (closingValue >= 1.0) {
+ _eyeStates[i] = EYE_CLOSED;
+ eyeCoefficients[i] = 1.0;
+ } else {
+ eyeCoefficients[i] = closingValue;
+ }
+ } else if (_eyeStates[i] == EYE_OPENING) {
+ // Open eyelid until it meets the current adjusted value
+ float openingValue = _lastEyeCoefficients[i] - EYELID_MOVEMENT_RATE * _averageMessageTime;
+ if (openingValue < eyeCoefficients[i] * EYE_OPEN_SCALE) {
+ _eyeStates[i] = EYE_OPEN;
+ eyeCoefficients[i] = eyeCoefficients[i] * EYE_OPEN_SCALE;
+ } else {
+ eyeCoefficients[i] = openingValue;
+ }
+ } else if (_eyeStates[i] == EYE_OPEN) {
+ // Reduce eyelid movement
eyeCoefficients[i] = eyeCoefficients[i] * EYE_OPEN_SCALE;
- } else {
- eyeCoefficients[i] = openingValue;
+ } else if (_eyeStates[i] == EYE_CLOSED) {
+ // Keep eyelid fully closed
+ eyeCoefficients[i] = 1.0;
}
- } else if (_eyeStates[i] == EYE_OPEN) {
- // Reduce eyelid movement
- eyeCoefficients[i] = eyeCoefficients[i] * EYE_OPEN_SCALE;
- } else if (_eyeStates[i] == EYE_CLOSED) {
- // Keep eyelid fully closed
- eyeCoefficients[i] = 1.0;
}
+
+ if (_eyeStates[0] == EYE_OPEN && _eyeStates[1] == EYE_OPEN) {
+ // Couple eyelids
+ eyeCoefficients[0] = eyeCoefficients[1] = (eyeCoefficients[0] + eyeCoefficients[0]) / 2.0f;
+ }
+
+ _lastEyeCoefficients[0] = eyeCoefficients[0];
+ _lastEyeCoefficients[1] = eyeCoefficients[1];
+ } else {
+ _eyeStates[0] = EYE_UNCONTROLLED;
+ _eyeStates[1] = EYE_UNCONTROLLED;
+
+ eyeCoefficients[0] = _filteredEyeBlinks[0];
+ eyeCoefficients[1] = _filteredEyeBlinks[1];
}
- if (_eyeStates[0] == EYE_OPEN && _eyeStates[1] == EYE_OPEN) {
- // Couple eyelids
- eyeCoefficients[0] = eyeCoefficients[1] = (eyeCoefficients[0] + eyeCoefficients[0]) / 2.0f;
- }
- _lastEyeCoefficients[0] = eyeCoefficients[0];
- _lastEyeCoefficients[1] = eyeCoefficients[1];
// Use EyeBlink values to control both EyeBlink and EyeOpen
if (eyeCoefficients[0] > 0) {
@@ -544,6 +560,10 @@ void DdeFaceTracker::decodePacket(const QByteArray& buffer) {
}
}
+void DdeFaceTracker::setEyeClosingThreshold(float eyeClosingThreshold) {
+ _eyeClosingThreshold.set(eyeClosingThreshold);
+}
+
static const int CALIBRATION_BILLBOARD_WIDTH = 240;
static const int CALIBRATION_BILLBOARD_HEIGHT = 180;
static const int CALIBRATION_BILLBOARD_TOP_MARGIN = 60;
diff --git a/interface/src/devices/DdeFaceTracker.h b/interface/src/devices/DdeFaceTracker.h
index 7019802603..8c30c5a4c3 100644
--- a/interface/src/devices/DdeFaceTracker.h
+++ b/interface/src/devices/DdeFaceTracker.h
@@ -50,6 +50,9 @@ public:
float getMouthSmileLeft() const { return getBlendshapeCoefficient(_mouthSmileLeftIndex); }
float getMouthSmileRight() const { return getBlendshapeCoefficient(_mouthSmileRightIndex); }
+ float getEyeClosingThreshold() { return _eyeClosingThreshold.get(); }
+ void setEyeClosingThreshold(float eyeClosingThreshold);
+
public slots:
void setEnabled(bool enabled);
void calibrate();
@@ -89,8 +92,7 @@ private:
int _rightBlinkIndex;
int _leftEyeOpenIndex;
int _rightEyeOpenIndex;
-
- // Brows
+
int _browDownLeftIndex;
int _browDownRightIndex;
int _browUpCenterIndex;
@@ -114,6 +116,7 @@ private:
float _filteredBrowUp;
enum EyeState {
+ EYE_UNCONTROLLED,
EYE_OPEN,
EYE_CLOSING,
EYE_CLOSED,
@@ -123,6 +126,8 @@ private:
float _lastEyeBlinks[2];
float _filteredEyeBlinks[2];
float _lastEyeCoefficients[2];
+ Setting::Handle _eyeClosingThreshold;
+
QVector _coefficientAverages;
bool _isCalibrating;
diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp
index 4622ffd7ed..febd6f90bf 100644
--- a/interface/src/ui/PreferencesDialog.cpp
+++ b/interface/src/ui/PreferencesDialog.cpp
@@ -14,6 +14,7 @@
#include
#include
+#include
#include
#include
#include
@@ -135,6 +136,10 @@ void PreferencesDialog::loadPreferences() {
ui.pupilDilationSlider->setValue(myAvatar->getHead()->getPupilDilation() *
ui.pupilDilationSlider->maximum());
+ auto dde = DependencyManager::get();
+ ui.ddeEyeClosingThresholdSlider->setValue(dde->getEyeClosingThreshold() *
+ ui.ddeEyeClosingThresholdSlider->maximum());
+
auto faceshift = DependencyManager::get();
ui.faceshiftEyeDeflectionSider->setValue(faceshift->getEyeDeflection() *
ui.faceshiftEyeDeflectionSider->maximum());
@@ -222,6 +227,10 @@ void PreferencesDialog::savePreferences() {
qApp->setFieldOfView(ui.fieldOfViewSpin->value());
+ auto dde = DependencyManager::get();
+ dde->setEyeClosingThreshold(ui.ddeEyeClosingThresholdSlider->value() /
+ (float)ui.ddeEyeClosingThresholdSlider->maximum());
+
auto faceshift = DependencyManager::get();
faceshift->setEyeDeflection(ui.faceshiftEyeDeflectionSider->value() /
(float)ui.faceshiftEyeDeflectionSider->maximum());
diff --git a/interface/ui/preferencesDialog.ui b/interface/ui/preferencesDialog.ui
index d26bdaee8a..e74b89075e 100644
--- a/interface/ui/preferencesDialog.ui
+++ b/interface/ui/preferencesDialog.ui
@@ -1256,7 +1256,7 @@
- Pupil dillation
+ Pupil dilation0
@@ -1310,6 +1310,82 @@
+
+
+
+ 0
+
+
+ 7
+
+
+ 0
+
+
+ 7
+
+
+
+
+
+ Arial
+
+
+
+ Camera binary eyelid threshold
+
+
+ 0
+
+
+ ddeEyeClosingThresholdSlider
+
+
+
+
+
+
+
+ Arial
+
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+
+
+
+
+ 0
+ 0
+
+
+
+
+ 130
+ 0
+
+
+
+
+ Arial
+
+
+
+ Qt::Horizontal
+
+
+
+
+
diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp
index 9b99c236b3..48d9655e43 100644
--- a/libraries/networking/src/AddressManager.cpp
+++ b/libraries/networking/src/AddressManager.cpp
@@ -43,11 +43,11 @@ bool AddressManager::isConnected() {
const QUrl AddressManager::currentAddress() const {
QUrl hifiURL;
-
+
hifiURL.setScheme(HIFI_URL_SCHEME);
hifiURL.setHost(_rootPlaceName);
hifiURL.setPath(currentPath());
-
+
return hifiURL;
}
@@ -64,10 +64,10 @@ void AddressManager::storeCurrentAddress() {
}
const QString AddressManager::currentPath(bool withOrientation) const {
-
+
if (_positionGetter) {
QString pathString = "/" + createByteArray(_positionGetter());
-
+
if (withOrientation) {
if (_orientationGetter) {
QString orientationString = createByteArray(_orientationGetter());
@@ -76,9 +76,9 @@ const QString AddressManager::currentPath(bool withOrientation) const {
qCDebug(networking) << "Cannot add orientation to path without a getter for position."
<< "Call AddressManager::setOrientationGetter to pass a function that will return a glm::quat";
}
-
+
}
-
+
return pathString;
} else {
qCDebug(networking) << "Cannot create address path without a getter for position."
@@ -90,29 +90,29 @@ const QString AddressManager::currentPath(bool withOrientation) const {
const JSONCallbackParameters& AddressManager::apiCallbackParameters() {
static bool hasSetupParameters = false;
static JSONCallbackParameters callbackParams;
-
+
if (!hasSetupParameters) {
callbackParams.jsonCallbackReceiver = this;
callbackParams.jsonCallbackMethod = "handleAPIResponse";
callbackParams.errorCallbackReceiver = this;
callbackParams.errorCallbackMethod = "handleAPIError";
}
-
+
return callbackParams;
}
bool AddressManager::handleUrl(const QUrl& lookupUrl) {
if (lookupUrl.scheme() == HIFI_URL_SCHEME) {
-
+
qCDebug(networking) << "Trying to go to URL" << lookupUrl.toString();
-
+
// there are 4 possible lookup strings
-
+
// 1. global place name (name of domain or place) - example: sanfrancisco
// 2. user name (prepended with @) - example: @philip
// 3. location string (posX,posY,posZ/eulerX,eulerY,eulerZ)
// 4. domain network address (IP or dns resolvable hostname)
-
+
// use our regex'ed helpers to figure out what we're supposed to do with this
if (!handleUsername(lookupUrl.authority())) {
// we're assuming this is either a network address or global place name
@@ -120,24 +120,23 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl) {
if (handleNetworkAddress(lookupUrl.host()
+ (lookupUrl.port() == -1 ? "" : ":" + QString::number(lookupUrl.port())))) {
// we may have a path that defines a relative viewpoint - if so we should jump to that now
- handleRelativeViewpoint(lookupUrl.path());
+ handlePath(lookupUrl.path());
} else {
// wasn't an address - lookup the place name
// we may have a path that defines a relative viewpoint - pass that through the lookup so we can go to it after
attemptPlaceNameLookup(lookupUrl.host(), lookupUrl.path());
-
}
}
-
+
return true;
} else if (lookupUrl.toString().startsWith('/')) {
qCDebug(networking) << "Going to relative path" << lookupUrl.path();
-
+
// if this is a relative path then handle it as a relative viewpoint
- handleRelativeViewpoint(lookupUrl.path());
+ handlePath(lookupUrl.path());
emit lookupResultsFinished();
}
-
+
return false;
}
@@ -146,16 +145,16 @@ void AddressManager::handleLookupString(const QString& lookupString) {
// make this a valid hifi URL and handle it off to handleUrl
QString sanitizedString = lookupString.trimmed();
QUrl lookupURL;
-
+
if (!lookupString.startsWith('/')) {
const QRegExp HIFI_SCHEME_REGEX = QRegExp(HIFI_URL_SCHEME + ":\\/{1,2}", Qt::CaseInsensitive);
sanitizedString = sanitizedString.remove(HIFI_SCHEME_REGEX);
-
+
lookupURL = QUrl(HIFI_URL_SCHEME + "://" + sanitizedString);
} else {
lookupURL = QUrl(lookupString);
}
-
+
handleUrl(lookupURL);
}
}
@@ -163,101 +162,103 @@ void AddressManager::handleLookupString(const QString& lookupString) {
void AddressManager::handleAPIResponse(QNetworkReply& requestReply) {
QJsonObject responseObject = QJsonDocument::fromJson(requestReply.readAll()).object();
QJsonObject dataObject = responseObject["data"].toObject();
-
+
goToAddressFromObject(dataObject.toVariantMap(), requestReply);
-
+
emit lookupResultsFinished();
}
const char OVERRIDE_PATH_KEY[] = "override_path";
void AddressManager::goToAddressFromObject(const QVariantMap& dataObject, const QNetworkReply& reply) {
-
+
const QString DATA_OBJECT_PLACE_KEY = "place";
const QString DATA_OBJECT_USER_LOCATION_KEY = "location";
-
+
QVariantMap locationMap;
if (dataObject.contains(DATA_OBJECT_PLACE_KEY)) {
locationMap = dataObject[DATA_OBJECT_PLACE_KEY].toMap();
} else {
locationMap = dataObject[DATA_OBJECT_USER_LOCATION_KEY].toMap();
}
-
+
if (!locationMap.isEmpty()) {
const QString LOCATION_API_ROOT_KEY = "root";
const QString LOCATION_API_DOMAIN_KEY = "domain";
const QString LOCATION_API_ONLINE_KEY = "online";
-
+
if (!locationMap.contains(LOCATION_API_ONLINE_KEY)
|| locationMap[LOCATION_API_ONLINE_KEY].toBool()) {
-
+
QVariantMap rootMap = locationMap[LOCATION_API_ROOT_KEY].toMap();
if (rootMap.isEmpty()) {
rootMap = locationMap;
}
-
+
QVariantMap domainObject = rootMap[LOCATION_API_DOMAIN_KEY].toMap();
-
+
if (!domainObject.isEmpty()) {
const QString DOMAIN_NETWORK_ADDRESS_KEY = "network_address";
const QString DOMAIN_NETWORK_PORT_KEY = "network_port";
const QString DOMAIN_ICE_SERVER_ADDRESS_KEY = "ice_server_address";
-
+
if (domainObject.contains(DOMAIN_NETWORK_ADDRESS_KEY)) {
QString domainHostname = domainObject[DOMAIN_NETWORK_ADDRESS_KEY].toString();
-
+
quint16 domainPort = domainObject.contains(DOMAIN_NETWORK_PORT_KEY)
? domainObject[DOMAIN_NETWORK_PORT_KEY].toUInt()
: DEFAULT_DOMAIN_SERVER_PORT;
-
+
qCDebug(networking) << "Possible domain change required to connect to" << domainHostname
<< "on" << domainPort;
emit possibleDomainChangeRequired(domainHostname, domainPort);
} else {
QString iceServerAddress = domainObject[DOMAIN_ICE_SERVER_ADDRESS_KEY].toString();
-
+
const QString DOMAIN_ID_KEY = "id";
QString domainIDString = domainObject[DOMAIN_ID_KEY].toString();
QUuid domainID(domainIDString);
-
+
qCDebug(networking) << "Possible domain change required to connect to domain with ID" << domainID
<< "via ice-server at" << iceServerAddress;
-
+
emit possibleDomainChangeRequiredViaICEForID(iceServerAddress, domainID);
}
-
+
// set our current root place id to the ID that came back
const QString PLACE_ID_KEY = "id";
_rootPlaceID = rootMap[PLACE_ID_KEY].toUuid();
-
+
// set our current root place name to the name that came back
const QString PLACE_NAME_KEY = "name";
QString newRootPlaceName = rootMap[PLACE_NAME_KEY].toString();
setRootPlaceName(newRootPlaceName);
-
+
// check if we had a path to override the path returned
QString overridePath = reply.property(OVERRIDE_PATH_KEY).toString();
-
+
if (!overridePath.isEmpty()) {
- if (!handleRelativeViewpoint(overridePath)){
- qCDebug(networking) << "User entered path could not be handled as a viewpoint - " << overridePath;
- }
+ handlePath(overridePath);
} else {
// take the path that came back
const QString PLACE_PATH_KEY = "path";
QString returnedPath = locationMap[PLACE_PATH_KEY].toString();
-
+
bool shouldFaceViewpoint = locationMap.contains(LOCATION_API_ONLINE_KEY);
-
+
if (!returnedPath.isEmpty()) {
// try to parse this returned path as a viewpoint, that's the only thing it could be for now
- if (!handleRelativeViewpoint(returnedPath, shouldFaceViewpoint)) {
- qCDebug(networking) << "Received a location path that was could not be handled as a viewpoint -" << returnedPath;
+ if (!handleViewpoint(returnedPath, shouldFaceViewpoint)) {
+ qCDebug(networking) << "Received a location path that was could not be handled as a viewpoint -"
+ << returnedPath;
}
+ } else {
+ // we didn't override the path or get one back - ask the DS for the viewpoint of its index path
+ // which we will jump to if it exists
+ emit pathChangeRequired(INDEX_PATH);
}
}
-
-
+
} else {
qCDebug(networking) << "Received an address manager API response with no domain key. Cannot parse.";
qCDebug(networking) << locationMap;
@@ -274,7 +275,7 @@ void AddressManager::goToAddressFromObject(const QVariantMap& dataObject, const
void AddressManager::handleAPIError(QNetworkReply& errorReply) {
qCDebug(networking) << "AddressManager API error -" << errorReply.error() << "-" << errorReply.errorString();
-
+
if (errorReply.error() == QNetworkReply::ContentNotFoundError) {
emit lookupResultIsNotFound();
}
@@ -286,12 +287,12 @@ const QString GET_PLACE = "/api/v1/places/%1";
void AddressManager::attemptPlaceNameLookup(const QString& lookupString, const QString& overridePath) {
// assume this is a place name and see if we can get any info on it
QString placeName = QUrl::toPercentEncoding(lookupString);
-
+
QVariantMap requestParams;
if (!overridePath.isEmpty()) {
requestParams.insert(OVERRIDE_PATH_KEY, overridePath);
}
-
+
AccountManager::getInstance().sendRequest(GET_PLACE.arg(placeName),
AccountManagerAuth::None,
QNetworkAccessManager::GetOperation,
@@ -302,47 +303,55 @@ void AddressManager::attemptPlaceNameLookup(const QString& lookupString, const Q
bool AddressManager::handleNetworkAddress(const QString& lookupString) {
const QString IP_ADDRESS_REGEX_STRING = "^((?:(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}"
"(?:[0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))(?::(\\d{1,5}))?$";
-
+
const QString HOSTNAME_REGEX_STRING = "^((?:[A-Z0-9]|[A-Z0-9][A-Z0-9\\-]{0,61}[A-Z0-9])"
"(?:\\.(?:[A-Z0-9]|[A-Z0-9][A-Z0-9\\-]{0,61}[A-Z0-9]))+|localhost)(?::(\\d{1,5}))?$";
-
+
QRegExp ipAddressRegex(IP_ADDRESS_REGEX_STRING);
-
+
if (ipAddressRegex.indexIn(lookupString) != -1) {
QString domainIPString = ipAddressRegex.cap(1);
-
+
qint16 domainPort = DEFAULT_DOMAIN_SERVER_PORT;
if (!ipAddressRegex.cap(2).isEmpty()) {
domainPort = (qint16) ipAddressRegex.cap(2).toInt();
}
-
+
emit lookupResultsFinished();
setDomainInfo(domainIPString, domainPort);
-
+
return true;
}
-
+
QRegExp hostnameRegex(HOSTNAME_REGEX_STRING, Qt::CaseInsensitive);
-
+
if (hostnameRegex.indexIn(lookupString) != -1) {
QString domainHostname = hostnameRegex.cap(1);
-
+
quint16 domainPort = DEFAULT_DOMAIN_SERVER_PORT;
-
+
if (!hostnameRegex.cap(2).isEmpty()) {
domainPort = (qint16) hostnameRegex.cap(2).toInt();
}
-
+
emit lookupResultsFinished();
setDomainInfo(domainHostname, domainPort);
-
+
return true;
}
-
+
return false;
}
-bool AddressManager::handleRelativeViewpoint(const QString& lookupString, bool shouldFace) {
+void AddressManager::handlePath(const QString& path) {
+ if (!handleViewpoint(path)) {
+ qCDebug(networking) << "User entered path could not be handled as a viewpoint - " << path <<
+ "- wll attempt to ask domain-server to resolve.";
+ emit pathChangeRequired(path);
+ }
+}
+
+bool AddressManager::handleViewpoint(const QString& viewpointString, bool shouldFace) {
const QString FLOAT_REGEX_STRING = "([-+]?[0-9]*\\.?[0-9]+(?:[eE][-+]?[0-9]+)?)";
const QString SPACED_COMMA_REGEX_STRING = "\\s*,\\s*";
const QString POSITION_REGEX_STRING = QString("\\/") + FLOAT_REGEX_STRING + SPACED_COMMA_REGEX_STRING +
@@ -350,29 +359,29 @@ bool AddressManager::handleRelativeViewpoint(const QString& lookupString, bool s
const QString QUAT_REGEX_STRING = QString("\\/") + FLOAT_REGEX_STRING + SPACED_COMMA_REGEX_STRING +
FLOAT_REGEX_STRING + SPACED_COMMA_REGEX_STRING + FLOAT_REGEX_STRING + SPACED_COMMA_REGEX_STRING +
FLOAT_REGEX_STRING + "\\s*$";
-
+
QRegExp positionRegex(POSITION_REGEX_STRING);
-
- if (positionRegex.indexIn(lookupString) != -1) {
+
+ if (positionRegex.indexIn(viewpointString) != -1) {
// we have at least a position, so emit our signal to say we need to change position
glm::vec3 newPosition(positionRegex.cap(1).toFloat(),
positionRegex.cap(2).toFloat(),
positionRegex.cap(3).toFloat());
-
+
if (!isNaN(newPosition.x) && !isNaN(newPosition.y) && !isNaN(newPosition.z)) {
glm::quat newOrientation;
-
+
QRegExp orientationRegex(QUAT_REGEX_STRING);
-
+
// we may also have an orientation
- if (lookupString[positionRegex.matchedLength() - 1] == QChar('/')
- && orientationRegex.indexIn(lookupString, positionRegex.matchedLength() - 1) != -1) {
-
+ if (viewpointString[positionRegex.matchedLength() - 1] == QChar('/')
+ && orientationRegex.indexIn(viewpointString, positionRegex.matchedLength() - 1) != -1) {
+
glm::quat newOrientation = glm::normalize(glm::quat(orientationRegex.cap(4).toFloat(),
orientationRegex.cap(1).toFloat(),
orientationRegex.cap(2).toFloat(),
orientationRegex.cap(3).toFloat()));
-
+
if (!isNaN(newOrientation.x) && !isNaN(newOrientation.y) && !isNaN(newOrientation.z)
&& !isNaN(newOrientation.w)) {
emit locationChangeRequired(newPosition, true, newOrientation, shouldFace);
@@ -381,31 +390,31 @@ bool AddressManager::handleRelativeViewpoint(const QString& lookupString, bool s
qCDebug(networking) << "Orientation parsed from lookup string is invalid. Will not use for location change.";
}
}
-
+
emit locationChangeRequired(newPosition, false, newOrientation, shouldFace);
-
+
} else {
qCDebug(networking) << "Could not jump to position from lookup string because it has an invalid value.";
}
-
+
return true;
+ } else {
+ return false;
}
-
- return false;
}
const QString GET_USER_LOCATION = "/api/v1/users/%1/location";
bool AddressManager::handleUsername(const QString& lookupString) {
const QString USERNAME_REGEX_STRING = "^@(\\S+)";
-
+
QRegExp usernameRegex(USERNAME_REGEX_STRING);
-
+
if (usernameRegex.indexIn(lookupString) != -1) {
goToUser(usernameRegex.cap(1));
return true;
}
-
+
return false;
}
@@ -420,9 +429,9 @@ void AddressManager::setRootPlaceName(const QString& rootPlaceName) {
void AddressManager::setDomainInfo(const QString& hostname, quint16 port) {
_rootPlaceName = hostname;
_rootPlaceID = QUuid();
-
+
qCDebug(networking) << "Possible domain change required to connect to domain at" << hostname << "on" << port;
-
+
emit possibleDomainChangeRequired(hostname, port);
}
diff --git a/libraries/networking/src/AddressManager.h b/libraries/networking/src/AddressManager.h
index db1cd6a3c8..5831d62603 100644
--- a/libraries/networking/src/AddressManager.h
+++ b/libraries/networking/src/AddressManager.h
@@ -24,6 +24,7 @@
const QString HIFI_URL_SCHEME = "hifi";
const QString DEFAULT_HIFI_ADDRESS = "hifi://entry";
+const QString INDEX_PATH = "/";
typedef const glm::vec3& (*PositionGetter)();
typedef glm::quat (*OrientationGetter)();
@@ -39,32 +40,34 @@ class AddressManager : public QObject, public Dependency {
public:
bool isConnected();
const QString& getProtocol() { return HIFI_URL_SCHEME; };
-
+
const QUrl currentAddress() const;
const QString currentPath(bool withOrientation = true) const;
-
+
const QUuid& getRootPlaceID() const { return _rootPlaceID; }
-
+
const QString& getRootPlaceName() const { return _rootPlaceName; }
void setRootPlaceName(const QString& rootPlaceName);
-
+
void attemptPlaceNameLookup(const QString& lookupString, const QString& overridePath = QString());
-
+
void setPositionGetter(PositionGetter positionGetter) { _positionGetter = positionGetter; }
void setOrientationGetter(OrientationGetter orientationGetter) { _orientationGetter = orientationGetter; }
-
+
void loadSettings(const QString& lookupString = QString());
-
+
public slots:
void handleLookupString(const QString& lookupString);
+
void goToUser(const QString& username);
void goToAddressFromObject(const QVariantMap& addressMap, const QNetworkReply& reply);
-
+ bool goToViewpoint(const QString& viewpointString) { return handleViewpoint(viewpointString); }
+
void storeCurrentAddress();
-
+
void copyAddress();
void copyPath();
-
+
signals:
void lookupResultsFinished();
void lookupResultIsOffline();
@@ -74,6 +77,7 @@ signals:
void locationChangeRequired(const glm::vec3& newPosition,
bool hasOrientationChange, const glm::quat& newOrientation,
bool shouldFaceLocation);
+ void pathChangeRequired(const QString& newPath);
void rootPlaceNameChanged(const QString& newRootPlaceName);
protected:
AddressManager();
@@ -82,15 +86,16 @@ private slots:
void handleAPIError(QNetworkReply& errorReply);
private:
void setDomainInfo(const QString& hostname, quint16 port);
-
+
const JSONCallbackParameters& apiCallbackParameters();
-
+
bool handleUrl(const QUrl& lookupUrl);
-
+
bool handleNetworkAddress(const QString& lookupString);
- bool handleRelativeViewpoint(const QString& pathSubsection, bool shouldFace = false);
+ void handlePath(const QString& path);
+ bool handleViewpoint(const QString& viewpointString, bool shouldFace = false);
bool handleUsername(const QString& lookupString);
-
+
QString _rootPlaceName;
QUuid _rootPlaceID;
PositionGetter _positionGetter;
diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp
index 4f9585cceb..f7d460d7dd 100644
--- a/libraries/networking/src/DomainHandler.cpp
+++ b/libraries/networking/src/DomainHandler.cpp
@@ -36,19 +36,19 @@ DomainHandler::DomainHandler(QObject* parent) :
_settingsObject(),
_failedSettingsRequests(0)
{
-
+
}
void DomainHandler::clearConnectionInfo() {
_uuid = QUuid();
-
+
_icePeer = NetworkPeer();
-
+
if (requiresICE()) {
// if we connected to this domain with ICE, re-set the socket so we reconnect through the ice-server
- _sockAddr.setAddress(QHostAddress::Null);
+ _sockAddr.clear();
}
-
+
setIsConnected(false);
}
@@ -65,12 +65,15 @@ void DomainHandler::softReset() {
void DomainHandler::hardReset() {
softReset();
-
+
qCDebug(networking) << "Hard reset in NodeList DomainHandler.";
_iceDomainID = QUuid();
_iceServerSockAddr = HifiSockAddr();
_hostname = QString();
- _sockAddr.setAddress(QHostAddress::Null);
+ _sockAddr.clear();
+
+ // clear any pending path we may have wanted to ask the previous DS about
+ _pendingPath.clear();
}
void DomainHandler::setSockAddr(const HifiSockAddr& sockAddr, const QString& hostname) {
@@ -80,7 +83,7 @@ void DomainHandler::setSockAddr(const HifiSockAddr& sockAddr, const QString& hos
// change the sockAddr
_sockAddr = sockAddr;
}
-
+
// some callers may pass a hostname, this is not to be used for lookup but for DTLS certificate verification
_hostname = hostname;
}
@@ -93,29 +96,29 @@ void DomainHandler::setUUID(const QUuid& uuid) {
}
void DomainHandler::setHostnameAndPort(const QString& hostname, quint16 port) {
-
+
if (hostname != _hostname || _sockAddr.getPort() != port) {
// re-set the domain info so that auth information is reloaded
hardReset();
-
+
if (hostname != _hostname) {
// set the new hostname
_hostname = hostname;
-
+
qCDebug(networking) << "Updated domain hostname to" << _hostname;
-
+
// re-set the sock addr to null and fire off a lookup of the IP address for this domain-server's hostname
qCDebug(networking, "Looking up DS hostname %s.", _hostname.toLocal8Bit().constData());
QHostInfo::lookupHost(_hostname, this, SLOT(completedHostnameLookup(const QHostInfo&)));
-
+
UserActivityLogger::getInstance().changedDomain(_hostname);
emit hostnameChanged(_hostname);
}
-
+
if (_sockAddr.getPort() != port) {
qCDebug(networking) << "Updated domain port to" << port;
}
-
+
// grab the port by reading the string after the colon
_sockAddr.setPort(port);
}
@@ -125,16 +128,16 @@ void DomainHandler::setIceServerHostnameAndID(const QString& iceServerHostname,
if (id != _uuid) {
// re-set the domain info to connect to new domain
hardReset();
-
+
_iceDomainID = id;
-
+
HifiSockAddr* replaceableSockAddr = &_iceServerSockAddr;
replaceableSockAddr->~HifiSockAddr();
replaceableSockAddr = new (replaceableSockAddr) HifiSockAddr(iceServerHostname, ICE_SERVER_DEFAULT_PORT);
-
+
// refresh our ICE client UUID to something new
_iceClientID = QUuid::createUuid();
-
+
qCDebug(networking) << "ICE required to connect to domain via ice server at" << iceServerHostname;
}
}
@@ -142,23 +145,29 @@ void DomainHandler::setIceServerHostnameAndID(const QString& iceServerHostname,
void DomainHandler::activateICELocalSocket() {
_sockAddr = _icePeer.getLocalSocket();
_hostname = _sockAddr.getAddress().toString();
+ emit completedSocketDiscovery();
}
void DomainHandler::activateICEPublicSocket() {
_sockAddr = _icePeer.getPublicSocket();
_hostname = _sockAddr.getAddress().toString();
+ emit completedSocketDiscovery();
}
void DomainHandler::completedHostnameLookup(const QHostInfo& hostInfo) {
for (int i = 0; i < hostInfo.addresses().size(); i++) {
if (hostInfo.addresses()[i].protocol() == QAbstractSocket::IPv4Protocol) {
_sockAddr.setAddress(hostInfo.addresses()[i]);
+
qCDebug(networking, "DS at %s is at %s", _hostname.toLocal8Bit().constData(),
_sockAddr.getAddress().toString().toLocal8Bit().constData());
+
+ emit completedSocketDiscovery();
+
return;
- }
+ }
}
-
+
// if we got here then we failed to lookup the address
qCDebug(networking, "Failed domain server lookup");
}
@@ -166,10 +175,10 @@ void DomainHandler::completedHostnameLookup(const QHostInfo& hostInfo) {
void DomainHandler::setIsConnected(bool isConnected) {
if (_isConnected != isConnected) {
_isConnected = isConnected;
-
+
if (_isConnected) {
emit connectedToDomain(_hostname);
-
+
// we've connected to new domain - time to ask it for global settings
requestDomainSettings();
} else {
@@ -195,9 +204,9 @@ void DomainHandler::requestDomainSettings() {
settingsJSONURL.setPath("/settings.json");
Assignment::Type assignmentType = Assignment::typeForNodeType(DependencyManager::get()->getOwnerType());
settingsJSONURL.setQuery(QString("type=%1").arg(assignmentType));
-
+
qCDebug(networking) << "Requesting domain-server settings at" << settingsJSONURL.toString();
-
+
QNetworkRequest settingsRequest(settingsJSONURL);
settingsRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT);
QNetworkReply* reply = NetworkAccessManager::getInstance().get(settingsRequest);
@@ -210,23 +219,23 @@ const int MAX_SETTINGS_REQUEST_FAILED_ATTEMPTS = 5;
void DomainHandler::settingsRequestFinished() {
QNetworkReply* settingsReply = reinterpret_cast(sender());
-
+
int replyCode = settingsReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
-
+
if (settingsReply->error() == QNetworkReply::NoError && replyCode != 301 && replyCode != 302) {
// parse the JSON to a QJsonObject and save it
_settingsObject = QJsonDocument::fromJson(settingsReply->readAll()).object();
-
+
qCDebug(networking) << "Received domain settings.";
emit settingsReceived(_settingsObject);
-
+
// reset failed settings requests to 0, we got them
_failedSettingsRequests = 0;
} else {
// error grabbing the settings - in some cases this means we are stuck
// so we should retry until we get it
qCDebug(networking) << "Error getting domain settings -" << settingsReply->errorString() << "- retrying";
-
+
if (++_failedSettingsRequests >= MAX_SETTINGS_REQUEST_FAILED_ATTEMPTS) {
qCDebug(networking) << "Failed to retreive domain-server settings" << MAX_SETTINGS_REQUEST_FAILED_ATTEMPTS
<< "times. Re-setting connection to domain.";
@@ -235,7 +244,7 @@ void DomainHandler::settingsRequestFinished() {
emit settingsReceiveFail();
} else {
requestDomainSettings();
- }
+ }
}
settingsReply->deleteLater();
}
@@ -243,30 +252,30 @@ void DomainHandler::settingsRequestFinished() {
void DomainHandler::parseDTLSRequirementPacket(const QByteArray& dtlsRequirementPacket) {
// figure out the port that the DS wants us to use for us to talk to them with DTLS
int numBytesPacketHeader = numBytesForPacketHeader(dtlsRequirementPacket);
-
+
unsigned short dtlsPort = 0;
memcpy(&dtlsPort, dtlsRequirementPacket.data() + numBytesPacketHeader, sizeof(dtlsPort));
-
+
qCDebug(networking) << "domain-server DTLS port changed to" << dtlsPort << "- Enabling DTLS.";
-
+
_sockAddr.setPort(dtlsPort);
-
+
// initializeDTLSSession();
}
void DomainHandler::processICEResponsePacket(const QByteArray& icePacket) {
QDataStream iceResponseStream(icePacket);
iceResponseStream.skipRawData(numBytesForPacketHeader(icePacket));
-
+
NetworkPeer packetPeer;
iceResponseStream >> packetPeer;
-
+
if (packetPeer.getUUID() != _iceDomainID) {
qCDebug(networking) << "Received a network peer with ID that does not match current domain. Will not attempt connection.";
} else {
qCDebug(networking) << "Received network peer object for domain -" << packetPeer;
_icePeer = packetPeer;
-
+
emit requestICEConnectionAttempt();
}
}
diff --git a/libraries/networking/src/DomainHandler.h b/libraries/networking/src/DomainHandler.h
index 35ef7d8e2c..80a211405b 100644
--- a/libraries/networking/src/DomainHandler.h
+++ b/libraries/networking/src/DomainHandler.h
@@ -31,67 +31,79 @@ class DomainHandler : public QObject {
Q_OBJECT
public:
DomainHandler(QObject* parent = 0);
-
+
void clearConnectionInfo();
void clearSettings();
-
+
const QUuid& getUUID() const { return _uuid; }
void setUUID(const QUuid& uuid);
-
+
const QString& getHostname() const { return _hostname; }
-
+
const QHostAddress& getIP() const { return _sockAddr.getAddress(); }
void setIPToLocalhost() { _sockAddr.setAddress(QHostAddress(QHostAddress::LocalHost)); }
-
+
const HifiSockAddr& getSockAddr() { return _sockAddr; }
void setSockAddr(const HifiSockAddr& sockAddr, const QString& hostname);
-
+
unsigned short getPort() const { return _sockAddr.getPort(); }
void setPort(quint16 port) { _sockAddr.setPort(port); }
-
+
const QUuid& getAssignmentUUID() const { return _assignmentUUID; }
void setAssignmentUUID(const QUuid& assignmentUUID) { _assignmentUUID = assignmentUUID; }
-
+
const QUuid& getICEDomainID() const { return _iceDomainID; }
-
+
const QUuid& getICEClientID() const { return _iceClientID; }
-
+
bool requiresICE() const { return !_iceServerSockAddr.isNull(); }
const HifiSockAddr& getICEServerSockAddr() const { return _iceServerSockAddr; }
NetworkPeer& getICEPeer() { return _icePeer; }
void activateICELocalSocket();
void activateICEPublicSocket();
-
+
bool isConnected() const { return _isConnected; }
void setIsConnected(bool isConnected);
-
+
bool hasSettings() const { return !_settingsObject.isEmpty(); }
void requestDomainSettings();
const QJsonObject& getSettingsObject() const { return _settingsObject; }
-
+
void parseDTLSRequirementPacket(const QByteArray& dtlsRequirementPacket);
void processICEResponsePacket(const QByteArray& icePacket);
-
+
+ void setPendingPath(const QString& pendingPath) { _pendingPath = pendingPath; }
+ const QString& getPendingPath() { return _pendingPath; }
+ void clearPendingPath() { _pendingPath.clear(); }
+
+ bool isSocketKnown() const { return !_sockAddr.getAddress().isNull(); }
+
void softReset();
public slots:
void setHostnameAndPort(const QString& hostname, quint16 port = DEFAULT_DOMAIN_SERVER_PORT);
void setIceServerHostnameAndID(const QString& iceServerHostname, const QUuid& id);
-
+
private slots:
void completedHostnameLookup(const QHostInfo& hostInfo);
void settingsRequestFinished();
signals:
void hostnameChanged(const QString& hostname);
+
+ // NOTE: the emission of completedSocketDiscovery does not mean a connection to DS is established
+ // It means that, either from DNS lookup or ICE, we think we have a socket we can talk to DS on
+ void completedSocketDiscovery();
+
void connectedToDomain(const QString& hostname);
void disconnectedFromDomain();
+
void requestICEConnectionAttempt();
-
+
void settingsReceived(const QJsonObject& domainSettingsObject);
void settingsReceiveFail();
-
+
private:
void hardReset();
-
+
QUuid _uuid;
QString _hostname;
HifiSockAddr _sockAddr;
@@ -103,6 +115,7 @@ private:
bool _isConnected;
QJsonObject _settingsObject;
int _failedSettingsRequests;
+ QString _pendingPath;
};
#endif // hifi_DomainHandler_h
diff --git a/libraries/networking/src/HifiSockAddr.h b/libraries/networking/src/HifiSockAddr.h
index 4d3944012e..9151e51af2 100644
--- a/libraries/networking/src/HifiSockAddr.h
+++ b/libraries/networking/src/HifiSockAddr.h
@@ -29,26 +29,27 @@ public:
HifiSockAddr(const HifiSockAddr& otherSockAddr);
HifiSockAddr(const QString& hostname, quint16 hostOrderPort, bool shouldBlockForLookup = false);
HifiSockAddr(const sockaddr* sockaddr);
-
+
bool isNull() const { return _address.isNull() && _port == 0; }
+ void clear() { _address = QHostAddress::Null; _port = 0;}
HifiSockAddr& operator=(const HifiSockAddr& rhsSockAddr);
void swap(HifiSockAddr& otherSockAddr);
-
+
bool operator==(const HifiSockAddr& rhsSockAddr) const;
bool operator!=(const HifiSockAddr& rhsSockAddr) const { return !(*this == rhsSockAddr); }
-
+
const QHostAddress& getAddress() const { return _address; }
QHostAddress* getAddressPointer() { return &_address; }
void setAddress(const QHostAddress& address) { _address = address; }
-
+
quint16 getPort() const { return _port; }
quint16* getPortPointer() { return &_port; }
void setPort(quint16 port) { _port = port; }
-
+
static int packSockAddr(unsigned char* packetData, const HifiSockAddr& packSockAddr);
static int unpackSockAddr(const unsigned char* packetData, HifiSockAddr& unpackDestSockAddr);
-
+
friend QDebug operator<<(QDebug debug, const HifiSockAddr& sockAddr);
friend QDataStream& operator<<(QDataStream& dataStream, const HifiSockAddr& sockAddr);
friend QDataStream& operator>>(QDataStream& dataStream, HifiSockAddr& sockAddr);
diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp
index 7f57642e9e..dcfb006b2b 100644
--- a/libraries/networking/src/NodeList.cpp
+++ b/libraries/networking/src/NodeList.cpp
@@ -46,23 +46,30 @@ NodeList::NodeList(char newOwnerType, unsigned short socketListenPort, unsigned
firstCall = false;
}
auto addressManager = DependencyManager::get();
-
+
// handle domain change signals from AddressManager
connect(addressManager.data(), &AddressManager::possibleDomainChangeRequired,
&_domainHandler, &DomainHandler::setHostnameAndPort);
-
+
connect(addressManager.data(), &AddressManager::possibleDomainChangeRequiredViaICEForID,
&_domainHandler, &DomainHandler::setIceServerHostnameAndID);
-
+
+ // handle a request for a path change from the AddressManager
+ connect(addressManager.data(), &AddressManager::pathChangeRequired, this, &NodeList::handleDSPathQuery);
+
+ // in case we don't know how to talk to DS when a path change is requested
+ // fire off any pending DS path query when we get socket information
+ connect(&_domainHandler, &DomainHandler::completedSocketDiscovery, this, &NodeList::sendPendingDSPathQuery);
+
// clear our NodeList when the domain changes
connect(&_domainHandler, &DomainHandler::disconnectedFromDomain, this, &NodeList::reset);
-
+
// handle ICE signal from DS so connection is attempted immediately
connect(&_domainHandler, &DomainHandler::requestICEConnectionAttempt, this, &NodeList::handleICEConnectionToDomainServer);
// clear out NodeList when login is finished
connect(&AccountManager::getInstance(), &AccountManager::loginComplete , this, &NodeList::reset);
-
+
// clear our NodeList when logout is requested
connect(&AccountManager::getInstance(), &AccountManager::logoutComplete , this, &NodeList::reset);
}
@@ -75,8 +82,8 @@ qint64 NodeList::sendStats(const QJsonObject& statsObject, const HifiSockAddr& d
QStringList statsStringList = JSONBreakableMarshal::toStringList(statsObject, "");
int numBytesWritten = numBytesForPacketHeader;
-
- // enumerate the resulting strings - pack them and send off packets once we hit MTU size
+
+ // enumerate the resulting strings - pack them and send off packets once we hit MTU size
foreach(const QString& statsItem, statsStringList) {
QByteArray utf8String = statsItem.toUtf8();
utf8String.append('\0');
@@ -85,14 +92,14 @@ qint64 NodeList::sendStats(const QJsonObject& statsObject, const HifiSockAddr& d
// send off the current packet since the next string will make us too big
statsPacket.resize(numBytesWritten);
writeUnverifiedDatagram(statsPacket, destination);
-
+
// reset the number of bytes written to the size of our packet header
numBytesWritten = numBytesForPacketHeader;
}
-
+
// write this string into the stats packet
statsPacket.replace(numBytesWritten, utf8String.size(), utf8String);
-
+
// keep track of the number of bytes we have written
numBytesWritten += utf8String.size();
}
@@ -102,7 +109,7 @@ qint64 NodeList::sendStats(const QJsonObject& statsObject, const HifiSockAddr& d
statsPacket.resize(numBytesWritten);
writeUnverifiedDatagram(statsPacket, destination);
}
-
+
// enumerate the resulting strings, breaking them into MTU sized packets
return 0;
}
@@ -114,26 +121,26 @@ qint64 NodeList::sendStatsToDomainServer(const QJsonObject& statsObject) {
void NodeList::timePingReply(const QByteArray& packet, const SharedNodePointer& sendingNode) {
QDataStream packetStream(packet);
packetStream.skipRawData(numBytesForPacketHeader(packet));
-
+
quint8 pingType;
quint64 ourOriginalTime, othersReplyTime;
-
+
packetStream >> pingType >> ourOriginalTime >> othersReplyTime;
-
+
quint64 now = usecTimestampNow();
int pingTime = now - ourOriginalTime;
int oneWayFlightTime = pingTime / 2; // half of the ping is our one way flight
-
+
// The other node's expected time should be our original time plus the one way flight time
// anything other than that is clock skew
quint64 othersExprectedReply = ourOriginalTime + oneWayFlightTime;
int clockSkew = othersReplyTime - othersExprectedReply;
-
+
sendingNode->setPingMs(pingTime / 1000);
sendingNode->updateClockSkewUsec(clockSkew);
const bool wantDebug = false;
-
+
if (wantDebug) {
qCDebug(networking) << "PING_REPLY from node " << *sendingNode << "\n" <<
" now: " << now << "\n" <<
@@ -172,7 +179,7 @@ void NodeList::processNodeData(const HifiSockAddr& senderSockAddr, const QByteAr
matchingNode->setLastHeardMicrostamp(usecTimestampNow());
QByteArray replyPacket = constructPingReplyPacket(packet);
writeDatagram(replyPacket, matchingNode, senderSockAddr);
-
+
// If we don't have a symmetric socket for this node and this socket doesn't match
// what we have for public and local then set it as the symmetric.
// This allows a server on a reachable port to communicate with nodes on symmetric NATs
@@ -182,7 +189,7 @@ void NodeList::processNodeData(const HifiSockAddr& senderSockAddr, const QByteAr
}
}
}
-
+
break;
}
case PacketTypePingReply: {
@@ -190,14 +197,14 @@ void NodeList::processNodeData(const HifiSockAddr& senderSockAddr, const QByteAr
if (sendingNode) {
sendingNode->setLastHeardMicrostamp(usecTimestampNow());
-
+
// activate the appropriate socket for this node, if not yet updated
activateSocketFromNodeCommunication(packet, sendingNode);
-
+
// set the ping time for this node for stat collection
timePingReply(packet, sendingNode);
}
-
+
break;
}
case PacketTypeUnverifiedPing: {
@@ -208,7 +215,7 @@ void NodeList::processNodeData(const HifiSockAddr& senderSockAddr, const QByteAr
}
case PacketTypeUnverifiedPingReply: {
qCDebug(networking) << "Received reply from domain-server on" << senderSockAddr;
-
+
// for now we're unsafely assuming this came back from the domain
if (senderSockAddr == _domainHandler.getICEPeer().getLocalSocket()) {
qCDebug(networking) << "Connecting to domain using local socket";
@@ -226,6 +233,10 @@ void NodeList::processNodeData(const HifiSockAddr& senderSockAddr, const QByteAr
processSTUNResponse(packet);
break;
}
+ case PacketTypeDomainServerPathResponse: {
+ handleDSPathQueryResponse(packet);
+ break;
+ }
default:
LimitedNodeList::processNodeData(senderSockAddr, packet);
break;
@@ -234,17 +245,17 @@ void NodeList::processNodeData(const HifiSockAddr& senderSockAddr, const QByteAr
void NodeList::reset() {
LimitedNodeList::reset();
-
+
_numNoReplyDomainCheckIns = 0;
// refresh the owner UUID to the NULL UUID
setSessionUUID(QUuid());
-
+
if (sender() != &_domainHandler) {
// clear the domain connection information, unless they're the ones that asked us to reset
_domainHandler.softReset();
}
-
+
// if we setup the DTLS socket, also disconnect from the DTLS socket readyRead() so it can handle handshaking
if (_dtlsSocket) {
disconnect(_dtlsSocket, 0, this, 0);
@@ -267,7 +278,7 @@ void NodeList::sendSTUNRequest() {
if (!_hasCompletedInitialSTUNFailure) {
qCDebug(networking) << "Sending intial stun request to" << STUN_SERVER_HOSTNAME;
}
-
+
LimitedNodeList::sendSTUNRequest();
_stunRequestsSinceSuccess++;
@@ -292,9 +303,9 @@ bool NodeList::processSTUNResponse(const QByteArray& packet) {
if (LimitedNodeList::processSTUNResponse(packet)) {
// reset the number of failed STUN requests since last success
_stunRequestsSinceSuccess = 0;
-
+
_hasCompletedInitialSTUNFailure = true;
-
+
return true;
} else {
return false;
@@ -309,23 +320,23 @@ void NodeList::sendDomainServerCheckIn() {
} else if (_domainHandler.getIP().isNull() && _domainHandler.requiresICE()) {
handleICEConnectionToDomainServer();
} else if (!_domainHandler.getIP().isNull()) {
-
+
bool isUsingDTLS = false;
-
+
PacketType domainPacketType = !_domainHandler.isConnected()
? PacketTypeDomainConnectRequest : PacketTypeDomainListRequest;
-
+
if (!_domainHandler.isConnected()) {
qCDebug(networking) << "Sending connect request to domain-server at" << _domainHandler.getHostname();
-
+
// is this our localhost domain-server?
// if so we need to make sure we have an up-to-date local port in case it restarted
-
+
if (_domainHandler.getSockAddr().getAddress() == QHostAddress::LocalHost
|| _domainHandler.getHostname() == "localhost") {
-
+
static QSharedMemory* localDSPortSharedMem = NULL;
-
+
quint16 domainPort = DEFAULT_DOMAIN_SERVER_PORT;
getLocalServerPortFromSharedMemory(DOMAIN_SERVER_LOCAL_PORT_SMEM_KEY,
localDSPortSharedMem,
@@ -333,12 +344,12 @@ void NodeList::sendDomainServerCheckIn() {
qCDebug(networking) << "Local domain-server port read from shared memory (or default) is" << domainPort;
_domainHandler.setPort(domainPort);
}
-
+
}
-
+
// construct the DS check in packet
QUuid packetUUID = _sessionUUID;
-
+
if (domainPacketType == PacketTypeDomainConnectRequest) {
if (!_domainHandler.getAssignmentUUID().isNull()) {
// this is a connect request and we're an assigned node
@@ -351,70 +362,166 @@ void NodeList::sendDomainServerCheckIn() {
packetUUID = _domainHandler.getICEClientID();
}
}
-
+
QByteArray domainServerPacket = byteArrayWithUUIDPopulatedHeader(domainPacketType, packetUUID);
QDataStream packetStream(&domainServerPacket, QIODevice::Append);
-
+
// pack our data to send to the domain-server
packetStream << _ownerType << _publicSockAddr << _localSockAddr << _nodeTypesOfInterest.toList();
-
-
+
+
// if this is a connect request, and we can present a username signature, send it along
if (!_domainHandler.isConnected()) {
DataServerAccountInfo& accountInfo = AccountManager::getInstance().getAccountInfo();
packetStream << accountInfo.getUsername();
-
+
const QByteArray& usernameSignature = AccountManager::getInstance().getAccountInfo().getUsernameSignature();
-
+
if (!usernameSignature.isEmpty()) {
qCDebug(networking) << "Including username signature in domain connect request.";
packetStream << usernameSignature;
}
}
-
+
if (!isUsingDTLS) {
writeUnverifiedDatagram(domainServerPacket, _domainHandler.getSockAddr());
}
-
+
const int NUM_DOMAIN_SERVER_CHECKINS_PER_STUN_REQUEST = 5;
static unsigned int numDomainCheckins = 0;
-
+
// send a STUN request every Nth domain server check in so we update our public socket, if required
if (numDomainCheckins++ % NUM_DOMAIN_SERVER_CHECKINS_PER_STUN_REQUEST == 0) {
sendSTUNRequest();
}
-
+
if (_numNoReplyDomainCheckIns >= MAX_SILENT_DOMAIN_SERVER_CHECK_INS) {
// we haven't heard back from DS in MAX_SILENT_DOMAIN_SERVER_CHECK_INS
// so emit our signal that says that
emit limitOfSilentDomainCheckInsReached();
}
-
+
// increment the count of un-replied check-ins
_numNoReplyDomainCheckIns++;
}
}
+void NodeList::handleDSPathQuery(const QString& newPath) {
+ if (_domainHandler.isSocketKnown()) {
+ // if we have a DS socket we assume it will get this packet and send if off right away
+ sendDSPathQuery(newPath);
+ } else {
+ // otherwise we make it pending so that it can be sent once a connection is established
+ _domainHandler.setPendingPath(newPath);
+ }
+}
+
+void NodeList::sendPendingDSPathQuery() {
+
+ QString pendingPath = _domainHandler.getPendingPath();
+
+ if (!pendingPath.isEmpty()) {
+ qCDebug(networking) << "Attemping to send pending query to DS for path" << pendingPath;
+
+ // this is a slot triggered if we just established a network link with a DS and want to send a path query
+ sendDSPathQuery(_domainHandler.getPendingPath());
+
+ // clear whatever the pending path was
+ _domainHandler.clearPendingPath();
+ }
+}
+
+void NodeList::sendDSPathQuery(const QString& newPath) {
+ // only send a path query if we know who our DS is or is going to be
+ if (_domainHandler.isSocketKnown()) {
+ // construct the path query packet
+ QByteArray pathQueryPacket = byteArrayWithPopulatedHeader(PacketTypeDomainServerPathQuery);
+
+ // get the UTF8 representation of path query
+ QByteArray pathQueryUTF8 = newPath.toUtf8();
+
+ // get the size of the UTF8 representation of the desired path
+ quint16 numPathBytes = pathQueryUTF8.size();
+
+ if (pathQueryPacket.size() + numPathBytes + sizeof(numPathBytes) < MAX_PACKET_SIZE) {
+ // append the size of the path to the query packet
+ pathQueryPacket.append(reinterpret_cast(&numPathBytes), sizeof(numPathBytes));
+
+ // append the path itself to the query packet
+ pathQueryPacket.append(pathQueryUTF8);
+
+ qCDebug(networking) << "Sending a path query packet for path" << newPath << "to domain-server at"
+ << _domainHandler.getSockAddr();
+
+ // send off the path query
+ writeUnverifiedDatagram(pathQueryPacket, _domainHandler.getSockAddr());
+ } else {
+ qCDebug(networking) << "Path" << newPath << "would make PacketTypeDomainServerPathQuery packet > MAX_PACKET_SIZE." <<
+ "Will not send query.";
+ }
+ }
+}
+
+void NodeList::handleDSPathQueryResponse(const QByteArray& packet) {
+ // This is a response to a path query we theoretically made.
+ // In the future we may want to check that this was actually from our DS and for a query we actually made.
+
+ int numHeaderBytes = numBytesForPacketHeaderGivenPacketType(PacketTypeDomainServerPathResponse);
+ const char* startPosition = packet.data() + numHeaderBytes;
+ const char* currentPosition = startPosition;
+
+ // figure out how many bytes the path query is
+ qint16 numPathBytes;
+ memcpy(&numPathBytes, currentPosition, sizeof(numPathBytes));
+ currentPosition += sizeof(numPathBytes);
+
+ // make sure it is safe to pull the path
+ if (numPathBytes <= packet.size() - numHeaderBytes - (currentPosition - startPosition)) {
+ // pull the path from the packet
+ QString pathQuery = QString::fromUtf8(currentPosition, numPathBytes);
+ currentPosition += numPathBytes;
+
+ // figure out how many bytes the viewpoint is
+ qint16 numViewpointBytes;
+ memcpy(&numViewpointBytes, currentPosition, sizeof(numViewpointBytes));
+ currentPosition += sizeof(numViewpointBytes);
+
+ // make sure it is safe to pull the viewpoint
+ if (numViewpointBytes <= packet.size() - numHeaderBytes - (currentPosition - startPosition)) {
+ // pull the viewpoint from the packet
+ QString viewpoint = QString::fromUtf8(currentPosition, numViewpointBytes);
+
+ // Hand it off to the AddressManager so it can handle it as a relative viewpoint
+ if (DependencyManager::get()->goToViewpoint(viewpoint)) {
+ qCDebug(networking) << "Going to viewpoint" << viewpoint << "which was the lookup result for path" << pathQuery;
+ } else {
+ qCDebug(networking) << "Could not go to viewpoint" << viewpoint
+ << "which was the lookup result for path" << pathQuery;
+ }
+ }
+ }
+}
+
void NodeList::handleICEConnectionToDomainServer() {
if (_domainHandler.getICEPeer().isNull()
|| _domainHandler.getICEPeer().getConnectionAttempts() >= MAX_ICE_CONNECTION_ATTEMPTS) {
-
+
_domainHandler.getICEPeer().resetConnectionAttemps();
-
+
LimitedNodeList::sendHeartbeatToIceServer(_domainHandler.getICEServerSockAddr(),
_domainHandler.getICEClientID(),
_domainHandler.getICEDomainID());
} else {
qCDebug(networking) << "Sending ping packets to establish connectivity with domain-server with ID"
<< uuidStringWithoutCurlyBraces(_domainHandler.getICEDomainID());
-
+
// send the ping packet to the local and public sockets for this node
QByteArray localPingPacket = constructPingPacket(PingType::Local, false, _domainHandler.getICEClientID());
writeUnverifiedDatagram(localPingPacket, _domainHandler.getICEPeer().getLocalSocket());
-
+
QByteArray publicPingPacket = constructPingPacket(PingType::Public, false, _domainHandler.getICEClientID());
writeUnverifiedDatagram(publicPingPacket, _domainHandler.getICEPeer().getPublicSocket());
-
+
_domainHandler.getICEPeer().incrementConnectionAttempts();
}
}
@@ -422,18 +529,18 @@ void NodeList::handleICEConnectionToDomainServer() {
int NodeList::processDomainServerList(const QByteArray& packet) {
// this is a packet from the domain server, reset the count of un-replied check-ins
_numNoReplyDomainCheckIns = 0;
-
+
// if this was the first domain-server list from this domain, we've now connected
if (!_domainHandler.isConnected()) {
_domainHandler.setUUID(uuidFromPacketHeader(packet));
_domainHandler.setIsConnected(true);
}
-
+
int readNodes = 0;
QDataStream packetStream(packet);
packetStream.skipRawData(numBytesForPacketHeader(packet));
-
+
// pull our owner UUID from the packet, it's always the first thing
QUuid newUUID;
packetStream >> newUUID;
@@ -446,7 +553,7 @@ int NodeList::processDomainServerList(const QByteArray& packet) {
bool thisNodeCanRez;
packetStream >> thisNodeCanRez;
setThisNodeCanRez(thisNodeCanRez);
-
+
// pull each node in the packet
while(packetStream.device()->pos() < packet.size()) {
// setup variables to read into from QDataStream
@@ -466,11 +573,11 @@ int NodeList::processDomainServerList(const QByteArray& packet) {
SharedNodePointer node = addOrUpdateNode(nodeUUID, nodeType, nodePublicSocket,
nodeLocalSocket, canAdjustLocks, canRez);
-
+
packetStream >> connectionUUID;
node->setConnectionSecret(connectionUUID);
}
-
+
// ping inactive nodes in conjunction with receipt of list from domain-server
// this makes it happen every second and also pings any newly added nodes
pingInactiveNodes();
@@ -479,25 +586,25 @@ int NodeList::processDomainServerList(const QByteArray& packet) {
}
void NodeList::sendAssignment(Assignment& assignment) {
-
+
PacketType assignmentPacketType = assignment.getCommand() == Assignment::CreateCommand
? PacketTypeCreateAssignment
: PacketTypeRequestAssignment;
-
+
QByteArray packet = byteArrayWithPopulatedHeader(assignmentPacketType);
QDataStream packetStream(&packet, QIODevice::Append);
-
+
packetStream << assignment;
_nodeSocket.writeDatagram(packet, _assignmentServerSocket.getAddress(), _assignmentServerSocket.getPort());
}
void NodeList::pingPunchForInactiveNode(const SharedNodePointer& node) {
-
+
// send the ping packet to the local and public sockets for this node
QByteArray localPingPacket = constructPingPacket(PingType::Local);
writeDatagram(localPingPacket, node, node->getLocalSocket());
-
+
QByteArray publicPingPacket = constructPingPacket(PingType::Public);
writeDatagram(publicPingPacket, node, node->getPublicSocket());
@@ -520,10 +627,10 @@ void NodeList::activateSocketFromNodeCommunication(const QByteArray& packet, con
// deconstruct this ping packet to see if it is a public or local reply
QDataStream packetStream(packet);
packetStream.skipRawData(numBytesForPacketHeader(packet));
-
+
quint8 pingType;
packetStream >> pingType;
-
+
// if this is a local or public ping then we can activate a socket
// we do nothing with agnostic pings, those are simply for timing
if (pingType == PingType::Local && sendingNode->getActiveSocket() != &sendingNode->getLocalSocket()) {
diff --git a/libraries/networking/src/NodeList.h b/libraries/networking/src/NodeList.h
index d907da6c5a..39b1e3e2d2 100644
--- a/libraries/networking/src/NodeList.h
+++ b/libraries/networking/src/NodeList.h
@@ -43,7 +43,7 @@ class Assignment;
class NodeList : public LimitedNodeList {
Q_OBJECT
SINGLETON_DEPENDENCY
-
+
public:
NodeType_t getOwnerType() const { return _ownerType; }
void setOwnerType(NodeType_t ownerType) { _ownerType = ownerType; }
@@ -53,42 +53,49 @@ public:
int getNumNoReplyDomainCheckIns() const { return _numNoReplyDomainCheckIns; }
DomainHandler& getDomainHandler() { return _domainHandler; }
-
+
const NodeSet& getNodeInterestSet() const { return _nodeTypesOfInterest; }
void addNodeTypeToInterestSet(NodeType_t nodeTypeToAdd);
void addSetOfNodeTypesToNodeInterestSet(const NodeSet& setOfNodeTypes);
void resetNodeInterestSet() { _nodeTypesOfInterest.clear(); }
void processNodeData(const HifiSockAddr& senderSockAddr, const QByteArray& packet);
-
+
int processDomainServerList(const QByteArray& packet);
void setAssignmentServerSocket(const HifiSockAddr& serverSocket) { _assignmentServerSocket = serverSocket; }
void sendAssignment(Assignment& assignment);
-
+
void pingPunchForInactiveNode(const SharedNodePointer& node);
public slots:
void reset();
void sendDomainServerCheckIn();
void pingInactiveNodes();
+ void handleDSPathQuery(const QString& newPath);
signals:
void limitOfSilentDomainCheckInsReached();
+private slots:
+ void sendPendingDSPathQuery();
private:
NodeList() : LimitedNodeList(0, 0) { assert(false); } // Not implemented, needed for DependencyManager templates compile
NodeList(char ownerType, unsigned short socketListenPort = 0, unsigned short dtlsListenPort = 0);
NodeList(NodeList const&); // Don't implement, needed to avoid copies of singleton
void operator=(NodeList const&); // Don't implement, needed to avoid copies of singleton
-
+
void sendSTUNRequest();
bool processSTUNResponse(const QByteArray& packet);
void handleICEConnectionToDomainServer();
-
+
void processDomainServerAuthRequest(const QByteArray& packet);
void requestAuthForDomainServer();
void activateSocketFromNodeCommunication(const QByteArray& packet, const SharedNodePointer& sendingNode);
void timePingReply(const QByteArray& packet, const SharedNodePointer& sendingNode);
-
+
+ void handleDSPathQueryResponse(const QByteArray& packet);
+
+ void sendDSPathQuery(const QString& newPath);
+
NodeType_t _ownerType;
NodeSet _nodeTypesOfInterest;
DomainHandler _domainHandler;
@@ -96,7 +103,7 @@ private:
HifiSockAddr _assignmentServerSocket;
bool _hasCompletedInitialSTUNFailure;
unsigned int _stunRequestsSinceSuccess;
-
+
friend class Application;
};
diff --git a/libraries/networking/src/PacketHeaders.cpp b/libraries/networking/src/PacketHeaders.cpp
index 5f9c5c4fd0..887634cf80 100644
--- a/libraries/networking/src/PacketHeaders.cpp
+++ b/libraries/networking/src/PacketHeaders.cpp
@@ -72,7 +72,7 @@ PacketVersion versionForPacketType(PacketType packetType) {
return 1;
case PacketTypeEntityAddOrEdit:
case PacketTypeEntityData:
- return VERSION_ENTITIES_PARTICLE_ENTITIES_HAVE_TEXTURES;
+ return VERSION_ENTITIES_HAVE_LINE_TYPE;
case PacketTypeEntityErase:
return 2;
case PacketTypeAudioStreamStats:
diff --git a/libraries/networking/src/PacketHeaders.h b/libraries/networking/src/PacketHeaders.h
index 6f71655edf..1dff5d7c79 100644
--- a/libraries/networking/src/PacketHeaders.h
+++ b/libraries/networking/src/PacketHeaders.h
@@ -47,8 +47,8 @@ enum PacketType {
PacketTypeMuteEnvironment,
PacketTypeAudioStreamStats,
PacketTypeDataServerConfirm, // 20
- UNUSED_1,
- UNUSED_2,
+ PacketTypeDomainServerPathQuery,
+ PacketTypeDomainServerPathResponse,
UNUSED_3,
UNUSED_4,
UNUSED_5, // 25
@@ -96,7 +96,8 @@ const QSet NON_VERIFIED_PACKETS = QSet()
<< PacketTypeNodeJsonStats << PacketTypeEntityQuery
<< PacketTypeOctreeDataNack << PacketTypeEntityEditNack
<< PacketTypeIceServerHeartbeat << PacketTypeIceServerHeartbeatResponse
- << PacketTypeUnverifiedPing << PacketTypeUnverifiedPingReply << PacketTypeStopNode;
+ << PacketTypeUnverifiedPing << PacketTypeUnverifiedPingReply << PacketTypeStopNode
+ << PacketTypeDomainServerPathQuery << PacketTypeDomainServerPathResponse;
const QSet SEQUENCE_NUMBERED_PACKETS = QSet()
<< PacketTypeAvatarData;
@@ -122,7 +123,7 @@ int numSequenceNumberBytesForType(PacketType packetType);
int numBytesForPacketHeader(const QByteArray& packet);
int numBytesForPacketHeader(const char* packet);
-int numBytesForArithmeticCodedPacketType(PacketType packetType);
+int numBytesForArithmeticCodedPacketType(PacketType packetType);
int numBytesForPacketHeaderGivenPacketType(PacketType packetType);
QUuid uuidFromPacketHeader(const QByteArray& packet);
@@ -138,9 +139,9 @@ QByteArray hashForPacketAndConnectionUUID(const QByteArray& packet, const QUuid&
PacketSequenceNumber sequenceNumberFromHeader(const QByteArray& packet, PacketType packetType = PacketTypeUnknown);
-void replaceHashInPacket(QByteArray& packet, const QUuid& connectionUUID, PacketType packetType = PacketTypeUnknown);
+void replaceHashInPacket(QByteArray& packet, const QUuid& connectionUUID, PacketType packetType = PacketTypeUnknown);
-void replaceSequenceNumberInPacket(QByteArray& packet, PacketSequenceNumber sequenceNumber,
+void replaceSequenceNumberInPacket(QByteArray& packet, PacketSequenceNumber sequenceNumber,
PacketType packetType = PacketTypeUnknown);
void replaceHashAndSequenceNumberInPacket(QByteArray& packet, const QUuid& connectionUUID, PacketSequenceNumber sequenceNumber,
@@ -174,5 +175,6 @@ const PacketVersion VERSION_ENTITIES_ZONE_ENTITIES_HAVE_ATMOSPHERE = 20;
const PacketVersion VERSION_ENTITIES_ZONE_ENTITIES_HAVE_SKYBOX = 21;
const PacketVersion VERSION_ENTITIES_ZONE_ENTITIES_STAGE_HAS_AUTOMATIC_HOURDAY = 22;
const PacketVersion VERSION_ENTITIES_PARTICLE_ENTITIES_HAVE_TEXTURES = 23;
+const PacketVersion VERSION_ENTITIES_HAVE_LINE_TYPE = 24;
#endif // hifi_PacketHeaders_h
diff --git a/libraries/shared/src/HifiConfigVariantMap.cpp b/libraries/shared/src/HifiConfigVariantMap.cpp
index c92260210e..3fe5d9ec3f 100644
--- a/libraries/shared/src/HifiConfigVariantMap.cpp
+++ b/libraries/shared/src/HifiConfigVariantMap.cpp
@@ -72,7 +72,7 @@ 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
configFilePath = argumentList[configIndex + 1];
@@ -82,8 +82,8 @@ QVariantMap HifiConfigVariantMap::mergeCLParametersWithJSONConfig(const QStringL
QCoreApplication::organizationName(),
QCoreApplication::applicationName());
}
-
-
+
+
return mergedMap;
}
@@ -94,23 +94,23 @@ HifiConfigVariantMap::HifiConfigVariantMap() :
_userConfig(),
_mergedConfig()
{
-
+
}
void HifiConfigVariantMap::loadMasterAndUserConfig(const QStringList& argumentList) {
// check if there is a master config file
const QString MASTER_CONFIG_FILE_OPTION = "--master-config";
-
+
int masterConfigIndex = argumentList.indexOf(MASTER_CONFIG_FILE_OPTION);
if (masterConfigIndex != -1) {
QString masterConfigFilepath = argumentList[masterConfigIndex + 1];
-
+
loadMapFromJSONFile(_masterConfig, masterConfigFilepath);
}
-
+
// load the user config
const QString USER_CONFIG_FILE_OPTION = "--user-config";
-
+
int userConfigIndex = argumentList.indexOf(USER_CONFIG_FILE_OPTION);
if (userConfigIndex != -1) {
_userConfigFilename = argumentList[userConfigIndex + 1];
@@ -119,26 +119,26 @@ void HifiConfigVariantMap::loadMasterAndUserConfig(const QStringList& argumentLi
QCoreApplication::organizationName(),
QCoreApplication::applicationName());
}
-
+
loadMapFromJSONFile(_userConfig, _userConfigFilename);
-
+
// the merged config is initially matched to the master config
_mergedConfig = _masterConfig;
-
+
// then we merge in anything missing from the user config
addMissingValuesToExistingMap(_mergedConfig, _userConfig);
}
void HifiConfigVariantMap::loadMapFromJSONFile(QVariantMap& existingMap, const QString& filename) {
QFile configFile(filename);
-
+
if (configFile.exists()) {
qCDebug(shared) << "Reading JSON config file at" << filename;
configFile.open(QIODevice::ReadOnly);
-
+
QJsonDocument configDocument = QJsonDocument::fromJson(configFile.readAll());
existingMap = configDocument.toVariant().toMap();
-
+
} else {
qCDebug(shared) << "Could not find JSON config file at" << filename;
}
@@ -148,7 +148,7 @@ void HifiConfigVariantMap::addMissingValuesToExistingMap(QVariantMap& existingMa
foreach(const QString& key, newMap.keys()) {
if (existingMap.contains(key)) {
// if this is just a regular value, we're done - we don't ovveride
-
+
if (newMap[key].canConvert(QMetaType::QVariantMap) && existingMap[key].canConvert(QMetaType::QVariantMap)) {
// there's a variant map below and the existing map has one too, so we need to keep recursing
addMissingValuesToExistingMap(*static_cast(existingMap[key].data()), newMap[key].toMap());
@@ -159,11 +159,11 @@ void HifiConfigVariantMap::addMissingValuesToExistingMap(QVariantMap& existingMa
}
}
-const QVariant* valueForKeyPath(QVariantMap& variantMap, const QString& keyPath) {
+QVariant* valueForKeyPath(QVariantMap& variantMap, const QString& keyPath) {
int dotIndex = keyPath.indexOf('.');
-
+
QString firstKey = (dotIndex == -1) ? keyPath : keyPath.mid(0, dotIndex);
-
+
if (variantMap.contains(firstKey)) {
if (dotIndex == -1) {
return &variantMap[firstKey];
@@ -171,6 +171,6 @@ const QVariant* valueForKeyPath(QVariantMap& variantMap, const QString& keyPath)
return valueForKeyPath(*static_cast(variantMap[firstKey].data()), keyPath.mid(dotIndex + 1));
}
}
-
+
return NULL;
}
diff --git a/libraries/shared/src/HifiConfigVariantMap.h b/libraries/shared/src/HifiConfigVariantMap.h
index 6bdeb15589..3566f446a2 100644
--- a/libraries/shared/src/HifiConfigVariantMap.h
+++ b/libraries/shared/src/HifiConfigVariantMap.h
@@ -17,26 +17,26 @@
class HifiConfigVariantMap {
public:
static QVariantMap mergeCLParametersWithJSONConfig(const QStringList& argumentList);
-
+
HifiConfigVariantMap();
void loadMasterAndUserConfig(const QStringList& argumentList);
-
+
const QVariantMap& getMasterConfig() const { return _masterConfig; }
QVariantMap& getUserConfig() { return _userConfig; }
QVariantMap& getMergedConfig() { return _mergedConfig; }
-
+
const QString& getUserConfigFilename() const { return _userConfigFilename; }
private:
QString _userConfigFilename;
-
+
QVariantMap _masterConfig;
QVariantMap _userConfig;
QVariantMap _mergedConfig;
-
+
void loadMapFromJSONFile(QVariantMap& existingMap, const QString& filename);
void addMissingValuesToExistingMap(QVariantMap& existingMap, const QVariantMap& newMap);
};
-const QVariant* valueForKeyPath(QVariantMap& variantMap, const QString& keyPath);
+QVariant* valueForKeyPath(QVariantMap& variantMap, const QString& keyPath);
#endif // hifi_HifiConfigVariantMap_h
diff --git a/libraries/ui/src/InfoView.cpp b/libraries/ui/src/InfoView.cpp
index 3f80c97c4c..dd754f209a 100644
--- a/libraries/ui/src/InfoView.cpp
+++ b/libraries/ui/src/InfoView.cpp
@@ -51,15 +51,16 @@ void InfoView::show(const QString& path, bool firstOrChangedOnly) {
}
if (firstOrChangedOnly) {
const QString lastVersion = infoVersion.get();
+ const QString version = fetchVersion(url);
// If we have version information stored
if (lastVersion != QString::null) {
// Check to see the document version. If it's valid and matches
// the stored version, we're done, so exit
- const QString version = fetchVersion(url);
if (version == QString::null || version == lastVersion) {
return;
}
}
+ infoVersion.set(version);
}
auto offscreenUi = DependencyManager::get();
QString infoViewName(NAME + "_" + path);