From c0fc0a226b6eacc83d4a38f601a40a64b95e3ddf Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Mon, 11 May 2015 13:10:36 -0700 Subject: [PATCH 01/27] Fixing info-view popup on every launch --- libraries/ui/src/InfoView.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/libraries/ui/src/InfoView.cpp b/libraries/ui/src/InfoView.cpp index 3f80c97c4c..2d292d9700 100644 --- a/libraries/ui/src/InfoView.cpp +++ b/libraries/ui/src/InfoView.cpp @@ -59,6 +59,7 @@ void InfoView::show(const QString& path, bool firstOrChangedOnly) { if (version == QString::null || version == lastVersion) { return; } + infoVersion.set(version); } } auto offscreenUi = DependencyManager::get(); From 667ad9b0cb6c9db029251f83987de94d36c3863b Mon Sep 17 00:00:00 2001 From: David Rowe Date: Mon, 11 May 2015 15:48:51 -0700 Subject: [PATCH 02/27] Add menu option to enable/disable binary webcam eyelid control --- interface/src/Application.cpp | 1 + interface/src/Menu.cpp | 2 + interface/src/Menu.h | 1 + interface/src/devices/DdeFaceTracker.cpp | 109 +++++++++++++---------- interface/src/devices/DdeFaceTracker.h | 1 + 5 files changed, 67 insertions(+), 47 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 088b2ddf96..ad7e788d5f 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1947,6 +1947,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/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 6ed253c1ec..4141f9fb82 100644 --- a/interface/src/devices/DdeFaceTracker.cpp +++ b/interface/src/devices/DdeFaceTracker.cpp @@ -194,8 +194,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 +450,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); + if (Menu::getInstance()->isOptionChecked(MenuOption::BinaryEyelidControl)) { + if (_eyeStates[0] == EYE_UNCONTROLLED) { + _eyeStates[0] = EYE_OPEN; + _eyeStates[1] = EYE_OPEN; + } - // 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) + 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; + _eyeStates[i] = EYE_OPENING; + } + + 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 if (_eyeStates[i] == EYE_CLOSED) { + // Keep eyelid fully closed + eyeCoefficients[i] = 1.0; + } } - 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 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) { diff --git a/interface/src/devices/DdeFaceTracker.h b/interface/src/devices/DdeFaceTracker.h index 7019802603..f627464c6f 100644 --- a/interface/src/devices/DdeFaceTracker.h +++ b/interface/src/devices/DdeFaceTracker.h @@ -114,6 +114,7 @@ private: float _filteredBrowUp; enum EyeState { + EYE_UNCONTROLLED, EYE_OPEN, EYE_CLOSING, EYE_CLOSED, From 5832259643549b4f5aea25923ecadc9795a5e936 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Mon, 11 May 2015 16:41:43 -0700 Subject: [PATCH 03/27] Add preferences slider for webcam eyelid closing threshold --- interface/src/devices/DdeFaceTracker.cpp | 15 +++-- interface/src/devices/DdeFaceTracker.h | 8 ++- interface/src/ui/PreferencesDialog.cpp | 9 +++ interface/ui/preferencesDialog.ui | 76 ++++++++++++++++++++++++ 4 files changed, 101 insertions(+), 7 deletions(-) diff --git a/interface/src/devices/DdeFaceTracker.cpp b/interface/src/devices/DdeFaceTracker.cpp index 4141f9fb82..31fb523933 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), _calibrationValues(), _calibrationCount(0), @@ -464,12 +465,12 @@ void DdeFaceTracker::decodePacket(const QByteArray& buffer) { // 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) { + 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] < EYE_OPENING_THRESHOLD) { + && eyeCoefficients[i] < eyeOpeningThreshold) { _eyeStates[i] = EYE_OPENING; } @@ -559,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 f627464c6f..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; @@ -124,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..a949b42684 100644 --- a/interface/ui/preferencesDialog.ui +++ b/interface/ui/preferencesDialog.ui @@ -1310,6 +1310,82 @@ + + + + 0 + + + 7 + + + 0 + + + 7 + + + + + + Arial + + + + Camera eye closing threshold + + + 0 + + + ddeEyeClosingThresholdSlider + + + + + + + + Arial + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + 130 + 0 + + + + + Arial + + + + Qt::Horizontal + + + + + From d92aca72a08a54f8dd0b01a5f60ed85373d9374d Mon Sep 17 00:00:00 2001 From: David Rowe Date: Mon, 11 May 2015 16:42:00 -0700 Subject: [PATCH 04/27] Fix typo --- interface/ui/preferencesDialog.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/ui/preferencesDialog.ui b/interface/ui/preferencesDialog.ui index a949b42684..ecc99fd9aa 100644 --- a/interface/ui/preferencesDialog.ui +++ b/interface/ui/preferencesDialog.ui @@ -1256,7 +1256,7 @@ - Pupil dillation + Pupil dilation 0 From 909aaab434ba4cb9a224b9914609bf113a30ff5c Mon Sep 17 00:00:00 2001 From: David Rowe Date: Mon, 11 May 2015 17:19:50 -0700 Subject: [PATCH 05/27] Better preference description --- interface/ui/preferencesDialog.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/ui/preferencesDialog.ui b/interface/ui/preferencesDialog.ui index ecc99fd9aa..e74b89075e 100644 --- a/interface/ui/preferencesDialog.ui +++ b/interface/ui/preferencesDialog.ui @@ -1332,7 +1332,7 @@ - Camera eye closing threshold + Camera binary eyelid threshold 0 From 542ce944efe034dce1ea54f53393af808d11e460 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Mon, 11 May 2015 17:44:35 -0700 Subject: [PATCH 06/27] add paths, handling for non-grouped values --- .../resources/describe-settings.json | 30 ++++++++++++++++- domain-server/resources/web/js/settings.js | 31 +++++++++++------- .../resources/web/settings/index.shtml | 2 +- .../src/DomainServerSettingsManager.cpp | 32 +++++++++++++------ 4 files changed, 73 insertions(+), 22 deletions(-) diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index db995f3296..d95e77bf11 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -1,7 +1,7 @@ [ { "name": "metaverse", - "label": "Metaverse Registration", + "label": "Metaverse / Networking", "settings": [ { "name": "access_token", @@ -44,6 +44,34 @@ } ] }, + { + "label": "Paths", + "settings": [ + { + "name": "paths", + "label": "", + "help": "", + "type": "table", + "key": { + "name": "path", + "label": "Path", + "placeholder": "/garden" + }, + "columns": [ + { + "name": "viewpoint", + "label": "Viewpoint", + "placeholder": "/512,512,512" + } + ], + "default": { + "/": { + "viewpoint": "/512,512,512" + } + } + } + ] + }, { "name": "security", "label": "Security", diff --git a/domain-server/resources/web/js/settings.js b/domain-server/resources/web/js/settings.js index 4fac753959..a4ac292b5c 100644 --- a/domain-server/resources/web/js/settings.js +++ b/domain-server/resources/web/js/settings.js @@ -22,26 +22,30 @@ var Settings = { var viewHelpers = { getFormGroup: function(groupName, setting, values, isAdvanced, isLocked) { - setting_name = groupName + "." + setting.name - - form_group = "
" + if (groupName) { + setting_name = groupName + "." + setting.name; + } else { + setting_name = setting.name; + } + + form_group = "
"; if (_.has(values, groupName) && _.has(values[groupName], setting.name)) { - setting_value = values[groupName][setting.name] + setting_value = values[groupName][setting.name]; } else if (_.has(setting, 'default')) { - setting_value = 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 + "'" + + "id='" + setting_name + "'"; if (setting.type === 'checkbox') { if (setting.label) { @@ -186,7 +190,7 @@ $(document).ready(function(){ // $('body').scrollspy({ target: '#setup-sidebar'}) - reloadSettings() + reloadSettings(); }) function reloadSettings() { @@ -265,7 +269,12 @@ function makeTable(setting, setting_name, setting_value, isLocked) { setting.can_order = false; } - var html = "" + setting.help + "" + var html = ""; + + if (setting.help) { + html += "" + setting.help + "" + } + html += "" diff --git a/domain-server/resources/web/settings/index.shtml b/domain-server/resources/web/settings/index.shtml index 598f137285..bdfd3f9f48 100644 --- a/domain-server/resources/web/settings/index.shtml +++ b/domain-server/resources/web/settings/index.shtml @@ -87,4 +87,4 @@ - \ No newline at end of file + diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index f29b1f48ae..a3bba6f71e 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -130,7 +130,7 @@ 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); @@ -201,31 +201,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 (!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 { + _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; } From f0aeaa1af8b44ce91d1b876cf7a66b5c30e40cd9 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Mon, 11 May 2015 18:07:33 -0700 Subject: [PATCH 07/27] handle sidebar badging for new root values --- domain-server/resources/web/js/settings.js | 19 +++++++++++++++---- .../resources/web/settings/index.shtml | 11 +++++++++-- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/domain-server/resources/web/js/settings.js b/domain-server/resources/web/js/settings.js index a4ac292b5c..2313cbe6b1 100644 --- a/domain-server/resources/web/js/settings.js +++ b/domain-server/resources/web/js/settings.js @@ -391,10 +391,21 @@ 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 diff --git a/domain-server/resources/web/settings/index.shtml b/domain-server/resources/web/settings/index.shtml index bdfd3f9f48..ad6ed0f41c 100644 --- a/domain-server/resources/web/settings/index.shtml +++ b/domain-server/resources/web/settings/index.shtml @@ -10,8 +10,9 @@
" - + html += "
" + // Column names html += "" - + if (setting.numbered === true) { html += "" // Row number } - + if (setting.key) { html += "" // Key } - + _.each(setting.columns, function(col) { html += "" // 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 += "" } - + if (setting.key) { html += "" } - + _.each(setting.columns, function(col) { 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 += "
#" + setting.key.label + "" + col.label + "
" + row_num + "" + indexOrName + "" - + 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 += "
" - + 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') - + // 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 { @@ -407,32 +403,32 @@ function badgeSidebarForDifferences(changedElement) { } 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() @@ -453,7 +449,7 @@ function addTableRow(add_glyphicon) { return } } - + // Check empty fields var empty = false; _.each(row.children('.' + Settings.DATA_COL_CLASS + ' input'), function(element) { @@ -462,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) { @@ -498,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)) { @@ -591,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)) } @@ -599,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) { @@ -631,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); @@ -666,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.

" @@ -705,25 +701,25 @@ function chooseFromHighFidelityDomains(clickedButton) { window.open("https://metaverse.highfidelity.com/user/domains", '_blank'); } } - modal_body = "

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 ad6ed0f41c..9bbf913b1a 100644 --- a/domain-server/resources/web/settings/index.shtml +++ b/domain-server/resources/web/settings/index.shtml @@ -5,7 +5,7 @@
- +
- +
- + - +
- +
- + @@ -90,6 +92,7 @@ + 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/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 From d8b17ffeb015ea8d220dce689d46aa3bcbd8d0ce Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 12 May 2015 12:00:03 -0700 Subject: [PATCH 15/27] add packets for a path query and path response --- libraries/networking/src/PacketHeaders.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/libraries/networking/src/PacketHeaders.h b/libraries/networking/src/PacketHeaders.h index 978ba190bb..a8a611ea94 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 @@ -122,7 +122,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 +138,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, From e0ccc986b1facc20cf03e0bc4abe26bc7f3e49fd Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 12 May 2015 12:29:09 -0700 Subject: [PATCH 16/27] add a method to DS to respond to path query --- domain-server/src/DomainServer.cpp | 66 ++++++++++++++++++++++++ domain-server/src/DomainServer.h | 58 +++++++++++---------- libraries/networking/src/PacketHeaders.h | 3 +- 3 files changed, 98 insertions(+), 29 deletions(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 1b9d84f746..44cb040b2d 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -1398,6 +1398,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) { @@ -2190,3 +2195,64 @@ 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 + quint8 numPathBytes = *packetDataStart; + + if (numPathBytes <= receivedPacket.size() - numHeaderBytes - 1) { + // the number of path bytes makes sense for the sent packet - pull out the path + QString pathQuery = QString::fromUtf8(packetDataStart + 1, 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 = "paths.%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(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); + + // first append the number of bytes the viewpoint is, for unpacking on the other side + quint8 numViewpointBytes = responseViewpoint.toUtf8().size(); + + // are we going to be able to fit this viewpoint in a packet? + if (numViewpointBytes + pathResponsePacket.size() + sizeof(numViewpointBytes) < MAX_PACKET_SIZE) { + pathResponsePacket.append(reinterpret_cast(&numViewpointBytes), sizeof(numViewpointBytes)); + + // append the viewpoint itself + pathResponsePacket.append(viewpointUTF8); + + // send off the packet - see if we can associate this outbound data to a particular node + SharedNodePointer matchingNode = nodeList->sendingNodeForPacket(receivedPacket); + nodeList->writeUnverifiedDatagram(pathResponsePacket, matchingNode, 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/libraries/networking/src/PacketHeaders.h b/libraries/networking/src/PacketHeaders.h index a8a611ea94..95ba84ae6d 100644 --- a/libraries/networking/src/PacketHeaders.h +++ b/libraries/networking/src/PacketHeaders.h @@ -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; From 993984134c51636c87c4f33590eb59af3516b7db Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 12 May 2015 12:48:01 -0700 Subject: [PATCH 17/27] add the path to DS response for path query --- domain-server/src/DomainServer.cpp | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 37eb8eca83..19aeec9aa0 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -2205,7 +2205,7 @@ void DomainServer::respondToPathQuery(const QByteArray& receivedPacket, const Hi const char* packetDataStart = receivedPacket.data() + numHeaderBytes; // figure out how many bytes the sender said this path is - quint8 numPathBytes = *packetDataStart; + quint16 numPathBytes = *packetDataStart; if (numPathBytes <= receivedPacket.size() - numHeaderBytes - 1) { // the number of path bytes makes sense for the sent packet - pull out the path @@ -2234,11 +2234,19 @@ void DomainServer::respondToPathQuery(const QByteArray& receivedPacket, const Hi // prepare a packet for the response QByteArray pathResponsePacket = nodeList->byteArrayWithPopulatedHeader(PacketTypeDomainServerPathResponse); - // first append the number of bytes the viewpoint is, for unpacking on the other side - quint8 numViewpointBytes = responseViewpoint.toUtf8().size(); + // check the number of bytes the viewpoint is + quint16 numViewpointBytes = responseViewpoint.toUtf8().size(); - // are we going to be able to fit this viewpoint in a packet? - if (numViewpointBytes + pathResponsePacket.size() + sizeof(numViewpointBytes) < MAX_PACKET_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 From 11ab97e0fc6257b8c0ca3c42e78fa29455353e96 Mon Sep 17 00:00:00 2001 From: Philip Rosedale Date: Tue, 12 May 2015 13:11:11 -0700 Subject: [PATCH 18/27] update position and rotation from the script, even though we should not have to. --- examples/grab.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/grab.js b/examples/grab.js index 552a194100..97dcfa52a5 100644 --- a/examples/grab.js +++ b/examples/grab.js @@ -243,9 +243,11 @@ function update(deltaTime) { } Entities.editEntity(grabbedEntity, { + position: currentPosition, + rotation: currentRotation, velocity: newVelocity, angularVelocity: angularVelocity - }) + }); updateDropLine(targetPosition); } } From 7330e5255dbd8fcb4c49c2726d49f023db948179 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 12 May 2015 13:48:41 -0700 Subject: [PATCH 19/27] handle path query response from DS in NL/AM --- domain-server/src/DomainServer.cpp | 6 +- libraries/networking/src/AddressManager.cpp | 179 +++++++++-------- libraries/networking/src/AddressManager.h | 34 ++-- libraries/networking/src/NodeList.cpp | 211 +++++++++++++------- libraries/networking/src/NodeList.h | 19 +- 5 files changed, 270 insertions(+), 179 deletions(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 19aeec9aa0..1f11faa648 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -2207,9 +2207,9 @@ void DomainServer::respondToPathQuery(const QByteArray& receivedPacket, const Hi // figure out how many bytes the sender said this path is quint16 numPathBytes = *packetDataStart; - if (numPathBytes <= receivedPacket.size() - numHeaderBytes - 1) { + 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 + 1, numPathBytes); + 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("/")) { @@ -2252,6 +2252,8 @@ void DomainServer::respondToPathQuery(const QByteArray& receivedPacket, const Hi // 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 SharedNodePointer matchingNode = nodeList->sendingNodeForPacket(receivedPacket); nodeList->writeUnverifiedDatagram(pathResponsePacket, matchingNode, senderSockAddr); diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp index 9b99c236b3..0dd621a7b7 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,24 @@ 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 +146,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 +163,100 @@ 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 { qCDebug(networking) << "Received an address manager API response with no domain key. Cannot parse."; qCDebug(networking) << locationMap; @@ -274,7 +273,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 +285,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 +301,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 +357,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 +388,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 +427,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..08d5cc59a8 100644 --- a/libraries/networking/src/AddressManager.h +++ b/libraries/networking/src/AddressManager.h @@ -39,32 +39,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 +76,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 +85,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/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 7f57642e9e..f56a710980 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -46,23 +46,25 @@ 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); - + + connect(addressManager.data(), &AddressManager::pathChangeRequired, this, &NodeList::sendDSPathQuery); + // 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 +77,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 +87,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 +104,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 +116,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 +174,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 +184,7 @@ void NodeList::processNodeData(const HifiSockAddr& senderSockAddr, const QByteAr } } } - + break; } case PacketTypePingReply: { @@ -190,14 +192,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 +210,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 +228,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 +240,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 +273,7 @@ void NodeList::sendSTUNRequest() { if (!_hasCompletedInitialSTUNFailure) { qCDebug(networking) << "Sending intial stun request to" << STUN_SERVER_HOSTNAME; } - + LimitedNodeList::sendSTUNRequest(); _stunRequestsSinceSuccess++; @@ -292,9 +298,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 +315,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 +339,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 +357,139 @@ 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::sendDSPathQuery(const QString& newPath) { + // only send a path query if we know who our DS is or is going to be + if (!_domainHandler.getSockAddr().isNull()) { + // 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); + + qDebug() << "Sending a path query packet for path" << newPath << "to domain-server at" << _domainHandler.getSockAddr(); + + // send off the path query + writeUnverifiedDatagram(pathQueryPacket, _domainHandler.getSockAddr()); + } else { + qDebug() << "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)) { + qDebug() << "Going to viewpoint" << viewpoint << "which was the lookup result for path" << pathQuery; + } else { + qDebug() << "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 +497,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 +521,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 +541,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 +554,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 +595,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..2b755823a6 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,24 +53,25 @@ 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 sendDSPathQuery(const QString& newPath); signals: void limitOfSilentDomainCheckInsReached(); private: @@ -78,17 +79,19 @@ private: 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); + NodeType_t _ownerType; NodeSet _nodeTypesOfInterest; DomainHandler _domainHandler; @@ -96,7 +99,7 @@ private: HifiSockAddr _assignmentServerSocket; bool _hasCompletedInitialSTUNFailure; unsigned int _stunRequestsSinceSuccess; - + friend class Application; }; From 2e31cca7e5ce1cafb63b973c67be9cb809f8b099 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Tue, 12 May 2015 14:03:06 -0700 Subject: [PATCH 20/27] Make edit color picker slimmer --- examples/html/entityProperties.html | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/html/entityProperties.html b/examples/html/entityProperties.html index 93ff80ce33..7e6c72712c 100644 --- a/examples/html/entityProperties.html +++ b/examples/html/entityProperties.html @@ -623,7 +623,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); @@ -641,7 +641,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); @@ -674,7 +674,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); @@ -690,7 +690,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); @@ -702,7 +702,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); @@ -739,7 +739,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); From 332601b91cfe19ad043b297623a8e1bcb724c90c Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 12 May 2015 14:34:14 -0700 Subject: [PATCH 21/27] final fixes for path query from DS --- domain-server/src/DomainServer.cpp | 4 +- interface/src/DatagramProcessor.cpp | 36 ++++----- libraries/networking/src/AddressManager.cpp | 6 +- libraries/networking/src/AddressManager.h | 1 + libraries/networking/src/DomainHandler.cpp | 85 ++++++++++++--------- libraries/networking/src/DomainHandler.h | 49 +++++++----- libraries/networking/src/HifiSockAddr.h | 13 ++-- libraries/networking/src/NodeList.cpp | 44 +++++++++-- libraries/networking/src/NodeList.h | 6 +- 9 files changed, 153 insertions(+), 91 deletions(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 1f11faa648..09ab25ad91 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -2255,8 +2255,8 @@ void DomainServer::respondToPathQuery(const QByteArray& receivedPacket, const Hi 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 - SharedNodePointer matchingNode = nodeList->sendingNodeForPacket(receivedPacket); - nodeList->writeUnverifiedDatagram(pathResponsePacket, matchingNode, senderSockAddr); + // TODO: does this senderSockAddr always work for a punched DS client? + nodeList->writeUnverifiedDatagram(pathResponsePacket, senderSockAddr); } } 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/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp index 0dd621a7b7..48d9655e43 100644 --- a/libraries/networking/src/AddressManager.cpp +++ b/libraries/networking/src/AddressManager.cpp @@ -125,7 +125,6 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl) { // 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()); - } } @@ -253,10 +252,13 @@ void AddressManager::goToAddressFromObject(const QVariantMap& dataObject, const 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; diff --git a/libraries/networking/src/AddressManager.h b/libraries/networking/src/AddressManager.h index 08d5cc59a8..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)(); 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 f56a710980..dcfb006b2b 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -54,7 +54,12 @@ NodeList::NodeList(char newOwnerType, unsigned short socketListenPort, unsigned connect(addressManager.data(), &AddressManager::possibleDomainChangeRequiredViaICEForID, &_domainHandler, &DomainHandler::setIceServerHostnameAndID); - connect(addressManager.data(), &AddressManager::pathChangeRequired, this, &NodeList::sendDSPathQuery); + // 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); @@ -401,9 +406,34 @@ 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.getSockAddr().isNull()) { + if (_domainHandler.isSocketKnown()) { // construct the path query packet QByteArray pathQueryPacket = byteArrayWithPopulatedHeader(PacketTypeDomainServerPathQuery); @@ -420,12 +450,13 @@ void NodeList::sendDSPathQuery(const QString& newPath) { // append the path itself to the query packet pathQueryPacket.append(pathQueryUTF8); - qDebug() << "Sending a path query packet for path" << newPath << "to domain-server at" << _domainHandler.getSockAddr(); + 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 { - qDebug() << "Path" << newPath << "would make PacketTypeDomainServerPathQuery packet > MAX_PACKET_SIZE." << + qCDebug(networking) << "Path" << newPath << "would make PacketTypeDomainServerPathQuery packet > MAX_PACKET_SIZE." << "Will not send query."; } } @@ -462,9 +493,10 @@ void NodeList::handleDSPathQueryResponse(const QByteArray& packet) { // Hand it off to the AddressManager so it can handle it as a relative viewpoint if (DependencyManager::get()->goToViewpoint(viewpoint)) { - qDebug() << "Going to viewpoint" << viewpoint << "which was the lookup result for path" << pathQuery; + qCDebug(networking) << "Going to viewpoint" << viewpoint << "which was the lookup result for path" << pathQuery; } else { - qDebug() << "Could not go to viewpoint" << viewpoint << "which was the lookup result for path" << pathQuery; + qCDebug(networking) << "Could not go to viewpoint" << viewpoint + << "which was the lookup result for path" << pathQuery; } } } diff --git a/libraries/networking/src/NodeList.h b/libraries/networking/src/NodeList.h index 2b755823a6..39b1e3e2d2 100644 --- a/libraries/networking/src/NodeList.h +++ b/libraries/networking/src/NodeList.h @@ -71,9 +71,11 @@ public slots: void reset(); void sendDomainServerCheckIn(); void pingInactiveNodes(); - void sendDSPathQuery(const QString& newPath); + 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); @@ -92,6 +94,8 @@ private: void handleDSPathQueryResponse(const QByteArray& packet); + void sendDSPathQuery(const QString& newPath); + NodeType_t _ownerType; NodeSet _nodeTypesOfInterest; DomainHandler _domainHandler; From 4e679c7e3afcae0f5b89bc6dfab694974babf9b6 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Tue, 12 May 2015 15:08:26 -0700 Subject: [PATCH 22/27] use toGlm for color. remove some commented-out code --- .../src/RenderableLineEntityItem.cpp | 26 +++++-------------- 1 file changed, 7 insertions(+), 19 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableLineEntityItem.cpp b/libraries/entities-renderer/src/RenderableLineEntityItem.cpp index 6f8aed930c..aaed52c20c 100644 --- a/libraries/entities-renderer/src/RenderableLineEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableLineEntityItem.cpp @@ -26,31 +26,19 @@ void RenderableLineEntityItem::render(RenderArgs* args) { PerformanceTimer perfTimer("RenderableLineEntityItem::render"); assert(getType() == EntityTypes::Line); glm::vec3 position = getPosition(); - // glm::vec3 center = getCenter(); glm::vec3 dimensions = getDimensions(); glm::quat rotation = getRotation(); const float MAX_COLOR = 255.0f; - glm::vec4 lineColor(getColor()[RED_INDEX] / MAX_COLOR, getColor()[GREEN_INDEX] / MAX_COLOR, - getColor()[BLUE_INDEX] / MAX_COLOR, getLocalRenderAlpha()); - + glm::vec4 lineColor(toGlm(getColor()), getLocalRenderAlpha()); glPushMatrix(); - glTranslatef(position.x, position.y, position.z); - glm::vec3 axis = glm::axis(rotation); - glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z); - // glPushMatrix(); - // glm::vec3 positionToCenter = center - position; - // glTranslatef(positionToCenter.x, positionToCenter.y, positionToCenter.z); - // glScalef(dimensions.x, dimensions.y, dimensions.z); - - glm::vec3 p1 = {0.0f, 0.0f, 0.0f}; - glm::vec3& p2 = dimensions; - - DependencyManager::get()->renderLine(p1, p2, lineColor, lineColor); - - // glPopMatrix(); + glTranslatef(position.x, position.y, position.z); + glm::vec3 axis = glm::axis(rotation); + glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z); + glm::vec3 p1 = {0.0f, 0.0f, 0.0f}; + glm::vec3& p2 = dimensions; + DependencyManager::get()->renderLine(p1, p2, lineColor, lineColor); glPopMatrix(); - RenderableDebugableEntityItem::render(this, args); }; From 500bb13cbb40b2deb5dbb13665e0fe9073c9c389 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Tue, 12 May 2015 15:17:27 -0700 Subject: [PATCH 23/27] c++ --- libraries/entities-renderer/src/RenderableLineEntityItem.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableLineEntityItem.cpp b/libraries/entities-renderer/src/RenderableLineEntityItem.cpp index aaed52c20c..14628d0a7a 100644 --- a/libraries/entities-renderer/src/RenderableLineEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableLineEntityItem.cpp @@ -28,10 +28,7 @@ void RenderableLineEntityItem::render(RenderArgs* args) { glm::vec3 position = getPosition(); glm::vec3 dimensions = getDimensions(); glm::quat rotation = getRotation(); - - const float MAX_COLOR = 255.0f; - - glm::vec4 lineColor(toGlm(getColor()), getLocalRenderAlpha()); + glm::vec4 lineColor(toGlm(getXColor()), getLocalRenderAlpha()); glPushMatrix(); glTranslatef(position.x, position.y, position.z); glm::vec3 axis = glm::axis(rotation); From edadaea60447dbb8cc9983caf418b5be321962f3 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 12 May 2015 15:30:25 -0700 Subject: [PATCH 24/27] add help text for domain-server paths --- domain-server/resources/describe-settings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index db7a82c0d0..e18e863d41 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -50,7 +50,7 @@ { "name": "paths", "label": "", - "help": "", + "help": "Clients can enter a path to reach an exact viewpoint in your domain.
Add rows to the table below to map a path to a viewpoint.
The index path ( / ) is where clients will enter if they do not enter an explicit path.", "type": "table", "key": { "name": "path", From 89cb3d66e4ba3232732e069500f84b98d6a5703a Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 12 May 2015 15:39:22 -0700 Subject: [PATCH 25/27] make sure paths are prepended with a slash --- domain-server/src/DomainServer.cpp | 5 +++-- domain-server/src/DomainServerSettingsManager.cpp | 13 ++++++++++++- domain-server/src/DomainServerSettingsManager.h | 2 ++ 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 09ab25ad91..0b42f78fe2 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -2216,12 +2216,13 @@ void DomainServer::respondToPathQuery(const QByteArray& receivedPacket, const Hi pathQuery.prepend("/"); } - const QString PATHS_SETTINGS_KEYPATH_FORMAT = "paths.%2"; + 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(pathQuery)); + 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(); diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index 665038de39..589231b56a 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -297,7 +297,18 @@ void DomainServerSettingsManager::updateSetting(const QString& key, const QJsonV } } - updateSetting(childKey, newValue.toObject()[childKey], thisMap, childDescriptionObject); + QString sanitizedKey = childKey; + + if (key == SETTINGS_PATHS_KEY) { + // We perform special handling for paths here. + // If we got sent a path without a leading slash then we add it. + + if (!sanitizedKey.startsWith("/")) { + sanitizedKey.prepend("/"); + } + } + + updateSetting(sanitizedKey, newValue.toObject()[childKey], thisMap, childDescriptionObject); } if (settingMap[key].toMap().isEmpty()) { diff --git a/domain-server/src/DomainServerSettingsManager.h b/domain-server/src/DomainServerSettingsManager.h index 1e581c5a21..450baf66ae 100644 --- a/domain-server/src/DomainServerSettingsManager.h +++ b/domain-server/src/DomainServerSettingsManager.h @@ -18,6 +18,8 @@ #include #include +const QString SETTINGS_PATHS_KEY = "paths"; + class DomainServerSettingsManager : public QObject { Q_OBJECT public: From e88cfc913f55f29c22c93fba108f8adc476941cd Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 12 May 2015 15:45:13 -0700 Subject: [PATCH 26/27] make sure viewpoint is prepended with slash --- .../src/DomainServerSettingsManager.cpp | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index 589231b56a..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() @@ -267,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()) { @@ -299,13 +309,10 @@ void DomainServerSettingsManager::updateSetting(const QString& key, const QJsonV QString sanitizedKey = childKey; - if (key == SETTINGS_PATHS_KEY) { + 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. - - if (!sanitizedKey.startsWith("/")) { - sanitizedKey.prepend("/"); - } + sanitizedKey.prepend("/"); } updateSetting(sanitizedKey, newValue.toObject()[childKey], thisMap, childDescriptionObject); From db704874b9bfde11f1d8d923ccb23be01240e956 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Tue, 12 May 2015 16:02:45 -0700 Subject: [PATCH 27/27] bump protocol version for line entities --- libraries/networking/src/PacketHeaders.cpp | 2 +- libraries/networking/src/PacketHeaders.h | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) 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..bdfb791971 100644 --- a/libraries/networking/src/PacketHeaders.h +++ b/libraries/networking/src/PacketHeaders.h @@ -174,5 +174,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