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 += "
";
+ $('#' + 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;
}