diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index 7600aefc09..c888fa301b 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -1,5 +1,5 @@ { - "version": 1.5, + "version": 1.7, "settings": [ { "name": "metaverse", @@ -395,7 +395,7 @@ }, { "label": "Permissions ?", - "span": 6 + "span": 7 } ], @@ -445,6 +445,13 @@ "type": "checkbox", "editable": true, "default": false + }, + { + "name": "id_can_kick", + "label": "Kick Users", + "type": "checkbox", + "editable": true, + "default": false } ], @@ -468,7 +475,7 @@ }, { "label": "Permissions ?", - "span": 6 + "span": 7 } ], @@ -543,6 +550,13 @@ "type": "checkbox", "editable": true, "default": false + }, + { + "name": "id_can_kick", + "label": "Kick Users", + "type": "checkbox", + "editable": true, + "default": false } ] }, @@ -563,7 +577,7 @@ }, { "label": "Permissions ?", - "span": 6 + "span": 7 } ], @@ -635,23 +649,29 @@ "type": "checkbox", "editable": true, "default": false + }, + { + "name": "id_can_kick", + "label": "Kick Users", + "type": "checkbox", + "editable": true, + "default": false } ] }, { - "name": "permissions", + "name": "ip_permissions", "type": "table", - "caption": "Permissions for Specific Users", + "caption": "Permissions for Users from IP Addresses", "can_add_new_rows": true, - "groups": [ { - "label": "User", + "label": "IP Address", "span": 1 }, { - "label": "Permissions ?", - "span": 6 + "label": "Permissions ?", + "span": 7 } ], @@ -701,6 +721,86 @@ "type": "checkbox", "editable": true, "default": false + }, + { + "name": "id_can_kick", + "label": "Kick Users", + "type": "checkbox", + "editable": true, + "default": false + } + ] + }, + { + "name": "permissions", + "type": "table", + "caption": "Permissions for Specific Users", + "can_add_new_rows": true, + + "groups": [ + { + "label": "User", + "span": 1 + }, + { + "label": "Permissions ?", + "span": 7 + } + ], + + "columns": [ + { + "name": "permissions_id", + "label": "" + }, + { + "name": "id_can_connect", + "label": "Connect", + "type": "checkbox", + "editable": true, + "default": false + }, + { + "name": "id_can_adjust_locks", + "label": "Lock / Unlock", + "type": "checkbox", + "editable": true, + "default": false + }, + { + "name": "id_can_rez", + "label": "Rez", + "type": "checkbox", + "editable": true, + "default": false + }, + { + "name": "id_can_rez_tmp", + "label": "Rez Temporary", + "type": "checkbox", + "editable": true, + "default": false + }, + { + "name": "id_can_write_to_asset_server", + "label": "Write Assets", + "type": "checkbox", + "editable": true, + "default": false + }, + { + "name": "id_can_connect_past_max_capacity", + "label": "Ignore Max Capacity", + "type": "checkbox", + "editable": true, + "default": false + }, + { + "name": "id_can_kick", + "label": "Kick Users", + "type": "checkbox", + "editable": true, + "default": false } ] } diff --git a/domain-server/resources/web/css/style.css b/domain-server/resources/web/css/style.css index b66b7df258..ef967a47bf 100644 --- a/domain-server/resources/web/css/style.css +++ b/domain-server/resources/web/css/style.css @@ -75,15 +75,6 @@ span.port { color: #666666; } -.locked { - color: #428bca; -} - -.locked-table { - cursor: not-allowed; - background-color: #eee; -} - .advanced-setting { display: none; } diff --git a/domain-server/resources/web/settings/index.shtml b/domain-server/resources/web/settings/index.shtml index 3f969ef913..4c937d6139 100644 --- a/domain-server/resources/web/settings/index.shtml +++ b/domain-server/resources/web/settings/index.shtml @@ -57,15 +57,13 @@
<% _.each(split_settings[0], function(setting) { %> <% keypath = isGrouped ? group.name + "." + setting.name : setting.name %> - <%= getFormGroup(keypath, setting, values, false, - (_.has(locked, group.name) && _.has(locked[group.name], setting.name))) %> + <%= getFormGroup(keypath, setting, values, false) %> <% }); %> <% if (!_.isEmpty(split_settings[1])) { %> <% $("#advanced-toggle-button").show() %> <% _.each(split_settings[1], function(setting) { %> <% keypath = isGrouped ? group.name + "." + setting.name : setting.name %> - <%= getFormGroup(keypath, setting, values, true, - (_.has(locked, group.name) && _.has(locked[group.name], setting.name))) %> + <%= getFormGroup(keypath, setting, values, true) %> <% }); %> <% }%>
diff --git a/domain-server/resources/web/settings/js/settings.js b/domain-server/resources/web/settings/js/settings.js index c1005de105..42fcb05d2e 100644 --- a/domain-server/resources/web/settings/js/settings.js +++ b/domain-server/resources/web/settings/js/settings.js @@ -41,7 +41,7 @@ var Settings = { }; var viewHelpers = { - getFormGroup: function(keypath, setting, values, isAdvanced, isLocked) { + getFormGroup: function(keypath, setting, values, isAdvanced) { form_group = "
"; setting_value = _(values).valueForKeyPath(keypath); @@ -54,9 +54,6 @@ var viewHelpers = { } label_class = 'control-label'; - if (isLocked) { - label_class += ' locked'; - } function common_attrs(extra_classes) { extra_classes = (!_.isUndefined(extra_classes) ? extra_classes : ""); @@ -71,9 +68,8 @@ var viewHelpers = { form_group += "" } - form_group += "
" - form_group += "" + form_group += "
" + form_group += "" if (setting.help) { form_group += "" + setting.help + ""; @@ -88,7 +84,7 @@ var viewHelpers = { } if (input_type === 'table') { - form_group += makeTable(setting, keypath, setting_value, isLocked) + form_group += makeTable(setting, keypath, setting_value) } else { if (input_type === 'select') { form_group += "" + "' value='" + setting_value + "'/>" } form_group += "" + setting.help + "" @@ -459,10 +453,8 @@ function setupHFAccountButton() { $("[data-keypath='metaverse.automatic_networking']").hide(); } - var tokenLocked = _(Settings.data).valueForKeyPath("locked.metaverse.access_token"); - // use the existing getFormGroup helper to ask for a button - var buttonGroup = viewHelpers.getFormGroup('', buttonSetting, Settings.data.values, false, tokenLocked); + var buttonGroup = viewHelpers.getFormGroup('', buttonSetting, Settings.data.values, false); // add the button group to the top of the metaverse panel $('#metaverse .panel-body').prepend(buttonGroup); @@ -673,7 +665,7 @@ function setupPlacesTable() { } // get a table for the places - var placesTableGroup = viewHelpers.getFormGroup('', placesTableSetting, Settings.data.values, false, false); + var placesTableGroup = viewHelpers.getFormGroup('', placesTableSetting, Settings.data.values, false); // append the places table in the right place $('#places_paths .panel-body').prepend(placesTableGroup); @@ -873,10 +865,8 @@ function reloadSettings(callback) { Settings.data = data; Settings.initialValues = form2js('settings-form', ".", false, cleanupFormValues, true); - if (!_.has(data["locked"], "metaverse") && !_.has(data["locked"]["metaverse"], "id")) { - // append the domain selection modal, as long as it's not locked - appendDomainIDButtons(); - } + // append the domain selection modal + appendDomainIDButtons(); // call our method to setup the HF account button setupHFAccountButton(); @@ -889,12 +879,6 @@ function reloadSettings(callback) { $('[data-toggle="tooltip"]').tooltip(); - // add tooltip to locked settings - $('label.locked').tooltip({ - placement: 'right', - title: 'This setting is in the master config file and cannot be changed' - }); - // call the callback now that settings are loaded callback(true); }).fail(function() { @@ -943,11 +927,11 @@ $('body').on('click', '.save-button', function(e){ return false; }); -function makeTable(setting, keypath, setting_value, isLocked) { +function makeTable(setting, keypath, setting_value) { var isArray = !_.has(setting, 'key'); var categoryKey = setting.categorize_by_key; var isCategorized = !!categoryKey && isArray; - + if (!isArray && setting.can_order) { setting.can_order = false; } @@ -961,7 +945,7 @@ function makeTable(setting, keypath, setting_value, isLocked) { var nonDeletableRowKey = setting["non-deletable-row-key"]; var nonDeletableRowValues = setting["non-deletable-row-values"]; - html += ""; @@ -976,7 +960,7 @@ function makeTable(setting, keypath, setting_value, isLocked) { _.each(setting.groups, function (group) { html += "" }) - if (!isLocked && !setting.read_only) { + if (!setting.read_only) { if (setting.can_order) { html += ""; @@ -1004,7 +988,7 @@ function makeTable(setting, keypath, setting_value, isLocked) { (col.class ? col.class : '') + "'>" + col.label + "" // Data }) - if (!isLocked && !setting.read_only) { + if (!setting.read_only) { if (setting.can_order) { numVisibleColumns++; html += "
" + group.label + "" @@ -1108,7 +1092,7 @@ function makeTable(setting, keypath, setting_value, isLocked) { } // populate inputs in the table for new values - if (!isLocked && !setting.read_only) { + if (!setting.read_only) { if (setting.can_add_new_categories) { html += makeTableCategoryInput(setting, numVisibleColumns); } diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index 5b2e5a2bb0..8f8c8e001c 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -120,8 +120,9 @@ void DomainGatekeeper::processConnectRequestPacket(QSharedPointer_settingsManager.hasPermissionsForIP(senderAddress)) { + // this user comes from an IP we have in our permissions table, apply those permissions + userPerms = _server->_settingsManager.getPermissionsForIP(senderAddress); + +#ifdef WANT_DEBUG + qDebug() << "| user-permissions: specific IP matches, so:" << userPerms; +#endif + } } else { - userPerms.setID(verifiedUsername); if (_server->_settingsManager.havePermissionsForName(verifiedUsername)) { userPerms = _server->_settingsManager.getPermissionsForName(verifiedUsername); - userPerms.setVerifiedUserName(verifiedUsername); #ifdef WANT_DEBUG qDebug() << "| user-permissions: specific user matches, so:" << userPerms; +#endif + } else if (_server->_settingsManager.hasPermissionsForIP(senderAddress)) { + // this user comes from an IP we have in our permissions table, apply those permissions + userPerms = _server->_settingsManager.getPermissionsForIP(senderAddress); + +#ifdef WANT_DEBUG + qDebug() << "| user-permissions: specific IP matches, so:" << userPerms; #endif } else { - userPerms.setVerifiedUserName(verifiedUsername); // they are logged into metaverse, but we don't have specific permissions for them. userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameLoggedIn); #ifdef WANT_DEBUG @@ -191,6 +205,9 @@ NodePermissions DomainGatekeeper::setPermissionsForUser(bool isLocalUser, QStrin } } } + + userPerms.setID(verifiedUsername); + userPerms.setVerifiedUserName(verifiedUsername); } #ifdef WANT_DEBUG @@ -225,7 +242,12 @@ void DomainGatekeeper::updateNodePermissions() { const QHostAddress& addr = node->getLocalSocket().getAddress(); bool isLocalUser = (addr == limitedNodeList->getLocalSockAddr().getAddress() || addr == QHostAddress::LocalHost); - userPerms = setPermissionsForUser(isLocalUser, verifiedUsername); + + // at this point we don't have a sending socket for packets from this node - assume it is the active socket + // or the public socket if we haven't activated a socket for the node yet + HifiSockAddr connectingAddr = node->getActiveSocket() ? *node->getActiveSocket() : node->getPublicSocket(); + + userPerms = setPermissionsForUser(isLocalUser, verifiedUsername, connectingAddr.getAddress()); } node->setPermissions(userPerms); @@ -337,7 +359,7 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect } } - userPerms = setPermissionsForUser(isLocalUser, verifiedUsername); + userPerms = setPermissionsForUser(isLocalUser, verifiedUsername, nodeConnection.senderSockAddr.getAddress()); if (!userPerms.can(NodePermissions::Permission::canConnectToDomain)) { sendConnectionDeniedPacket("You lack the required permissions to connect to this domain.", @@ -430,11 +452,11 @@ SharedNodePointer DomainGatekeeper::addVerifiedNodeFromConnectRequest(const Node bool DomainGatekeeper::verifyUserSignature(const QString& username, const QByteArray& usernameSignature, const HifiSockAddr& senderSockAddr) { - // it's possible this user can be allowed to connect, but we need to check their username signature - QByteArray publicKeyArray = _userPublicKeys.value(username.toLower()); + auto lowerUsername = username.toLower(); + QByteArray publicKeyArray = _userPublicKeys.value(lowerUsername); - const QUuid& connectionToken = _connectionTokenHash.value(username.toLower()); + const QUuid& connectionToken = _connectionTokenHash.value(lowerUsername); if (!publicKeyArray.isEmpty() && !connectionToken.isNull()) { // if we do have a public key for the user, check for a signature match @@ -444,8 +466,8 @@ bool DomainGatekeeper::verifyUserSignature(const QString& username, // first load up the public key into an RSA struct RSA* rsaPublicKey = d2i_RSA_PUBKEY(NULL, &publicKeyData, publicKeyArray.size()); - QByteArray lowercaseUsername = username.toLower().toUtf8(); - QByteArray usernameWithToken = QCryptographicHash::hash(lowercaseUsername.append(connectionToken.toRfc4122()), + QByteArray lowercaseUsernameUTF8 = lowerUsername.toUtf8(); + QByteArray usernameWithToken = QCryptographicHash::hash(lowercaseUsernameUTF8.append(connectionToken.toRfc4122()), QCryptographicHash::Sha256); if (rsaPublicKey) { @@ -575,10 +597,7 @@ void DomainGatekeeper::publicKeyJSONCallback(QNetworkReply& requestReply) { QJsonObject jsonObject = QJsonDocument::fromJson(requestReply.readAll()).object(); QString username = extractUsernameFromPublicKeyRequest(requestReply); - if (jsonObject["status"].toString() == "success" && username != "") { - // figure out which user this is for - const QString PUBLIC_KEY_URL_REGEX_STRING = "api\\/v1\\/users\\/([A-Za-z0-9_\\.]+)\\/public_key"; - qDebug() << "Storing a public key for user" << username; + if (jsonObject["status"].toString() == "success" && !username.isEmpty()) { // pull the public key as a QByteArray from this response const QString JSON_DATA_KEY = "data"; const QString JSON_PUBLIC_KEY_KEY = "public_key"; diff --git a/domain-server/src/DomainGatekeeper.h b/domain-server/src/DomainGatekeeper.h index 12697b8f3b..06ecfcf285 100644 --- a/domain-server/src/DomainGatekeeper.h +++ b/domain-server/src/DomainGatekeeper.h @@ -106,7 +106,8 @@ private: QSet _domainOwnerFriends; // keep track of friends of the domain owner QSet _inFlightGroupMembershipsRequests; // keep track of which we've already asked for - NodePermissions setPermissionsForUser(bool isLocalUser, QString verifiedUsername); + NodePermissions setPermissionsForUser(bool isLocalUser, QString verifiedUsername, const QHostAddress& senderAddress); + void getGroupMemberships(const QString& username); // void getIsGroupMember(const QString& username, const QUuid groupID); void getDomainOwnerFriendsList(); diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 8af364992c..23e37efaf1 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -411,6 +411,7 @@ void DomainServer::setupNodeListAndAssignments() { // NodeList won't be available to the settings manager when it is created, so call registerListener here packetReceiver.registerListener(PacketType::DomainSettingsRequest, &_settingsManager, "processSettingsRequestPacket"); + packetReceiver.registerListener(PacketType::NodeKickRequest, &_settingsManager, "processNodeKickRequestPacket"); // register the gatekeeper for the packets it needs to receive packetReceiver.registerListener(PacketType::DomainConnectRequest, &_gatekeeper, "processConnectRequestPacket"); diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index a02b19f9fd..dc49bc6126 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -95,6 +95,8 @@ void DomainServerSettingsManager::processSettingsRequestPacket(QSharedPointercanConvert(QMetaType::QVariantList) @@ -129,9 +131,7 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList // In the pre-toggle system the user had a list of allowed users, so // we need to set security.restricted_access to true - QVariant* restrictedAccess = valueForKeyPath(_configMap.getUserConfig(), - RESTRICTED_ACCESS_SETTINGS_KEYPATH, - true); + QVariant* restrictedAccess = _configMap.valueForKeyPath(RESTRICTED_ACCESS_SETTINGS_KEYPATH, true); *restrictedAccess = QVariant(true); @@ -149,21 +149,20 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList static const QString ENTITY_FILE_PATH_KEYPATH = ENTITY_SERVER_SETTINGS_KEY + ".persistFilePath"; // this was prior to change of poorly named entitiesFileName to entitiesFilePath - QVariant* persistFileNameVariant = valueForKeyPath(_configMap.getMergedConfig(), - ENTITY_SERVER_SETTINGS_KEY + "." + ENTITY_FILE_NAME_KEY); + QVariant* persistFileNameVariant = _configMap.valueForKeyPath(ENTITY_SERVER_SETTINGS_KEY + "." + ENTITY_FILE_NAME_KEY); if (persistFileNameVariant && persistFileNameVariant->canConvert(QMetaType::QString)) { QString persistFileName = persistFileNameVariant->toString(); qDebug() << "Migrating persistFilename to persistFilePath for entity-server settings"; // grab the persistFilePath option, create it if it doesn't exist - QVariant* persistFilePath = valueForKeyPath(_configMap.getUserConfig(), ENTITY_FILE_PATH_KEYPATH, true); + QVariant* persistFilePath = _configMap.valueForKeyPath(ENTITY_FILE_PATH_KEYPATH, true); // write the migrated value *persistFilePath = persistFileName; // remove the old setting - QVariant* entityServerVariant = valueForKeyPath(_configMap.getUserConfig(), ENTITY_SERVER_SETTINGS_KEY); + QVariant* entityServerVariant = _configMap.valueForKeyPath(ENTITY_SERVER_SETTINGS_KEY); if (entityServerVariant && entityServerVariant->canConvert(QMetaType::QVariantMap)) { QVariantMap entityServerMap = entityServerVariant->toMap(); entityServerMap.remove(ENTITY_FILE_NAME_KEY); @@ -185,7 +184,7 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList // If we have a password in the previous settings file, make it base 64 static const QString BASIC_AUTH_PASSWORD_KEY_PATH { "security.http_password" }; - QVariant* passwordVariant = valueForKeyPath(_configMap.getUserConfig(), BASIC_AUTH_PASSWORD_KEY_PATH); + QVariant* passwordVariant = _configMap.valueForKeyPath(BASIC_AUTH_PASSWORD_KEY_PATH); if (passwordVariant && passwordVariant->canConvert(QMetaType::QString)) { QString plaintextPassword = passwordVariant->toString(); @@ -273,6 +272,28 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList // This was prior to operating hours, so add default hours validateDescriptorsMap(); } + + if (oldVersion < 1.6) { + unpackPermissions(); + + // This was prior to addition of kick permissions, add that to localhost permissions by default + _standardAgentPermissions[NodePermissions::standardNameLocalhost]->set(NodePermissions::Permission::canKick); + + packPermissions(); + } + + if (oldVersion < 1.7) { + // This was prior to the removal of the master config file + // So we write the merged config to the user config file, and stop reading from the user config file + + qDebug() << "Migrating merged config to user config file. The master config file is deprecated."; + + // replace the user config by the merged config + _configMap.getConfig() = _configMap.getMergedConfig(); + + // persist the new config so the user config file has the correctly merged config + persistToFile(); + } } unpackPermissions(); @@ -293,9 +314,9 @@ void DomainServerSettingsManager::validateDescriptorsMap() { static const QString WEEKEND_HOURS{ "descriptors.weekend_hours" }; static const QString UTC_OFFSET{ "descriptors.utc_offset" }; - QVariant* weekdayHours = valueForKeyPath(_configMap.getUserConfig(), WEEKDAY_HOURS, true); - QVariant* weekendHours = valueForKeyPath(_configMap.getUserConfig(), WEEKEND_HOURS, true); - QVariant* utcOffset = valueForKeyPath(_configMap.getUserConfig(), UTC_OFFSET, true); + QVariant* weekdayHours = _configMap.valueForKeyPath(WEEKDAY_HOURS, true); + QVariant* weekendHours = _configMap.valueForKeyPath(WEEKEND_HOURS, true); + QVariant* utcOffset = _configMap.valueForKeyPath(UTC_OFFSET, true); static const QString OPEN{ "open" }; static const QString CLOSE{ "close" }; @@ -318,9 +339,6 @@ void DomainServerSettingsManager::validateDescriptorsMap() { if (wasMalformed) { // write the new settings to file persistToFile(); - - // reload the master and user config so the merged config is correct - _configMap.loadMasterAndUserConfig(_argumentList); } } @@ -354,16 +372,14 @@ void DomainServerSettingsManager::packPermissionsForMap(QString mapName, NodePermissionsMap& permissionsRows, QString keyPath) { // find (or create) the "security" section of the settings map - QVariant* security = valueForKeyPath(_configMap.getUserConfig(), "security"); - if (!security || !security->canConvert(QMetaType::QVariantMap)) { - security = valueForKeyPath(_configMap.getUserConfig(), "security", true); + QVariant* security = _configMap.valueForKeyPath("security", true); + if (!security->canConvert(QMetaType::QVariantMap)) { (*security) = QVariantMap(); } // find (or create) whichever subsection of "security" we are packing - QVariant* permissions = valueForKeyPath(_configMap.getUserConfig(), keyPath); - if (!permissions || !permissions->canConvert(QMetaType::QVariantList)) { - permissions = valueForKeyPath(_configMap.getUserConfig(), keyPath, true); + QVariant* permissions = _configMap.valueForKeyPath(keyPath, true); + if (!permissions->canConvert(QMetaType::QVariantList)) { (*permissions) = QVariantList(); } @@ -420,6 +436,9 @@ void DomainServerSettingsManager::packPermissions() { // save settings for specific users packPermissionsForMap("permissions", _agentPermissions, AGENT_PERMISSIONS_KEYPATH); + // save settings for IP addresses + packPermissionsForMap("permissions", _ipPermissions, IP_PERMISSIONS_KEYPATH); + // save settings for groups packPermissionsForMap("permissions", _groupPermissions, GROUP_PERMISSIONS_KEYPATH); @@ -427,139 +446,103 @@ void DomainServerSettingsManager::packPermissions() { packPermissionsForMap("permissions", _groupForbiddens, GROUP_FORBIDDENS_KEYPATH); persistToFile(); - _configMap.loadMasterAndUserConfig(_argumentList); } -void DomainServerSettingsManager::unpackPermissions() { - // transfer details from _configMap to _agentPermissions; +bool DomainServerSettingsManager::unpackPermissionsForKeypath(const QString& keyPath, + NodePermissionsMap* mapPointer, + std::function customUnpacker) { - _standardAgentPermissions.clear(); - _agentPermissions.clear(); - _groupPermissions.clear(); - _groupForbiddens.clear(); + mapPointer->clear(); - bool foundLocalhost = false; - bool foundAnonymous = false; - bool foundLoggedIn = false; - bool foundFriends = false; - bool needPack = false; - - QVariant* standardPermissions = valueForKeyPath(_configMap.getUserConfig(), AGENT_STANDARD_PERMISSIONS_KEYPATH); - if (!standardPermissions || !standardPermissions->canConvert(QMetaType::QVariantList)) { - qDebug() << "failed to extract standard permissions from settings."; - standardPermissions = valueForKeyPath(_configMap.getUserConfig(), AGENT_STANDARD_PERMISSIONS_KEYPATH, true); - (*standardPermissions) = QVariantList(); - } - QVariant* permissions = valueForKeyPath(_configMap.getUserConfig(), AGENT_PERMISSIONS_KEYPATH); - if (!permissions || !permissions->canConvert(QMetaType::QVariantList)) { - qDebug() << "failed to extract permissions from settings."; - permissions = valueForKeyPath(_configMap.getUserConfig(), AGENT_PERMISSIONS_KEYPATH, true); + QVariant* permissions = _configMap.valueForKeyPath(keyPath, true); + if (!permissions->canConvert(QMetaType::QVariantList)) { + qDebug() << "Failed to extract permissions for key path" << keyPath << "from settings."; (*permissions) = QVariantList(); } - QVariant* groupPermissions = valueForKeyPath(_configMap.getUserConfig(), GROUP_PERMISSIONS_KEYPATH); - if (!groupPermissions || !groupPermissions->canConvert(QMetaType::QVariantList)) { - qDebug() << "failed to extract group permissions from settings."; - groupPermissions = valueForKeyPath(_configMap.getUserConfig(), GROUP_PERMISSIONS_KEYPATH, true); - (*groupPermissions) = QVariantList(); - } - QVariant* groupForbiddens = valueForKeyPath(_configMap.getUserConfig(), GROUP_FORBIDDENS_KEYPATH); - if (!groupForbiddens || !groupForbiddens->canConvert(QMetaType::QVariantList)) { - qDebug() << "failed to extract group forbiddens from settings."; - groupForbiddens = valueForKeyPath(_configMap.getUserConfig(), GROUP_FORBIDDENS_KEYPATH, true); - (*groupForbiddens) = QVariantList(); - } - QList standardPermissionsList = standardPermissions->toList(); - foreach (QVariant permsHash, standardPermissionsList) { - NodePermissionsPointer perms { new NodePermissions(permsHash.toMap()) }; - QString id = perms->getID(); - NodePermissionsKey idKey = NodePermissionsKey(id, 0); - foundLocalhost |= (idKey == NodePermissions::standardNameLocalhost); - foundAnonymous |= (idKey == NodePermissions::standardNameAnonymous); - foundLoggedIn |= (idKey == NodePermissions::standardNameLoggedIn); - foundFriends |= (idKey == NodePermissions::standardNameFriends); - if (_standardAgentPermissions.contains(idKey)) { - qDebug() << "duplicate name in standard permissions table: " << id; - *(_standardAgentPermissions[idKey]) |= *perms; - needPack = true; - } else { - _standardAgentPermissions[idKey] = perms; - } - } + bool needPack = false; QList permissionsList = permissions->toList(); foreach (QVariant permsHash, permissionsList) { NodePermissionsPointer perms { new NodePermissions(permsHash.toMap()) }; QString id = perms->getID(); - NodePermissionsKey idKey = NodePermissionsKey(id, 0); - if (_agentPermissions.contains(idKey)) { - qDebug() << "duplicate name in permissions table: " << id; - *(_agentPermissions[idKey]) |= *perms; + + NodePermissionsKey idKey = perms->getKey(); + + if (mapPointer->contains(idKey)) { + qDebug() << "Duplicate name in permissions table for" << keyPath << " - " << id; + *((*mapPointer)[idKey]) |= *perms; needPack = true; } else { - _agentPermissions[idKey] = perms; + (*mapPointer)[idKey] = perms; + } + + if (customUnpacker) { + customUnpacker(perms); } } - QList groupPermissionsList = groupPermissions->toList(); - foreach (QVariant permsHash, groupPermissionsList) { - NodePermissionsPointer perms { new NodePermissions(permsHash.toMap()) }; - QString id = perms->getID(); - NodePermissionsKey idKey = perms->getKey(); - if (_groupPermissions.contains(idKey)) { - qDebug() << "duplicate name in group permissions table: " << id; - *(_groupPermissions[idKey]) |= *perms; - needPack = true; - } else { - *(_groupPermissions[idKey]) = *perms; - } - if (perms->isGroup()) { - // the group-id was cached. hook-up the uuid in the uuid->group hash - _groupPermissionsByUUID[GroupByUUIDKey(perms->getGroupID(), perms->getRankID())] = _groupPermissions[idKey]; - needPack |= setGroupID(perms->getID(), perms->getGroupID()); - } - } + return needPack; - QList groupForbiddensList = groupForbiddens->toList(); - foreach (QVariant permsHash, groupForbiddensList) { - NodePermissionsPointer perms { new NodePermissions(permsHash.toMap()) }; - QString id = perms->getID(); - NodePermissionsKey idKey = perms->getKey(); - if (_groupForbiddens.contains(idKey)) { - qDebug() << "duplicate name in group forbiddens table: " << id; - *(_groupForbiddens[idKey]) |= *perms; - needPack = true; - } else { - _groupForbiddens[idKey] = perms; - } - if (perms->isGroup()) { - // the group-id was cached. hook-up the uuid in the uuid->group hash - _groupForbiddensByUUID[GroupByUUIDKey(perms->getGroupID(), perms->getRankID())] = _groupForbiddens[idKey]; - needPack |= setGroupID(perms->getID(), perms->getGroupID()); - } - } +} + +void DomainServerSettingsManager::unpackPermissions() { + // transfer details from _configMap to _agentPermissions + + bool needPack = false; + + needPack |= unpackPermissionsForKeypath(AGENT_STANDARD_PERMISSIONS_KEYPATH, &_standardAgentPermissions); + + needPack |= unpackPermissionsForKeypath(AGENT_PERMISSIONS_KEYPATH, &_agentPermissions); + + needPack |= unpackPermissionsForKeypath(IP_PERMISSIONS_KEYPATH, &_ipPermissions, + [&](NodePermissionsPointer perms){ + // make sure that this permission row is for a valid IP address + if (QHostAddress(perms->getKey().first).isNull()) { + _ipPermissions.remove(perms->getKey()); + + // we removed a row from the IP permissions, we'll need a re-pack + needPack = true; + } + }); + + + needPack |= unpackPermissionsForKeypath(GROUP_PERMISSIONS_KEYPATH, &_groupPermissions, + [&](NodePermissionsPointer perms){ + if (perms->isGroup()) { + // the group-id was cached. hook-up the uuid in the uuid->group hash + _groupPermissionsByUUID[GroupByUUIDKey(perms->getGroupID(), perms->getRankID())] = _groupPermissions[perms->getKey()]; + needPack |= setGroupID(perms->getID(), perms->getGroupID()); + } + }); + + needPack |= unpackPermissionsForKeypath(GROUP_FORBIDDENS_KEYPATH, &_groupForbiddens, + [&](NodePermissionsPointer perms) { + if (perms->isGroup()) { + // the group-id was cached. hook-up the uuid in the uuid->group hash + _groupForbiddensByUUID[GroupByUUIDKey(perms->getGroupID(), perms->getRankID())] = _groupForbiddens[perms->getKey()]; + needPack |= setGroupID(perms->getID(), perms->getGroupID()); + } + }); // if any of the standard names are missing, add them - if (!foundLocalhost) { - NodePermissionsPointer perms { new NodePermissions(NodePermissions::standardNameLocalhost) }; - perms->setAll(true); - _standardAgentPermissions[perms->getKey()] = perms; - needPack = true; - } - if (!foundAnonymous) { - NodePermissionsPointer perms { new NodePermissions(NodePermissions::standardNameAnonymous) }; - _standardAgentPermissions[perms->getKey()] = perms; - needPack = true; - } - if (!foundLoggedIn) { - NodePermissionsPointer perms { new NodePermissions(NodePermissions::standardNameLoggedIn) }; - _standardAgentPermissions[perms->getKey()] = perms; - needPack = true; - } - if (!foundFriends) { - NodePermissionsPointer perms { new NodePermissions(NodePermissions::standardNameFriends) }; - _standardAgentPermissions[perms->getKey()] = perms; - needPack = true; + foreach(const QString& standardName, NodePermissions::standardNames) { + NodePermissionsKey standardKey { standardName, 0 }; + if (!_standardAgentPermissions.contains(standardKey)) { + // we don't have permissions for one of the standard groups, so we'll add them now + NodePermissionsPointer perms { new NodePermissions(standardKey) }; + + // the localhost user is granted all permissions by default + if (standardKey == NodePermissions::standardNameLocalhost) { + perms->setAll(true); + } + + // add the permissions to the standard map + _standardAgentPermissions[standardKey] = perms; + + // this will require a packing of permissions + needPack = true; + } } needPack |= ensurePermissionsForGroupRanks(); @@ -572,7 +555,7 @@ void DomainServerSettingsManager::unpackPermissions() { qDebug() << "--------------- permissions ---------------------"; QList> permissionsSets; permissionsSets << _standardAgentPermissions.get() << _agentPermissions.get() - << _groupPermissions.get() << _groupForbiddens.get(); + << _groupPermissions.get() << _groupForbiddens.get() << _ipPermissions.get(); foreach (auto permissionSet, permissionsSets) { QHashIterator i(permissionSet); while (i.hasNext()) { @@ -648,6 +631,89 @@ bool DomainServerSettingsManager::ensurePermissionsForGroupRanks() { return changed; } + +void DomainServerSettingsManager::processNodeKickRequestPacket(QSharedPointer message, SharedNodePointer sendingNode) { + // before we do any processing on this packet make sure it comes from a node that is allowed to kick + if (sendingNode->getCanKick()) { + // pull the UUID being kicked from the packet + QUuid nodeUUID = QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID)); + + if (!nodeUUID.isNull() && nodeUUID != sendingNode->getUUID()) { + // make sure we actually have a node with this UUID + auto limitedNodeList = DependencyManager::get(); + + auto matchingNode = limitedNodeList->nodeWithUUID(nodeUUID); + + if (matchingNode) { + // we have a matching node, time to decide how to store updated permissions for this node + + NodePermissionsPointer destinationPermissions; + + auto verifiedUsername = matchingNode->getPermissions().getVerifiedUserName(); + + bool hadExistingPermissions = false; + + if (!verifiedUsername.isEmpty()) { + // if we have a verified user name for this user, we apply the kick to the username + + // check if there were already permissions + hadExistingPermissions = havePermissionsForName(verifiedUsername); + + // grab or create permissions for the given username + destinationPermissions = _agentPermissions[matchingNode->getPermissions().getKey()]; + } else { + // otherwise we apply the kick to the IP from active socket for this node + // (falling back to the public socket if not yet active) + auto& kickAddress = matchingNode->getActiveSocket() + ? matchingNode->getActiveSocket()->getAddress() + : matchingNode->getPublicSocket().getAddress(); + + NodePermissionsKey ipAddressKey(kickAddress.toString(), QUuid()); + + // check if there were already permissions for the IP + hadExistingPermissions = hasPermissionsForIP(kickAddress); + + // grab or create permissions for the given IP address + destinationPermissions = _ipPermissions[ipAddressKey]; + } + + // make sure we didn't already have existing permissions that disallowed connect + if (!hadExistingPermissions + || destinationPermissions->can(NodePermissions::Permission::canConnectToDomain)) { + + qDebug() << "Removing connect permission for node" << uuidStringWithoutCurlyBraces(matchingNode->getUUID()) + << "after kick request"; + + // ensure that the connect permission is clear + destinationPermissions->clear(NodePermissions::Permission::canConnectToDomain); + + // we've changed permissions, time to store them to disk and emit our signal to say they have changed + packPermissions(); + + emit updateNodePermissions(); + } else { + qWarning() << "Received kick request for node" << uuidStringWithoutCurlyBraces(matchingNode->getUUID()) + << "that already did not have permission to connect"; + + // in this case, though we don't expect the node to be connected to the domain, it is + // emit updateNodePermissions so that the DomainGatekeeper kicks it out + emit updateNodePermissions(); + } + + } else { + qWarning() << "Node kick request received for unknown node. Refusing to process."; + } + } else { + // this isn't a UUID we can use + qWarning() << "Node kick request received for invalid node ID or from node being kicked. Refusing to process."; + } + + } else { + qWarning() << "Refusing to process a kick packet from node" << uuidStringWithoutCurlyBraces(sendingNode->getUUID()) + << "that does not have kick permissions."; + } +} + QStringList DomainServerSettingsManager::getAllNames() const { QStringList result; foreach (auto key, _agentPermissions.keys()) { @@ -675,6 +741,16 @@ NodePermissions DomainServerSettingsManager::getPermissionsForName(const QString return nullPermissions; } +NodePermissions DomainServerSettingsManager::getPermissionsForIP(const QHostAddress& address) const { + NodePermissionsKey ipKey = NodePermissionsKey(address.toString(), 0); + if (_ipPermissions.contains(ipKey)) { + return *(_ipPermissions[ipKey].get()); + } + NodePermissions nullPermissions; + nullPermissions.setAll(false); + return nullPermissions; +} + NodePermissions DomainServerSettingsManager::getPermissionsForGroup(const QString& groupName, QUuid rankID) const { NodePermissionsKey groupRankKey = NodePermissionsKey(groupName, rankID); if (_groupPermissions.contains(groupRankKey)) { @@ -719,7 +795,7 @@ NodePermissions DomainServerSettingsManager::getForbiddensForGroup(const QUuid& } QVariant DomainServerSettingsManager::valueOrDefaultValueForKeyPath(const QString& keyPath) { - const QVariant* foundValue = valueForKeyPath(_configMap.getMergedConfig(), keyPath); + const QVariant* foundValue = _configMap.valueForKeyPath(keyPath); if (foundValue) { return *foundValue; @@ -802,12 +878,10 @@ bool DomainServerSettingsManager::handleAuthenticatedHTTPRequest(HTTPConnection // setup a JSON Object with descriptions and non-omitted settings const QString SETTINGS_RESPONSE_DESCRIPTION_KEY = "descriptions"; const QString SETTINGS_RESPONSE_VALUE_KEY = "values"; - const QString SETTINGS_RESPONSE_LOCKED_VALUES_KEY = "locked"; QJsonObject rootObject; rootObject[SETTINGS_RESPONSE_DESCRIPTION_KEY] = _descriptionArray; rootObject[SETTINGS_RESPONSE_VALUE_KEY] = responseObjectForType("", true); - rootObject[SETTINGS_RESPONSE_LOCKED_VALUES_KEY] = QJsonDocument::fromVariant(_configMap.getMasterConfig()).object(); connection->respond(HTTPConnection::StatusCode200, QJsonDocument(rootObject).toJson(), "application/json"); } @@ -852,13 +926,13 @@ QJsonObject DomainServerSettingsManager::responseObjectForType(const QString& ty QVariant variantValue; if (!groupKey.isEmpty()) { - QVariant settingsMapGroupValue = _configMap.getMergedConfig().value(groupKey); + QVariant settingsMapGroupValue = _configMap.value(groupKey); if (!settingsMapGroupValue.isNull()) { variantValue = settingsMapGroupValue.toMap().value(settingName); } } else { - variantValue = _configMap.getMergedConfig().value(settingName); + variantValue = _configMap.value(settingName); } QJsonValue result; @@ -1000,7 +1074,7 @@ QJsonObject DomainServerSettingsManager::settingDescriptionFromGroup(const QJson } bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject) { - auto& settingsVariant = _configMap.getUserConfig(); + auto& settingsVariant = _configMap.getConfig(); bool needRestart = false; // Iterate on the setting groups @@ -1112,22 +1186,22 @@ bool permissionVariantLessThan(const QVariant &v1, const QVariant &v2) { void DomainServerSettingsManager::sortPermissions() { // sort the permission-names - QVariant* standardPermissions = valueForKeyPath(_configMap.getUserConfig(), AGENT_STANDARD_PERMISSIONS_KEYPATH); + QVariant* standardPermissions = _configMap.valueForKeyPath(AGENT_STANDARD_PERMISSIONS_KEYPATH); if (standardPermissions && standardPermissions->canConvert(QMetaType::QVariantList)) { QList* standardPermissionsList = reinterpret_cast(standardPermissions); std::sort((*standardPermissionsList).begin(), (*standardPermissionsList).end(), permissionVariantLessThan); } - QVariant* permissions = valueForKeyPath(_configMap.getUserConfig(), AGENT_PERMISSIONS_KEYPATH); + QVariant* permissions = _configMap.valueForKeyPath(AGENT_PERMISSIONS_KEYPATH); if (permissions && permissions->canConvert(QMetaType::QVariantList)) { QList* permissionsList = reinterpret_cast(permissions); std::sort((*permissionsList).begin(), (*permissionsList).end(), permissionVariantLessThan); } - QVariant* groupPermissions = valueForKeyPath(_configMap.getUserConfig(), GROUP_PERMISSIONS_KEYPATH); + QVariant* groupPermissions = _configMap.valueForKeyPath(GROUP_PERMISSIONS_KEYPATH); if (groupPermissions && groupPermissions->canConvert(QMetaType::QVariantList)) { QList* permissionsList = reinterpret_cast(groupPermissions); std::sort((*permissionsList).begin(), (*permissionsList).end(), permissionVariantLessThan); } - QVariant* forbiddenPermissions = valueForKeyPath(_configMap.getUserConfig(), GROUP_FORBIDDENS_KEYPATH); + QVariant* forbiddenPermissions = _configMap.valueForKeyPath(GROUP_FORBIDDENS_KEYPATH); if (forbiddenPermissions && forbiddenPermissions->canConvert(QMetaType::QVariantList)) { QList* permissionsList = reinterpret_cast(forbiddenPermissions); std::sort((*permissionsList).begin(), (*permissionsList).end(), permissionVariantLessThan); @@ -1147,9 +1221,12 @@ void DomainServerSettingsManager::persistToFile() { QFile settingsFile(_configMap.getUserConfigFilename()); if (settingsFile.open(QIODevice::WriteOnly)) { - settingsFile.write(QJsonDocument::fromVariant(_configMap.getUserConfig()).toJson()); + settingsFile.write(QJsonDocument::fromVariant(_configMap.getConfig()).toJson()); } else { qCritical("Could not write to JSON settings file. Unable to persist settings."); + + // failed to write, reload whatever the current config state is + _configMap.loadConfig(_argumentList); } } diff --git a/domain-server/src/DomainServerSettingsManager.h b/domain-server/src/DomainServerSettingsManager.h index f56b1ecd21..144589326c 100644 --- a/domain-server/src/DomainServerSettingsManager.h +++ b/domain-server/src/DomainServerSettingsManager.h @@ -27,6 +27,7 @@ const QString SETTINGS_PATH = "/settings"; const QString SETTINGS_PATH_JSON = SETTINGS_PATH + ".json"; const QString AGENT_STANDARD_PERMISSIONS_KEYPATH = "security.standard_permissions"; const QString AGENT_PERMISSIONS_KEYPATH = "security.permissions"; +const QString IP_PERMISSIONS_KEYPATH = "security.ip_permissions"; const QString GROUP_PERMISSIONS_KEYPATH = "security.group_permissions"; const QString GROUP_FORBIDDENS_KEYPATH = "security.group_forbiddens"; @@ -43,8 +44,7 @@ public: void setupConfigMap(const QStringList& argumentList); QVariant valueOrDefaultValueForKeyPath(const QString& keyPath); - QVariantMap& getUserSettingsMap() { return _configMap.getUserConfig(); } - QVariantMap& getSettingsMap() { return _configMap.getMergedConfig(); } + QVariantMap& getSettingsMap() { return _configMap.getConfig(); } QVariantMap& getDescriptorsMap(); @@ -58,6 +58,10 @@ public: NodePermissions getPermissionsForName(const NodePermissionsKey& key) const { return getPermissionsForName(key.first); } QStringList getAllNames() const; + // these give access to permissions for specific IPs from the domain-server settings page + bool hasPermissionsForIP(const QHostAddress& address) const { return _ipPermissions.contains(address.toString(), 0); } + NodePermissions getPermissionsForIP(const QHostAddress& address) const; + // these give access to permissions for specific groups from the domain-server settings page bool havePermissionsForGroup(const QString& groupName, QUuid rankID) const { return _groupPermissions.contains(groupName, rankID); @@ -100,6 +104,7 @@ public slots: private slots: void processSettingsRequestPacket(QSharedPointer message); + void processNodeKickRequestPacket(QSharedPointer message, SharedNodePointer sendingNode); private: QStringList _argumentList; @@ -129,11 +134,15 @@ private: void packPermissionsForMap(QString mapName, NodePermissionsMap& permissionsRows, QString keyPath); void packPermissions(); void unpackPermissions(); + bool unpackPermissionsForKeypath(const QString& keyPath, NodePermissionsMap* destinationMapPointer, + std::function customUnpacker = {}); bool ensurePermissionsForGroupRanks(); NodePermissionsMap _standardAgentPermissions; // anonymous, logged-in, localhost, friend-of-domain-owner NodePermissionsMap _agentPermissions; // specific account-names + NodePermissionsMap _ipPermissions; // permissions granted by node IP address + NodePermissionsMap _groupPermissions; // permissions granted by membership to specific groups NodePermissionsMap _groupForbiddens; // permissions denied due to membership in a specific group // these are like _groupPermissions and _groupForbiddens but with uuids rather than group-names in the keys diff --git a/interface/resources/qml/controls-uit/WebView.qml b/interface/resources/qml/controls-uit/WebView.qml index 4c165fc587..b599e29fe0 100644 --- a/interface/resources/qml/controls-uit/WebView.qml +++ b/interface/resources/qml/controls-uit/WebView.qml @@ -35,7 +35,6 @@ WebEngineView { } onUrlChanged: { - console.log("Url changed to " + url); var originalUrl = url.toString(); newUrl = urlHandler.fixupUrl(originalUrl).toString(); if (newUrl !== originalUrl) { diff --git a/interface/resources/qml/controls/WebView.qml b/interface/resources/qml/controls/WebView.qml index 2f94740fe6..bba91c64f6 100644 --- a/interface/resources/qml/controls/WebView.qml +++ b/interface/resources/qml/controls/WebView.qml @@ -26,7 +26,6 @@ WebEngineView { } onUrlChanged: { - console.log("Url changed to " + url); var originalUrl = url.toString(); newUrl = urlHandler.fixupUrl(originalUrl).toString(); if (newUrl !== originalUrl) { diff --git a/interface/resources/qml/dialogs/FileDialog.qml b/interface/resources/qml/dialogs/FileDialog.qml index fa5be18cd3..6a37886cb3 100644 --- a/interface/resources/qml/dialogs/FileDialog.qml +++ b/interface/resources/qml/dialogs/FileDialog.qml @@ -87,6 +87,15 @@ ModalWindow { currentSelection.text = d.capitalizeDrive(helper.urlToPath(initialFolder)); } + helper.contentsChanged.connect(function() { + if (folderListModel) { + // Make folderListModel refresh. + var save = folderListModel.folder; + folderListModel.folder = ""; + folderListModel.folder = save; + } + }); + fileTableView.forceActiveFocus(); } @@ -343,12 +352,14 @@ ModalWindow { onFolderChanged: { if (folder === rootFolder) { model = driveListModel; + helper.monitorDirectory(""); update(); } else { var needsUpdate = model === driveListModel && folder === folderListModel.folder; model = folderListModel; folderListModel.folder = folder; + helper.monitorDirectory(helper.urlToPath(folder)); if (needsUpdate) { update(); diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 4e8ecf3054..564a58708f 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -916,6 +916,16 @@ bool RenderableModelEntityItem::contains(const glm::vec3& point) const { return false; } +bool RenderableModelEntityItem::shouldBePhysical() const { + // If we have a model, make sure it hasn't failed to download. + // If it has, we'll report back that we shouldn't be physical so that physics aren't held waiting for us to be ready. + if (_model && _model->didGeometryRequestFail()) { + return false; + } else { + return ModelEntityItem::shouldBePhysical(); + } +} + glm::quat RenderableModelEntityItem::getAbsoluteJointRotationInObjectFrame(int index) const { if (_model) { glm::quat result; diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.h b/libraries/entities-renderer/src/RenderableModelEntityItem.h index 339c907532..f487e79880 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.h +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.h @@ -65,6 +65,8 @@ public: virtual bool contains(const glm::vec3& point) const override; + virtual bool shouldBePhysical() const override; + // these are in the frame of this object (model space) virtual glm::quat getAbsoluteJointRotationInObjectFrame(int index) const override; virtual glm::vec3 getAbsoluteJointTranslationInObjectFrame(int index) const override; diff --git a/libraries/model-networking/src/model-networking/ModelCache.cpp b/libraries/model-networking/src/model-networking/ModelCache.cpp index 26798070a6..c4b2a6dd22 100644 --- a/libraries/model-networking/src/model-networking/ModelCache.cpp +++ b/libraries/model-networking/src/model-networking/ModelCache.cpp @@ -403,6 +403,8 @@ void GeometryResourceWatcher::setResource(GeometryResource::Pointer resource) { void GeometryResourceWatcher::resourceFinished(bool success) { if (success) { _geometryRef = std::make_shared(*_resource); + } else { + emit resourceFailed(); } } diff --git a/libraries/model-networking/src/model-networking/ModelCache.h b/libraries/model-networking/src/model-networking/ModelCache.h index 4a0a921a04..62037d67bc 100644 --- a/libraries/model-networking/src/model-networking/ModelCache.h +++ b/libraries/model-networking/src/model-networking/ModelCache.h @@ -111,6 +111,9 @@ public: QUrl getURL() const { return (bool)_resource ? _resource->getURL() : QUrl(); } +signals: + void resourceFailed(); + private: void startWatching(); void stopWatching(); diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index b470c2fe25..431d372089 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -151,6 +151,10 @@ void LimitedNodeList::setPermissions(const NodePermissions& newPermissions) { newPermissions.can(NodePermissions::Permission::canWriteToAssetServer)) { emit canWriteAssetsChanged(_permissions.can(NodePermissions::Permission::canWriteToAssetServer)); } + if (originalPermissions.can(NodePermissions::Permission::canKick) != + newPermissions.can(NodePermissions::Permission::canKick)) { + emit canKickChanged(_permissions.can(NodePermissions::Permission::canKick)); + } } QUdpSocket& LimitedNodeList::getDTLSSocket() { diff --git a/libraries/networking/src/LimitedNodeList.h b/libraries/networking/src/LimitedNodeList.h index 7cbdcd5361..48379b5e39 100644 --- a/libraries/networking/src/LimitedNodeList.h +++ b/libraries/networking/src/LimitedNodeList.h @@ -110,6 +110,7 @@ public: bool getThisNodeCanRez() const { return _permissions.can(NodePermissions::Permission::canRezPermanentEntities); } bool getThisNodeCanRezTmp() const { return _permissions.can(NodePermissions::Permission::canRezTemporaryEntities); } bool getThisNodeCanWriteAssets() const { return _permissions.can(NodePermissions::Permission::canWriteToAssetServer); } + bool getThisNodeCanKick() const { return _permissions.can(NodePermissions::Permission::canKick); } quint16 getSocketLocalPort() const { return _nodeSocket.localPort(); } QUdpSocket& getDTLSSocket(); @@ -258,6 +259,7 @@ signals: void canRezChanged(bool canRez); void canRezTmpChanged(bool canRezTmp); void canWriteAssetsChanged(bool canWriteAssets); + void canKickChanged(bool canKick); protected slots: void connectedForLocalSocketTest(); diff --git a/libraries/networking/src/Node.h b/libraries/networking/src/Node.h index 270814c35c..18088c6cea 100644 --- a/libraries/networking/src/Node.h +++ b/libraries/networking/src/Node.h @@ -69,6 +69,7 @@ public: bool getCanRez() const { return _permissions.can(NodePermissions::Permission::canRezPermanentEntities); } bool getCanRezTmp() const { return _permissions.can(NodePermissions::Permission::canRezTemporaryEntities); } bool getCanWriteToAssetServer() const { return _permissions.can(NodePermissions::Permission::canWriteToAssetServer); } + bool getCanKick() const { return _permissions.can(NodePermissions::Permission::canKick); } void parseIgnoreRequestMessage(QSharedPointer message); void addIgnoredNode(const QUuid& otherNodeID); diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 05b5532560..781cc00c1c 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -727,7 +727,7 @@ void NodeList::ignoreNodeBySessionID(const QUuid& nodeID) { emit ignoredNode(nodeID); } else { - qWarning() << "UsersScriptingInterface::ignore called with an invalid ID or an ID which matches the current session ID."; + qWarning() << "NodeList::ignoreNodeBySessionID called with an invalid ID or an ID which matches the current session ID."; } } @@ -759,3 +759,28 @@ void NodeList::maybeSendIgnoreSetToNode(SharedNodePointer newNode) { } } } + +void NodeList::kickNodeBySessionID(const QUuid& nodeID) { + // send a request to domain-server to kick the node with the given session ID + // the domain-server will handle the persistence of the kick (via username or IP) + + if (!nodeID.isNull() && _sessionUUID != nodeID ) { + if (getThisNodeCanKick()) { + // setup the packet + auto kickPacket = NLPacket::create(PacketType::NodeKickRequest, NUM_BYTES_RFC4122_UUID, true); + + // write the node ID to the packet + kickPacket->write(nodeID.toRfc4122()); + + qDebug() << "Sending packet to kick node" << uuidStringWithoutCurlyBraces(nodeID); + + sendPacket(std::move(kickPacket), _domainHandler.getSockAddr()); + } else { + qWarning() << "You do not have permissions to kick in this domain." + << "Request to kick node" << uuidStringWithoutCurlyBraces(nodeID) << "will not be sent"; + } + } else { + qWarning() << "NodeList::kickNodeBySessionID called with an invalid ID or an ID which matches the current session ID."; + + } +} diff --git a/libraries/networking/src/NodeList.h b/libraries/networking/src/NodeList.h index ff994ce612..f3cd5bed0d 100644 --- a/libraries/networking/src/NodeList.h +++ b/libraries/networking/src/NodeList.h @@ -73,6 +73,8 @@ public: void ignoreNodeBySessionID(const QUuid& nodeID); bool isIgnoringNode(const QUuid& nodeID) const; + void kickNodeBySessionID(const QUuid& nodeID); + public slots: void reset(); void sendDomainServerCheckIn(); diff --git a/libraries/networking/src/NodePermissions.cpp b/libraries/networking/src/NodePermissions.cpp index a815884dc5..a1d4fc182e 100644 --- a/libraries/networking/src/NodePermissions.cpp +++ b/libraries/networking/src/NodePermissions.cpp @@ -44,6 +44,7 @@ NodePermissions::NodePermissions(QMap perms) { permissions |= perms["id_can_write_to_asset_server"].toBool() ? Permission::canWriteToAssetServer : Permission::none; permissions |= perms["id_can_connect_past_max_capacity"].toBool() ? Permission::canConnectPastMaxCapacity : Permission::none; + permissions |= perms["id_can_kick"].toBool() ? Permission::canKick : Permission::none; } QVariant NodePermissions::toVariant(QHash groupRanks) { @@ -63,6 +64,7 @@ QVariant NodePermissions::toVariant(QHash groupRanks) { values["id_can_rez_tmp"] = can(Permission::canRezTemporaryEntities); values["id_can_write_to_asset_server"] = can(Permission::canWriteToAssetServer); values["id_can_connect_past_max_capacity"] = can(Permission::canConnectPastMaxCapacity); + values["id_can_kick"] = can(Permission::canKick); return QVariant(values); } @@ -123,6 +125,9 @@ QDebug operator<<(QDebug debug, const NodePermissions& perms) { if (perms.can(NodePermissions::Permission::canConnectPastMaxCapacity)) { debug << " ignore-max-cap"; } + if (perms.can(NodePermissions::Permission::canKick)) { + debug << " kick"; + } debug.nospace() << "]"; return debug.nospace(); } diff --git a/libraries/networking/src/NodePermissions.h b/libraries/networking/src/NodePermissions.h index 6f4309a447..5d2755f9b5 100644 --- a/libraries/networking/src/NodePermissions.h +++ b/libraries/networking/src/NodePermissions.h @@ -63,7 +63,8 @@ public: canRezPermanentEntities = 4, canRezTemporaryEntities = 8, canWriteToAssetServer = 16, - canConnectPastMaxCapacity = 32 + canConnectPastMaxCapacity = 32, + canKick = 64 }; Q_DECLARE_FLAGS(Permissions, Permission) Permissions permissions; diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index ce1f25d45d..e9d61a827a 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -26,7 +26,7 @@ const QSet NON_VERIFIED_PACKETS = QSet() << PacketType::NodeJsonStats << PacketType::EntityQuery << PacketType::OctreeDataNack << PacketType::EntityEditNack << PacketType::DomainListRequest << PacketType::StopNode - << PacketType::DomainDisconnectRequest; + << PacketType::DomainDisconnectRequest << PacketType::NodeKickRequest; const QSet NON_SOURCED_PACKETS = QSet() << PacketType::StunResponse << PacketType::CreateAssignment << PacketType::RequestAssignment diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 7281e24fa9..40524e2288 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -98,7 +98,8 @@ public: NegotiateAudioFormat, SelectedAudioFormat, MoreEntityShapes, - LAST_PACKET_TYPE = MoreEntityShapes + NodeKickRequest, + LAST_PACKET_TYPE = NodeKickRequest }; }; diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index b04a1d8023..581bd285e2 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -102,13 +102,17 @@ Model::Model(RigPointer rig, QObject* parent) : _calculatedMeshTrianglesValid(false), _meshGroupsKnown(false), _isWireframe(false), - _rig(rig) { + _rig(rig) +{ // we may have been created in the network thread, but we live in the main thread if (_viewState) { moveToThread(_viewState->getMainThread()); } setSnapModelToRegistrationPoint(true, glm::vec3(0.5f)); + + // handle download failure reported by the GeometryResourceWatcher + connect(&_renderWatcher, &GeometryResourceWatcher::resourceFailed, this, &Model::handleGeometryResourceFailure); } Model::~Model() { @@ -818,6 +822,7 @@ void Model::setURL(const QUrl& url) { _needsReload = true; _needsUpdateTextures = true; _meshGroupsKnown = false; + _geometryRequestFailed = false; invalidCalculatedMeshBoxes(); deleteGeometry(); diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 98e50c66f4..b95c0318b4 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -147,8 +147,9 @@ public: Q_INVOKABLE void setCollisionModelURL(const QUrl& url); const QUrl& getCollisionURL() const { return _collisionUrl; } - bool isActive() const { return isLoaded(); } + + bool didGeometryRequestFail() const { return _geometryRequestFailed; } bool convexHullContains(glm::vec3 point); @@ -392,6 +393,11 @@ protected: RigPointer _rig; uint32_t _deleteGeometryCounter { 0 }; + + bool _geometryRequestFailed { false }; + +private slots: + void handleGeometryResourceFailure() { _geometryRequestFailed = true; } }; Q_DECLARE_METATYPE(ModelPointer) diff --git a/libraries/script-engine/src/UsersScriptingInterface.cpp b/libraries/script-engine/src/UsersScriptingInterface.cpp index ff7ccb0164..69ad8e04ad 100644 --- a/libraries/script-engine/src/UsersScriptingInterface.cpp +++ b/libraries/script-engine/src/UsersScriptingInterface.cpp @@ -13,7 +13,23 @@ #include +UsersScriptingInterface::UsersScriptingInterface() { + // emit a signal when kick permissions have changed + auto nodeList = DependencyManager::get(); + connect(nodeList.data(), &LimitedNodeList::canKickChanged, this, &UsersScriptingInterface::canKickChanged); +} + void UsersScriptingInterface::ignore(const QUuid& nodeID) { // ask the NodeList to ignore this user (based on the session ID of their node) DependencyManager::get()->ignoreNodeBySessionID(nodeID); } + +void UsersScriptingInterface::kick(const QUuid& nodeID) { + // ask the NodeList to kick the user with the given session ID + DependencyManager::get()->kickNodeBySessionID(nodeID); +} + +bool UsersScriptingInterface::getCanKick() { + // ask the NodeList to return our ability to kick + return DependencyManager::get()->getThisNodeCanKick(); +} diff --git a/libraries/script-engine/src/UsersScriptingInterface.h b/libraries/script-engine/src/UsersScriptingInterface.h index 0dc62c088c..712eeedeb6 100644 --- a/libraries/script-engine/src/UsersScriptingInterface.h +++ b/libraries/script-engine/src/UsersScriptingInterface.h @@ -20,8 +20,19 @@ class UsersScriptingInterface : public QObject, public Dependency { Q_OBJECT SINGLETON_DEPENDENCY + Q_PROPERTY(bool canKick READ getCanKick) + +public: + UsersScriptingInterface(); + public slots: void ignore(const QUuid& nodeID); + void kick(const QUuid& nodeID); + + bool getCanKick(); + +signals: + void canKickChanged(bool canKick); }; diff --git a/libraries/shared/src/HifiConfigVariantMap.cpp b/libraries/shared/src/HifiConfigVariantMap.cpp index 5ae5ff740d..252079f182 100644 --- a/libraries/shared/src/HifiConfigVariantMap.cpp +++ b/libraries/shared/src/HifiConfigVariantMap.cpp @@ -111,6 +111,13 @@ void HifiConfigVariantMap::loadMasterAndUserConfig(const QStringList& argumentLi loadMapFromJSONFile(_masterConfig, masterConfigFilepath); } + // load the user config - that method replace loadMasterAndUserConfig after the 1.7 migration + loadConfig(argumentList); + + mergeMasterAndUserConfigs(); +} + +void HifiConfigVariantMap::loadConfig(const QStringList& argumentList) { // load the user config const QString USER_CONFIG_FILE_OPTION = "--user-config"; static const QString USER_CONFIG_FILE_NAME = "config.json"; @@ -159,12 +166,10 @@ void HifiConfigVariantMap::loadMasterAndUserConfig(const QStringList& argumentLi } } } - + } - + loadMapFromJSONFile(_userConfig, _userConfigFilename); - - mergeMasterAndUserConfigs(); } void HifiConfigVariantMap::mergeMasterAndUserConfigs() { diff --git a/libraries/shared/src/HifiConfigVariantMap.h b/libraries/shared/src/HifiConfigVariantMap.h index e92561cff5..cb6e92df96 100644 --- a/libraries/shared/src/HifiConfigVariantMap.h +++ b/libraries/shared/src/HifiConfigVariantMap.h @@ -15,16 +15,22 @@ #include #include +QVariant* valueForKeyPath(QVariantMap& variantMap, const QString& keyPath, bool shouldCreateIfMissing = false); + class HifiConfigVariantMap { public: static QVariantMap mergeCLParametersWithJSONConfig(const QStringList& argumentList); HifiConfigVariantMap(); void loadMasterAndUserConfig(const QStringList& argumentList); + void loadConfig(const QStringList& argumentList); + + const QVariant value(const QString& key) const { return _userConfig.value(key); } + QVariant* valueForKeyPath(const QString& keyPath, bool shouldCreateIfMissing = false) + { return ::valueForKeyPath(_userConfig, keyPath, shouldCreateIfMissing); } - const QVariantMap& getMasterConfig() const { return _masterConfig; } - QVariantMap& getUserConfig() { return _userConfig; } QVariantMap& getMergedConfig() { return _mergedConfig; } + QVariantMap& getConfig() { return _userConfig; } void mergeMasterAndUserConfigs(); @@ -40,6 +46,4 @@ private: void addMissingValuesToExistingMap(QVariantMap& existingMap, const QVariantMap& newMap); }; -QVariant* valueForKeyPath(QVariantMap& variantMap, const QString& keyPath, bool shouldCreateIfMissing = false); - #endif // hifi_HifiConfigVariantMap_h diff --git a/libraries/ui/src/FileDialogHelper.cpp b/libraries/ui/src/FileDialogHelper.cpp index 3a12e054df..9d791ec562 100644 --- a/libraries/ui/src/FileDialogHelper.cpp +++ b/libraries/ui/src/FileDialogHelper.cpp @@ -115,3 +115,15 @@ QList FileDialogHelper::urlToList(const QUrl& url) { return results; } +void FileDialogHelper::monitorDirectory(const QString& path) { + if (!_fsWatcherPath.isEmpty()) { + _fsWatcher.removePath(_fsWatcherPath); + _fsWatcherPath = ""; + } + + if (!path.isEmpty()) { + _fsWatcher.addPath(path); + _fsWatcherPath = path; + connect(&_fsWatcher, &QFileSystemWatcher::directoryChanged, this, &FileDialogHelper::contentsChanged); + } +} diff --git a/libraries/ui/src/FileDialogHelper.h b/libraries/ui/src/FileDialogHelper.h index 6058f8f7bb..6c352ecdfc 100644 --- a/libraries/ui/src/FileDialogHelper.h +++ b/libraries/ui/src/FileDialogHelper.h @@ -9,11 +9,12 @@ #ifndef hifi_ui_FileDialogHelper_h #define hifi_ui_FileDialogHelper_h +#include #include #include -#include #include #include +#include class FileDialogHelper : public QObject { @@ -61,6 +62,15 @@ public: Q_INVOKABLE QList urlToList(const QUrl& url); Q_INVOKABLE void openDirectory(const QString& path); + + Q_INVOKABLE void monitorDirectory(const QString& path); + +signals: + void contentsChanged(); + +private: + QFileSystemWatcher _fsWatcher; + QString _fsWatcherPath; }; diff --git a/scripts/defaultScripts.js b/scripts/defaultScripts.js index 817d63582d..0efcd0c140 100644 --- a/scripts/defaultScripts.js +++ b/scripts/defaultScripts.js @@ -17,7 +17,7 @@ Script.load("system/goto.js"); Script.load("system/hmd.js"); Script.load("system/marketplace.js"); Script.load("system/edit.js"); -Script.load("system/ignore.js"); +Script.load("system/mod.js"); Script.load("system/selectAudioDevice.js"); Script.load("system/notifications.js"); Script.load("system/controllers/handControllerGrab.js"); diff --git a/scripts/system/assets/images/ignore-target-01.svg b/scripts/system/assets/images/ignore-target-01.svg deleted file mode 100644 index 98cee89ca1..0000000000 --- a/scripts/system/assets/images/ignore-target-01.svg +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/scripts/system/assets/images/ignore-target.svg b/scripts/system/assets/images/ignore-target.svg new file mode 100644 index 0000000000..3d685139ec --- /dev/null +++ b/scripts/system/assets/images/ignore-target.svg @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/system/assets/images/kick-target.svg b/scripts/system/assets/images/kick-target.svg new file mode 100644 index 0000000000..21cb3a5462 --- /dev/null +++ b/scripts/system/assets/images/kick-target.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + diff --git a/scripts/system/assets/images/progress-bar-background.svg b/scripts/system/assets/images/progress-bar-background.svg index 732acd05ad..a8b4e1aab5 100644 --- a/scripts/system/assets/images/progress-bar-background.svg +++ b/scripts/system/assets/images/progress-bar-background.svg @@ -1,5 +1,5 @@  - - + + diff --git a/scripts/system/assets/images/progress-bar.svg b/scripts/system/assets/images/progress-bar.svg index 0df6f98686..e24a2cbff4 100644 --- a/scripts/system/assets/images/progress-bar.svg +++ b/scripts/system/assets/images/progress-bar.svg @@ -1,7 +1,12 @@ - - - + viewBox="0 0 960 10" enable-background="new -159 536 960 30" xml:space="preserve"> + + + + + + + + diff --git a/scripts/system/assets/images/tools/kick.svg b/scripts/system/assets/images/tools/kick.svg new file mode 100644 index 0000000000..1eed6e7f43 --- /dev/null +++ b/scripts/system/assets/images/tools/kick.svg @@ -0,0 +1,140 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/scripts/system/ignore.js b/scripts/system/mod.js similarity index 62% rename from scripts/system/ignore.js rename to scripts/system/mod.js index 1c996a7fcc..035d7726a1 100644 --- a/scripts/system/ignore.js +++ b/scripts/system/mod.js @@ -1,5 +1,5 @@ // -// ignore.js +// mod.js // scripts/system/ // // Created by Stephen Birarda on 07/11/2016 @@ -12,10 +12,14 @@ // grab the toolbar var toolbar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system"); -// setup the ignore button and add it to the toolbar +function buttonImageURL() { + return Script.resolvePath("assets/images/tools/" + (Users.canKick ? 'kick.svg' : 'ignore.svg')); +} + +// setup the mod button and add it to the toolbar var button = toolbar.addButton({ - objectName: 'ignore', - imageURL: Script.resolvePath("assets/images/tools/ignore.svg"), + objectName: 'mod', + imageURL: buttonImageURL(), visible: true, buttonState: 1, defaultState: 2, @@ -23,19 +27,24 @@ var button = toolbar.addButton({ alpha: 0.9 }); +// if this user's kick permissions change, change the state of the button in the HUD +Users.canKickChanged.connect(function(canKick){ + button.writeProperty('imageURL', buttonImageURL()); +}); + var isShowingOverlays = false; -var ignoreOverlays = {}; +var modOverlays = {}; function removeOverlays() { // enumerate the overlays and remove them - var ignoreOverlayKeys = Object.keys(ignoreOverlays); + var modOverlayKeys = Object.keys(modOverlays); - for (i = 0; i < ignoreOverlayKeys.length; ++i) { - var avatarID = ignoreOverlayKeys[i]; - Overlays.deleteOverlay(ignoreOverlays[avatarID]); + for (i = 0; i < modOverlayKeys.length; ++i) { + var avatarID = modOverlayKeys[i]; + Overlays.deleteOverlay(modOverlays[avatarID]); } - ignoreOverlays = {}; + modOverlays = {}; } // handle clicks on the toolbar button @@ -54,6 +63,10 @@ function buttonClicked(){ button.clicked.connect(buttonClicked); +function overlayURL() { + return Script.resolvePath("assets") + "/images/" + (Users.canKick ? "kick-target.svg" : "ignore-target.svg"); +} + function updateOverlays() { if (isShowingOverlays) { @@ -80,17 +93,18 @@ function updateOverlays() { var overlayPosition = avatar.getJointPosition("Head"); overlayPosition.y += 0.45; - if (avatarID in ignoreOverlays) { + if (avatarID in modOverlays) { // keep the overlay above the current position of this avatar - Overlays.editOverlay(ignoreOverlays[avatarID], { - position: overlayPosition + Overlays.editOverlay(modOverlays[avatarID], { + position: overlayPosition, + url: overlayURL() }); } else { // add the overlay above this avatar var newOverlay = Overlays.addOverlay("image3d", { - url: Script.resolvePath("assets/images/ignore-target-01.svg"), + url: overlayURL(), position: overlayPosition, - size: 0.4, + size: 1, scale: 0.4, color: { red: 255, green: 255, blue: 255}, alpha: 1, @@ -100,7 +114,7 @@ function updateOverlays() { }); // push this overlay to our array of overlays - ignoreOverlays[avatarID] = newOverlay; + modOverlays[avatarID] = newOverlay; } } } @@ -113,24 +127,28 @@ AvatarList.avatarRemovedEvent.connect(function(avatarID){ // we are currently showing overlays and an avatar just went away // first remove the rendered overlay - Overlays.deleteOverlay(ignoreOverlays[avatarID]); + Overlays.deleteOverlay(modOverlays[avatarID]); - // delete the saved ID of the overlay from our ignored overlays object - delete ignoreOverlays[avatarID]; + // delete the saved ID of the overlay from our mod overlays object + delete modOverlays[avatarID]; } }); function handleSelectedOverlay(clickedOverlay) { - // see this is one of our ignore overlays + // see this is one of our mod overlays - var ignoreOverlayKeys = Object.keys(ignoreOverlays) - for (i = 0; i < ignoreOverlayKeys.length; ++i) { - var avatarID = ignoreOverlayKeys[i]; - var ignoreOverlay = ignoreOverlays[avatarID]; + var modOverlayKeys = Object.keys(modOverlays) + for (i = 0; i < modOverlayKeys.length; ++i) { + var avatarID = modOverlayKeys[i]; + var modOverlay = modOverlays[avatarID]; - if (clickedOverlay.overlayID == ignoreOverlay) { - // matched to an overlay, ask for the matching avatar to be ignored - Users.ignore(avatarID); + if (clickedOverlay.overlayID == modOverlay) { + // matched to an overlay, ask for the matching avatar to be kicked or ignored + if (Users.canKick) { + Users.kick(avatarID); + } else { + Users.ignore(avatarID); + } // cleanup of the overlay is handled by the connection to avatarRemovedEvent } @@ -161,15 +179,9 @@ Controller.mousePressEvent.connect(function(event){ // But we dont' get mousePressEvents. var triggerMapping = Controller.newMapping(Script.resolvePath('') + '-click'); -var TRIGGER_GRAB_VALUE = 0.85; // From handControllerGrab/Pointer.js. Should refactor. -var TRIGGER_ON_VALUE = 0.4; -var TRIGGER_OFF_VALUE = 0.15; -var triggered = false; -var activeHand = Controller.Standard.RightHand; - -function controllerComputePickRay() { - var controllerPose = Controller.getPoseValue(activeHand); - if (controllerPose.valid && triggered) { +function controllerComputePickRay(hand) { + var controllerPose = Controller.getPoseValue(hand); + if (controllerPose.valid) { var controllerPosition = Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, controllerPose.translation), MyAvatar.position); // This gets point direction right, but if you want general quaternion it would be more complicated: @@ -178,37 +190,27 @@ function controllerComputePickRay() { } } -function makeTriggerHandler(hand) { - return function (value) { - if (isShowingOverlays) { - if (!triggered && (value > TRIGGER_GRAB_VALUE)) { // should we smooth? - triggered = true; - if (activeHand !== hand) { - // No switching while the other is already triggered, so no need to release. - activeHand = (activeHand === Controller.Standard.RightHand) ? Controller.Standard.LeftHand : Controller.Standard.RightHand; +function makeClickHandler(hand) { + return function(clicked) { + if (clicked == 1.0 && isShowingOverlays) { + var pickRay = controllerComputePickRay(hand); + if (pickRay) { + var overlayIntersection = Overlays.findRayIntersection(pickRay); + if (overlayIntersection.intersects) { + handleSelectedOverlay(overlayIntersection); } - - var pickRay = controllerComputePickRay(); - if (pickRay) { - var overlayIntersection = Overlays.findRayIntersection(pickRay); - if (overlayIntersection.intersects) { - handleSelectedOverlay(overlayIntersection); - } - } - } else if (triggered && (value < TRIGGER_OFF_VALUE)) { - triggered = false; } } }; } -triggerMapping.from(Controller.Standard.RT).peek().to(makeTriggerHandler(Controller.Standard.RightHand)); -triggerMapping.from(Controller.Standard.LT).peek().to(makeTriggerHandler(Controller.Standard.LeftHand)); +triggerMapping.from(Controller.Standard.RTClick).peek().to(makeClickHandler(Controller.Standard.RightHand)); +triggerMapping.from(Controller.Standard.LTClick).peek().to(makeClickHandler(Controller.Standard.LeftHand)); triggerMapping.enable(); // cleanup the toolbar button and overlays when script is stopped Script.scriptEnding.connect(function() { - toolbar.removeButton('ignore'); + toolbar.removeButton('mod'); removeOverlays(); triggerMapping.disable(); }); diff --git a/scripts/system/progress.js b/scripts/system/progress.js index c6537eef52..7c1c475e99 100644 --- a/scripts/system/progress.js +++ b/scripts/system/progress.js @@ -13,6 +13,11 @@ (function() { + function debug() { + return; + print.apply(null, arguments); + } + var rawProgress = 100, // % raw value. displayProgress = 100, // % smoothed value to display. DISPLAY_PROGRESS_MINOR_MAXIMUM = 8, // % displayed progress bar goes up to while 0% raw progress. @@ -28,35 +33,18 @@ FADE_OUT_WAIT = 1000, // Wait before starting to fade out after progress 100%. visible = false, BAR_WIDTH = 480, // Dimension of SVG in pixels of visible portion (half) of the bar. - BAR_HEIGHT = 30, + BAR_HEIGHT = 10, + BAR_Y_OFFSET_2D = -10, // Offset of progress bar while in desktop mode + BAR_Y_OFFSET_HMD = -300, // Offset of progress bar while in HMD BAR_URL = Script.resolvePath("assets/images/progress-bar.svg"), - BACKGROUND_WIDTH = 540, - BACKGROUND_HEIGHT = 90, + BACKGROUND_WIDTH = 520, + BACKGROUND_HEIGHT = 50, BACKGROUND_URL = Script.resolvePath("assets/images/progress-bar-background.svg"), - isOnHMD = false, windowWidth = 0, windowHeight = 0, background2D = {}, bar2D = {}, - SCALE_2D = 0.35, // Scale the SVGs for 2D display. - background3D = {}, - bar3D = {}, - PROGRESS_3D_DIRECTION = 0.0, // Degrees from avatar orientation. - PROGRESS_3D_DISTANCE = 0.602, // Horizontal distance from avatar position. - PROGRESS_3D_ELEVATION = -0.8, // Height of top middle of top notification relative to avatar eyes. - PROGRESS_3D_YAW = 0.0, // Degrees relative to notifications direction. - PROGRESS_3D_PITCH = -60.0, // Degrees from vertical. - SCALE_3D = 0.0011, // Scale the bar SVG for 3D display. - BACKGROUND_3D_SIZE = { - x: 0.76, - y: 0.08 - }, // Match up with the 3D background with those of notifications.js notices. - BACKGROUND_3D_COLOR = { - red: 2, - green: 2, - blue: 2 - }, - BACKGROUND_3D_ALPHA = 0.7; + SCALE_2D = 0.35; // Scale the SVGs for 2D display. function fade() { @@ -64,9 +52,7 @@ if (alpha < 0) { alpha = 0; - } - - if (alpha > 1) { + } else if (alpha > 1) { alpha = 1; } @@ -79,115 +65,123 @@ visible = false; } - if (isOnHMD) { - Overlays.editOverlay(background3D.overlay, { - backgroundAlpha: alpha * BACKGROUND_3D_ALPHA, - visible: visible - }); - } else { - Overlays.editOverlay(background2D.overlay, { - alpha: alpha, - visible: visible - }); - } - Overlays.editOverlay(isOnHMD ? bar3D.overlay : bar2D.overlay, { + Overlays.editOverlay(background2D.overlay, { + alpha: alpha, + visible: visible + }); + Overlays.editOverlay(bar2D.overlay, { alpha: alpha, visible: visible }); } + Window.domainChanged.connect(function() { + isDownloading = false; + bestRawProgress = 100; + rawProgress = 100; + displayProgress = 100; + }); + + // Max seen since downloads started. This is reset when all downloads have completed. + var maxSeen = 0; + + // Progress is defined as: (pending_downloads + active_downloads) / max_seen + // We keep track of both the current progress (rawProgress) and the + // best progress we've seen (bestRawProgress). As you are downloading, you may + // encounter new assets that require downloads, increasing the number of + // pending downloads and thus decreasing your overall progress. + var bestRawProgress = 0; + + // True if we have known active downloads + var isDownloading = false; + + // Entities are streamed to users, so you don't receive them all at once; instead, you + // receive them over a period of time. In many cases we end up in a situation where + // + // The initial delay cooldown keeps us from tracking progress before the allotted time + // has passed. + var INITIAL_DELAY_COOLDOWN_TIME = 1000; + var initialDelayCooldown = 0; function onDownloadInfoChanged(info) { var i; + debug("PROGRESS: Download info changed ", info.downloading.length, info.pending, maxSeen); + // Update raw progress value if (info.downloading.length + info.pending === 0) { + isDownloading = false; rawProgress = 100; + bestRawProgress = 100; + initialDelayCooldown = INITIAL_DELAY_COOLDOWN_TIME; } else { - rawProgress = 0; - for (i = 0; i < info.downloading.length; i += 1) { - rawProgress += info.downloading[i]; + var count = info.downloading.length + info.pending; + if (!isDownloading) { + isDownloading = true; + bestRawProgress = 0; + rawProgress = 0; + initialDelayCooldown = INITIAL_DELAY_COOLDOWN_TIME; + displayProgress = 0; + maxSeen = count; + } + if (count > maxSeen) { + maxSeen = count; + } + if (initialDelayCooldown <= 0) { + rawProgress = ((maxSeen - count) / maxSeen) * 100; + + if (rawProgress > bestRawProgress) { + bestRawProgress = rawProgress; + } } - rawProgress = rawProgress / (info.downloading.length + info.pending); } + debug("PROGRESS:", rawProgress, bestRawProgress, maxSeen); } function createOverlays() { - if (isOnHMD) { - - background3D.overlay = Overlays.addOverlay("rectangle3d", { - size: BACKGROUND_3D_SIZE, - color: BACKGROUND_3D_COLOR, - alpha: BACKGROUND_3D_ALPHA, - solid: true, - isFacingAvatar: false, - visible: false, - ignoreRayIntersection: true - }); - bar3D.overlay = Overlays.addOverlay("image3d", { - url: BAR_URL, - subImage: { - x: BAR_WIDTH, - y: 0, - width: BAR_WIDTH, - height: BAR_HEIGHT - }, - scale: SCALE_3D * BAR_WIDTH, - isFacingAvatar: false, - visible: false, - alpha: 0.0, - ignoreRayIntersection: true - }); - - } else { - - background2D.overlay = Overlays.addOverlay("image", { - imageURL: BACKGROUND_URL, - width: background2D.width, - height: background2D.height, - visible: false, - alpha: 0.0 - }); - bar2D.overlay = Overlays.addOverlay("image", { - imageURL: BAR_URL, - subImage: { - x: BAR_WIDTH, - y: 0, - width: BAR_WIDTH, - height: BAR_HEIGHT - }, - width: bar2D.width, - height: bar2D.height, - visible: false, - alpha: 0.0 - }); - } + background2D.overlay = Overlays.addOverlay("image", { + imageURL: BACKGROUND_URL, + width: background2D.width, + height: background2D.height, + visible: false, + alpha: 0.0 + }); + bar2D.overlay = Overlays.addOverlay("image", { + imageURL: BAR_URL, + subImage: { + x: 0, + y: 0, + width: BAR_WIDTH, + height: BAR_HEIGHT + }, + width: bar2D.width, + height: bar2D.height, + visible: false, + alpha: 0.0 + }); } function deleteOverlays() { - Overlays.deleteOverlay(isOnHMD ? background3D.overlay : background2D.overlay); - Overlays.deleteOverlay(isOnHMD ? bar3D.overlay : bar2D.overlay); + Overlays.deleteOverlay(background2D.overlay); + Overlays.deleteOverlay(bar2D.overlay); } + var b = 0; + var currentOrientation = null; function update() { + initialDelayCooldown -= 30; var viewport, eyePosition, avatarOrientation; - if (isOnHMD !== HMD.active) { - deleteOverlays(); - isOnHMD = !isOnHMD; - createOverlays(); + if (displayProgress < rawProgress) { + var diff = rawProgress - displayProgress; + if (diff < 0.5) { + displayProgress = rawProgress; + } else { + displayProgress += diff * 0.05; + } } - // Calculate progress value to display - if (rawProgress === 0 && displayProgress <= DISPLAY_PROGRESS_MINOR_MAXIMUM) { - displayProgress = Math.min(displayProgress + DISPLAY_PROGRESS_MINOR_INCREMENT, DISPLAY_PROGRESS_MINOR_MAXIMUM); - } else if (rawProgress < displayProgress) { - displayProgress = rawProgress; - } else if (rawProgress > displayProgress) { - displayProgress = Math.min(rawProgress, displayProgress + DISPLAY_PROGRESS_MAJOR_INCREMENT); - } // else (rawProgress === displayProgress); do nothing. - // Update state if (!visible) { // Not visible because no recent downloads if (displayProgress < 100) { // Have started downloading so fade in @@ -197,7 +191,7 @@ } } else if (alphaDelta !== 0.0) { // Fading in or out if (alphaDelta > 0) { - if (displayProgress === 100) { // Was downloading but now have finished so fade out + if (rawProgress === 100) { // Was downloading but now have finished so fade out alphaDelta = ALPHA_DELTA_OUT; } } else { @@ -207,7 +201,7 @@ } } else { // Fully visible because downloading or recently so if (fadeWaitTimer === null) { - if (displayProgress === 100) { // Was downloading but have finished so fade out soon + if (rawProgress === 100) { // Was downloading but have finished so fade out soon fadeWaitTimer = Script.setTimeout(function() { alphaDelta = ALPHA_DELTA_OUT; fadeTimer = Script.setInterval(fade, FADE_INTERVAL); @@ -225,73 +219,58 @@ if (visible) { // Update progress bar - Overlays.editOverlay(isOnHMD ? bar3D.overlay : bar2D.overlay, { - visible: visible, + Overlays.editOverlay(bar2D.overlay, { + visible: true, subImage: { x: BAR_WIDTH * (1 - displayProgress / 100), y: 0, width: BAR_WIDTH, height: BAR_HEIGHT - } + }, }); - // Update position - if (isOnHMD) { - // Update 3D overlays to maintain positions relative to avatar - eyePosition = MyAvatar.getDefaultEyePosition(); - avatarOrientation = MyAvatar.orientation; + Overlays.editOverlay(background2D.overlay, { + visible: true, + }); - Overlays.editOverlay(background3D.overlay, { - position: Vec3.sum(eyePosition, Vec3.multiplyQbyV(avatarOrientation, background3D.offset)), - rotation: Quat.multiply(avatarOrientation, background3D.orientation) - }); - Overlays.editOverlay(bar3D.overlay, { - position: Vec3.sum(eyePosition, Vec3.multiplyQbyV(avatarOrientation, bar3D.offset)), - rotation: Quat.multiply(avatarOrientation, bar3D.orientation) - }); + // Update 2D overlays to maintain positions at bottom middle of window + viewport = Controller.getViewportDimensions(); - } else { - // Update 2D overlays to maintain positions at bottom middle of window - viewport = Controller.getViewportDimensions(); - - if (viewport.x !== windowWidth || viewport.y !== windowHeight) { - windowWidth = viewport.x; - windowHeight = viewport.y; - - Overlays.editOverlay(background2D.overlay, { - x: windowWidth / 2 - background2D.width / 2, - y: windowHeight - background2D.height - bar2D.height - }); - - Overlays.editOverlay(bar2D.overlay, { - x: windowWidth / 2 - bar2D.width / 2, - y: windowHeight - background2D.height - bar2D.height + (background2D.height - bar2D.height) / 2 - }); - } + if (viewport.x !== windowWidth || viewport.y !== windowHeight) { + updateProgressBarLocation(); } } } + function updateProgressBarLocation() { + viewport = Controller.getViewportDimensions(); + windowWidth = viewport.x; + windowHeight = viewport.y; + + var yOffset = HMD.active ? BAR_Y_OFFSET_HMD : BAR_Y_OFFSET_2D; + + background2D.width = SCALE_2D * BACKGROUND_WIDTH; + background2D.height = SCALE_2D * BACKGROUND_HEIGHT; + bar2D.width = SCALE_2D * BAR_WIDTH; + bar2D.height = SCALE_2D * BAR_HEIGHT; + + Overlays.editOverlay(background2D.overlay, { + x: windowWidth / 2 - background2D.width / 2, + y: windowHeight - background2D.height - bar2D.height + yOffset + }); + + Overlays.editOverlay(bar2D.overlay, { + x: windowWidth / 2 - bar2D.width / 2, + y: windowHeight - background2D.height - bar2D.height + (background2D.height - bar2D.height) / 2 + yOffset + }); + } + function setUp() { background2D.width = SCALE_2D * BACKGROUND_WIDTH; background2D.height = SCALE_2D * BACKGROUND_HEIGHT; bar2D.width = SCALE_2D * BAR_WIDTH; bar2D.height = SCALE_2D * BAR_HEIGHT; - background3D.offset = Vec3.multiplyQbyV(Quat.fromPitchYawRollDegrees(0, PROGRESS_3D_DIRECTION, 0), { - x: 0, - y: 0, - z: -PROGRESS_3D_DISTANCE - }); - background3D.offset.y += PROGRESS_3D_ELEVATION; - background3D.orientation = Quat.fromPitchYawRollDegrees(PROGRESS_3D_PITCH, PROGRESS_3D_DIRECTION + PROGRESS_3D_YAW, 0); - bar3D.offset = Vec3.sum(background3D.offset, { - x: 0, - y: 0, - z: 0.001 - }); // Just in front of background - bar3D.orientation = background3D.orientation; - createOverlays(); } @@ -302,6 +281,6 @@ setUp(); GlobalServices.downloadInfoChanged.connect(onDownloadInfoChanged); GlobalServices.updateDownloadInfo(); - Script.update.connect(update); + Script.setInterval(update, 1000/60); Script.scriptEnding.connect(tearDown); -}()); \ No newline at end of file +}());