Merge pull request #4847 from birarda/domain-paths

add initial support for local paths in domain
This commit is contained in:
Philip Rosedale 2015-05-12 17:09:13 -07:00
commit 349886c146
20 changed files with 1169 additions and 818 deletions

View file

@ -1,7 +1,7 @@
[
{
"name": "metaverse",
"label": "Metaverse Registration",
"label": "Metaverse / Networking",
"settings": [
{
"name": "access_token",
@ -44,6 +44,29 @@
}
]
},
{
"label": "Paths",
"settings": [
{
"name": "paths",
"label": "",
"help": "Clients can enter a path to reach an exact viewpoint in your domain.<br/>Add rows to the table below to map a path to a viewpoint.<br/>The index path ( / ) is where clients will enter if they do not enter an explicit path.",
"type": "table",
"key": {
"name": "path",
"label": "Path",
"placeholder": "/"
},
"columns": [
{
"name": "viewpoint",
"label": "Viewpoint",
"placeholder": "/512,512,512"
}
]
}
]
},
{
"name": "security",
"label": "Security",

View file

@ -21,34 +21,34 @@ var Settings = {
};
var viewHelpers = {
getFormGroup: function(groupName, setting, values, isAdvanced, isLocked) {
setting_name = groupName + "." + setting.name
getFormGroup: function(keypath, setting, values, isAdvanced, isLocked) {
form_group = "<div class='form-group " + (isAdvanced ? Settings.ADVANCED_CLASS : "") + "'>";
form_group = "<div class='form-group " + (isAdvanced ? Settings.ADVANCED_CLASS : "") + "'>"
setting_value = _(values).valueForKeyPath(keypath);
if (_.has(values, groupName) && _.has(values[groupName], setting.name)) {
setting_value = values[groupName][setting.name]
} else if (_.has(setting, 'default')) {
setting_value = setting.default
if (!setting_value) {
if (_.has(setting, 'default')) {
setting_value = setting.default;
} else {
setting_value = ""
setting_value = "";
}
}
label_class = 'control-label'
label_class = 'control-label';
if (isLocked) {
label_class += ' locked'
label_class += ' locked';
}
common_attrs = " class='" + (setting.type !== 'checkbox' ? 'form-control' : '')
+ " " + Settings.TRIGGER_CHANGE_CLASS + "' data-short-name='" + setting.name + "' name='" + setting_name + "' "
+ "id='" + setting_name + "'"
+ " " + Settings.TRIGGER_CHANGE_CLASS + "' data-short-name='" + setting.name + "' name='" + keypath + "' "
+ "id='" + keypath + "'";
if (setting.type === 'checkbox') {
if (setting.label) {
form_group += "<label class='" + label_class + "'>" + setting.label + "</label>"
}
form_group += "<div class='checkbox" + (isLocked ? " disabled" : "") + "'>"
form_group += "<label for='" + setting_name + "'>"
form_group += "<label for='" + keypath + "'>"
form_group += "<input type='checkbox'" + common_attrs + (setting_value ? "checked" : "") + (isLocked ? " disabled" : "") + "/>"
form_group += " " + setting.help + "</label>";
form_group += "</div>"
@ -56,14 +56,14 @@ var viewHelpers = {
input_type = _.has(setting, 'type') ? setting.type : "text"
if (setting.label) {
form_group += "<label for='" + setting_name + "' class='" + label_class + "'>" + setting.label + "</label>";
form_group += "<label for='" + keypath + "' class='" + label_class + "'>" + setting.label + "</label>";
}
if (input_type === 'table') {
form_group += makeTable(setting, setting_name, setting_value, isLocked)
form_group += makeTable(setting, keypath, setting_value, isLocked)
} else {
if (input_type === 'select') {
form_group += "<select class='form-control' data-hidden-input='" + setting_name + "'>'"
form_group += "<select class='form-control' data-hidden-input='" + keypath + "'>'"
_.each(setting.options, function(option) {
form_group += "<option value='" + option.value + "'" +
@ -186,7 +186,7 @@ $(document).ready(function(){
// $('body').scrollspy({ target: '#setup-sidebar'})
reloadSettings()
reloadSettings();
})
function reloadSettings() {
@ -258,16 +258,21 @@ $('body').on('click', '.save-button', function(e){
return false;
});
function makeTable(setting, setting_name, setting_value, isLocked) {
function makeTable(setting, keypath, setting_value, isLocked) {
var isArray = !_.has(setting, 'key')
if (!isArray && setting.can_order) {
setting.can_order = false;
}
var html = "<span class='help-block'>" + setting.help + "</span>"
html += "<table class='table table-bordered " + (isLocked ? "locked-table" : "") + "' data-short-name='" + setting.name + "' name='" + setting_name
+ "' data-setting-type='" + (isArray ? 'array' : 'hash') + "'>"
var html = "";
if (setting.help) {
html += "<span class='help-block'>" + setting.help + "</span>"
}
html += "<table class='table table-bordered " + (isLocked ? "locked-table" : "") + "' data-short-name='" + setting.name
+ "' name='" + keypath + "' data-setting-type='" + (isArray ? 'array' : 'hash') + "'>"
// Column names
html += "<tr class='headers'>"
@ -296,7 +301,7 @@ function makeTable(setting, setting_name, setting_value, isLocked) {
var row_num = 1
_.each(setting_value, function(row, indexOrName) {
html += "<tr class='" + Settings.DATA_ROW_CLASS + "'" + (isArray ? "" : "name='" + setting_name + "." + indexOrName + "'") + ">"
html += "<tr class='" + Settings.DATA_ROW_CLASS + "'" + (isArray ? "" : "name='" + keypath + "." + indexOrName + "'") + ">"
if (setting.numbered === true) {
html += "<td class='numbered'>" + row_num + "</td>"
@ -315,7 +320,7 @@ function makeTable(setting, setting_name, setting_value, isLocked) {
html += colValue
// for arrays we add a hidden input to this td so that values can be posted appropriately
html += "<input type='hidden' name='" + setting_name + "[" + indexOrName + "]"
html += "<input type='hidden' name='" + keypath + "[" + indexOrName + "]"
+ (rowIsObject ? "." + col.name : "") + "' value='" + colValue + "'/>"
} else if (row.hasOwnProperty(col.name)) {
html += row[col.name]
@ -382,9 +387,20 @@ function badgeSidebarForDifferences(changedElement) {
// figure out which group this input is in
var panelParentID = changedElement.closest('.panel').attr('id')
// 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]
var initialPanelJSON = Settings.initialValues[panelParentID]
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

View file

@ -10,8 +10,9 @@
<div id="setup-sidebar" data-clampedwidth="#setup-sidebar-col" class="hidden-xs" data-spy="affix" data-offset-top="55">
<script id="list-group-template" type="text/template">
<% _.each(descriptions, function(group){ %>
<% panelID = group.name ? group.name : group.label %>
<li>
<a href="#<%-group.name %>" class="list-group-item">
<a href="#<%- panelID %>" class="list-group-item">
<span class="badge"></span>
<%- group.label %>
</a>
@ -37,24 +38,32 @@
<% if (isAdvanced) { %>
<% $("a[href=#" + group.name + "]").addClass('advanced-setting').hide() %>
<% } %>
<div class="panel panel-default <%- (isAdvanced) ? 'advanced-setting' : '' %>" id="<%- group.name %>">
<% isGrouped = !!group.name %>
<% panelID = isGrouped ? group.name : group.label %>
<div class="panel panel-default<%- (isAdvanced) ? ' advanced-setting' : '' %><%- (isGrouped) ? ' grouped' : '' %>"
id="<%- panelID %>">
<div class="panel-heading">
<h3 class="panel-title"><%- group.label %></h3>
</div>
<div class="panel-body">
<% _.each(split_settings[0], function(setting) { %>
<%= getFormGroup(group.name, setting, values, false,
<% keypath = isGrouped ? group.name + "." + setting.name : setting.name %>
<%= getFormGroup(keypath, setting, values, false,
(_.has(locked, group.name) && _.has(locked[group.name], setting.name))) %>
<% }); %>
<% if (!_.isEmpty(split_settings[1])) { %>
<% $("#advanced-toggle-button").show() %>
<% _.each(split_settings[1], function(setting) { %>
<%= getFormGroup(group.name, setting, values, true,
<% keypath = isGrouped ? group.name + "." + setting.name : setting.name %>
<%= getFormGroup(keypath, setting, values, true,
(_.has(locked, group.name) && _.has(locked[group.name], setting.name))) %>
<% }); %>
<% }%>
</div>
</div>
<% }); %>
</script>
@ -83,6 +92,7 @@
<!--#include virtual="footer.html"-->
<script src='/js/underscore-min.js'></script>
<script src='/js/underscore-keypath.min.js'></script>
<script src='/js/bootbox.min.js'></script>
<script src='/js/sweet-alert.min.js'></script>
<script src='/js/settings.js'></script>

View file

@ -9,6 +9,6 @@
<script src='js/json.human.js'></script>
<script src='js/highcharts-custom.js'></script>
<script src='/js/underscore-min.js'></script>
<script src='js/underscore-keypath.min.js'></script>
<script src='/js/underscore-keypath.min.js'></script>
<script src='/js/bootbox.min.js'></script>
<!--#include virtual="page-end.html"-->

View file

@ -1399,6 +1399,11 @@ void DomainServer::processDatagram(const QByteArray& receivedPacket, const HifiS
break;
}
case PacketTypeDomainServerPathQuery: {
// have our private method attempt to respond to this path query
respondToPathQuery(receivedPacket, senderSockAddr);
break;
}
case PacketTypeNodeJsonStats: {
SharedNodePointer matchingNode = nodeList->sendingNodeForPacket(receivedPacket);
if (matchingNode) {
@ -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<LimitedNodeList>();
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<char*>(&numPathBytes), sizeof(numPathBytes));
// append the path itself
pathResponsePacket.append(pathQuery.toUtf8());
// append the number of bytes the resulting viewpoint is
pathResponsePacket.append(reinterpret_cast<char*>(&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.";
}
}
}

View file

@ -112,6 +112,8 @@ private:
void refreshStaticAssignmentAndAddToQueue(SharedAssignmentPointer& assignment);
void addStaticAssignmentsToQueue();
void respondToPathQuery(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr);
QUrl oauthRedirectURL();
QUrl oauthAuthorizationURL(const QUuid& stateUUID = QUuid::createUuid());

View file

@ -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()
@ -130,10 +132,10 @@ bool DomainServerSettingsManager::handleAuthenticatedHTTPRequest(HTTPConnection
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();
@ -201,31 +203,45 @@ QJsonObject DomainServerSettingsManager::responseObjectForType(const QString& ty
// 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 (!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;
}
@ -236,25 +252,6 @@ QJsonObject DomainServerSettingsManager::responseObjectForType(const QString& ty
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;
}
void DomainServerSettingsManager::updateSetting(const QString& key, const QJsonValue& newValue, QVariantMap& settingMap,
const QJsonObject& settingDescription) {
if (newValue.isString()) {
@ -272,7 +269,15 @@ void DomainServerSettingsManager::updateSetting(const QString& key, const QJsonV
} 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()) {
@ -302,7 +307,15 @@ void DomainServerSettingsManager::updateSetting(const QString& key, const QJsonV
}
}
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()) {
@ -316,32 +329,89 @@ void DomainServerSettingsManager::updateSetting(const QString& key, const QJsonV
}
}
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;
}
}
return QJsonObject();
}
void DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject,
QVariantMap& settingsVariant,
const QJsonArray& descriptionArray) {
QVariantMap& settingsVariant) {
// Iterate on the setting groups
foreach(const QString& groupKey, postedObject.keys()) {
QJsonValue groupValue = postedObject[groupKey];
foreach(const QString& rootKey, postedObject.keys()) {
QJsonValue rootValue = postedObject[rootKey];
if (!settingsVariant.contains(groupKey)) {
if (!settingsVariant.contains(rootKey)) {
// we don't have a map below this key yet, so set it up now
settingsVariant[groupKey] = QVariantMap();
settingsVariant[rootKey] = QVariantMap();
}
// Iterate on the settings
foreach(const QString& settingKey, groupValue.toObject().keys()) {
QJsonValue settingValue = groupValue.toObject()[settingKey];
QVariantMap& thisMap = settingsVariant;
QJsonObject thisDescription;
if (settingExists(groupKey, settingKey, descriptionArray, thisDescription)) {
QVariantMap& thisMap = *reinterpret_cast<QVariantMap*>(settingsVariant[groupKey].data());
updateSetting(settingKey, settingValue, thisMap, thisDescription);
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<QVariantMap*>(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);
}
}
}

View file

@ -18,6 +18,8 @@
#include <HifiConfigVariantMap.h>
#include <HTTPManager.h>
const QString SETTINGS_PATHS_KEY = "paths";
class DomainServerSettingsManager : public QObject {
Q_OBJECT
public:
@ -31,12 +33,11 @@ public:
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;

View file

@ -120,12 +120,11 @@ 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());
}
}
@ -134,7 +133,7 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl) {
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();
}
@ -239,9 +238,7 @@ void AddressManager::goToAddressFromObject(const QVariantMap& dataObject, const
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";
@ -251,12 +248,16 @@ void AddressManager::goToAddressFromObject(const QVariantMap& dataObject, const
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.";
@ -342,7 +343,15 @@ bool AddressManager::handleNetworkAddress(const QString& lookupString) {
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 +
@ -353,7 +362,7 @@ bool AddressManager::handleRelativeViewpoint(const QString& lookupString, bool 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(),
@ -365,8 +374,8 @@ bool AddressManager::handleRelativeViewpoint(const QString& lookupString, bool s
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(),
@ -389,10 +398,10 @@ bool AddressManager::handleRelativeViewpoint(const QString& lookupString, bool s
}
return true;
}
} else {
return false;
}
}
const QString GET_USER_LOCATION = "/api/v1/users/%1/location";

View file

@ -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)();
@ -57,8 +58,10 @@ public:
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();
@ -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();
@ -88,7 +92,8 @@ private:
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;

View file

@ -46,7 +46,7 @@ void DomainHandler::clearConnectionInfo() {
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);
@ -70,7 +70,10 @@ void DomainHandler::hardReset() {
_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) {
@ -142,19 +145,25 @@ 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;
}
}

View file

@ -72,6 +72,12 @@ public:
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);
@ -82,8 +88,14 @@ private slots:
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);
@ -103,6 +115,7 @@ private:
bool _isConnected;
QJsonObject _settingsObject;
int _failedSettingsRequests;
QString _pendingPath;
};
#endif // hifi_DomainHandler_h

View file

@ -31,6 +31,7 @@ public:
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);

View file

@ -54,6 +54,13 @@ NodeList::NodeList(char newOwnerType, unsigned short socketListenPort, unsigned
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);
@ -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;
@ -395,6 +406,102 @@ void NodeList::sendDomainServerCheckIn() {
}
}
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<char*>(&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<AddressManager>()->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) {

View file

@ -71,8 +71,11 @@ 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);
@ -89,6 +92,10 @@ private:
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;

View file

@ -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<PacketType> NON_VERIFIED_PACKETS = QSet<PacketType>()
<< PacketTypeNodeJsonStats << PacketTypeEntityQuery
<< PacketTypeOctreeDataNack << PacketTypeEntityEditNack
<< PacketTypeIceServerHeartbeat << PacketTypeIceServerHeartbeatResponse
<< PacketTypeUnverifiedPing << PacketTypeUnverifiedPingReply << PacketTypeStopNode;
<< PacketTypeUnverifiedPing << PacketTypeUnverifiedPingReply << PacketTypeStopNode
<< PacketTypeDomainServerPathQuery << PacketTypeDomainServerPathResponse;
const QSet<PacketType> SEQUENCE_NUMBERED_PACKETS = QSet<PacketType>()
<< PacketTypeAvatarData;

View file

@ -159,7 +159,7 @@ 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);

View file

@ -37,6 +37,6 @@ private:
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