diff --git a/assignment-client/src/avatars/MixerAvatar.cpp b/assignment-client/src/avatars/MixerAvatar.cpp index f5d598a5cf..6d6358ddf5 100644 --- a/assignment-client/src/avatars/MixerAvatar.cpp +++ b/assignment-client/src/avatars/MixerAvatar.cpp @@ -71,7 +71,7 @@ void MixerAvatar::fetchAvatarFST() { connect(fstRequest, &ResourceRequest::finished, this, &MixerAvatar::fstRequestComplete); fstRequest->send(); } else { - qCDebug(avatars) << "Couldn't create FST request for" << avatarURL; + qCDebug(avatars) << "Couldn't create FST request for" << avatarURL << getSessionUUID(); _verifyState = error; } _needsIdentityUpdate = true; @@ -84,7 +84,7 @@ void MixerAvatar::fstRequestComplete() { auto result = fstRequest->getResult(); if (result != ResourceRequest::Success) { _verifyState = error; - qCDebug(avatars) << "FST request for" << fstRequest->getUrl() << "failed:" << result; + qCDebug(avatars) << "FST request for" << fstRequest->getUrl() << "(user " << getSessionUUID() << ") failed:" << result; } else { _avatarFSTContents = fstRequest->getData(); _verifyState = receivedFST; @@ -178,7 +178,8 @@ void MixerAvatar::ownerRequestComplete() { } else { auto jsonData = QJsonDocument::fromJson(networkReply->readAll())["data"]; if (!jsonData.isUndefined() && !jsonData.toObject()["message"].isUndefined()) { - qCDebug(avatars) << "Owner lookup failed for" << getDisplayName() << ":" + qCDebug(avatars) << "Owner lookup failed for" << getDisplayName() << "(" + << getSessionUUID() << ") :" << jsonData.toObject()["message"].toString(); _verifyState = error; _pendingEvent = false; @@ -221,7 +222,7 @@ void MixerAvatar::processCertifyEvents() { } else { _needsIdentityUpdate = true; _pendingEvent = false; - qCDebug(avatars) << "Avatar" << getDisplayName() << "FAILED static certification"; + qCDebug(avatars) << "Avatar" << getDisplayName() << "(" << getSessionUUID() << ") FAILED static certification"; } } else { // FST doesn't have a certificate, so noncertified rather than failed: _pendingEvent = false; @@ -261,6 +262,8 @@ void MixerAvatar::processCertifyEvents() { _pendingEvent = true; } else { _verifyState = error; + qCDebug(avatars) << "Get owner status - couldn't parse response for" << getSessionUUID() + << ":" << _dynamicMarketResponse; } } else { qCDebug(avatars) << "Get owner status failed for " << getDisplayName() << _marketplaceIdFromURL << @@ -332,7 +335,7 @@ void MixerAvatar::sendOwnerChallenge() { nonceHash.addData(nonce); _challengeNonceHash = nonceHash.result(); - static constexpr int CHALLENGE_TIMEOUT_MS = 5 * 1000; // 5 s + static constexpr int CHALLENGE_TIMEOUT_MS = 10 * 1000; // 10 s if (_challengeTimeout) { _challengeTimeout->deleteLater(); } @@ -344,6 +347,7 @@ void MixerAvatar::sendOwnerChallenge() { _pendingEvent = false; _verifyState = verificationFailed; _needsIdentityUpdate = true; + qCDebug(avatars) << "Dynamic verification TIMED-OUT for " << getDisplayName() << getSessionUUID(); } }); _challengeTimeout->start(); diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index 488b2091f3..9cb4c2cab9 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -1788,6 +1788,39 @@ "default": false } ] + }, + { + "name": "installed_content", + "label": "Installed Content", + "hidden": true, + "settings": [ + { + "name": "filename", + "content_setting": true, + "default": "" + }, + { + "name": "name", + "content_setting": true, + "default": "" + }, + { + "name": "creation_time", + "content_setting": true, + "default": 0 + }, + { + "name": "install_time", + "type": "int", + "content_setting": true, + "default": 0 + }, + { + "name": "installed_by", + "content_setting": true, + "default": "" + } + ] } ] } diff --git a/domain-server/resources/web/content/js/content.js b/domain-server/resources/web/content/js/content.js index 85bd9e68b3..9b5c807245 100644 --- a/domain-server/resources/web/content/js/content.js +++ b/domain-server/resources/web/content/js/content.js @@ -4,8 +4,14 @@ $(document).ready(function(){ 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'; + var INSTALLED_CONTENT_FILENAME_ID = 'installed-content-filename'; + var INSTALLED_CONTENT_NAME_ID = 'installed-content-name'; + var INSTALLED_CONTENT_CREATED_ID = 'installed-content-created'; + var INSTALLED_CONTENT_INSTALLED_ID = 'installed-content-installed'; + var INSTALLED_CONTENT_INSTALLED_BY_ID = 'installed-content-installed-by'; var isRestoring = false; + var restoreErrorShown = false; function progressBarHTML(extraClass, label) { var html = "
"; @@ -64,11 +70,23 @@ $(document).ready(function(){ var ajaxObject = $.ajax(ajaxParams); ajaxObject.fail(function (jqXHR, textStatus, errorThrown) { - showErrorMessage( - "Error", - "There was a problem restoring domain content.\n" - + "Please ensure that the content archive or entity file is valid and try again." - ); + // status of 0 means the connection was reset, which + // happens after the content is parsed and the server restarts + // in the case of json and json.gz files + if (jqXHR.status != 0) { + showErrorMessage( + "Error", + "There was a problem restoring domain content.\n" + + "Please ensure that the content archive or entity file is valid and try again." + ); + } else { + isRestoring = true; + + // immediately reload backup information since one should be restoring now + reloadBackupInformation(); + + swal.close(); + } }); updateProgressBars($('.upload-content-progress'), (offset + nextChunkSize) * 100 / fileSize); @@ -103,10 +121,25 @@ $(document).ready(function(){ html += "Restore in progress"; html += progressBarHTML('recovery', 'Restoring'); html += "
"; - $('#' + Settings.UPLOAD_CONTENT_BACKUP_PANEL_ID + ' .panel-body').html(html); } + function setupInstalledContentInfo() { + var html = ""; + html += ""; + html += ""; + html += ""; + html += ""; + //html += ""; + html += ""; + html += "
NameFile NameCreatedInstalledInstalled By
"; + html += ""; + html += ""; + html += ""; + //html += "
"; + $('#' + Settings.INSTALLED_CONTENT + ' .panel-body').html(html); + } + // handle content archive or entity file upload // when the selected file is changed, enable the button if there's a selected file @@ -135,6 +168,7 @@ $(document).ready(function(){ var GENERATE_ARCHIVE_BUTTON_ID = 'generate-archive-button'; var CONTENT_ARCHIVES_NORMAL_ID = 'content-archives-success'; + var CONTENT_ARCHIVES_CONTENT_INFO_ID = 'content-archives-content-info'; var CONTENT_ARCHIVES_ERROR_ID = 'content-archives-error'; var AUTOMATIC_ARCHIVES_TABLE_ID = 'automatic-archives-table'; var AUTOMATIC_ARCHIVES_TBODY_ID = 'automatic-archives-tbody'; @@ -230,13 +264,27 @@ $(document).ready(function(){ url: '/api/backups', cache: false }).done(function(data) { - // split the returned data into manual and automatic manual backups var splitBackups = _.partition(data.backups, function(value, index) { return value.isManualBackup; }); - - if (isRestoring && !data.status.isRecovering) { + if (data.status.recoveryError && !restoreErrorShown) { + restoreErrorShown = true; + swal({ + title: "Error", + text: "There was a problem restoring domain content.\n" + + data.status.recoveryError, + type: "error", + showCancelButton: false, + confirmButtonText: "Restart", + closeOnConfirm: true, + }, + function () { + $.get("/restart"); + showRestartModal(); + }); + } + if (isRestoring && !data.status.isRecovering && !data.status.recoveryError) { // we were recovering and we finished - the DS is going to restart so show the restart modal showRestartModal(); return; @@ -327,6 +375,12 @@ $(document).ready(function(){ $('#' + UPLOAD_CONTENT_ALLOWED_DIV_ID).toggle(!data.status.isRecovering); $('#' + UPLOAD_CONTENT_RECOVERING_DIV_ID).toggle(data.status.isRecovering); + $('#' + INSTALLED_CONTENT_NAME_ID).text(data.installed_content.name); + $('#' + INSTALLED_CONTENT_FILENAME_ID).text(data.installed_content.filename); + $('#' + INSTALLED_CONTENT_CREATED_ID).text(data.installed_content.creation_time ? moment(data.installed_content.creation_time).format('lll') : ""); + $('#' + INSTALLED_CONTENT_INSTALLED_ID).text(data.installed_content.install_time ? moment(data.installed_content.install_time).format('lll') : ""); + //$('#' + INSTALLED_CONTENT_INSTALLED_BY_ID).text(data.installed_content.installed_by); + // update the progress bars for current restore status if (data.status.isRecovering) { updateProgressBars($('.recovery.progress-bar'), data.status.recoveryProgress * 100); @@ -514,6 +568,7 @@ $(document).ready(function(){ Settings.afterReloadActions = function() { setupBackupUpload(); setupContentArchives(); + setupInstalledContentInfo(); // load the latest backups immediately reloadBackupInformation(); diff --git a/domain-server/resources/web/js/domain-server.js b/domain-server/resources/web/js/domain-server.js index 2c12e2683a..a8b7267b88 100644 --- a/domain-server/resources/web/js/domain-server.js +++ b/domain-server/resources/web/js/domain-server.js @@ -57,10 +57,14 @@ $(document).ready(function(){ // define extra groups to add to setting panels, with their splice index Settings.extraContentGroupsAtIndex = { 0: { + html_id: Settings.INSTALLED_CONTENT, + label: 'Installed Content' + }, + 1: { html_id: Settings.CONTENT_ARCHIVES_PANEL_ID, label: 'Content Archives' }, - 1: { + 2: { html_id: Settings.UPLOAD_CONTENT_BACKUP_PANEL_ID, label: 'Upload Content' } diff --git a/domain-server/resources/web/js/shared.js b/domain-server/resources/web/js/shared.js index cdfcc40eab..abcb2cb9eb 100644 --- a/domain-server/resources/web/js/shared.js +++ b/domain-server/resources/web/js/shared.js @@ -44,7 +44,8 @@ $.extend(Settings, { INVALID_ROW_CLASS: 'invalid-input', DATA_ROW_INDEX: 'data-row-index', CONTENT_ARCHIVES_PANEL_ID: 'content_archives', - UPLOAD_CONTENT_BACKUP_PANEL_ID: 'upload_content' + UPLOAD_CONTENT_BACKUP_PANEL_ID: 'upload_content', + INSTALLED_CONTENT: 'installed_content' }); var URLs = { diff --git a/domain-server/src/AssetsBackupHandler.cpp b/domain-server/src/AssetsBackupHandler.cpp index 0f63817d80..d978e4ea56 100644 --- a/domain-server/src/AssetsBackupHandler.cpp +++ b/domain-server/src/AssetsBackupHandler.cpp @@ -278,17 +278,19 @@ void AssetsBackupHandler::createBackup(const QString& backupName, QuaZip& zip) { _backups.emplace_back(backupName, mappings, false); } -void AssetsBackupHandler::recoverBackup(const QString& backupName, QuaZip& zip) { +std::pair AssetsBackupHandler::recoverBackup(const QString& backupName, QuaZip& zip, const QString& sourceFilename) { Q_ASSERT(QThread::currentThread() == thread()); if (operationInProgress()) { - qCWarning(asset_backup) << "There is already a backup/restore in progress."; - return; + QString errorStr ("There is already a backup/restore in progress. Please wait."); + qWarning() << errorStr; + return { false, errorStr }; } if (_lastMappingsRefresh.time_since_epoch().count() == 0) { - qCWarning(asset_backup) << "Current mappings not yet loaded."; - return; + QString errorStr ("Current mappings not yet loaded. Please wait."); + qWarning() << errorStr; + return { false, errorStr }; } if ((p_high_resolution_clock::now() - _lastMappingsRefresh) > MAX_REFRESH_TIME) { @@ -301,6 +303,16 @@ void AssetsBackupHandler::recoverBackup(const QString& backupName, QuaZip& zip) if (it == end(_backups)) { loadBackup(backupName, zip); + auto emplaced_backup = find_if(begin(_backups), end(_backups), [&](const AssetServerBackup& backup) { + return backup.name == backupName; + }); + + if(emplaced_backup->corruptedBackup) { + QString errorStr ("Current mappings file is corrupted."); + qWarning() << errorStr; + return { false, errorStr }; + } + QuaZipDir zipDir { &zip, ZIP_ASSETS_FOLDER }; auto assetNames = zipDir.entryList(QDir::Files); @@ -330,8 +342,9 @@ void AssetsBackupHandler::recoverBackup(const QString& backupName, QuaZip& zip) }); if (it == end(_backups)) { - qCCritical(asset_backup) << "Failed to recover backup:" << backupName; - return; + QString errorStr ("Failed to recover backup: " + backupName); + qWarning() << errorStr; + return { false, errorStr }; } } @@ -339,6 +352,7 @@ void AssetsBackupHandler::recoverBackup(const QString& backupName, QuaZip& zip) computeServerStateDifference(_currentMappings, newMappings); restoreAllAssets(); + return { true, QString() }; } void AssetsBackupHandler::deleteBackup(const QString& backupName) { diff --git a/domain-server/src/AssetsBackupHandler.h b/domain-server/src/AssetsBackupHandler.h index 427dc6831a..c8f20ab965 100644 --- a/domain-server/src/AssetsBackupHandler.h +++ b/domain-server/src/AssetsBackupHandler.h @@ -38,7 +38,7 @@ public: void loadBackup(const QString& backupName, QuaZip& zip) override; void loadingComplete() override; void createBackup(const QString& backupName, QuaZip& zip) override; - void recoverBackup(const QString& backupName, QuaZip& zip) override; + std::pair recoverBackup(const QString& backupName, QuaZip& zip, const QString& sourceFilename) override; void deleteBackup(const QString& backupName) override; void consolidateBackup(const QString& backupName, QuaZip& zip) override; bool isCorruptedBackup(const QString& backupName) override; diff --git a/domain-server/src/BackupHandler.h b/domain-server/src/BackupHandler.h index 547339e01b..278d43ade3 100644 --- a/domain-server/src/BackupHandler.h +++ b/domain-server/src/BackupHandler.h @@ -30,7 +30,7 @@ public: virtual void loadBackup(const QString& backupName, QuaZip& zip) = 0; virtual void loadingComplete() = 0; virtual void createBackup(const QString& backupName, QuaZip& zip) = 0; - virtual void recoverBackup(const QString& backupName, QuaZip& zip) = 0; + virtual std::pair recoverBackup(const QString& backupName, QuaZip& zip, const QString& sourceFilename) = 0; virtual void deleteBackup(const QString& backupName) = 0; virtual void consolidateBackup(const QString& backupName, QuaZip& zip) = 0; virtual bool isCorruptedBackup(const QString& backupName) = 0; diff --git a/domain-server/src/ContentSettingsBackupHandler.cpp b/domain-server/src/ContentSettingsBackupHandler.cpp index de7669b6a5..b3748a66a3 100644 --- a/domain-server/src/ContentSettingsBackupHandler.cpp +++ b/domain-server/src/ContentSettingsBackupHandler.cpp @@ -10,6 +10,7 @@ // #include "ContentSettingsBackupHandler.h" +#include "DomainContentBackupManager.h" #if !defined(__clang__) && defined(__GNUC__) #pragma GCC diagnostic push @@ -24,6 +25,7 @@ #pragma GCC diagnostic pop #endif +static const QString DATETIME_FORMAT { "yyyy-MM-dd_HH-mm-ss" }; ContentSettingsBackupHandler::ContentSettingsBackupHandler(DomainServerSettingsManager& domainServerSettingsManager) : _settingsManager(domainServerSettingsManager) @@ -41,6 +43,26 @@ void ContentSettingsBackupHandler::createBackup(const QString& backupName, QuaZi DomainServerSettingsManager::IncludeContentSettings, DomainServerSettingsManager::NoDefaultSettings, DomainServerSettingsManager::ForBackup ); + QString prefixFormat = "(" + QRegExp::escape(AUTOMATIC_BACKUP_PREFIX) + "|" + QRegExp::escape(MANUAL_BACKUP_PREFIX) + ")"; + QString nameFormat = "(.+)"; + QString dateTimeFormat = "(" + DATETIME_FORMAT_RE + ")"; + QRegExp backupNameFormat { prefixFormat + nameFormat + "-" + dateTimeFormat + "\\.zip" }; + + QString name{ "" }; + QDateTime createdAt; + + if (backupNameFormat.exactMatch(backupName)) { + name = backupNameFormat.cap(2); + auto dateTime = backupNameFormat.cap(3); + createdAt = QDateTime::fromString(dateTime, DATETIME_FORMAT); + } + + QJsonObject installed_content { + { INSTALLED_CONTENT_NAME, name}, + { INSTALLED_CONTENT_CREATION_TIME, createdAt.currentMSecsSinceEpoch()} + }; + + contentSettingsJSON.insert(INSTALLED_CONTENT, installed_content); // make a QJsonDocument using the object QJsonDocument contentSettingsDocument { contentSettingsJSON }; @@ -62,24 +84,48 @@ void ContentSettingsBackupHandler::createBackup(const QString& backupName, QuaZi } } -void ContentSettingsBackupHandler::recoverBackup(const QString& backupName, QuaZip& zip) { +std::pair ContentSettingsBackupHandler::recoverBackup(const QString& backupName, QuaZip& zip, const QString& sourceFilename) { if (!zip.setCurrentFile(CONTENT_SETTINGS_BACKUP_FILENAME)) { - qWarning() << "Failed to find" << CONTENT_SETTINGS_BACKUP_FILENAME << "while recovering backup"; - return; + QString errorStr("Failed to find " + CONTENT_SETTINGS_BACKUP_FILENAME + " while recovering backup"); + qWarning() << errorStr; + return { false, errorStr }; } QuaZipFile zipFile { &zip }; if (!zipFile.open(QIODevice::ReadOnly)) { - qCritical() << "Failed to open" << CONTENT_SETTINGS_BACKUP_FILENAME << "in backup"; - return; + QString errorStr("Failed to open " + CONTENT_SETTINGS_BACKUP_FILENAME + " in backup"); + qCritical() << errorStr; + return { false, errorStr }; } auto rawData = zipFile.readAll(); zipFile.close(); - QJsonDocument jsonDocument = QJsonDocument::fromJson(rawData); - - if (!_settingsManager.restoreSettingsFromObject(jsonDocument.object(), ContentSettings)) { - qCritical() << "Failed to restore settings from" << CONTENT_SETTINGS_BACKUP_FILENAME << "in content archive"; + if (zipFile.getZipError() != UNZ_OK) { + QString errorStr("Failed to unzip " + CONTENT_SETTINGS_BACKUP_FILENAME + ": " + zipFile.getZipError()); + qCritical() << errorStr; + return { false, errorStr }; } + + QJsonDocument jsonDocument = QJsonDocument::fromJson(rawData); + QJsonObject jsonObject = jsonDocument.object(); + + auto archiveJson = jsonObject.find(INSTALLED_CONTENT)->toObject(); + + QJsonObject installed_content { + { INSTALLED_CONTENT_FILENAME, sourceFilename }, + { INSTALLED_CONTENT_NAME, archiveJson[INSTALLED_CONTENT_NAME].toString()}, + { INSTALLED_CONTENT_CREATION_TIME, archiveJson[INSTALLED_CONTENT_CREATION_TIME].toVariant().toLongLong() }, + { INSTALLED_CONTENT_INSTALL_TIME, QDateTime::currentDateTime().currentMSecsSinceEpoch() }, + { INSTALLED_CONTENT_INSTALLED_BY, "" } + }; + + jsonObject.insert(INSTALLED_CONTENT, installed_content); + + if (!_settingsManager.restoreSettingsFromObject(jsonObject, ContentSettings)) { + QString errorStr("Failed to restore settings from " + CONTENT_SETTINGS_BACKUP_FILENAME + " in content archive"); + qCritical() << errorStr; + return { false, errorStr }; + } + return { true, QString() }; } diff --git a/domain-server/src/ContentSettingsBackupHandler.h b/domain-server/src/ContentSettingsBackupHandler.h index 8454de0786..0e44a18424 100644 --- a/domain-server/src/ContentSettingsBackupHandler.h +++ b/domain-server/src/ContentSettingsBackupHandler.h @@ -28,7 +28,7 @@ public: void createBackup(const QString& backupName, QuaZip& zip) override; - void recoverBackup(const QString& backupName, QuaZip& zip) override; + std::pair recoverBackup(const QString& backupName, QuaZip& zip, const QString& sourceFilename) override; void deleteBackup(const QString& backupName) override {} diff --git a/domain-server/src/DomainContentBackupManager.cpp b/domain-server/src/DomainContentBackupManager.cpp index 52bc7d679f..11930f0b49 100644 --- a/domain-server/src/DomainContentBackupManager.cpp +++ b/domain-server/src/DomainContentBackupManager.cpp @@ -42,9 +42,7 @@ const std::chrono::seconds DomainContentBackupManager::DEFAULT_PERSIST_INTERVAL // Backup format looks like: daily_backup-TIMESTAMP.zip static const QString DATETIME_FORMAT { "yyyy-MM-dd_HH-mm-ss" }; -static const QString DATETIME_FORMAT_RE { "\\d{4}-\\d{2}-\\d{2}_\\d{2}-\\d{2}-\\d{2}" }; -static const QString AUTOMATIC_BACKUP_PREFIX { "autobackup-" }; -static const QString MANUAL_BACKUP_PREFIX { "backup-" }; +static const QString PRE_UPLOAD_SUFFIX{ "pre_upload" }; static const QString MANUAL_BACKUP_NAME_RE { "[a-zA-Z0-9\\-_ ]+" }; void DomainContentBackupManager::addBackupHandler(BackupHandlerPointer handler) { @@ -52,9 +50,10 @@ void DomainContentBackupManager::addBackupHandler(BackupHandlerPointer handler) } DomainContentBackupManager::DomainContentBackupManager(const QString& backupDirectory, - const QVariantList& backupRules, + DomainServerSettingsManager& domainServerSettingsManager, std::chrono::milliseconds persistInterval, bool debugTimestampNow) : + _settingsManager(domainServerSettingsManager), _consolidatedBackupDirectory(PathUtils::generateTemporaryDir()), _backupDirectory(backupDirectory), _persistInterval(persistInterval), _lastCheck(p_high_resolution_clock::now()) { @@ -63,7 +62,8 @@ DomainContentBackupManager::DomainContentBackupManager(const QString& backupDire // Make sure the backup directory exists. QDir(_backupDirectory).mkpath("."); - parseBackupRules(backupRules); + static const QString BACKUP_RULES_KEYPATH = AUTOMATIC_CONTENT_ARCHIVES_GROUP + ".backup_rules"; + parseBackupRules(_settingsManager.valueOrDefaultValueForKeyPath(BACKUP_RULES_KEYPATH).toList()); constexpr int CONSOLIDATED_BACKUP_CLEANER_INTERVAL_MSECS = 30 * 1000; _consolidatedBackupCleanupTimer.setInterval(CONSOLIDATED_BACKUP_CLEANER_INTERVAL_MSECS); @@ -170,7 +170,9 @@ bool DomainContentBackupManager::process() { return handler->getRecoveryStatus().first; }); - if (!isStillRecovering) { + // if an error occurred, don't restart the server so that the user + // can be notified of the error and take action. + if (!isStillRecovering && _recoveryError.isEmpty()) { _isRecovering = false; _recoveryFilename = ""; emit recoveryCompleted(); @@ -277,7 +279,7 @@ void DomainContentBackupManager::deleteBackup(MiniPromise::Promise promise, cons }); } -bool DomainContentBackupManager::recoverFromBackupZip(const QString& backupName, QuaZip& zip) { +bool DomainContentBackupManager::recoverFromBackupZip(const QString& backupName, QuaZip& zip, const QString& sourceFilename, bool rollingBack) { if (!zip.open(QuaZip::Mode::mdUnzip)) { qWarning() << "Failed to unzip file: " << backupName; return false; @@ -286,7 +288,15 @@ bool DomainContentBackupManager::recoverFromBackupZip(const QString& backupName, _recoveryFilename = backupName; for (auto& handler : _backupHandlers) { - handler->recoverBackup(backupName, zip); + bool success; + QString errorStr; + std::tie(success, errorStr) = handler->recoverBackup(backupName, zip, sourceFilename); + if (!success) { + if (!rollingBack) { + _recoveryError = errorStr; + } + return false; + } } qDebug() << "Successfully started recovering from " << backupName; @@ -309,7 +319,7 @@ void DomainContentBackupManager::recoverFromBackup(MiniPromise::Promise promise, } qDebug() << "Recovering from" << backupName; - + _recoveryError = ""; bool success { false }; QDir backupDir { _backupDirectory }; auto backupFilePath { backupDir.filePath(backupName) }; @@ -317,7 +327,7 @@ void DomainContentBackupManager::recoverFromBackup(MiniPromise::Promise promise, if (backupFile.open(QIODevice::ReadOnly)) { QuaZip zip { &backupFile }; - success = recoverFromBackupZip(backupName, zip); + success = recoverFromBackupZip(backupName, zip, backupName); backupFile.close(); } else { @@ -345,29 +355,51 @@ void DomainContentBackupManager::recoverFromUploadedBackup(MiniPromise::Promise QuaZip uploadedZip { &uploadedBackupBuffer }; QString backupName = MANUAL_BACKUP_PREFIX + "uploaded.zip"; - bool success = recoverFromBackupZip(backupName, uploadedZip); + bool success = recoverFromBackupZip(backupName, uploadedZip, QString()); promise->resolve({ { "success", success } }); } -void DomainContentBackupManager::recoverFromUploadedFile(MiniPromise::Promise promise, QString uploadedFilename) { +void DomainContentBackupManager::recoverFromUploadedFile(MiniPromise::Promise promise, QString uploadedFilename, QString sourceFilename) { if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "recoverFromUploadedFile", Q_ARG(MiniPromise::Promise, promise), - Q_ARG(QString, uploadedFilename)); + Q_ARG(QString, uploadedFilename), Q_ARG(QString, sourceFilename)); return; } - qDebug() << "Recovering from uploaded file -" << uploadedFilename; + qDebug() << "Recovering from uploaded file -" << uploadedFilename << "source" << sourceFilename; + bool success; + QString path; + std::tie(success, path) = createBackup(AUTOMATIC_BACKUP_PREFIX, PRE_UPLOAD_SUFFIX); + if(!success) { + _recoveryError = "Failed to create backup for " + PRE_UPLOAD_SUFFIX + " at " + path; + qCWarning(domain_server) << _recoveryError; + } else { + QFile uploadedFile(uploadedFilename); + QuaZip uploadedZip { &uploadedFile }; - QFile uploadedFile(uploadedFilename); - QuaZip uploadedZip { &uploadedFile }; + QString backupName = MANUAL_BACKUP_PREFIX + "uploaded.zip"; - QString backupName = MANUAL_BACKUP_PREFIX + "uploaded.zip"; + bool success = recoverFromBackupZip(backupName, uploadedZip, sourceFilename); - bool success = recoverFromBackupZip(backupName, uploadedZip); + if (!success) { + // attempt to rollback to + QString filename; + QDateTime filetime; + if (getMostRecentBackup(PRE_UPLOAD_SUFFIX, filename, filetime)) { + QFile uploadedFile(uploadedFilename); + QuaZip uploadedZip { &uploadedFile }; + + QString backupName = MANUAL_BACKUP_PREFIX + "uploaded.zip"; + recoverFromBackupZip(backupName, uploadedZip, sourceFilename, true); + + } + } + + } promise->resolve({ { "success", success } }); @@ -455,9 +487,44 @@ void DomainContentBackupManager::getAllBackupsAndStatus(MiniPromise::Promise pro { "recoveryProgress", recoveryProgress } }; + if(!_recoveryError.isEmpty()) { + status["recoveryError"] = _recoveryError; + } + + + QString filename = _settingsManager.valueForKeyPath(CONTENT_SETTINGS_INSTALLED_CONTENT_FILENAME).toString(); + QString name = _settingsManager.valueForKeyPath(CONTENT_SETTINGS_INSTALLED_CONTENT_NAME).toString(); + auto creationTime = _settingsManager.valueForKeyPath(CONTENT_SETTINGS_INSTALLED_CONTENT_CREATION_TIME).toULongLong(); + + if (name.isEmpty() || creationTime == 0) { + QString prefixFormat = "(" + QRegExp::escape(AUTOMATIC_BACKUP_PREFIX) + "|" + QRegExp::escape(MANUAL_BACKUP_PREFIX) + ")"; + QString nameFormat = "(.+)"; + QString dateTimeFormat = "(" + DATETIME_FORMAT_RE + ")"; + QRegExp backupNameFormat { prefixFormat + nameFormat + "-" + dateTimeFormat + "\\.zip" }; + + + if (backupNameFormat.exactMatch(filename)) { + if (name.isEmpty()) { + name = backupNameFormat.cap(2); + } + if (creationTime == 0) { + auto dateTime = backupNameFormat.cap(3); + creationTime = QDateTime::fromString(dateTime, DATETIME_FORMAT).toMSecsSinceEpoch(); + } + } + } + + QVariantMap currentArchive; + currentArchive["filename"] = filename; + currentArchive["name"] = name; + currentArchive["creation_time"] = creationTime; + currentArchive["install_time"] = _settingsManager.valueForKeyPath(CONTENT_SETTINGS_INSTALLED_CONTENT_INSTALL_TIME).toULongLong(); + currentArchive["installed_by"] = _settingsManager.valueForKeyPath(CONTENT_SETTINGS_INSTALLED_CONTENT_INSTALLED_BY).toString(); + QVariantMap info { { "backups", variantBackups }, - { "status", status } + { "status", status }, + { "installed_content", currentArchive } }; promise->resolve(info); diff --git a/domain-server/src/DomainContentBackupManager.h b/domain-server/src/DomainContentBackupManager.h index 4af3ae5bfd..f5957d74f5 100644 --- a/domain-server/src/DomainContentBackupManager.h +++ b/domain-server/src/DomainContentBackupManager.h @@ -28,11 +28,22 @@ #include #include "BackupHandler.h" +#include "DomainServerSettingsManager.h" #include #include +const QString DATETIME_FORMAT_RE { "\\d{4}-\\d{2}-\\d{2}_\\d{2}-\\d{2}-\\d{2}" }; +const QString AUTOMATIC_BACKUP_PREFIX { "autobackup-" }; +const QString MANUAL_BACKUP_PREFIX { "backup-" }; +const QString INSTALLED_CONTENT = "installed_content"; +const QString INSTALLED_CONTENT_FILENAME = "filename"; +const QString INSTALLED_CONTENT_NAME = "name"; +const QString INSTALLED_CONTENT_CREATION_TIME = "creation_time"; +const QString INSTALLED_CONTENT_INSTALL_TIME = "install_time"; +const QString INSTALLED_CONTENT_INSTALLED_BY = "installed_by"; + struct BackupItemInfo { BackupItemInfo(QString pId, QString pName, QString pAbsolutePath, QDateTime pCreatedAt, bool pIsManualBackup) : id(pId), name(pName), absolutePath(pAbsolutePath), createdAt(pCreatedAt), isManualBackup(pIsManualBackup) { }; @@ -71,7 +82,7 @@ public: static const std::chrono::seconds DEFAULT_PERSIST_INTERVAL; DomainContentBackupManager(const QString& rootBackupDirectory, - const QVariantList& settings, + DomainServerSettingsManager& domainServerSettingsManager, std::chrono::milliseconds persistInterval = DEFAULT_PERSIST_INTERVAL, bool debugTimestampNow = false); @@ -86,7 +97,7 @@ public slots: void createManualBackup(MiniPromise::Promise promise, const QString& name); void recoverFromBackup(MiniPromise::Promise promise, const QString& backupName); void recoverFromUploadedBackup(MiniPromise::Promise promise, QByteArray uploadedBackup); - void recoverFromUploadedFile(MiniPromise::Promise promise, QString uploadedFilename); + void recoverFromUploadedFile(MiniPromise::Promise promise, QString uploadedFilename, QString sourceFilename); void deleteBackup(MiniPromise::Promise promise, const QString& backupName); signals: @@ -108,13 +119,15 @@ protected: std::pair createBackup(const QString& prefix, const QString& name); - bool recoverFromBackupZip(const QString& backupName, QuaZip& backupZip); + bool recoverFromBackupZip(const QString& backupName, QuaZip& backupZip, const QString& sourceFilename, bool rollingBack = false); private slots: void removeOldConsolidatedBackups(); void consolidateBackupInternal(QString fileName); private: + DomainServerSettingsManager& _settingsManager; + QTimer _consolidatedBackupCleanupTimer; const QString _consolidatedBackupDirectory; @@ -126,6 +139,7 @@ private: std::unordered_map _consolidatedBackups; std::atomic _isRecovering { false }; + QString _recoveryError; QString _recoveryFilename { }; p_high_resolution_clock::time_point _lastCheck; diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index b7c723ab48..fa4bf89ad6 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -307,11 +307,7 @@ DomainServer::DomainServer(int argc, char* argv[]) : } maybeHandleReplacementEntityFile(); - - static const QString BACKUP_RULES_KEYPATH = AUTOMATIC_CONTENT_ARCHIVES_GROUP + ".backup_rules"; - auto backupRulesVariant = _settingsManager.valueOrDefaultValueForKeyPath(BACKUP_RULES_KEYPATH); - - _contentManager.reset(new DomainContentBackupManager(getContentBackupDir(), backupRulesVariant.toList())); + _contentManager.reset(new DomainContentBackupManager(getContentBackupDir(), _settingsManager)); connect(_contentManager.get(), &DomainContentBackupManager::started, _contentManager.get(), [this](){ _contentManager->addBackupHandler(BackupHandlerPointer(new EntitiesBackupHandler(getEntitiesFilePath(), getEntitiesReplacementFilePath()))); @@ -2194,7 +2190,7 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url return true; } else if (url.path() == URI_RESTART) { - connection->respond(HTTPConnection::StatusCode200); + connection->respond(HTTPConnection::StatusCode204); restart(); return true; } else if (url.path() == URI_API_METAVERSE_INFO) { @@ -2333,8 +2329,7 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url QJsonObject rootJSON; auto success = result["success"].toBool(); - rootJSON["success"] = success; - QJsonDocument docJSON(rootJSON); + QJsonDocument docJSON(QJsonObject::fromVariantMap(result)); connectionPtr->respond(success ? HTTPConnection::StatusCode200 : HTTPConnection::StatusCode400, docJSON.toJson(), JSON_MIME_TYPE.toUtf8()); }); @@ -2362,8 +2357,7 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url QJsonObject rootJSON; auto success = result["success"].toBool(); - rootJSON["success"] = success; - QJsonDocument docJSON(rootJSON); + QJsonDocument docJSON(QJsonObject::fromVariantMap(result)); connectionPtr->respond(success ? HTTPConnection::StatusCode200 : HTTPConnection::StatusCode400, docJSON.toJson(), JSON_MIME_TYPE.toUtf8()); }); @@ -2467,8 +2461,7 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url QJsonObject rootJSON; auto success = result["success"].toBool(); - rootJSON["success"] = success; - QJsonDocument docJSON(rootJSON); + QJsonDocument docJSON(QJsonObject::fromVariantMap(result)); connectionPtr->respond(success ? HTTPConnection::StatusCode200 : HTTPConnection::StatusCode400, docJSON.toJson(), JSON_MIME_TYPE.toUtf8()); }); @@ -2590,7 +2583,7 @@ bool DomainServer::processPendingContent(HTTPConnection* connection, QString ite _pendingFileContent.close(); // Respond immediately - will timeout if we wait for restore. - connection->respond(HTTPConnection::StatusCode200); + connection->respond(HTTPConnection::StatusCode204); if (itemName == "restore-file" || itemName == "restore-file-chunk-final" || itemName == "restore-file-chunk-only") { auto deferred = makePromise("recoverFromUploadedBackup"); @@ -2598,7 +2591,7 @@ bool DomainServer::processPendingContent(HTTPConnection* connection, QString ite _pendingContentFiles.erase(sessionId); }); - _contentManager->recoverFromUploadedFile(deferred, _pendingFileContent.fileName()); + _contentManager->recoverFromUploadedFile(deferred, _pendingFileContent.fileName(), filename); } } else if (filename.endsWith(".json", Qt::CaseInsensitive) || filename.endsWith(".json.gz", Qt::CaseInsensitive)) { @@ -2608,14 +2601,16 @@ bool DomainServer::processPendingContent(HTTPConnection* connection, QString ite } QByteArray& _pendingUploadedContent = _pendingUploadedContents[sessionId]; _pendingUploadedContent += dataChunk; - connection->respond(HTTPConnection::StatusCode200); if (itemName == "restore-file" || itemName == "restore-file-chunk-final" || itemName == "restore-file-chunk-only") { // invoke our method to hand the new octree file off to the octree server - QMetaObject::invokeMethod(this, "handleOctreeFileReplacement", - Qt::QueuedConnection, Q_ARG(QByteArray, _pendingUploadedContent)); + if (!handleOctreeFileReplacement(_pendingUploadedContent, filename, QString())) { + connection->respond(HTTPConnection::StatusCode400); + return false; + } _pendingUploadedContents.erase(sessionId); } + connection->respond(HTTPConnection::StatusCode204); } else { connection->respond(HTTPConnection::StatusCode400); return false; @@ -2687,11 +2682,12 @@ void DomainServer::profileRequestFinished() { bool DomainServer::isAuthenticatedRequest(HTTPConnection* connection, const QUrl& url) { - const QByteArray HTTP_COOKIE_HEADER_KEY = "Cookie"; - const QString ADMIN_USERS_CONFIG_KEY = "admin-users"; - const QString ADMIN_ROLES_CONFIG_KEY = "admin-roles"; - const QString BASIC_AUTH_USERNAME_KEY_PATH = "security.http_username"; - const QString BASIC_AUTH_PASSWORD_KEY_PATH = "security.http_password"; + static const QByteArray HTTP_COOKIE_HEADER_KEY = "Cookie"; + static const QString ADMIN_USERS_CONFIG_KEY = "admin-users"; + static const QString ADMIN_ROLES_CONFIG_KEY = "admin-roles"; + static const QString BASIC_AUTH_USERNAME_KEY_PATH = "security.http_username"; + static const QString BASIC_AUTH_PASSWORD_KEY_PATH = "security.http_password"; + const QString COOKIE_UUID_REGEX_STRING = HIFI_SESSION_COOKIE_KEY + "=([\\d\\w-]+)($|;)"; const QByteArray UNAUTHENTICATED_BODY = "You do not have permission to access this domain-server."; @@ -2702,7 +2698,6 @@ bool DomainServer::isAuthenticatedRequest(HTTPConnection* connection, const QUrl && (adminUsersVariant.isValid() || adminRolesVariant.isValid())) { QString cookieString = connection->requestHeader(HTTP_COOKIE_HEADER_KEY); - const QString COOKIE_UUID_REGEX_STRING = HIFI_SESSION_COOKIE_KEY + "=([\\d\\w-]+)($|;)"; QRegExp cookieUUIDRegex(COOKIE_UUID_REGEX_STRING); QUuid cookieUUID; @@ -3498,7 +3493,7 @@ void DomainServer::maybeHandleReplacementEntityFile() { } } -void DomainServer::handleOctreeFileReplacement(QByteArray octreeFile) { +bool DomainServer::handleOctreeFileReplacement(QByteArray octreeFile, QString sourceFilename, QString name) { OctreeUtils::RawEntityData data; if (data.readOctreeDataInfoFromData(octreeFile)) { data.resetIdAndVersion(); @@ -3514,15 +3509,32 @@ void DomainServer::handleOctreeFileReplacement(QByteArray octreeFile) { // process it when it comes back up qInfo() << "Wrote octree replacement file to" << replacementFilePath << "- stopping server"; + QJsonObject installed_content { + { INSTALLED_CONTENT_FILENAME, sourceFilename }, + { INSTALLED_CONTENT_NAME, name }, + { INSTALLED_CONTENT_CREATION_TIME, 0 }, + { INSTALLED_CONTENT_INSTALL_TIME, QDateTime::currentDateTime().currentMSecsSinceEpoch() }, + { INSTALLED_CONTENT_INSTALLED_BY, "" } + }; + + QJsonObject jsonObject { { INSTALLED_CONTENT, installed_content } }; + + _settingsManager.recurseJSONObjectAndOverwriteSettings(jsonObject, ContentSettings); + QMetaObject::invokeMethod(this, "restart", Qt::QueuedConnection); + return true; } else { qWarning() << "Could not write replacement octree data to file - refusing to process"; + return false; } } else { qDebug() << "Received replacement octree file that is invalid - refusing to process"; + return false; } } +static const QString CONTENT_SET_NAME_QUERY_PARAM = "name"; + void DomainServer::handleDomainContentReplacementFromURLRequest(QSharedPointer message) { qInfo() << "Received request to replace content from a url"; auto node = DependencyManager::get()->findNodeWithAddr(message->getSenderSockAddr()); @@ -3534,13 +3546,16 @@ void DomainServer::handleDomainContentReplacementFromURLRequest(QSharedPointererror(); if (networkError == QNetworkReply::NoError) { if (modelsURL.fileName().endsWith(".json.gz")) { - handleOctreeFileReplacement(reply->readAll()); + QUrlQuery urlQuery(modelsURL.query(QUrl::FullyEncoded)); + + QString itemName = urlQuery.queryItemValue(CONTENT_SET_NAME_QUERY_PARAM); + handleOctreeFileReplacement(reply->readAll(), modelsURL.fileName(), itemName); } else if (modelsURL.fileName().endsWith(".zip")) { auto deferred = makePromise("recoverFromUploadedBackup"); _contentManager->recoverFromUploadedBackup(deferred, reply->readAll()); @@ -3555,6 +3570,6 @@ void DomainServer::handleDomainContentReplacementFromURLRequest(QSharedPointer message) { auto node = DependencyManager::get()->nodeWithLocalID(message->getSourceID()); if (node->getCanReplaceContent()) { - handleOctreeFileReplacement(message->readAll()); + handleOctreeFileReplacement(message->readAll(), QString(), QString()); } } diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index 54b7fbe466..aef59a4e4a 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -99,7 +99,7 @@ private slots: void handleDomainContentReplacementFromURLRequest(QSharedPointer message); void handleOctreeFileReplacementRequest(QSharedPointer message); - void handleOctreeFileReplacement(QByteArray octreeFile); + bool handleOctreeFileReplacement(QByteArray octreeFile, QString sourceFilename, QString name); void processOctreeDataRequestMessage(QSharedPointer message); void processOctreeDataPersistMessage(QSharedPointer message); diff --git a/domain-server/src/DomainServerSettingsManager.h b/domain-server/src/DomainServerSettingsManager.h index 2020561205..e28b9f6cd1 100644 --- a/domain-server/src/DomainServerSettingsManager.h +++ b/domain-server/src/DomainServerSettingsManager.h @@ -35,6 +35,11 @@ const QString MACHINE_FINGERPRINT_PERMISSIONS_KEYPATH = "security.machine_finger const QString GROUP_PERMISSIONS_KEYPATH = "security.group_permissions"; const QString GROUP_FORBIDDENS_KEYPATH = "security.group_forbiddens"; const QString AUTOMATIC_CONTENT_ARCHIVES_GROUP = "automatic_content_archives"; +const QString CONTENT_SETTINGS_INSTALLED_CONTENT_FILENAME = "installed_content.filename"; +const QString CONTENT_SETTINGS_INSTALLED_CONTENT_NAME = "installed_content.name"; +const QString CONTENT_SETTINGS_INSTALLED_CONTENT_CREATION_TIME = "installed_content.creation_time"; +const QString CONTENT_SETTINGS_INSTALLED_CONTENT_INSTALL_TIME = "installed_content.install_time"; +const QString CONTENT_SETTINGS_INSTALLED_CONTENT_INSTALLED_BY = "installed_content.installed_by"; using GroupByUUIDKey = QPair; // groupID, rankID diff --git a/domain-server/src/EntitiesBackupHandler.cpp b/domain-server/src/EntitiesBackupHandler.cpp index 90a066036d..03baec9164 100644 --- a/domain-server/src/EntitiesBackupHandler.cpp +++ b/domain-server/src/EntitiesBackupHandler.cpp @@ -57,36 +57,41 @@ void EntitiesBackupHandler::createBackup(const QString& backupName, QuaZip& zip) } } -void EntitiesBackupHandler::recoverBackup(const QString& backupName, QuaZip& zip) { +std::pair EntitiesBackupHandler::recoverBackup(const QString& backupName, QuaZip& zip, const QString& sourceFilename) { if (!zip.setCurrentFile(ENTITIES_BACKUP_FILENAME)) { - qWarning() << "Failed to find" << ENTITIES_BACKUP_FILENAME << "while recovering backup"; - return; + QString errorStr("Failed to find " + ENTITIES_BACKUP_FILENAME + " while recovering backup"); + qWarning() << errorStr; + return { false, errorStr }; } QuaZipFile zipFile { &zip }; if (!zipFile.open(QIODevice::ReadOnly)) { - qCritical() << "Failed to open" << ENTITIES_BACKUP_FILENAME << "in backup"; - return; + QString errorStr("Failed to open " + ENTITIES_BACKUP_FILENAME + " in backup"); + qCritical() << errorStr; + return { false, errorStr }; } auto rawData = zipFile.readAll(); zipFile.close(); + if (zipFile.getZipError() != UNZ_OK) { + QString errorStr("Failed to unzip " + ENTITIES_BACKUP_FILENAME + ": " + zipFile.getZipError()); + qCritical() << errorStr; + return { false, errorStr }; + } + OctreeUtils::RawEntityData data; if (!data.readOctreeDataInfoFromData(rawData)) { - qCritical() << "Unable to parse octree data during backup recovery"; - return; + QString errorStr("Unable to parse octree data during backup recovery"); + qCritical() << errorStr; + return { false, errorStr }; } data.resetIdAndVersion(); - if (zipFile.getZipError() != UNZ_OK) { - qCritical().nospace() << "Failed to unzip " << ENTITIES_BACKUP_FILENAME << ": " << zipFile.getZipError(); - return; - } - QFile entitiesFile { _entitiesReplacementFilePath }; if (entitiesFile.open(QIODevice::WriteOnly)) { entitiesFile.write(data.toGzippedByteArray()); } + return { true, QString() }; } diff --git a/domain-server/src/EntitiesBackupHandler.h b/domain-server/src/EntitiesBackupHandler.h index d95ad695a8..f8b6cba8a0 100644 --- a/domain-server/src/EntitiesBackupHandler.h +++ b/domain-server/src/EntitiesBackupHandler.h @@ -29,7 +29,7 @@ public: void createBackup(const QString& backupName, QuaZip& zip) override; // Recover from a full backup - void recoverBackup(const QString& backupName, QuaZip& zip) override; + std::pair recoverBackup(const QString& backupName, QuaZip& zip, const QString& sourceFilename) override; // Delete a skeleton backup void deleteBackup(const QString& backupName) override {} diff --git a/interface/resources/qml/hifi/audio/MicBarApplication.qml b/interface/resources/qml/hifi/audio/MicBarApplication.qml index 3070ba3bbd..76551be906 100644 --- a/interface/resources/qml/hifi/audio/MicBarApplication.qml +++ b/interface/resources/qml/hifi/audio/MicBarApplication.qml @@ -39,8 +39,8 @@ Rectangle { } Component.onCompleted: { - AudioScriptingInterface.noiseGateOpened.connect(function() { root.gated = false; }); - AudioScriptingInterface.noiseGateClosed.connect(function() { root.gated = true; }); + AudioScriptingInterface.noiseGateOpened.connect(function() { micBar.gated = false; }); + AudioScriptingInterface.noiseGateClosed.connect(function() { micBar.gated = true; }); HMD.displayModeChanged.connect(function() { muted = AudioScriptingInterface.muted; pushToTalk = AudioScriptingInterface.pushToTalk; @@ -151,7 +151,7 @@ Rectangle { readonly property string yellow: "#C0C000"; readonly property string fill: "#55000000"; readonly property string border: standalone ? "#80FFFFFF" : "#55FFFFFF"; - readonly property string icon: (muted || clipping) ? mutedColor : root.gated ? gatedColor : unmutedColor; + readonly property string icon: (muted || clipping) ? mutedColor : micBar.gated ? gatedColor : unmutedColor; } Item { @@ -169,7 +169,7 @@ Rectangle { Image { id: image; source: (pushToTalk) ? pushToTalkIcon : muted ? mutedIcon : - clipping ? clippingIcon : root.gated ? gatedIcon : unmutedIcon; + clipping ? clippingIcon : micBar.gated ? gatedIcon : unmutedIcon; width: 29; height: 32; anchors { diff --git a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml index 3ace6f381f..41ac9232e2 100644 --- a/interface/resources/qml/hifi/commerce/checkout/Checkout.qml +++ b/interface/resources/qml/hifi/commerce/checkout/Checkout.qml @@ -786,7 +786,7 @@ Rectangle { } lightboxPopup.button2text = "CONFIRM"; lightboxPopup.button2method = function() { - Commerce.replaceContentSet(root.itemHref, root.certificateId); + Commerce.replaceContentSet(root.itemHref, root.certificateId, root.itemName); lightboxPopup.visible = false; rezzedNotifContainer.visible = true; rezzedNotifContainerTimer.start(); diff --git a/interface/resources/qml/hifi/commerce/marketplaceItemTester/ItemUnderTest.qml b/interface/resources/qml/hifi/commerce/marketplaceItemTester/ItemUnderTest.qml index 966f359e92..157f3dda6a 100644 --- a/interface/resources/qml/hifi/commerce/marketplaceItemTester/ItemUnderTest.qml +++ b/interface/resources/qml/hifi/commerce/marketplaceItemTester/ItemUnderTest.qml @@ -36,7 +36,7 @@ Rectangle { break; case "content set": urlHandler.handleUrl("hifi://localhost/0,0,0"); - Commerce.replaceContentSet(toUrl(resource), ""); + Commerce.replaceContentSet(toUrl(resource), "", ""); break; case "entity": case "wearable": diff --git a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml index 0e3402a6a9..1dbcd34cce 100644 --- a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml +++ b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml @@ -729,7 +729,7 @@ Item { onClicked: { Tablet.playSound(TabletEnums.ButtonClick); if (root.itemType === "contentSet") { - sendToPurchases({method: 'showReplaceContentLightbox', itemHref: root.itemHref, certID: root.certificateId}); + sendToPurchases({method: 'showReplaceContentLightbox', itemHref: root.itemHref, certID: root.certificateId, itemName: root.itemName}); } else if (root.itemType === "avatar") { sendToPurchases({method: 'showChangeAvatarLightbox', itemName: root.itemName, itemHref: root.itemHref}); } else if (root.itemType === "app") { diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml index 9a2bf62e08..03fbbb178e 100644 --- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml +++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml @@ -609,7 +609,7 @@ Rectangle { } lightboxPopup.button2text = "CONFIRM"; lightboxPopup.button2method = function() { - Commerce.replaceContentSet(msg.itemHref, msg.certID); + Commerce.replaceContentSet(msg.itemHref, msg.certID, msg.itemName); lightboxPopup.visible = false; }; lightboxPopup.visible = true; diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 1872a03221..a7cd45ae37 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1225,8 +1225,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo #endif bool isStore = property(hifi::properties::OCULUS_STORE).toBool(); - - DependencyManager::get()->setLimitedCommerce(isStore); // Or we could make it a separate arg, or if either arg is set, etc. And should this instead by a hifi::properties? + // Or we could make it a separate arg, or if either arg is set, etc. And should this instead by a hifi::properties? + DependencyManager::get()->setLimitedCommerce(isStore || property(hifi::properties::STEAM).toBool()); updateHeartbeat(); @@ -7789,9 +7789,15 @@ bool Application::askToWearAvatarAttachmentUrl(const QString& url) { return true; } -void Application::replaceDomainContent(const QString& url) { +static const QString CONTENT_SET_NAME_QUERY_PARAM = "name"; + +void Application::replaceDomainContent(const QString& url, const QString& itemName) { qCDebug(interfaceapp) << "Attempting to replace domain content"; - QByteArray urlData(url.toUtf8()); + QUrl msgUrl(url); + QUrlQuery urlQuery(msgUrl.query()); + urlQuery.addQueryItem(CONTENT_SET_NAME_QUERY_PARAM, itemName); + msgUrl.setQuery(urlQuery.query(QUrl::QUrl::FullyEncoded)); + QByteArray urlData(msgUrl.toString(QUrl::QUrl::FullyEncoded).toUtf8()); auto limitedNodeList = DependencyManager::get(); const auto& domainHandler = limitedNodeList->getDomainHandler(); @@ -7825,7 +7831,7 @@ bool Application::askToReplaceDomainContent(const QString& url) { QString details; if (static_cast(answer.toInt()) == QMessageBox::Yes) { // Given confirmation, send request to domain server to replace content - replaceDomainContent(url); + replaceDomainContent(url, QString()); details = "SuccessfulRequestToReplaceContent"; } else { details = "UserDeclinedToReplaceContent"; diff --git a/interface/src/Application.h b/interface/src/Application.h index 1e7180e203..913671473d 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -326,7 +326,7 @@ public: bool isInterstitialMode() const { return _interstitialMode; } bool failedToConnectToEntityServer() const { return _failedToConnectToEntityServer; } - void replaceDomainContent(const QString& url); + void replaceDomainContent(const QString& url, const QString& itemName); void loadAvatarScripts(const QVector& urls); void unloadAvatarScripts(); diff --git a/interface/src/LODManager.cpp b/interface/src/LODManager.cpp index 0c9587d3ae..0cf795e35b 100644 --- a/interface/src/LODManager.cpp +++ b/interface/src/LODManager.cpp @@ -54,6 +54,7 @@ void LODManager::setRenderTimes(float presentTime, float engineRunTime, float ba } void LODManager::autoAdjustLOD(float realTimeDelta) { + std::lock_guard { _automaticLODLock }; // The "render time" is the worse of: // - engineRunTime: Time spent in the render thread in the engine producing the gpu::Frame N @@ -235,6 +236,7 @@ void LODManager::resetLODAdjust() { } void LODManager::setAutomaticLODAdjust(bool value) { + std::lock_guard { _automaticLODLock }; _automaticLODAdjust = value; emit autoLODChanged(); } @@ -426,7 +428,6 @@ float LODManager::getWorldDetailQuality() const { return HIGH; } - void LODManager::setLODQualityLevel(float quality) { _lodQualityLevel = quality; } diff --git a/interface/src/LODManager.h b/interface/src/LODManager.h index 77cb1a0d39..649e0e8e34 100644 --- a/interface/src/LODManager.h +++ b/interface/src/LODManager.h @@ -12,6 +12,8 @@ #ifndef hifi_LODManager_h #define hifi_LODManager_h +#include + #include #include #include @@ -47,11 +49,6 @@ class AABox; * @property {number} presentTime Read-only. * @property {number} engineRunTime Read-only. * @property {number} gpuTime Read-only. - * @property {number} avgRenderTime Read-only. - * @property {number} fps Read-only. - * @property {number} lodLevel Read-only. - * @property {number} lodDecreaseFPS Read-only. - * @property {number} lodIncreaseFPS Read-only. */ class LODManager : public QObject, public Dependency { @@ -240,6 +237,7 @@ signals: private: LODManager(); + std::mutex _automaticLODLock; bool _automaticLODAdjust = true; float _presentTime{ 0.0f }; // msec diff --git a/interface/src/commerce/QmlCommerce.cpp b/interface/src/commerce/QmlCommerce.cpp index 046e697b6d..c8ea044f4b 100644 --- a/interface/src/commerce/QmlCommerce.cpp +++ b/interface/src/commerce/QmlCommerce.cpp @@ -259,7 +259,7 @@ void QmlCommerce::authorizeAssetTransfer(const QString& couponID, ledger->authorizeAssetTransfer(key, couponID, certificateID, amount, optionalMessage); } -void QmlCommerce::replaceContentSet(const QString& itemHref, const QString& certificateID) { +void QmlCommerce::replaceContentSet(const QString& itemHref, const QString& certificateID, const QString& itemName) { if (!certificateID.isEmpty()) { auto ledger = DependencyManager::get(); ledger->updateLocation( @@ -267,7 +267,7 @@ void QmlCommerce::replaceContentSet(const QString& itemHref, const QString& cert DependencyManager::get()->getPlaceName(), true); } - qApp->replaceDomainContent(itemHref); + qApp->replaceDomainContent(itemHref, itemName); QJsonObject messageProperties = { { "status", "SuccessfulRequestToReplaceContent" }, { "content_set_url", itemHref } }; diff --git a/interface/src/commerce/QmlCommerce.h b/interface/src/commerce/QmlCommerce.h index 99b3e32e8b..713a5ee94f 100644 --- a/interface/src/commerce/QmlCommerce.h +++ b/interface/src/commerce/QmlCommerce.h @@ -90,7 +90,7 @@ protected: Q_INVOKABLE void transferAssetToUsername(const QString& username, const QString& certificateID, const int& amount, const QString& optionalMessage); Q_INVOKABLE void authorizeAssetTransfer(const QString& couponID, const QString& certificateID, const int& amount, const QString& optionalMessage); - Q_INVOKABLE void replaceContentSet(const QString& itemHref, const QString& certificateID); + Q_INVOKABLE void replaceContentSet(const QString& itemHref, const QString& certificateID, const QString& itemName); Q_INVOKABLE QString getInstalledApps(const QString& justInstalledAppID = ""); Q_INVOKABLE bool installApp(const QString& appHref, const bool& alsoOpenImmediately = false); diff --git a/interface/src/ui/InteractiveWindow.cpp b/interface/src/ui/InteractiveWindow.cpp index 4359995448..c0f3e82b29 100644 --- a/interface/src/ui/InteractiveWindow.cpp +++ b/interface/src/ui/InteractiveWindow.cpp @@ -136,12 +136,16 @@ InteractiveWindow::InteractiveWindow(const QString& sourceUrl, const QVariantMap if (properties.contains(PRESENTATION_MODE_PROPERTY)) { presentationMode = (InteractiveWindowPresentationMode) properties[PRESENTATION_MODE_PROPERTY].toInt(); } + + _interactiveWindowProxy = std::unique_ptr>(new InteractiveWindowProxy, [](InteractiveWindowProxy *p) { + p->deleteLater(); + }); - if (!_interactiveWindowProxy) { - _interactiveWindowProxy = new InteractiveWindowProxy(); - QObject::connect(_interactiveWindowProxy, &InteractiveWindowProxy::webEventReceived, this, &InteractiveWindow::emitWebEvent, Qt::QueuedConnection); - QObject::connect(this, &InteractiveWindow::scriptEventReceived, _interactiveWindowProxy, &InteractiveWindowProxy::emitScriptEvent, Qt::QueuedConnection); - } + connect(_interactiveWindowProxy.get(), &InteractiveWindowProxy::webEventReceived, + this, &InteractiveWindow::emitWebEvent, Qt::QueuedConnection); + connect(this, &InteractiveWindow::scriptEventReceived, _interactiveWindowProxy.get(), + &InteractiveWindowProxy::emitScriptEvent, Qt::QueuedConnection); if (properties.contains(DOCKED_PROPERTY) && presentationMode == InteractiveWindowPresentationMode::Native) { QVariantMap nativeWindowInfo = properties[DOCKED_PROPERTY].toMap(); @@ -161,7 +165,7 @@ InteractiveWindow::InteractiveWindow(const QString& sourceUrl, const QVariantMap _dockWidget = std::shared_ptr(new DockWidget(title, mainWindow), dockWidgetDeleter); auto quickView = _dockWidget->getQuickView(); - Application::setupQmlSurface(quickView->rootContext() , true); + Application::setupQmlSurface(quickView->rootContext(), true); //add any whitelisted callbacks OffscreenUi::applyWhiteList(sourceUrl, quickView->rootContext()); @@ -201,7 +205,7 @@ InteractiveWindow::InteractiveWindow(const QString& sourceUrl, const QVariantMap QObject::connect(quickView.get(), &QQuickView::statusChanged, [&, this] (QQuickView::Status status) { if (status == QQuickView::Ready) { QQuickItem* rootItem = _dockWidget->getRootItem(); - _dockWidget->getQuickView()->rootContext()->setContextProperty(EVENT_BRIDGE_PROPERTY, _interactiveWindowProxy); + _dockWidget->getQuickView()->rootContext()->setContextProperty(EVENT_BRIDGE_PROPERTY, _interactiveWindowProxy.get()); // The qmlToScript method handles the thread-safety of this call. Because the QVariant argument // passed to the sendToScript signal may wrap an externally managed and thread-unsafe QJSValue, // qmlToScript needs to be called directly, so the QJSValue can be immediately converted to a plain QVariant. @@ -222,7 +226,7 @@ InteractiveWindow::InteractiveWindow(const QString& sourceUrl, const QVariantMap // Build the event bridge and wrapper on the main thread offscreenUi->loadInNewContext(CONTENT_WINDOW_QML, [&](QQmlContext* context, QObject* object) { _qmlWindowProxy = std::shared_ptr(new QmlWindowProxy(object, nullptr), qmlWindowProxyDeleter); - context->setContextProperty(EVENT_BRIDGE_PROPERTY, _interactiveWindowProxy); + context->setContextProperty(EVENT_BRIDGE_PROPERTY, _interactiveWindowProxy.get()); if (properties.contains(ADDITIONAL_FLAGS_PROPERTY)) { object->setProperty(ADDITIONAL_FLAGS_PROPERTY, properties[ADDITIONAL_FLAGS_PROPERTY].toUInt()); } @@ -313,10 +317,6 @@ void InteractiveWindow::close() { _qmlWindowProxy->deleteLater(); } - if (_interactiveWindowProxy) { - _interactiveWindowProxy->deleteLater(); - } - if (_dockWidget) { auto window = qApp->getWindow(); if (QThread::currentThread() != window->thread()) { diff --git a/interface/src/ui/InteractiveWindow.h b/interface/src/ui/InteractiveWindow.h index a1eede8b8a..67f0a9ea2e 100644 --- a/interface/src/ui/InteractiveWindow.h +++ b/interface/src/ui/InteractiveWindow.h @@ -41,7 +41,6 @@ class InteractiveWindowProxy : public QObject { Q_OBJECT public: InteractiveWindowProxy(){} - public slots: void emitScriptEvent(const QVariant& scriptMessage); @@ -53,7 +52,6 @@ signals: void webEventReceived(const QVariant& message); }; - namespace InteractiveWindowEnums { Q_NAMESPACE @@ -325,7 +323,7 @@ protected slots: private: std::shared_ptr _qmlWindowProxy; std::shared_ptr _dockWidget { nullptr }; - InteractiveWindowProxy *_interactiveWindowProxy{ nullptr }; + std::unique_ptr> _interactiveWindowProxy; }; typedef InteractiveWindow* InteractiveWindowPointer; diff --git a/interface/src/ui/ResourceImageItem.cpp b/interface/src/ui/ResourceImageItem.cpp index f14f4ca78e..46ffffe62d 100644 --- a/interface/src/ui/ResourceImageItem.cpp +++ b/interface/src/ui/ResourceImageItem.cpp @@ -11,11 +11,52 @@ #include "ResourceImageItem.h" #include - +#include #include +#include #include + +static const char* VERTEX_SHADER = R"SHADER( +#version 450 core + +out vec2 vTexCoord; + +void main(void) { + const float depth = 0.0; + const vec4 UNIT_QUAD[4] = vec4[4]( + vec4(-1.0, -1.0, depth, 1.0), + vec4(1.0, -1.0, depth, 1.0), + vec4(-1.0, 1.0, depth, 1.0), + vec4(1.0, 1.0, depth, 1.0) + ); + vec4 pos = UNIT_QUAD[gl_VertexID]; + + gl_Position = pos; + vTexCoord = (pos.xy + 1.0) * 0.5; +} +)SHADER"; + +static const char* FRAGMENT_SHADER = R"SHADER( +#version 450 core + +uniform sampler2D sampler; + +in vec2 vTexCoord; + +out vec4 FragColor; + +vec3 color_LinearTosRGB(vec3 lrgb) { + return mix(vec3(1.055) * pow(vec3(lrgb), vec3(0.41666)) - vec3(0.055), vec3(lrgb) * vec3(12.92), vec3(lessThan(lrgb, vec3(0.0031308)))); +} + +void main() { + FragColor = vec4(color_LinearTosRGB(texture(sampler, vTexCoord).rgb), 1.0); +} +)SHADER"; + + ResourceImageItem::ResourceImageItem() : QQuickFramebufferObject() { auto textureCache = DependencyManager::get(); connect(textureCache.data(), SIGNAL(spectatorCameraFramebufferReset()), this, SLOT(update())); @@ -95,16 +136,29 @@ void ResourceImageItemRenderer::render() { } if (_ready) { _fboMutex.lock(); - _copyFbo->bind(); - QOpenGLFramebufferObject::blitFramebuffer(framebufferObject(), _copyFbo, GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT, GL_NEAREST); - // this clears the copyFbo texture - // so next frame starts fresh - helps - // when aspect ratio changes - _copyFbo->takeTexture(); + + if (!_shader) { + _shader = new QOpenGLShaderProgram(); + _shader->addCacheableShaderFromSourceCode(QOpenGLShader::Vertex, VERTEX_SHADER); + _shader->addCacheableShaderFromSourceCode(QOpenGLShader::Fragment, FRAGMENT_SHADER); + _shader->link(); + glGenVertexArrays(1, &_vao); + } + framebufferObject()->bind(); + _shader->bind(); + + auto sourceTextureId = _copyFbo->takeTexture(); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, sourceTextureId); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glBindVertexArray(_vao); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + glDeleteTextures(1, &sourceTextureId); + _copyFbo->bind(); _copyFbo->release(); - _fboMutex.unlock(); } glFlush(); diff --git a/interface/src/ui/ResourceImageItem.h b/interface/src/ui/ResourceImageItem.h index d154588094..1a679ba2ee 100644 --- a/interface/src/ui/ResourceImageItem.h +++ b/interface/src/ui/ResourceImageItem.h @@ -22,6 +22,9 @@ #include +class QOpenGLFramebufferObject; +class QOpenGLShaderProgram; + class ResourceImageItemRenderer : public QObject, public QQuickFramebufferObject::Renderer { Q_OBJECT public: @@ -30,14 +33,16 @@ public: void synchronize(QQuickFramebufferObject* item) override; void render() override; private: - bool _ready; + bool _ready{ false }; QString _url; - bool _visible; + bool _visible{ false }; NetworkTexturePointer _networkTexture; - QQuickWindow* _window; + QQuickWindow* _window{ nullptr }; QMutex _fboMutex; + uint32_t _vao{ 0 }; QOpenGLFramebufferObject* _copyFbo { nullptr }; + QOpenGLShaderProgram* _shader{ nullptr }; GLsync _fenceSync { 0 }; QTimer _updateTimer; public slots: diff --git a/launchers/win32/LauncherApp.cpp b/launchers/win32/LauncherApp.cpp index 841a0bee3b..ff63841c5a 100644 --- a/launchers/win32/LauncherApp.cpp +++ b/launchers/win32/LauncherApp.cpp @@ -42,8 +42,7 @@ BOOL CLauncherApp::InitInstance() { bool uninstalling = false; bool restarting = false; bool noUpdate = false; - bool continueUpdating = false; - bool skipSplash = false; + LauncherManager::ContinueActionOnStart continueAction = LauncherManager::ContinueActionOnStart::ContinueNone; if (iNumOfArgs > 1) { for (int i = 1; i < iNumOfArgs; i++) { CString curArg = CString(pArgs[i]); @@ -53,10 +52,10 @@ BOOL CLauncherApp::InitInstance() { restarting = true; } else if (curArg.Compare(_T("--noUpdate")) == 0) { noUpdate = true; - } else if (curArg.Compare(_T("--continueUpdating")) == 0) { - continueUpdating = true; - } else if (curArg.Compare(_T("--skipSplash")) == 0) { - skipSplash = true; + } else if (curArg.Compare(_T("--continueAction")) == 0) { + if (i + 1 < iNumOfArgs) { + continueAction = LauncherManager::getContinueActionFromParam(pArgs[i + 1]); + } } } } @@ -71,7 +70,7 @@ BOOL CLauncherApp::InitInstance() { if (uninstalling) { _manager.uninstall(); } else { - _manager.init(!noUpdate, continueUpdating, skipSplash); + _manager.init(!noUpdate, continueAction); } if (!_manager.hasFailed() && !_manager.installLauncher()) { return FALSE; diff --git a/launchers/win32/LauncherDlg.cpp b/launchers/win32/LauncherDlg.cpp index d7d214842d..928bf7010f 100644 --- a/launchers/win32/LauncherDlg.cpp +++ b/launchers/win32/LauncherDlg.cpp @@ -357,7 +357,7 @@ void CLauncherDlg::drawVoxel(CHwndRenderTarget* pRenderTarget) { } void CLauncherDlg::drawProgress(CHwndRenderTarget* pRenderTarget, float progress, const D2D1::ColorF& color) { - auto size = pRenderTarget->GetPixelSize(); + auto size = pRenderTarget->GetSize(); if (progress == 0.0f) { return; } else { @@ -693,22 +693,26 @@ void CLauncherDlg::OnTimer(UINT_PTR nIDEvent) { } } } + + LauncherManager::ContinueActionOnStart continueAction = theApp._manager.getContinueAction(); if (_showSplash) { if (_splashStep == 0) { if (theApp._manager.needsUninstall()) { theApp._manager.addToLog(_T("Waiting to uninstall")); setDrawDialog(DrawStep::DrawProcessUninstall); - } else if (theApp._manager.shouldContinueUpdating()) { - _splashStep = SPLASH_DURATION; + } else if (continueAction == LauncherManager::ContinueActionOnStart::ContinueUpdate) { setDrawDialog(DrawStep::DrawProcessUpdate); theApp._manager.updateProgress(LauncherManager::ProcessType::Uninstall, 0.0f); + } else if (continueAction == LauncherManager::ContinueActionOnStart::ContinueLogIn) { + _splashStep = SPLASH_DURATION; + } else if (continueAction == LauncherManager::ContinueActionOnStart::ContinueFinish) { + theApp._manager.updateProgress(LauncherManager::ProcessType::Uninstall, 1.0f); + setDrawDialog(DrawStep::DrawProcessFinishUpdate); + _splashStep = SPLASH_DURATION; + _showSplash = false; } else { - if (theApp._manager.shouldSkipSplashScreen()) { - _splashStep = SPLASH_DURATION; - } else { - theApp._manager.addToLog(_T("Start splash screen")); - setDrawDialog(DrawStep::DrawLogo); - } + theApp._manager.addToLog(_T("Start splash screen")); + setDrawDialog(DrawStep::DrawLogo); } } else if (_splashStep > SPLASH_DURATION && !theApp._manager.needsToWait()) { _showSplash = false; diff --git a/launchers/win32/LauncherManager.cpp b/launchers/win32/LauncherManager.cpp index d7d92a18fb..49ae058ef5 100644 --- a/launchers/win32/LauncherManager.cpp +++ b/launchers/win32/LauncherManager.cpp @@ -16,27 +16,55 @@ LauncherManager::LauncherManager() { + int tokenPos = 0; + _launcherVersion = CString(LAUNCHER_BUILD_VERSION).Tokenize(_T("-"), tokenPos); } LauncherManager::~LauncherManager() { } -void LauncherManager::init(BOOL allowUpdate, BOOL continueUpdating, BOOL skipSplashScreen) { +void LauncherManager::init(BOOL allowUpdate, ContinueActionOnStart continueAction) { initLog(); - int tokenPos = 0; _updateLauncherAllowed = allowUpdate; - _continueUpdating = continueUpdating; - _skipSplashScreen = skipSplashScreen; - _shouldWait = !skipSplashScreen; - if (_continueUpdating) { + _continueAction = continueAction; + CString msg; + msg.Format(_T("Start Screen: %s"), getContinueActionParam(continueAction)); + addToLog(msg); + _shouldWait = _continueAction == ContinueActionOnStart::ContinueNone; + if (_continueAction == ContinueActionOnStart::ContinueUpdate) { _progressOffset = CONTINUE_UPDATING_GLOBAL_OFFSET; } - _launcherVersion = CString(LAUNCHER_BUILD_VERSION).Tokenize(_T("-"), tokenPos); addToLog(_T("Launcher is running version: " + _launcherVersion)); addToLog(_T("Getting most recent builds")); getMostRecentBuilds(_latestLauncherURL, _latestLauncherVersion, _latestApplicationURL, _latestVersion); } +CString LauncherManager::getContinueActionParam(LauncherManager::ContinueActionOnStart continueAction) { + switch (continueAction) { + case LauncherManager::ContinueActionOnStart::ContinueNone: + return _T(""); + case LauncherManager::ContinueActionOnStart::ContinueLogIn: + return _T("LogIn"); + case LauncherManager::ContinueActionOnStart::ContinueUpdate: + return _T("Update"); + case LauncherManager::ContinueActionOnStart::ContinueFinish: + return _T("Finish"); + default: + return _T(""); + } +} + +LauncherManager::ContinueActionOnStart LauncherManager::getContinueActionFromParam(const CString& param) { + if (param.Compare(_T("LogIn")) == 0) { + return ContinueActionOnStart::ContinueLogIn; + } else if (param.Compare(_T("Update")) == 0) { + return ContinueActionOnStart::ContinueUpdate; + } else if (param.Compare(_T("Finish")) == 0) { + return ContinueActionOnStart::ContinueFinish; + } else { + return ContinueActionOnStart::ContinueNone; + } +} BOOL LauncherManager::initLog() { CString logPath; auto result = getAndCreatePaths(PathType::Launcher_Directory, logPath); @@ -432,7 +460,8 @@ void LauncherManager::onMostRecentBuildsReceived(const CString& response, Launch addToLog(updatingMsg); _shouldUpdateLauncher = TRUE; _shouldDownloadLauncher = TRUE; - _willContinueUpdating = isInstalled && newInterfaceVersion; + _keepLoggingIn = !isInstalled; + _keepUpdating = isInstalled && newInterfaceVersion; } else { if (_updateLauncherAllowed) { addToLog(_T("Already running most recent build. Launching interface.exe")); @@ -612,11 +641,15 @@ void LauncherManager::onFileDownloaded(ProcessType type) { void LauncherManager::restartNewLauncher() { closeLog(); - if (_willContinueUpdating) { - LauncherUtils::launchApplication(_tempLauncherPath, _T(" --restart --noUpdate --continueUpdating")); - } else { - LauncherUtils::launchApplication(_tempLauncherPath, _T(" --restart --noUpdate --skipSplash")); + ContinueActionOnStart continueAction = ContinueActionOnStart::ContinueFinish; + if (_keepUpdating) { + continueAction = ContinueActionOnStart::ContinueUpdate; + } else if (_keepLoggingIn) { + continueAction = ContinueActionOnStart::ContinueLogIn; } + CStringW params; + params.Format(_T(" --restart --noUpdate --continueAction %s"), getContinueActionParam(continueAction)); + LauncherUtils::launchApplication(_tempLauncherPath, params.GetBuffer()); Sleep(500); } diff --git a/launchers/win32/LauncherManager.h b/launchers/win32/LauncherManager.h index 108327469d..5169edfa75 100644 --- a/launchers/win32/LauncherManager.h +++ b/launchers/win32/LauncherManager.h @@ -11,6 +11,7 @@ #pragma once #include "LauncherUtils.h" +#include "LauncherDlg.h" const CString DIRECTORY_NAME_APP = _T("HQ"); const CString DIRECTORY_NAME_DOWNLOADS = _T("downloads"); @@ -27,8 +28,7 @@ const float DOWNLOAD_APPLICATION_UPDATE_WEIGHT = 0.75f; const float EXTRACT_APPLICATION_UPDATE_WEIGHT = 0.25f; const float CONTINUE_UPDATING_GLOBAL_OFFSET = 0.2f; -class LauncherManager -{ +class LauncherManager { public: enum PathType { Running_Path = 0, @@ -57,22 +57,33 @@ public: UnzipApplication, Uninstall }; + enum ContinueActionOnStart { + ContinueNone = 0, + ContinueLogIn, + ContinueUpdate, + ContinueFinish + }; + LauncherManager(); ~LauncherManager(); - void init(BOOL allowUpdate, BOOL continueUpdating, BOOL skipSplashScreen); + void init(BOOL allowUpdate, ContinueActionOnStart continueAction); + static CString getContinueActionParam(ContinueActionOnStart continueAction); + static ContinueActionOnStart getContinueActionFromParam(const CString& param); BOOL initLog(); BOOL addToLog(const CString& line); void closeLog(); void saveErrorLog(); BOOL getAndCreatePaths(PathType type, CString& outPath); BOOL getInstalledVersion(const CString& path, CString& version); - BOOL isApplicationInstalled(CString& version, CString& domain, + BOOL isApplicationInstalled(CString& version, CString& domain, CString& content, bool& loggedIn); LauncherUtils::ResponseError getAccessTokenForCredentials(const CString& username, const CString& password); - void getMostRecentBuilds(CString& launcherUrlOut, CString& launcherVersionOut, - CString& interfaceUrlOut, CString& interfaceVersionOut); + void getMostRecentBuilds(CString& launcherUrlOut, + CString& launcherVersionOut, + CString& interfaceUrlOut, + CString& interfaceVersionOut); LauncherUtils::ResponseError readOrganizationJSON(const CString& hash); - LauncherUtils::ResponseError readConfigJSON(CString& version, CString& domain, + LauncherUtils::ResponseError readConfigJSON(CString& version, CString& domain, CString& content, bool& loggedIn); BOOL createConfigJSON(); BOOL createApplicationRegistryKeys(int size); @@ -90,7 +101,6 @@ public: const CString& getVersion() const { return _version; } BOOL shouldShutDown() const { return _shouldShutdown; } BOOL shouldLaunch() const { return _shouldLaunch; } - BOOL shouldSkipSplashScreen() const { return _skipSplashScreen; } BOOL needsUpdate() const { return _shouldUpdate; } BOOL needsSelfUpdate() const { return _shouldUpdateLauncher; } BOOL needsSelfDownload() const { return _shouldDownloadLauncher; } @@ -98,14 +108,17 @@ public: BOOL needsInstall() const { return _shouldInstall; } BOOL needsToWait() const { return _shouldWait; } BOOL needsRestartNewLauncher() const { return _shouldRestartNewLauncher; } - BOOL shouldContinueUpdating() const { return _continueUpdating; } - BOOL willContinueUpdating() const { return _willContinueUpdating; } + BOOL willContinueUpdating() const { return _keepUpdating; } + ContinueActionOnStart getContinueAction() { return _continueAction; } void setDisplayName(const CString& displayName) { _displayName = displayName; } bool isLoggedIn() const { return _loggedIn; } bool hasFailed() const { return _hasFailed; } void setFailed(bool hasFailed) { _hasFailed = hasFailed; } const CString& getLatestInterfaceURL() const { return _latestApplicationURL; } - void uninstall() { _shouldUninstall = true; _shouldWait = false; }; + void uninstall() { + _shouldUninstall = true; + _shouldWait = false; + }; BOOL downloadFile(ProcessType type, const CString& url, CString& localPath); BOOL downloadContent(); @@ -149,11 +162,10 @@ private: BOOL _shouldDownloadLauncher { FALSE }; BOOL _updateLauncherAllowed { TRUE }; BOOL _shouldRestartNewLauncher { FALSE }; - BOOL _continueUpdating { FALSE }; - BOOL _willContinueUpdating { FALSE }; - BOOL _skipSplashScreen { FALSE }; + BOOL _keepLoggingIn { FALSE }; + BOOL _keepUpdating { FALSE }; + ContinueActionOnStart _continueAction; float _progressOffset { 0.0f }; float _progress { 0.0f }; CStdioFile _logFile; }; - diff --git a/libraries/embedded-webserver/src/HTTPConnection.cpp b/libraries/embedded-webserver/src/HTTPConnection.cpp index f65cd87f6e..4c00ba676c 100644 --- a/libraries/embedded-webserver/src/HTTPConnection.cpp +++ b/libraries/embedded-webserver/src/HTTPConnection.cpp @@ -22,6 +22,7 @@ #include "HTTPManager.h" const char* HTTPConnection::StatusCode200 = "200 OK"; +const char* HTTPConnection::StatusCode204 = "204 No Content"; const char* HTTPConnection::StatusCode301 = "301 Moved Permanently"; const char* HTTPConnection::StatusCode302 = "302 Found"; const char* HTTPConnection::StatusCode400 = "400 Bad Request"; diff --git a/libraries/embedded-webserver/src/HTTPConnection.h b/libraries/embedded-webserver/src/HTTPConnection.h index 4b42acf296..eb4d2dd8c3 100644 --- a/libraries/embedded-webserver/src/HTTPConnection.h +++ b/libraries/embedded-webserver/src/HTTPConnection.h @@ -45,6 +45,7 @@ class HTTPConnection : public QObject { public: static const char* StatusCode200; + static const char* StatusCode204; static const char* StatusCode301; static const char* StatusCode302; static const char* StatusCode400; diff --git a/libraries/script-engine/src/ScriptAudioInjector.cpp b/libraries/script-engine/src/ScriptAudioInjector.cpp index ebc9287c2a..0e42ec31e7 100644 --- a/libraries/script-engine/src/ScriptAudioInjector.cpp +++ b/libraries/script-engine/src/ScriptAudioInjector.cpp @@ -30,6 +30,7 @@ ScriptAudioInjector::ScriptAudioInjector(const AudioInjectorPointer& injector) : _injector(injector) { QObject::connect(injector.data(), &AudioInjector::finished, this, &ScriptAudioInjector::finished); + connect(injector.data(), &QObject::destroyed, this, &QObject::deleteLater); } ScriptAudioInjector::~ScriptAudioInjector() { diff --git a/libraries/script-engine/src/ScriptAudioInjector.h b/libraries/script-engine/src/ScriptAudioInjector.h index 3023623d72..5f086019de 100644 --- a/libraries/script-engine/src/ScriptAudioInjector.h +++ b/libraries/script-engine/src/ScriptAudioInjector.h @@ -137,7 +137,7 @@ signals: void finished(); private: - AudioInjectorPointer _injector; + QWeakPointer _injector; friend QScriptValue injectorToScriptValue(QScriptEngine* engine, ScriptAudioInjector* const& in); }; diff --git a/scripts/simplifiedUI/ui/simplifiedUI.js b/scripts/simplifiedUI/ui/simplifiedUI.js index 50c626b594..f4f2627c66 100644 --- a/scripts/simplifiedUI/ui/simplifiedUI.js +++ b/scripts/simplifiedUI/ui/simplifiedUI.js @@ -531,21 +531,21 @@ function maybeUpdateOutputDeviceMutedOverlay() { var oldAutomaticLODAdjust; -var oldLODLevel; -var DEFAULT_AUTO_LOD_ADJUST = false; -var DEFAULT_LOD_LEVEL = 0.5; +var oldLODAngleDeg; +var SIMPLIFIED_UI_AUTO_LOD_ADJUST = false; +var SIMPLIFIED_UI_LOD_ANGLE_DEG = 0.5; function modifyLODSettings() { oldAutomaticLODAdjust = LODManager.automaticLODAdjust; - oldLODLevel = LODManager.lodQualityLevel; + oldLODAngleDeg = LODManager.lodAngleDeg; - LODManager.automaticLODAdjust = DEFAULT_AUTO_LOD_ADJUST; - LODManager.lodQualityLevel = DEFAULT_LOD_LEVEL; + LODManager.automaticLODAdjust = SIMPLIFIED_UI_AUTO_LOD_ADJUST; + LODManager.lodAngleDeg = SIMPLIFIED_UI_LOD_ANGLE_DEG; } function restoreLODSettings() { LODManager.automaticLODAdjust = oldAutomaticLODAdjust; - LODManager.lodQualityLevel = oldLODLevel; + LODManager.lodAngleDeg = oldLODAngleDeg; }