From 40078450dd855cb4dc8ea371ee9bcf5a0d0cab90 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 15 Feb 2018 11:20:02 -0800 Subject: [PATCH 01/15] add API to recover from content archive --- domain-server/src/DomainContentBackupManager.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/domain-server/src/DomainContentBackupManager.cpp b/domain-server/src/DomainContentBackupManager.cpp index 379aa640f8..40a2a55486 100644 --- a/domain-server/src/DomainContentBackupManager.cpp +++ b/domain-server/src/DomainContentBackupManager.cpp @@ -309,6 +309,7 @@ void DomainContentBackupManager::recoverFromBackup(MiniPromise::Promise promise, } void DomainContentBackupManager::recoverFromUploadedBackup(MiniPromise::Promise promise, QByteArray uploadedBackup) { + if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "recoverFromUploadedBackup", Q_ARG(MiniPromise::Promise, promise), Q_ARG(QByteArray, uploadedBackup)); From cb747c9cdfc394d53b12ba91a6041cd96f807d10 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 15 Feb 2018 17:32:53 -0800 Subject: [PATCH 02/15] refresh backups for availability and restore status --- .../resources/web/content/js/content.js | 89 +++++++++++++++---- domain-server/resources/web/css/style.css | 5 ++ .../resources/web/js/domain-server.js | 2 +- 3 files changed, 78 insertions(+), 18 deletions(-) diff --git a/domain-server/resources/web/content/js/content.js b/domain-server/resources/web/content/js/content.js index 1e5b6ac131..69a8c93f82 100644 --- a/domain-server/resources/web/content/js/content.js +++ b/domain-server/resources/web/content/js/content.js @@ -2,10 +2,19 @@ $(document).ready(function(){ var RESTORE_SETTINGS_UPLOAD_ID = 'restore-settings-button'; var RESTORE_SETTINGS_FILE_ID = 'restore-settings-file'; + var UPLOAD_CONTENT_ALLOWED_DIV_ID = 'upload-content-allowed'; + var UPLOAD_CONTENT_RECOVERING_DIV_ID = 'upload-content-recovering'; + + function progressBarHTML(extraClass, label) { + var html = "
"; + html += "
"; + html += label + "
"; + return html; + } function setupBackupUpload() { // construct the HTML needed for the settings backup panel - var html = "
"; + var html = "
"; html += "Upload a content archive (.zip) or entity file (.json, .json.gz) to replace the content of this domain."; html += "
Note: Your domain content will be replaced by the content you upload, but the existing backup files of your domain's content will not immediately be changed.
"; @@ -13,7 +22,10 @@ $(document).ready(function(){ html += ""; html += ""; - html += "
"; + html += "
"; + html += "Restore in progress"; + html += progressBarHTML('recovery', 'Restoring'); + html += "
"; $('#' + Settings.UPLOAD_CONTENT_BACKUP_PANEL_ID + ' .panel-body').html(html); } @@ -71,6 +83,7 @@ $(document).ready(function(){ var MANUAL_ARCHIVES_TABLE_ID = 'manual-archives-table'; var MANUAL_ARCHIVES_TBODY_ID = 'manual-archives-tbody'; var AUTO_ARCHIVES_SETTINGS_LINK_ID = 'auto-archives-settings-link'; + var ACTION_MENU_CLASS = 'action-menu'; var automaticBackups = []; var manualBackups = []; @@ -84,7 +97,9 @@ $(document).ready(function(){ html += ""; html += ""; - var backups_table_head = ""; + var backups_table_head = "" + + "" + + ""; html += backups_table_head; html += "
Archive NameArchive DateActions
Archive NameArchive DateActions
"; @@ -105,7 +120,7 @@ $(document).ready(function(){ var BACKUP_DOWNLOAD_LINK_CLASS = 'download-backup'; var BACKUP_DELETE_LINK_CLASS = 'delete-backup'; - function reloadLatestBackups() { + function reloadBackupInformation() { // make a GET request to get backup information to populate the table $.ajax({ url: '/api/backups', @@ -125,7 +140,7 @@ $(document).ready(function(){ return "" + "" + backup.name + "" + moment(backup.createdAtMillis).format('lll') - + "" + + "" + ""; } + function updateProgressBars($progressBar, value) { + $progressBar.attr('aria-valuenow', value).attr('style', 'width: ' + value + '%'); + $progressBar.find('.sr-only').html(data.status.recoveryProgress + "% Complete"); + } + + function updateOrAddTableRow(backup, tableBodyID) { + // check for a backup with this ID + var $backupRow = $("tr[data-backup-id='" + backup.id + "']"); + + if ($backupRow.length == 0) { + // create a new row and then add it to the table + $backupRow = $(createBackupTableRow(backup)); + $('#' + tableBodyID).append($backupRow); + } + + // update the row status column depending on if it is available or recovering + if (!backup.isAvailable) { + // add a progress bar to the status row for availability + $backupRow.find('td.backup-status').html(progressBarHTML('availability', 'Archiving')); + + // set the value of the progress bar based on availability progress + updateProgressBars($backupRow.find('.progress-bar'), backup.availabilityProgress); + } else if (backup.id == data.status.recoveringBackupId) { + // add a progress bar to the status row for recovery + $backupRow.find('td.backup-status').html(progressBarHTML('recovery', 'Restoring')); + } else { + // no special status for this row, use an empty status column + $backupRow.find('td.backup-status').html(''); + } + + $backupRow.find('td.' + ACTION_MENU_CLASS + ' .dropdown').toggle(backup.isAvailable); + } + var automaticRows = ""; if (automaticBackups.length > 0) { for (var backupIndex in automaticBackups) { - // create a table row for this backup and add it to the rows we'll put in the table body - automaticRows += createBackupTableRow(automaticBackups[backupIndex]); + updateOrAddTableRow(automaticBackups[backupIndex], AUTOMATIC_ARCHIVES_TBODY_ID) } } - $('#' + AUTOMATIC_ARCHIVES_TBODY_ID).html(automaticRows); - - var manualRows = ""; - if (manualBackups.length > 0) { for (var backupIndex in manualBackups) { - // create a table row for this backup and add it to the rows we'll put in the table body - manualRows += createBackupTableRow(manualBackups[backupIndex]); + updateOrAddTableRow(manualBackups[backupIndex], MANUAL_ARCHIVES_TBODY_ID) } } - $('#' + MANUAL_ARCHIVES_TBODY_ID).html(manualRows); + // check if the restore action on all rows should be enabled or disabled + $('.' + BACKUP_RESTORE_LINK_CLASS).parent().toggleClass('disabled', data.status.isRecovering); + + // hide or show the manual content upload file and button depending on our recovering status + $('#' + UPLOAD_CONTENT_ALLOWED_DIV_ID).toggle(!data.status.isRecovering); + $('#' + UPLOAD_CONTENT_RECOVERING_DIV_ID).toggle(data.status.isRecovering); + + // update the progress bars for current restore status + if (data.status.isRecovering) { + updateProgressBars($('.recovery.progress-bar'), data.status.recoveryProgress); + } // tell bootstrap sortable to update for the new rows $.bootstrapSortable({ applyLast: true }); @@ -247,7 +299,7 @@ $(document).ready(function(){ }).always(function(){ // reload the list of content archives in case we deleted a backup // or it's no longer an available backup for some other reason - reloadLatestBackups(); + reloadBackupInformation(); }); } ) @@ -306,7 +358,7 @@ $(document).ready(function(){ }).done(function(data) { // since we successfully setup a new content archive, reload the table of archives // which should show that this archive is pending creation - reloadLatestBackups(); + reloadBackupInformation(); }).fail(function(jqXHR, textStatus, errorThrown) { }); @@ -322,6 +374,9 @@ $(document).ready(function(){ setupContentArchives(); // load the latest backups immediately - reloadLatestBackups(); + reloadBackupInformation(); + + // setup a timer to reload them every 5 seconds + setTimeout(reloadBackupInformation(), 5000); }; }); diff --git a/domain-server/resources/web/css/style.css b/domain-server/resources/web/css/style.css index 2bcc870ecf..62f442584e 100644 --- a/domain-server/resources/web/css/style.css +++ b/domain-server/resources/web/css/style.css @@ -466,6 +466,11 @@ tr.gray-tr { background-color: #f5f5f5; } +table .action-menu { + text-align: right; + width: 90px; +} + .dropdown-toggle span.glyphicon-option-vertical { font-size: 110%; cursor: pointer; diff --git a/domain-server/resources/web/js/domain-server.js b/domain-server/resources/web/js/domain-server.js index 2c12e2683a..ed9559b6e9 100644 --- a/domain-server/resources/web/js/domain-server.js +++ b/domain-server/resources/web/js/domain-server.js @@ -62,7 +62,7 @@ $(document).ready(function(){ }, 1: { html_id: Settings.UPLOAD_CONTENT_BACKUP_PANEL_ID, - label: 'Upload Content' + label: 'Upload Content Archive' } }; From faacd986b3924ccc092801a58e79b63f8fe9563f Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 16 Feb 2018 10:37:15 -0800 Subject: [PATCH 03/15] remove deleted backups from content archives tables --- .../resources/web/content/js/content.js | 46 +++++++++++++------ .../src/DomainServerSettingsManager.cpp | 3 +- 2 files changed, 34 insertions(+), 15 deletions(-) diff --git a/domain-server/resources/web/content/js/content.js b/domain-server/resources/web/content/js/content.js index 69a8c93f82..5c2e134102 100644 --- a/domain-server/resources/web/content/js/content.js +++ b/domain-server/resources/web/content/js/content.js @@ -78,6 +78,8 @@ $(document).ready(function(){ }); var GENERATE_ARCHIVE_BUTTON_ID = 'generate-archive-button'; + var CONTENT_ARCHIVES_NORMAL_ID = 'content-archives-success'; + var CONTENT_ARCHIVES_ERROR_ID = 'content-archives-error'; var AUTOMATIC_ARCHIVES_TABLE_ID = 'automatic-archives-table'; var AUTOMATIC_ARCHIVES_TBODY_ID = 'automatic-archives-tbody'; var MANUAL_ARCHIVES_TABLE_ID = 'manual-archives-table'; @@ -90,10 +92,10 @@ $(document).ready(function(){ function setupContentArchives() { // construct the HTML needed for the content archives panel - var html = "
"; + var html = "
"; html += ""; html += "Your domain server makes regular archives of the content in your domain. In the list below, you can see and download all of your domain content and settings backups. " - html += "Click here to manage automatic content archive intervals."; + html += "Click here to manage automatic content archive intervals."; html += "
"; html += ""; @@ -110,7 +112,11 @@ $(document).ready(function(){ html += ""; html += "
"; html += backups_table_head; - html += "
"; + html += "
"; + + html += ""; // put the base HTML in the content archives panel $('#' + Settings.CONTENT_ARCHIVES_PANEL_ID + ' .panel-body').html(html); @@ -119,7 +125,8 @@ $(document).ready(function(){ var BACKUP_RESTORE_LINK_CLASS = 'restore-backup'; var BACKUP_DOWNLOAD_LINK_CLASS = 'download-backup'; var BACKUP_DELETE_LINK_CLASS = 'delete-backup'; - + var ACTIVE_BACKUP_ROW_CLASS = 'active-backup'; + function reloadBackupInformation() { // make a GET request to get backup information to populate the table $.ajax({ @@ -153,6 +160,10 @@ $(document).ready(function(){ $progressBar.find('.sr-only').html(data.status.recoveryProgress + "% Complete"); } + // before we add any new rows and update existing ones + // remove our flag for active rows + $('.' + ACTIVE_BACKUP_ROW_CLASS).removeClass(ACTIVE_BACKUP_ROW_CLASS); + function updateOrAddTableRow(backup, tableBodyID) { // check for a backup with this ID var $backupRow = $("tr[data-backup-id='" + backup.id + "']"); @@ -169,7 +180,7 @@ $(document).ready(function(){ $backupRow.find('td.backup-status').html(progressBarHTML('availability', 'Archiving')); // set the value of the progress bar based on availability progress - updateProgressBars($backupRow.find('.progress-bar'), backup.availabilityProgress); + updateProgressBars($backupRow.find('.progress-bar'), backup.availabilityProgress * 100); } else if (backup.id == data.status.recoveringBackupId) { // add a progress bar to the status row for recovery $backupRow.find('td.backup-status').html(progressBarHTML('recovery', 'Restoring')); @@ -179,22 +190,29 @@ $(document).ready(function(){ } $backupRow.find('td.' + ACTION_MENU_CLASS + ' .dropdown').toggle(backup.isAvailable); + + $backupRow.addClass(ACTIVE_BACKUP_ROW_CLASS); } var automaticRows = ""; if (automaticBackups.length > 0) { for (var backupIndex in automaticBackups) { - updateOrAddTableRow(automaticBackups[backupIndex], AUTOMATIC_ARCHIVES_TBODY_ID) + updateOrAddTableRow(automaticBackups[backupIndex], AUTOMATIC_ARCHIVES_TBODY_ID); + } } if (manualBackups.length > 0) { for (var backupIndex in manualBackups) { - updateOrAddTableRow(manualBackups[backupIndex], MANUAL_ARCHIVES_TBODY_ID) + updateOrAddTableRow(manualBackups[backupIndex], MANUAL_ARCHIVES_TBODY_ID); } } + // at this point, any rows that no longer have the ACTIVE_BACKUP_ROW_CLASS + // are deleted backups, so we remove them from the table + $('tbody tr:not(.' + ACTIVE_BACKUP_ROW_CLASS + ')').remove(); + // check if the restore action on all rows should be enabled or disabled $('.' + BACKUP_RESTORE_LINK_CLASS).parent().toggleClass('disabled', data.status.isRecovering); @@ -204,12 +222,15 @@ $(document).ready(function(){ // update the progress bars for current restore status if (data.status.isRecovering) { - updateProgressBars($('.recovery.progress-bar'), data.status.recoveryProgress); + updateProgressBars($('.recovery.progress-bar'), data.status.recoveryProgress * 100); } // tell bootstrap sortable to update for the new rows $.bootstrapSortable({ applyLast: true }); + $('#' + CONTENT_ARCHIVES_NORMAL_ID).toggle(true); + $('#' + CONTENT_ARCHIVES_ERROR_ID).toggle(false); + }).fail(function(){ // we've hit the very rare case where we couldn't load the list of backups from the domain server @@ -219,11 +240,8 @@ $(document).ready(function(){ // replace the content archives panel with a simple error message // stating that the user should reload the page - $('#' + Settings.CONTENT_ARCHIVES_PANEL_ID + ' .panel-body').html( - "
" + - "There was a problem loading your list of automatic and manual content archives. Please reload the page to try again." + - "
" - ); + $('#' + CONTENT_ARCHIVES_NORMAL_ID).toggle(false); + $('#' + CONTENT_ARCHIVES_ERROR_ID).toggle(true); }).always(function(){ // toggle showing or hiding the tables depending on if they have entries @@ -377,6 +395,6 @@ $(document).ready(function(){ reloadBackupInformation(); // setup a timer to reload them every 5 seconds - setTimeout(reloadBackupInformation(), 5000); + setInterval(reloadBackupInformation, 5000); }; }); diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index a3f99facea..cd7155d9da 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -1356,7 +1356,7 @@ QJsonObject DomainServerSettingsManager::settingsResponseObjectForType(const QSt BLOCKING_INVOKE_METHOD(this, "settingsResponseObjectForType", Q_RETURN_ARG(QJsonObject, responseObject), - Q_ARG(const QString&, typeValue), + Q_ARG(QString, typeValue), Q_ARG(bool, isAuthenticated), Q_ARG(bool, includeDomainSettings), Q_ARG(bool, includeContentSettings), @@ -1374,6 +1374,7 @@ QJsonObject DomainServerSettingsManager::settingsResponseObjectForType(const QSt // only enumerate the requested settings type (domain setting or content setting) QJsonArray* filteredDescriptionArray = &_descriptionArray; + if (includeDomainSettings && !includeContentSettings) { filteredDescriptionArray = &_domainSettingsDescription; } else if (includeContentSettings && !includeDomainSettings) { From 5dec3aba505a2ab8c3c210f19bb2441b457b1f73 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 16 Feb 2018 10:49:57 -0800 Subject: [PATCH 04/15] fix download link and restore behaviour with pending --- domain-server/resources/web/content/js/content.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/domain-server/resources/web/content/js/content.js b/domain-server/resources/web/content/js/content.js index 5c2e134102..717f149760 100644 --- a/domain-server/resources/web/content/js/content.js +++ b/domain-server/resources/web/content/js/content.js @@ -126,7 +126,7 @@ $(document).ready(function(){ var BACKUP_DOWNLOAD_LINK_CLASS = 'download-backup'; var BACKUP_DELETE_LINK_CLASS = 'delete-backup'; var ACTIVE_BACKUP_ROW_CLASS = 'active-backup'; - + function reloadBackupInformation() { // make a GET request to get backup information to populate the table $.ajax({ @@ -151,8 +151,8 @@ $(document).ready(function(){ + ""; + + "
  • Download
  • " + + "
  • Delete
  • "; } function updateProgressBars($progressBar, value) { @@ -267,12 +267,11 @@ $(document).ready(function(){ "Restore content", function() { // show a spinner while we send off our request - showSpinnerAlert("Restoring Content Archive " + backupName); + showSpinnerAlert("Starting restore of " + backupName); // setup an AJAX POST to request content restore $.post('/api/backups/recover/' + backupID).done(function(data, textStatus, jqXHR) { swal.close(); - showRestartModal(); }).fail(function(jqXHR, textStatus, errorThrown) { showErrorMessage( "Error", From 494f93304b9eb84c5815e5e9a50b1c0dd57d421e Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 16 Feb 2018 10:58:32 -0800 Subject: [PATCH 05/15] take down AssetClient after content manager --- domain-server/src/DomainServer.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 7cd6cd34fe..584cbe3513 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -380,11 +380,6 @@ void DomainServer::parseCommandLine() { DomainServer::~DomainServer() { qInfo() << "Domain Server is shutting down."; - // cleanup the AssetClient thread - DependencyManager::destroy(); - _assetClientThread.quit(); - _assetClientThread.wait(); - // destroy the LimitedNodeList before the DomainServer QCoreApplication is down DependencyManager::destroy(); @@ -392,6 +387,11 @@ DomainServer::~DomainServer() { _contentManager->aboutToFinish(); _contentManager->terminate(); } + + // cleanup the AssetClient thread + DependencyManager::destroy(); + _assetClientThread.quit(); + _assetClientThread.wait(); } void DomainServer::queuedQuit(QString quitMessage, int exitCode) { From 441b55301f8637fa86f6c297266e4cd250f66252 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 16 Feb 2018 11:01:22 -0800 Subject: [PATCH 06/15] cleanup LNL last during DS shutdown --- domain-server/src/DomainServer.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 584cbe3513..5b8d253110 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -380,9 +380,6 @@ void DomainServer::parseCommandLine() { DomainServer::~DomainServer() { qInfo() << "Domain Server is shutting down."; - // destroy the LimitedNodeList before the DomainServer QCoreApplication is down - DependencyManager::destroy(); - if (_contentManager) { _contentManager->aboutToFinish(); _contentManager->terminate(); @@ -392,6 +389,9 @@ DomainServer::~DomainServer() { DependencyManager::destroy(); _assetClientThread.quit(); _assetClientThread.wait(); + + // destroy the LimitedNodeList before the DomainServer QCoreApplication is down + DependencyManager::destroy(); } void DomainServer::queuedQuit(QString quitMessage, int exitCode) { From 8e621a95a3c9abb5a0c4d948d60389f3b80d2533 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 16 Feb 2018 11:04:58 -0800 Subject: [PATCH 07/15] fix typo in debug for writing new entities --- domain-server/src/DomainServer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 5b8d253110..81dcf65be5 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -1761,7 +1761,7 @@ void DomainServer::processOctreeDataPersistMessage(QSharedPointer Date: Fri, 16 Feb 2018 11:11:54 -0800 Subject: [PATCH 08/15] set DomainContentBackupManager object name so it appears on thread --- domain-server/src/DomainContentBackupManager.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/domain-server/src/DomainContentBackupManager.cpp b/domain-server/src/DomainContentBackupManager.cpp index 40a2a55486..f6c6e7a7ba 100644 --- a/domain-server/src/DomainContentBackupManager.cpp +++ b/domain-server/src/DomainContentBackupManager.cpp @@ -57,6 +57,8 @@ DomainContentBackupManager::DomainContentBackupManager(const QString& backupDire _persistInterval(persistInterval), _lastCheck(usecTimestampNow()) { + setObjectName("DomainContentBackupManager"); + // Make sure the backup directory exists. QDir(_backupDirectory).mkpath("."); From 1c053730eb997c5d0f1b037c882c9ab9bbae669d Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 16 Feb 2018 14:09:00 -0800 Subject: [PATCH 09/15] make DomainServerSettingsManager thread-safe for use in content backup --- .../src/DomainContentBackupManager.cpp | 2 +- domain-server/src/DomainGatekeeper.cpp | 15 +- domain-server/src/DomainMetadata.cpp | 17 +- domain-server/src/DomainServer.cpp | 150 ++++++++---------- .../src/DomainServerSettingsManager.cpp | 95 +++++------ .../src/DomainServerSettingsManager.h | 26 +-- 6 files changed, 153 insertions(+), 152 deletions(-) diff --git a/domain-server/src/DomainContentBackupManager.cpp b/domain-server/src/DomainContentBackupManager.cpp index f6c6e7a7ba..0bef6bb891 100644 --- a/domain-server/src/DomainContentBackupManager.cpp +++ b/domain-server/src/DomainContentBackupManager.cpp @@ -58,7 +58,7 @@ DomainContentBackupManager::DomainContentBackupManager(const QString& backupDire _lastCheck(usecTimestampNow()) { setObjectName("DomainContentBackupManager"); - + // Make sure the backup directory exists. QDir(_backupDirectory).mkpath("."); diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index 3aab7b4563..e697bbdda1 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -435,10 +435,11 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect if (!userPerms.can(NodePermissions::Permission::canConnectPastMaxCapacity) && !isWithinMaxCapacity()) { // we can't allow this user to connect because we are at max capacity QString redirectOnMaxCapacity; - const QVariant* redirectOnMaxCapacityVariant = - valueForKeyPath(_server->_settingsManager.getSettingsMap(), MAXIMUM_USER_CAPACITY_REDIRECT_LOCATION); - if (redirectOnMaxCapacityVariant && redirectOnMaxCapacityVariant->canConvert()) { - redirectOnMaxCapacity = redirectOnMaxCapacityVariant->toString(); + + QVariant redirectOnMaxCapacityVariant = + _server->_settingsManager.valueOrDefaultValueForKeyPath(MAXIMUM_USER_CAPACITY_REDIRECT_LOCATION); + if (redirectOnMaxCapacityVariant.canConvert()) { + redirectOnMaxCapacity = redirectOnMaxCapacityVariant.toString(); qDebug() << "Redirection domain:" << redirectOnMaxCapacity; } @@ -610,9 +611,9 @@ bool DomainGatekeeper::verifyUserSignature(const QString& username, bool DomainGatekeeper::isWithinMaxCapacity() { // find out what our maximum capacity is - const QVariant* maximumUserCapacityVariant = - valueForKeyPath(_server->_settingsManager.getSettingsMap(), MAXIMUM_USER_CAPACITY); - unsigned int maximumUserCapacity = maximumUserCapacityVariant ? maximumUserCapacityVariant->toUInt() : 0; + QVariant maximumUserCapacityVariant = + _server->_settingsManager.valueOrDefaultValueForKeyPath(MAXIMUM_USER_CAPACITY); + unsigned int maximumUserCapacity = !maximumUserCapacityVariant.isValid() ? maximumUserCapacityVariant.toUInt() : 0; if (maximumUserCapacity > 0) { unsigned int connectedUsers = _server->countConnectedUsers(); diff --git a/domain-server/src/DomainMetadata.cpp b/domain-server/src/DomainMetadata.cpp index eee5673af3..24d55d74b6 100644 --- a/domain-server/src/DomainMetadata.cpp +++ b/domain-server/src/DomainMetadata.cpp @@ -84,21 +84,22 @@ void DomainMetadata::descriptorsChanged() { // get descriptors assert(_metadata[DESCRIPTORS].canConvert()); auto& state = *static_cast(_metadata[DESCRIPTORS].data()); - auto& settings = static_cast(parent())->_settingsManager.getSettingsMap(); - auto& descriptors = static_cast(parent())->_settingsManager.getDescriptorsMap(); + + static const QString DESCRIPTORS_GROUP_KEYPATH = "descriptors"; + auto descriptorsMap = static_cast(parent())->_settingsManager.valueForKeyPath(DESCRIPTORS).toMap(); // copy simple descriptors (description/maturity) - state[Descriptors::DESCRIPTION] = descriptors[Descriptors::DESCRIPTION]; - state[Descriptors::MATURITY] = descriptors[Descriptors::MATURITY]; + state[Descriptors::DESCRIPTION] = descriptorsMap[Descriptors::DESCRIPTION]; + state[Descriptors::MATURITY] = descriptorsMap[Descriptors::MATURITY]; // copy array descriptors (hosts/tags) - state[Descriptors::HOSTS] = descriptors[Descriptors::HOSTS].toList(); - state[Descriptors::TAGS] = descriptors[Descriptors::TAGS].toList(); + state[Descriptors::HOSTS] = descriptorsMap[Descriptors::HOSTS].toList(); + state[Descriptors::TAGS] = descriptorsMap[Descriptors::TAGS].toList(); // parse capacity static const QString CAPACITY = "security.maximum_user_capacity"; - const QVariant* capacityVariant = valueForKeyPath(settings, CAPACITY); - unsigned int capacity = capacityVariant ? capacityVariant->toUInt() : 0; + QVariant capacityVariant = static_cast(parent())->_settingsManager.valueForKeyPath(CAPACITY); + unsigned int capacity = capacityVariant.isValid() ? capacityVariant.toUInt() : 0; state[Descriptors::CAPACITY] = capacity; #if DEV_BUILD || PR_BUILD diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 81dcf65be5..9cecea5f70 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -75,8 +75,8 @@ bool DomainServer::forwardMetaverseAPIRequest(HTTPConnection* connection, std::initializer_list optionalData, bool requireAccessToken) { - auto accessTokenVariant = valueForKeyPath(_settingsManager.getSettingsMap(), ACCESS_TOKEN_KEY_PATH); - if (accessTokenVariant == nullptr && requireAccessToken) { + auto accessTokenVariant = _settingsManager.valueForKeyPath(ACCESS_TOKEN_KEY_PATH); + if (!accessTokenVariant.isValid() && requireAccessToken) { connection->respond(HTTPConnection::StatusCode400, "User access token has not been set"); return true; } @@ -112,8 +112,8 @@ bool DomainServer::forwardMetaverseAPIRequest(HTTPConnection* connection, req.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); req.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); - if (accessTokenVariant != nullptr) { - auto accessTokenHeader = QString("Bearer ") + accessTokenVariant->toString(); + if (accessTokenVariant.isValid()) { + auto accessTokenHeader = QString("Bearer ") + accessTokenVariant.toString(); req.setRawHeader("Authorization", accessTokenHeader.toLatin1()); } @@ -417,8 +417,8 @@ bool DomainServer::optionallyReadX509KeyAndCertificate() { const QString X509_PRIVATE_KEY_OPTION = "key"; const QString X509_KEY_PASSPHRASE_ENV = "DOMAIN_SERVER_KEY_PASSPHRASE"; - QString certPath = _settingsManager.getSettingsMap().value(X509_CERTIFICATE_OPTION).toString(); - QString keyPath = _settingsManager.getSettingsMap().value(X509_PRIVATE_KEY_OPTION).toString(); + QString certPath = _settingsManager.valueForKeyPath(X509_CERTIFICATE_OPTION).toString(); + QString keyPath = _settingsManager.valueForKeyPath(X509_PRIVATE_KEY_OPTION).toString(); if (!certPath.isEmpty() && !keyPath.isEmpty()) { // the user wants to use the following cert and key for HTTPS @@ -461,8 +461,7 @@ bool DomainServer::optionallySetupOAuth() { const QString OAUTH_CLIENT_SECRET_ENV = "DOMAIN_SERVER_CLIENT_SECRET"; const QString REDIRECT_HOSTNAME_OPTION = "hostname"; - const QVariantMap& settingsMap = _settingsManager.getSettingsMap(); - _oauthProviderURL = QUrl(settingsMap.value(OAUTH_PROVIDER_URL_OPTION).toString()); + _oauthProviderURL = QUrl(_settingsManager.valueForKeyPath(OAUTH_PROVIDER_URL_OPTION).toString()); // if we don't have an oauth provider URL then we default to the default node auth url if (_oauthProviderURL.isEmpty()) { @@ -472,9 +471,9 @@ bool DomainServer::optionallySetupOAuth() { auto accountManager = DependencyManager::get(); accountManager->setAuthURL(_oauthProviderURL); - _oauthClientID = settingsMap.value(OAUTH_CLIENT_ID_OPTION).toString(); + _oauthClientID = _settingsManager.valueForKeyPath(OAUTH_CLIENT_ID_OPTION).toString(); _oauthClientSecret = QProcessEnvironment::systemEnvironment().value(OAUTH_CLIENT_SECRET_ENV); - _hostname = settingsMap.value(REDIRECT_HOSTNAME_OPTION).toString(); + _hostname = _settingsManager.valueForKeyPath(REDIRECT_HOSTNAME_OPTION).toString(); if (!_oauthClientID.isEmpty()) { if (_oauthProviderURL.isEmpty() @@ -499,11 +498,11 @@ static const QString METAVERSE_DOMAIN_ID_KEY_PATH = "metaverse.id"; void DomainServer::getTemporaryName(bool force) { // check if we already have a domain ID - const QVariant* idValueVariant = valueForKeyPath(_settingsManager.getSettingsMap(), METAVERSE_DOMAIN_ID_KEY_PATH); + QVariant idValueVariant = _settingsManager.valueForKeyPath(METAVERSE_DOMAIN_ID_KEY_PATH); qInfo() << "Requesting temporary domain name"; - if (idValueVariant) { - qDebug() << "A domain ID is already present in domain-server settings:" << idValueVariant->toString(); + if (idValueVariant.isValid()) { + qDebug() << "A domain ID is already present in domain-server settings:" << idValueVariant.toString(); if (force) { qDebug() << "Requesting temporary domain name to replace current ID:" << getID(); } else { @@ -543,9 +542,6 @@ void DomainServer::handleTempDomainSuccess(QNetworkReply& requestReply) { auto settingsDocument = QJsonDocument::fromJson(newSettingsJSON.toUtf8()); _settingsManager.recurseJSONObjectAndOverwriteSettings(settingsDocument.object(), DomainSettings); - // store the new ID and auto networking setting on disk - _settingsManager.persistToFile(); - // store the new token to the account info auto accountManager = DependencyManager::get(); accountManager->setTemporaryDomain(id, key); @@ -647,8 +643,6 @@ void DomainServer::setupNodeListAndAssignments() { QVariant localPortValue = _settingsManager.valueOrDefaultValueForKeyPath(CUSTOM_LOCAL_PORT_OPTION); int domainServerPort = localPortValue.toInt(); - QVariantMap& settingsMap = _settingsManager.getSettingsMap(); - int domainServerDTLSPort = INVALID_PORT; if (_isUsingDTLS) { @@ -656,8 +650,9 @@ void DomainServer::setupNodeListAndAssignments() { const QString CUSTOM_DTLS_PORT_OPTION = "dtls-port"; - if (settingsMap.contains(CUSTOM_DTLS_PORT_OPTION)) { - domainServerDTLSPort = (unsigned short) settingsMap.value(CUSTOM_DTLS_PORT_OPTION).toUInt(); + auto dtlsPortVariant = _settingsManager.valueForKeyPath(CUSTOM_DTLS_PORT_OPTION); + if (dtlsPortVariant.isValid()) { + domainServerDTLSPort = (unsigned short) dtlsPortVariant.toUInt(); } } @@ -687,9 +682,9 @@ void DomainServer::setupNodeListAndAssignments() { nodeList->setSessionUUID(_overridingDomainID); isMetaverseDomain = true; // assume metaverse domain } else { - const QVariant* idValueVariant = valueForKeyPath(settingsMap, METAVERSE_DOMAIN_ID_KEY_PATH); - if (idValueVariant) { - nodeList->setSessionUUID(idValueVariant->toString()); + QVariant idValueVariant = _settingsManager.valueForKeyPath(METAVERSE_DOMAIN_ID_KEY_PATH); + if (idValueVariant.isValid()) { + nodeList->setSessionUUID(idValueVariant.toString()); isMetaverseDomain = true; // if we have an ID, we'll assume we're a metaverse domain } else { nodeList->setSessionUUID(QUuid::createUuid()); // Use random UUID @@ -758,10 +753,10 @@ bool DomainServer::resetAccountManagerAccessToken() { QString accessToken = QProcessEnvironment::systemEnvironment().value(ENV_ACCESS_TOKEN_KEY); if (accessToken.isEmpty()) { - const QVariant* accessTokenVariant = valueForKeyPath(_settingsManager.getSettingsMap(), ACCESS_TOKEN_KEY_PATH); + QVariant accessTokenVariant = _settingsManager.valueForKeyPath(ACCESS_TOKEN_KEY_PATH); - if (accessTokenVariant && accessTokenVariant->canConvert(QMetaType::QString)) { - accessToken = accessTokenVariant->toString(); + if (accessTokenVariant.isValid() && accessTokenVariant.canConvert(QMetaType::QString)) { + accessToken = accessTokenVariant.toString(); } else { qWarning() << "No access token is present. Some operations that use the metaverse API will fail."; qDebug() << "Set an access token via the web interface, in your user config" @@ -892,31 +887,26 @@ void DomainServer::updateICEServerAddresses() { } void DomainServer::parseAssignmentConfigs(QSet& excludedTypes) { - const QString ASSIGNMENT_CONFIG_REGEX_STRING = "config-([\\d]+)"; - QRegExp assignmentConfigRegex(ASSIGNMENT_CONFIG_REGEX_STRING); - - const QVariantMap& settingsMap = _settingsManager.getSettingsMap(); + const QString ASSIGNMENT_CONFIG_PREFIX = "config-"; // scan for assignment config keys - QStringList variantMapKeys = settingsMap.keys(); - int configIndex = variantMapKeys.indexOf(assignmentConfigRegex); + for (int i = 0; i < Assignment::AllTypes; ++i) { + QVariant assignmentConfigVariant = _settingsManager.valueOrDefaultValueForKeyPath(ASSIGNMENT_CONFIG_PREFIX + QString::number(i)); - while (configIndex != -1) { - // figure out which assignment type this matches - Assignment::Type assignmentType = (Assignment::Type) assignmentConfigRegex.cap(1).toInt(); + if (assignmentConfigVariant.isValid()) { + // figure out which assignment type this matches + Assignment::Type assignmentType = static_cast(i); - if (assignmentType < Assignment::AllTypes && !excludedTypes.contains(assignmentType)) { - QVariant mapValue = settingsMap[variantMapKeys[configIndex]]; - QVariantList assignmentList = mapValue.toList(); + if (!excludedTypes.contains(assignmentType)) { + QVariantList assignmentList = assignmentConfigVariant.toList(); - if (assignmentType != Assignment::AgentType) { - createStaticAssignmentsForType(assignmentType, assignmentList); + if (assignmentType != Assignment::AgentType) { + createStaticAssignmentsForType(assignmentType, assignmentList); + } + + excludedTypes.insert(assignmentType); } - - excludedTypes.insert(assignmentType); } - - configIndex = variantMapKeys.indexOf(assignmentConfigRegex, configIndex + 1); } } @@ -928,10 +918,10 @@ void DomainServer::addStaticAssignmentToAssignmentHash(Assignment* newAssignment void DomainServer::populateStaticScriptedAssignmentsFromSettings() { const QString PERSISTENT_SCRIPTS_KEY_PATH = "scripts.persistent_scripts"; - const QVariant* persistentScriptsVariant = valueForKeyPath(_settingsManager.getSettingsMap(), PERSISTENT_SCRIPTS_KEY_PATH); + QVariant persistentScriptsVariant = _settingsManager.valueOrDefaultValueForKeyPath(PERSISTENT_SCRIPTS_KEY_PATH); - if (persistentScriptsVariant) { - QVariantList persistentScriptsList = persistentScriptsVariant->toList(); + if (persistentScriptsVariant.isValid()) { + QVariantList persistentScriptsList = persistentScriptsVariant.toList(); foreach(const QVariant& persistentScriptVariant, persistentScriptsList) { QVariantMap persistentScript = persistentScriptVariant.toMap(); @@ -1954,13 +1944,12 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url auto nodeList = DependencyManager::get(); - auto getSetting = [this](QString keyPath, QVariant& value) -> bool { - QVariantMap& settingsMap = _settingsManager.getSettingsMap(); - QVariant* var = valueForKeyPath(settingsMap, keyPath); - if (var == nullptr) { + auto getSetting = [this](QString keyPath, QVariant value) -> bool { + + value = _settingsManager.valueForKeyPath(keyPath); + if (!value.isValid()) { return false; } - value = *var; return true; }; @@ -2028,8 +2017,8 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url if (connection->requestOperation() == QNetworkAccessManager::GetOperation) { const QString URI_WIZARD = "/wizard/"; const QString WIZARD_COMPLETED_ONCE_KEY_PATH = "wizard.completed_once"; - const QVariant* wizardCompletedOnce = valueForKeyPath(_settingsManager.getSettingsMap(), WIZARD_COMPLETED_ONCE_KEY_PATH); - const bool completedOnce = wizardCompletedOnce && wizardCompletedOnce->toBool(); + QVariant wizardCompletedOnce = _settingsManager.valueForKeyPath(WIZARD_COMPLETED_ONCE_KEY_PATH); + const bool completedOnce = wizardCompletedOnce.isValid() && wizardCompletedOnce.toBool(); if (url.path() != URI_WIZARD && url.path().endsWith('/') && !completedOnce) { // First visit, redirect to the wizard @@ -2326,8 +2315,8 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url return true; } else if (url.path() == "/domain_settings") { - auto accessTokenVariant = valueForKeyPath(_settingsManager.getSettingsMap(), ACCESS_TOKEN_KEY_PATH); - if (!accessTokenVariant) { + auto accessTokenVariant = _settingsManager.valueForKeyPath(ACCESS_TOKEN_KEY_PATH); + if (!accessTokenVariant.isValid()) { connection->respond(HTTPConnection::StatusCode400); return true; } @@ -2360,8 +2349,8 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url return forwardMetaverseAPIRequest(connection, "/api/v1/domains/" + domainID, "domain", { }, { "network_address", "network_port", "label" }); } else if (url.path() == URI_API_PLACES) { - auto accessTokenVariant = valueForKeyPath(_settingsManager.getSettingsMap(), ACCESS_TOKEN_KEY_PATH); - if (!accessTokenVariant->isValid()) { + auto accessTokenVariant = _settingsManager.valueForKeyPath(ACCESS_TOKEN_KEY_PATH); + if (!accessTokenVariant.isValid()) { connection->respond(HTTPConnection::StatusCode400, "User access token has not been set"); return true; } @@ -2409,7 +2398,7 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url QUrl url { NetworkingConstants::METAVERSE_SERVER_URL().toString() + "/api/v1/places/" + place_id }; - url.setQuery("access_token=" + accessTokenVariant->toString()); + url.setQuery("access_token=" + accessTokenVariant.toString()); QNetworkRequest req(url); req.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); @@ -2604,10 +2593,11 @@ bool DomainServer::isAuthenticatedRequest(HTTPConnection* connection, const QUrl const QByteArray UNAUTHENTICATED_BODY = "You do not have permission to access this domain-server."; - QVariantMap& settingsMap = _settingsManager.getSettingsMap(); + QVariant adminUsersVariant = _settingsManager.valueForKeyPath(ADMIN_USERS_CONFIG_KEY); + QVariant adminRolesVariant = _settingsManager.valueForKeyPath(ADMIN_ROLES_CONFIG_KEY); if (!_oauthProviderURL.isEmpty() - && (settingsMap.contains(ADMIN_USERS_CONFIG_KEY) || settingsMap.contains(ADMIN_ROLES_CONFIG_KEY))) { + && (adminUsersVariant.isValid() || adminRolesVariant.isValid())) { QString cookieString = connection->requestHeaders().value(HTTP_COOKIE_HEADER_KEY); const QString COOKIE_UUID_REGEX_STRING = HIFI_SESSION_COOKIE_KEY + "=([\\d\\w-]+)($|;)"; @@ -2618,7 +2608,7 @@ bool DomainServer::isAuthenticatedRequest(HTTPConnection* connection, const QUrl cookieUUID = cookieUUIDRegex.cap(1); } - if (valueForKeyPath(settingsMap, BASIC_AUTH_USERNAME_KEY_PATH)) { + if (_settingsManager.valueForKeyPath(BASIC_AUTH_USERNAME_KEY_PATH).isValid()) { qDebug() << "Config file contains web admin settings for OAuth and basic HTTP authentication." << "These cannot be combined - using OAuth for authentication."; } @@ -2628,13 +2618,13 @@ bool DomainServer::isAuthenticatedRequest(HTTPConnection* connection, const QUrl DomainServerWebSessionData sessionData = _cookieSessionHash.value(cookieUUID); QString profileUsername = sessionData.getUsername(); - if (settingsMap.value(ADMIN_USERS_CONFIG_KEY).toStringList().contains(profileUsername)) { + if (_settingsManager.valueForKeyPath(ADMIN_USERS_CONFIG_KEY).toStringList().contains(profileUsername)) { // this is an authenticated user return true; } // loop the roles of this user and see if they are in the admin-roles array - QStringList adminRolesArray = settingsMap.value(ADMIN_ROLES_CONFIG_KEY).toStringList(); + QStringList adminRolesArray = _settingsManager.valueForKeyPath(ADMIN_ROLES_CONFIG_KEY).toStringList(); if (!adminRolesArray.isEmpty()) { foreach(const QString& userRole, sessionData.getRoles()) { @@ -2679,7 +2669,7 @@ bool DomainServer::isAuthenticatedRequest(HTTPConnection* connection, const QUrl // we don't know about this user yet, so they are not yet authenticated return false; } - } else if (valueForKeyPath(settingsMap, BASIC_AUTH_USERNAME_KEY_PATH)) { + } else if (_settingsManager.valueForKeyPath(BASIC_AUTH_USERNAME_KEY_PATH).isValid()) { // config file contains username and password combinations for basic auth const QByteArray BASIC_AUTH_HEADER_KEY = "Authorization"; @@ -2698,10 +2688,10 @@ bool DomainServer::isAuthenticatedRequest(HTTPConnection* connection, const QUrl QString headerPassword = credentialList[1]; // we've pulled a username and password - now check if there is a match in our basic auth hash - QString settingsUsername = valueForKeyPath(settingsMap, BASIC_AUTH_USERNAME_KEY_PATH)->toString(); - const QVariant* settingsPasswordVariant = valueForKeyPath(settingsMap, BASIC_AUTH_PASSWORD_KEY_PATH); + QString settingsUsername = _settingsManager.valueForKeyPath(BASIC_AUTH_USERNAME_KEY_PATH).toString(); + QVariant settingsPasswordVariant = _settingsManager.valueForKeyPath(BASIC_AUTH_PASSWORD_KEY_PATH); - QString settingsPassword = settingsPasswordVariant ? settingsPasswordVariant->toString() : ""; + QString settingsPassword = settingsPasswordVariant.isValid() ? settingsPasswordVariant.toString() : ""; QString hexHeaderPassword = headerPassword.isEmpty() ? "" : QCryptographicHash::hash(headerPassword.toUtf8(), QCryptographicHash::Sha256).toHex(); @@ -2838,13 +2828,14 @@ ReplicationServerInfo serverInformationFromSettings(QVariantMap serverMap, Repli } void DomainServer::updateReplicationNodes(ReplicationServerDirection direction) { - auto settings = _settingsManager.getSettingsMap(); - if (settings.contains(BROADCASTING_SETTINGS_KEY)) { + auto broadcastSettingsVariant = _settingsManager.valueForKeyPath(BROADCASTING_SETTINGS_KEY); + + if (broadcastSettingsVariant.isValid()) { auto nodeList = DependencyManager::get(); std::vector replicationNodesInSettings; - auto replicationSettings = settings.value(BROADCASTING_SETTINGS_KEY).toMap(); + auto replicationSettings = broadcastSettingsVariant.toMap(); QString serversKey = direction == Upstream ? "upstream_servers" : "downstream_servers"; QString replicationDirection = direction == Upstream ? "upstream" : "downstream"; @@ -2920,13 +2911,12 @@ void DomainServer::updateUpstreamNodes() { void DomainServer::updateReplicatedNodes() { // Make sure we have downstream nodes in our list - auto settings = _settingsManager.getSettingsMap(); - static const QString REPLICATED_USERS_KEY = "users"; _replicatedUsernames.clear(); - - if (settings.contains(BROADCASTING_SETTINGS_KEY)) { - auto replicationSettings = settings.value(BROADCASTING_SETTINGS_KEY).toMap(); + + auto replicationVariant = _settingsManager.valueForKeyPath(BROADCASTING_SETTINGS_KEY); + if (replicationVariant.isValid()) { + auto replicationSettings = replicationVariant.toMap(); if (replicationSettings.contains(REPLICATED_USERS_KEY)) { auto usersSettings = replicationSettings.value(REPLICATED_USERS_KEY).toList(); for (auto& username : usersSettings) { @@ -3114,17 +3104,17 @@ void DomainServer::processPathQueryPacket(QSharedPointer messag // check out paths in the _configMap to see if we have a match auto keypath = QString(PATHS_SETTINGS_KEYPATH_FORMAT).arg(SETTINGS_PATHS_KEY).arg(pathQuery); - const QVariant* pathMatch = valueForKeyPath(_settingsManager.getSettingsMap(), keypath); + QVariant pathMatch = _settingsManager.valueForKeyPath(keypath); - if (pathMatch || pathQuery == INDEX_PATH) { + if (pathMatch.isValid() || pathQuery == INDEX_PATH) { // we got a match, respond with the resulting viewpoint auto nodeList = DependencyManager::get(); QString responseViewpoint; // if we didn't match the path BUT this is for the index path then send back our default - if (pathMatch) { - responseViewpoint = pathMatch->toMap()[PATH_VIEWPOINT_KEY].toString(); + if (pathMatch.isValid()) { + responseViewpoint = pathMatch.toMap()[PATH_VIEWPOINT_KEY].toString(); } else { const QString DEFAULT_INDEX_PATH = "/0,0,0/0,0,0,1"; responseViewpoint = DEFAULT_INDEX_PATH; diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index cd7155d9da..ad0381a697 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -38,6 +38,9 @@ #include "DomainServerNodeData.h" const QString SETTINGS_DESCRIPTION_RELATIVE_PATH = "/resources/describe-settings.json"; +const QString SETTINGS_PATH = "/settings"; +const QString SETTINGS_PATH_JSON = SETTINGS_PATH + ".json"; +const QString CONTENT_SETTINGS_PATH_JSON = "/content-settings.json"; const QString DESCRIPTION_SETTINGS_KEY = "settings"; const QString SETTING_DEFAULT_KEY = "default"; @@ -190,6 +193,9 @@ void DomainServerSettingsManager::processSettingsRequestPacket(QSharedPointer(getSettingsMap()[DESCRIPTORS].data()); -} - void DomainServerSettingsManager::initializeGroupPermissions(NodePermissionsMap& permissionsRows, QString groupName, NodePermissionsPointer perms) { // this is called when someone has used the domain-settings webpage to add a group. They type the group's name @@ -487,6 +482,9 @@ void DomainServerSettingsManager::initializeGroupPermissions(NodePermissionsMap& void DomainServerSettingsManager::packPermissionsForMap(QString mapName, NodePermissionsMap& permissionsRows, QString keyPath) { + // grab a write lock on the settings mutex since we're about to change the config map + QWriteLocker locker(&_settingsLock); + // find (or create) the "security" section of the settings map QVariant* security = _configMap.valueForKeyPath("security", true); if (!security->canConvert(QMetaType::QVariantMap)) { @@ -576,15 +574,15 @@ bool DomainServerSettingsManager::unpackPermissionsForKeypath(const QString& key mapPointer->clear(); - QVariant* permissions = _configMap.valueForKeyPath(keyPath, true); - if (!permissions->canConvert(QMetaType::QVariantList)) { + QVariant permissions = valueForKeyPath(keyPath); + + if (!permissions.canConvert(QMetaType::QVariantList)) { qDebug() << "Failed to extract permissions for key path" << keyPath << "from settings."; - (*permissions) = QVariantList(); } bool needPack = false; - QList permissionsList = permissions->toList(); + QList permissionsList = permissions.toList(); foreach (QVariant permsHash, permissionsList) { NodePermissionsPointer perms { new NodePermissions(permsHash.toMap()) }; QString id = perms->getID(); @@ -1068,12 +1066,22 @@ NodePermissions DomainServerSettingsManager::getForbiddensForGroup(const QUuid& return getForbiddensForGroup(groupKey.first, groupKey.second); } +QVariant DomainServerSettingsManager::valueForKeyPath(const QString& keyPath) { + QReadLocker locker(&_settingsLock); + auto foundValue = _configMap.valueForKeyPath(keyPath); + return foundValue ? *foundValue : QVariant(); +} + QVariant DomainServerSettingsManager::valueOrDefaultValueForKeyPath(const QString& keyPath) { + QReadLocker locker(&_settingsLock); const QVariant* foundValue = _configMap.valueForKeyPath(keyPath); if (foundValue) { return *foundValue; } else { + // we don't need the settings lock anymore since we're done reading from the config map + _settingsLock.unlock(); + int dotIndex = keyPath.indexOf('.'); QString groupKey = keyPath.mid(0, dotIndex); @@ -1112,9 +1120,6 @@ bool DomainServerSettingsManager::handleAuthenticatedHTTPRequest(HTTPConnection // we recurse one level deep below each group for the appropriate setting bool restartRequired = recurseJSONObjectAndOverwriteSettings(postedObject, endpointType); - // store whatever the current _settingsMap is to file - persistToFile(); - // return success to the caller QString jsonSuccess = "{\"status\": \"success\"}"; connection->respond(HTTPConnection::StatusCode200, jsonSuccess.toUtf8(), "application/json"); @@ -1216,16 +1221,9 @@ bool DomainServerSettingsManager::handleAuthenticatedHTTPRequest(HTTPConnection } bool DomainServerSettingsManager::restoreSettingsFromObject(QJsonObject settingsToRestore, SettingsType settingsType) { - - if (thread() != QThread::currentThread()) { - bool success; - BLOCKING_INVOKE_METHOD(this, "restoreSettingsFromObject", - Q_RETURN_ARG(bool, success), - Q_ARG(QJsonObject, settingsToRestore), - Q_ARG(SettingsType, settingsType)); - return success; - } + // grab a write lock since we're about to change the settings map + QWriteLocker locker(&_settingsLock); QJsonArray* filteredDescriptionArray = settingsType == DomainSettings ? &_domainSettingsDescription : &_contentSettingsDescription; @@ -1341,6 +1339,10 @@ bool DomainServerSettingsManager::restoreSettingsFromObject(QJsonObject settings } else { // restore completed, persist the new settings qDebug() << "Restore completed, persisting restored settings to file"; + + // let go of the write lock since we're done making changes to the config map + locker.unlock(); + persistToFile(); return true; } @@ -1352,20 +1354,6 @@ QJsonObject DomainServerSettingsManager::settingsResponseObjectForType(const QSt bool includeDefaults, bool isForBackup) { QJsonObject responseObject; - if (thread() != QThread::currentThread()) { - - BLOCKING_INVOKE_METHOD(this, "settingsResponseObjectForType", - Q_RETURN_ARG(QJsonObject, responseObject), - Q_ARG(QString, typeValue), - Q_ARG(bool, isAuthenticated), - Q_ARG(bool, includeDomainSettings), - Q_ARG(bool, includeContentSettings), - Q_ARG(bool, includeDefaults), - Q_ARG(bool, isForBackup)); - - return responseObject; - } - if (!typeValue.isEmpty() || isAuthenticated) { // convert the string type value to a QJsonValue QJsonValue queryType = typeValue.isEmpty() ? QJsonValue() : QJsonValue(typeValue.toInt()); @@ -1414,21 +1402,21 @@ QJsonObject DomainServerSettingsManager::settingsResponseObjectForType(const QSt QVariant variantValue; if (!groupKey.isEmpty()) { - QVariant settingsMapGroupValue = _configMap.value(groupKey); + QVariant settingsMapGroupValue = valueForKeyPath(groupKey); if (!settingsMapGroupValue.isNull()) { variantValue = settingsMapGroupValue.toMap().value(settingName); } } else { - variantValue = _configMap.value(settingName); + variantValue = valueForKeyPath(settingName); } // final check for inclusion // either we include default values or we don't but this isn't a default value - if (includeDefaults || !variantValue.isNull()) { + if (includeDefaults || variantValue.isValid()) { QJsonValue result; - if (variantValue.isNull()) { + if (!variantValue.isValid()) { // no value for this setting, pass the default if (settingObject.contains(SETTING_DEFAULT_KEY)) { result = settingObject[SETTING_DEFAULT_KEY]; @@ -1567,6 +1555,10 @@ QJsonObject DomainServerSettingsManager::settingDescriptionFromGroup(const QJson bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject, SettingsType settingsType) { + + // take a write lock since we're about to overwrite settings in the config map + QWriteLocker locker(&_settingsLock); + static const QString SECURITY_ROOT_KEY = "security"; static const QString AC_SUBNET_WHITELIST_KEY = "ac_subnet_whitelist"; static const QString BROADCASTING_KEY = "broadcasting"; @@ -1664,6 +1656,12 @@ bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ } } + // we're done making changes to the config map, let go of our read lock + locker.unlock(); + + // store whatever the current config map is to file + persistToFile(); + return needRestart; } @@ -1690,6 +1688,9 @@ bool permissionVariantLessThan(const QVariant &v1, const QVariant &v2) { } void DomainServerSettingsManager::sortPermissions() { + // take a write lock since we're about to change the config map data + QWriteLocker locker(&_settingsLock); + // sort the permission-names QVariant* standardPermissions = _configMap.valueForKeyPath(AGENT_STANDARD_PERMISSIONS_KEYPATH); if (standardPermissions && standardPermissions->canConvert(QMetaType::QVariantList)) { @@ -1726,11 +1727,15 @@ void DomainServerSettingsManager::persistToFile() { QFile settingsFile(_configMap.getUserConfigFilename()); if (settingsFile.open(QIODevice::WriteOnly)) { + // take a read lock so we can grab the config and write it to file + QReadLocker locker(&_settingsLock); 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 + // with a write lock since we're about to overwrite the config map + QWriteLocker locker(&_settingsLock); _configMap.loadConfig(_argumentList); } } diff --git a/domain-server/src/DomainServerSettingsManager.h b/domain-server/src/DomainServerSettingsManager.h index 897a15485f..d81547410b 100644 --- a/domain-server/src/DomainServerSettingsManager.h +++ b/domain-server/src/DomainServerSettingsManager.h @@ -27,9 +27,6 @@ const QString SETTINGS_PATHS_KEY = "paths"; -const QString SETTINGS_PATH = "/settings"; -const QString SETTINGS_PATH_JSON = SETTINGS_PATH + ".json"; -const QString CONTENT_SETTINGS_PATH_JSON = "/content-settings.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"; @@ -53,11 +50,12 @@ public: bool handleAuthenticatedHTTPRequest(HTTPConnection* connection, const QUrl& url); void setupConfigMap(const QStringList& argumentList); + + // each of the three methods in this group takes a read lock of _settingsLock + // and cannot be called when the a write lock is held by the same thread QVariant valueOrDefaultValueForKeyPath(const QString& keyPath); - - QVariantMap& getSettingsMap() { return _configMap.getConfig(); } - - QVariantMap& getDescriptorsMap(); + QVariant valueForKeyPath(const QString& keyPath); + bool containsKeyPath(const QString& keyPath) { return valueForKeyPath(keyPath).isValid(); } // these give access to anonymous/localhost/logged-in settings from the domain-server settings page bool haveStandardPermissionsForName(const QString& name) const { return _standardAgentPermissions.contains(name, 0); } @@ -119,6 +117,8 @@ public: /// thread safe method to restore settings from a JSON object Q_INVOKABLE bool restoreSettingsFromObject(QJsonObject settingsToRestore, SettingsType settingsType); + bool recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject, SettingsType settingsType); + signals: void updateNodePermissions(); void settingsUpdated(); @@ -138,12 +138,13 @@ private: QStringList _argumentList; QJsonArray filteredDescriptionArray(bool isContentSettings); - bool recurseJSONObjectAndOverwriteSettings(const QJsonObject& postedObject, SettingsType settingsType); - void updateSetting(const QString& key, const QJsonValue& newValue, QVariantMap& settingMap, const QJsonObject& settingDescription); QJsonObject settingDescriptionFromGroup(const QJsonObject& groupObject, const QString& settingName); void sortPermissions(); + + // you cannot be holding the _settingsLock when persisting to file from the same thread + // since it may take either a read lock or write lock and recursive locking doesn't allow a change in type void persistToFile(); void splitSettingsDescription(); @@ -155,10 +156,10 @@ private: QJsonArray _contentSettingsDescription; QJsonObject _settingsMenuGroups; + // any method that calls _valueForKeyPath on this _configMap must get a write lock it keeps until it + // is done with the returned QVariant* HifiConfigVariantMap _configMap; - friend class DomainServer; - // these cause calls to metaverse's group api void apiGetGroupID(const QString& groupName); void apiGetGroupRanks(const QUuid& groupID); @@ -192,6 +193,9 @@ private: // keep track of answers to api queries about which users are in which groups QHash> _groupMembership; // QHash> + + /// guard read/write access from multiple threads to settings + QReadWriteLock _settingsLock { QReadWriteLock::Recursive }; }; #endif // hifi_DomainServerSettingsManager_h From 679513599cce5f1b7a31b9312e3198c46bede1d2 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 16 Feb 2018 14:09:14 -0800 Subject: [PATCH 10/15] fix row hiding and paste events for badging --- domain-server/resources/web/content/js/content.js | 4 ++-- domain-server/resources/web/js/base-settings.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/domain-server/resources/web/content/js/content.js b/domain-server/resources/web/content/js/content.js index 717f149760..525b989259 100644 --- a/domain-server/resources/web/content/js/content.js +++ b/domain-server/resources/web/content/js/content.js @@ -145,7 +145,7 @@ $(document).ready(function(){ // populate the backups tables with the backups function createBackupTableRow(backup) { return "" - + "" + backup.name + "" + + "" + backup.name + "" + moment(backup.createdAtMillis).format('lll') + "" + "