mirror of
https://github.com/HifiExperiments/overte.git
synced 2025-07-23 10:54:26 +02:00
Merge pull request #10312 from birarda/upload-new-models-to-entity
add an entity file replacement via domain-server content page
This commit is contained in:
commit
c518aa55be
15 changed files with 262 additions and 47 deletions
|
@ -11,12 +11,14 @@
|
||||||
|
|
||||||
#include "OctreeServer.h"
|
#include "OctreeServer.h"
|
||||||
|
|
||||||
|
#include <QJsonDocument>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
|
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
|
|
||||||
#include <AccountManager.h>
|
#include <AccountManager.h>
|
||||||
|
#include <Gzip.h>
|
||||||
#include <HTTPConnection.h>
|
#include <HTTPConnection.h>
|
||||||
#include <LogHandler.h>
|
#include <LogHandler.h>
|
||||||
#include <shared/NetworkUtils.h>
|
#include <shared/NetworkUtils.h>
|
||||||
|
@ -924,6 +926,57 @@ void OctreeServer::handleJurisdictionRequestPacket(QSharedPointer<ReceivedMessag
|
||||||
_jurisdictionSender->queueReceivedPacket(message, senderNode);
|
_jurisdictionSender->queueReceivedPacket(message, senderNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void OctreeServer::handleOctreeFileReplacement(QSharedPointer<ReceivedMessage> message) {
|
||||||
|
if (!_isFinished && !_isShuttingDown) {
|
||||||
|
// these messages are only allowed to come from the domain server, so make sure that is the case
|
||||||
|
auto nodeList = DependencyManager::get<NodeList>();
|
||||||
|
if (message->getSenderSockAddr() == nodeList->getDomainHandler().getSockAddr()) {
|
||||||
|
// it's far cleaner to load up the new content upon server startup
|
||||||
|
// so here we just store a special file at our persist path
|
||||||
|
// and then force a stop of the server so that it can pick it up when it relaunches
|
||||||
|
if (!_persistAbsoluteFilePath.isEmpty()) {
|
||||||
|
|
||||||
|
// before we restart the server and make it try and load this data, let's make sure it is valid
|
||||||
|
auto compressedOctree = message->getMessage();
|
||||||
|
QByteArray jsonOctree;
|
||||||
|
|
||||||
|
// assume we have GZipped content
|
||||||
|
bool wasCompressed = gunzip(compressedOctree, jsonOctree);
|
||||||
|
if (!wasCompressed) {
|
||||||
|
// the source was not compressed, assume we were sent regular JSON data
|
||||||
|
jsonOctree = compressedOctree;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check the JSON data to verify it is an object
|
||||||
|
if (QJsonDocument::fromJson(jsonOctree).isObject()) {
|
||||||
|
if (!wasCompressed) {
|
||||||
|
// source was not compressed, we compress it before we write it locally
|
||||||
|
gzip(jsonOctree, compressedOctree);
|
||||||
|
}
|
||||||
|
|
||||||
|
// write the compressed octree data to a special file
|
||||||
|
auto replacementFilePath = _persistAbsoluteFilePath.append(OctreePersistThread::REPLACEMENT_FILE_EXTENSION);
|
||||||
|
QFile replacementFile(replacementFilePath);
|
||||||
|
if (replacementFile.open(QIODevice::WriteOnly) && replacementFile.write(compressedOctree) != -1) {
|
||||||
|
// we've now written our replacement file, time to take the server down so it can
|
||||||
|
// process it when it comes back up
|
||||||
|
qInfo() << "Wrote octree replacement file to" << replacementFilePath << "- stopping server";
|
||||||
|
setFinished(true);
|
||||||
|
} else {
|
||||||
|
qWarning() << "Could not write replacement octree data to file - refusing to process";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
qDebug() << "Received replacement octree file that is invalid - refusing to process";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
qDebug() << "Cannot perform octree file replacement since current persist file path is not yet known";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
qDebug() << "Received an octree file replacement that was not from our domain server - refusing to process";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool OctreeServer::readOptionBool(const QString& optionName, const QJsonObject& settingsSectionObject, bool& result) {
|
bool OctreeServer::readOptionBool(const QString& optionName, const QJsonObject& settingsSectionObject, bool& result) {
|
||||||
result = false; // assume it doesn't exist
|
result = false; // assume it doesn't exist
|
||||||
bool optionAvailable = false;
|
bool optionAvailable = false;
|
||||||
|
@ -1148,6 +1201,7 @@ void OctreeServer::domainSettingsRequestComplete() {
|
||||||
packetReceiver.registerListener(getMyQueryMessageType(), this, "handleOctreeQueryPacket");
|
packetReceiver.registerListener(getMyQueryMessageType(), this, "handleOctreeQueryPacket");
|
||||||
packetReceiver.registerListener(PacketType::OctreeDataNack, this, "handleOctreeDataNackPacket");
|
packetReceiver.registerListener(PacketType::OctreeDataNack, this, "handleOctreeDataNackPacket");
|
||||||
packetReceiver.registerListener(PacketType::JurisdictionRequest, this, "handleJurisdictionRequestPacket");
|
packetReceiver.registerListener(PacketType::JurisdictionRequest, this, "handleJurisdictionRequestPacket");
|
||||||
|
packetReceiver.registerListener(PacketType::OctreeFileReplacement, this, "handleOctreeFileReplacement");
|
||||||
|
|
||||||
readConfiguration();
|
readConfiguration();
|
||||||
|
|
||||||
|
@ -1173,25 +1227,25 @@ void OctreeServer::domainSettingsRequestComplete() {
|
||||||
// If persist filename does not exist, let's see if there is one beside the application binary
|
// If persist filename does not exist, let's see if there is one beside the application binary
|
||||||
// If there is, let's copy it over to our target persist directory
|
// If there is, let's copy it over to our target persist directory
|
||||||
QDir persistPath { _persistFilePath };
|
QDir persistPath { _persistFilePath };
|
||||||
QString persistAbsoluteFilePath = persistPath.absolutePath();
|
_persistAbsoluteFilePath = persistPath.absolutePath();
|
||||||
|
|
||||||
if (persistPath.isRelative()) {
|
if (persistPath.isRelative()) {
|
||||||
// if the domain settings passed us a relative path, make an absolute path that is relative to the
|
// if the domain settings passed us a relative path, make an absolute path that is relative to the
|
||||||
// default data directory
|
// default data directory
|
||||||
persistAbsoluteFilePath = QDir(PathUtils::getAppDataFilePath("entities/")).absoluteFilePath(_persistFilePath);
|
_persistAbsoluteFilePath = QDir(PathUtils::getAppDataFilePath("entities/")).absoluteFilePath(_persistFilePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
static const QString ENTITY_PERSIST_EXTENSION = ".json.gz";
|
static const QString ENTITY_PERSIST_EXTENSION = ".json.gz";
|
||||||
|
|
||||||
// force the persist file to end with .json.gz
|
// force the persist file to end with .json.gz
|
||||||
if (!persistAbsoluteFilePath.endsWith(ENTITY_PERSIST_EXTENSION, Qt::CaseInsensitive)) {
|
if (!_persistAbsoluteFilePath.endsWith(ENTITY_PERSIST_EXTENSION, Qt::CaseInsensitive)) {
|
||||||
persistAbsoluteFilePath += ENTITY_PERSIST_EXTENSION;
|
_persistAbsoluteFilePath += ENTITY_PERSIST_EXTENSION;
|
||||||
} else {
|
} else {
|
||||||
// make sure the casing of .json.gz is correct
|
// make sure the casing of .json.gz is correct
|
||||||
persistAbsoluteFilePath.replace(ENTITY_PERSIST_EXTENSION, ENTITY_PERSIST_EXTENSION, Qt::CaseInsensitive);
|
_persistAbsoluteFilePath.replace(ENTITY_PERSIST_EXTENSION, ENTITY_PERSIST_EXTENSION, Qt::CaseInsensitive);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!QFile::exists(persistAbsoluteFilePath)) {
|
if (!QFile::exists(_persistAbsoluteFilePath)) {
|
||||||
qDebug() << "Persist file does not exist, checking for existence of persist file next to application";
|
qDebug() << "Persist file does not exist, checking for existence of persist file next to application";
|
||||||
|
|
||||||
static const QString OLD_DEFAULT_PERSIST_FILENAME = "resources/models.json.gz";
|
static const QString OLD_DEFAULT_PERSIST_FILENAME = "resources/models.json.gz";
|
||||||
|
@ -1217,7 +1271,7 @@ void OctreeServer::domainSettingsRequestComplete() {
|
||||||
pathToCopyFrom = oldDefaultPersistPath;
|
pathToCopyFrom = oldDefaultPersistPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
QDir persistFileDirectory { QDir::cleanPath(persistAbsoluteFilePath + "/..") };
|
QDir persistFileDirectory { QDir::cleanPath(_persistAbsoluteFilePath + "/..") };
|
||||||
|
|
||||||
if (!persistFileDirectory.exists()) {
|
if (!persistFileDirectory.exists()) {
|
||||||
qDebug() << "Creating data directory " << persistFileDirectory.absolutePath();
|
qDebug() << "Creating data directory " << persistFileDirectory.absolutePath();
|
||||||
|
@ -1225,15 +1279,15 @@ void OctreeServer::domainSettingsRequestComplete() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shouldCopy) {
|
if (shouldCopy) {
|
||||||
qDebug() << "Old persist file found, copying from " << pathToCopyFrom << " to " << persistAbsoluteFilePath;
|
qDebug() << "Old persist file found, copying from " << pathToCopyFrom << " to " << _persistAbsoluteFilePath;
|
||||||
|
|
||||||
QFile::copy(pathToCopyFrom, persistAbsoluteFilePath);
|
QFile::copy(pathToCopyFrom, _persistAbsoluteFilePath);
|
||||||
} else {
|
} else {
|
||||||
qDebug() << "No existing persist file found";
|
qDebug() << "No existing persist file found";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto persistFileDirectory = QFileInfo(persistAbsoluteFilePath).absolutePath();
|
auto persistFileDirectory = QFileInfo(_persistAbsoluteFilePath).absolutePath();
|
||||||
if (_backupDirectoryPath.isEmpty()) {
|
if (_backupDirectoryPath.isEmpty()) {
|
||||||
// Use the persist file's directory to store backups
|
// Use the persist file's directory to store backups
|
||||||
_backupDirectoryPath = persistFileDirectory;
|
_backupDirectoryPath = persistFileDirectory;
|
||||||
|
@ -1264,7 +1318,7 @@ void OctreeServer::domainSettingsRequestComplete() {
|
||||||
qDebug() << "Backups will be stored in: " << _backupDirectoryPath;
|
qDebug() << "Backups will be stored in: " << _backupDirectoryPath;
|
||||||
|
|
||||||
// now set up PersistThread
|
// now set up PersistThread
|
||||||
_persistThread = new OctreePersistThread(_tree, persistAbsoluteFilePath, _backupDirectoryPath, _persistInterval,
|
_persistThread = new OctreePersistThread(_tree, _persistAbsoluteFilePath, _backupDirectoryPath, _persistInterval,
|
||||||
_wantBackup, _settings, _debugTimestampNow, _persistAsFileType);
|
_wantBackup, _settings, _debugTimestampNow, _persistAsFileType);
|
||||||
_persistThread->initialize(true);
|
_persistThread->initialize(true);
|
||||||
}
|
}
|
||||||
|
|
|
@ -136,6 +136,7 @@ private slots:
|
||||||
void handleOctreeQueryPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
|
void handleOctreeQueryPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
|
||||||
void handleOctreeDataNackPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
|
void handleOctreeDataNackPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
|
||||||
void handleJurisdictionRequestPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
|
void handleJurisdictionRequestPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
|
||||||
|
void handleOctreeFileReplacement(QSharedPointer<ReceivedMessage> message);
|
||||||
void removeSendThread();
|
void removeSendThread();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
@ -172,6 +173,7 @@ protected:
|
||||||
QString _statusHost;
|
QString _statusHost;
|
||||||
|
|
||||||
QString _persistFilePath;
|
QString _persistFilePath;
|
||||||
|
QString _persistAbsoluteFilePath;
|
||||||
QString _persistAsFileType;
|
QString _persistAsFileType;
|
||||||
QString _backupDirectoryPath;
|
QString _backupDirectoryPath;
|
||||||
int _packetsPerClientPerInterval;
|
int _packetsPerClientPerInterval;
|
||||||
|
|
44
domain-server/resources/web/content/index.shtml
Normal file
44
domain-server/resources/web/content/index.shtml
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
<!--#include virtual="header.html"-->
|
||||||
|
|
||||||
|
<div class="col-md-10 col-md-offset-1">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-12">
|
||||||
|
<div class="alert" style="display:none;"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-12">
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-heading">
|
||||||
|
<h3 class="panel-title">Upload Entities File</h3>
|
||||||
|
</div>
|
||||||
|
<form id="upload-form" action="upload" enctype="multipart/form-data" method="post">
|
||||||
|
<div class="panel-body">
|
||||||
|
<p>
|
||||||
|
Upload an entities file (e.g.: models.json.gz) to replace the content of this domain.<br>
|
||||||
|
Note: <strong>Your domain's content will be replaced by the content you upload</strong>, but the backup files of your domain's content will not immediately be changed.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
If your domain has any content that you would like to re-use at a later date, save a manual backup of your models.json.gz file, which is usually stored at the following paths:<br>
|
||||||
|
<pre>C:\Users\[username]\AppData\Roaming\High Fidelity\assignment-client/entities/models.json.gz</pre>
|
||||||
|
<pre>/Users/[username]/Library/Application Support/High Fidelity/assignment-client/entities/models.json.gz</pre>
|
||||||
|
<pre>/home/[username]/.local/share/High Fidelity/assignment-client/entities/models.json.gz</pre>
|
||||||
|
</p>
|
||||||
|
<br>
|
||||||
|
<input type="file" name="entities-file" class="form-control-file" accept=".json, .gz">
|
||||||
|
<br>
|
||||||
|
</div>
|
||||||
|
<div class="panel-footer">
|
||||||
|
<input type="submit" class="btn btn-info" value="Upload">
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!--#include virtual="footer.html"-->
|
||||||
|
<script src='js/content.js'></script>
|
||||||
|
<script src='/js/sweetalert.min.js'></script>
|
||||||
|
<!--#include virtual="page-end.html"-->
|
45
domain-server/resources/web/content/js/content.js
Normal file
45
domain-server/resources/web/content/js/content.js
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
$(document).ready(function(){
|
||||||
|
|
||||||
|
function showSpinnerAlert(title) {
|
||||||
|
swal({
|
||||||
|
title: title,
|
||||||
|
text: '<div class="spinner" style="color:black;"><div class="bounce1"></div><div class="bounce2"></div><div class="bounce3"></div></div>',
|
||||||
|
html: true,
|
||||||
|
showConfirmButton: false,
|
||||||
|
allowEscapeKey: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var frm = $('#upload-form');
|
||||||
|
frm.submit(function (ev) {
|
||||||
|
$.ajax({
|
||||||
|
type: frm.attr('method'),
|
||||||
|
url: frm.attr('action'),
|
||||||
|
data: new FormData($(this)[0]),
|
||||||
|
cache: false,
|
||||||
|
contentType: false,
|
||||||
|
processData: false,
|
||||||
|
success: function (data) {
|
||||||
|
swal({
|
||||||
|
title: 'Uploaded',
|
||||||
|
type: 'success',
|
||||||
|
text: 'Your Entity Server is restarting to replace its local content with the uploaded file.',
|
||||||
|
confirmButtonText: 'OK'
|
||||||
|
})
|
||||||
|
},
|
||||||
|
error: function (data) {
|
||||||
|
swal({
|
||||||
|
title: '',
|
||||||
|
type: 'error',
|
||||||
|
text: 'Your entities file could not be transferred to the Entity Server.</br>Verify that the file is a <i>.json</i> or <i>.json.gz</i> entities file and try again.',
|
||||||
|
html: true,
|
||||||
|
confirmButtonText: 'OK',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ev.preventDefault();
|
||||||
|
|
||||||
|
showSpinnerAlert("Uploading Entities File");
|
||||||
|
});
|
||||||
|
});
|
|
@ -36,6 +36,7 @@
|
||||||
<li><a href="/assignment">New Assignment</a></li>
|
<li><a href="/assignment">New Assignment</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
<li><a href="/content/">Content</a></li>
|
||||||
<li><a href="/settings/">Settings</a></li>
|
<li><a href="/settings/">Settings</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -99,7 +99,7 @@
|
||||||
<script src='/js/underscore-keypath.min.js'></script>
|
<script src='/js/underscore-keypath.min.js'></script>
|
||||||
<script src='/js/bootbox.min.js'></script>
|
<script src='/js/bootbox.min.js'></script>
|
||||||
<script src='js/bootstrap-switch.min.js'></script>
|
<script src='js/bootstrap-switch.min.js'></script>
|
||||||
<script src='js/sweetalert.min.js'></script>
|
<script src='/js/sweetalert.min.js'></script>
|
||||||
<script src='js/settings.js'></script>
|
<script src='js/settings.js'></script>
|
||||||
<script src='js/form2js.min.js'></script>
|
<script src='js/form2js.min.js'></script>
|
||||||
<script src='js/sha256.js'></script>
|
<script src='js/sha256.js'></script>
|
||||||
|
|
|
@ -1633,6 +1633,15 @@ QString pathForAssignmentScript(const QUuid& assignmentUUID) {
|
||||||
return directory.absoluteFilePath(uuidStringWithoutCurlyBraces(assignmentUUID));
|
return directory.absoluteFilePath(uuidStringWithoutCurlyBraces(assignmentUUID));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString DomainServer::pathForRedirect(QString path) const {
|
||||||
|
// make sure the passed path has a leading slash
|
||||||
|
if (!path.startsWith('/')) {
|
||||||
|
path.insert(0, '/');
|
||||||
|
}
|
||||||
|
|
||||||
|
return "http://" + _hostname + ":" + QString::number(_httpManager.serverPort()) + path;
|
||||||
|
}
|
||||||
|
|
||||||
const QString URI_OAUTH = "/oauth";
|
const QString URI_OAUTH = "/oauth";
|
||||||
bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler) {
|
bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler) {
|
||||||
const QString JSON_MIME_TYPE = "application/json";
|
const QString JSON_MIME_TYPE = "application/json";
|
||||||
|
@ -1640,6 +1649,7 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
|
||||||
const QString URI_ASSIGNMENT = "/assignment";
|
const QString URI_ASSIGNMENT = "/assignment";
|
||||||
const QString URI_NODES = "/nodes";
|
const QString URI_NODES = "/nodes";
|
||||||
const QString URI_SETTINGS = "/settings";
|
const QString URI_SETTINGS = "/settings";
|
||||||
|
const QString URI_ENTITY_FILE_UPLOAD = "/content/upload";
|
||||||
|
|
||||||
const QString UUID_REGEX_STRING = "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}";
|
const QString UUID_REGEX_STRING = "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}";
|
||||||
|
|
||||||
|
@ -1869,6 +1879,25 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
|
||||||
// respond with a 200 code for successful upload
|
// respond with a 200 code for successful upload
|
||||||
connection->respond(HTTPConnection::StatusCode200);
|
connection->respond(HTTPConnection::StatusCode200);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} else if (url.path() == URI_ENTITY_FILE_UPLOAD) {
|
||||||
|
// this is an entity file upload, ask the HTTPConnection to parse the data
|
||||||
|
QList<FormData> formData = connection->parseFormData();
|
||||||
|
|
||||||
|
Headers redirectHeaders;
|
||||||
|
|
||||||
|
if (formData.size() > 0 && formData[0].second.size() > 0) {
|
||||||
|
// invoke our method to hand the new octree file off to the octree server
|
||||||
|
QMetaObject::invokeMethod(this, "handleOctreeFileReplacement",
|
||||||
|
Qt::QueuedConnection, Q_ARG(QByteArray, formData[0].second));
|
||||||
|
|
||||||
|
// respond with a 200 for success
|
||||||
|
connection->respond(HTTPConnection::StatusCode200);
|
||||||
|
} else {
|
||||||
|
// respond with a 400 for failure
|
||||||
|
connection->respond(HTTPConnection::StatusCode400);
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} else if (connection->requestOperation() == QNetworkAccessManager::DeleteOperation) {
|
} else if (connection->requestOperation() == QNetworkAccessManager::DeleteOperation) {
|
||||||
|
@ -2159,8 +2188,7 @@ Headers DomainServer::setupCookieHeadersFromProfileReply(QNetworkReply* profileR
|
||||||
cookieHeaders.insert("Set-Cookie", cookieString.toUtf8());
|
cookieHeaders.insert("Set-Cookie", cookieString.toUtf8());
|
||||||
|
|
||||||
// redirect the user back to the homepage so they can present their cookie and be authenticated
|
// redirect the user back to the homepage so they can present their cookie and be authenticated
|
||||||
QString redirectString = "http://" + _hostname + ":" + QString::number(_httpManager.serverPort());
|
cookieHeaders.insert("Location", pathForRedirect().toUtf8());
|
||||||
cookieHeaders.insert("Location", redirectString.toUtf8());
|
|
||||||
|
|
||||||
return cookieHeaders;
|
return cookieHeaders;
|
||||||
}
|
}
|
||||||
|
@ -2560,3 +2588,20 @@ void DomainServer::setupGroupCacheRefresh() {
|
||||||
_metaverseGroupCacheTimer->start(REFRESH_GROUPS_INTERVAL_MSECS);
|
_metaverseGroupCacheTimer->start(REFRESH_GROUPS_INTERVAL_MSECS);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DomainServer::handleOctreeFileReplacement(QByteArray octreeFile) {
|
||||||
|
// enumerate the nodes and find any octree type servers with active sockets
|
||||||
|
|
||||||
|
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
|
||||||
|
limitedNodeList->eachMatchingNode([](const SharedNodePointer& node) {
|
||||||
|
return node->getType() == NodeType::EntityServer && node->getActiveSocket();
|
||||||
|
}, [&octreeFile, limitedNodeList](const SharedNodePointer& octreeNode) {
|
||||||
|
// setup a packet to send to this octree server with the new octree file data
|
||||||
|
auto octreeFilePacketList = NLPacketList::create(PacketType::OctreeFileReplacement, QByteArray(), true, true);
|
||||||
|
octreeFilePacketList->write(octreeFile);
|
||||||
|
|
||||||
|
qDebug() << "Sending an octree file replacement of" << octreeFile.size() << "bytes to" << octreeNode;
|
||||||
|
|
||||||
|
limitedNodeList->sendPacketList(std::move(octreeFilePacketList), *octreeNode);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
@ -100,6 +100,8 @@ private slots:
|
||||||
void handleSuccessfulICEServerAddressUpdate(QNetworkReply& requestReply);
|
void handleSuccessfulICEServerAddressUpdate(QNetworkReply& requestReply);
|
||||||
void handleFailedICEServerAddressUpdate(QNetworkReply& requestReply);
|
void handleFailedICEServerAddressUpdate(QNetworkReply& requestReply);
|
||||||
|
|
||||||
|
void handleOctreeFileReplacement(QByteArray octreeFile);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void iceServerChanged();
|
void iceServerChanged();
|
||||||
void userConnected();
|
void userConnected();
|
||||||
|
@ -161,6 +163,8 @@ private:
|
||||||
|
|
||||||
void setupGroupCacheRefresh();
|
void setupGroupCacheRefresh();
|
||||||
|
|
||||||
|
QString pathForRedirect(QString path = QString()) const;
|
||||||
|
|
||||||
SubnetList _acSubnetWhitelist;
|
SubnetList _acSubnetWhitelist;
|
||||||
|
|
||||||
DomainGatekeeper _gatekeeper;
|
DomainGatekeeper _gatekeeper;
|
||||||
|
|
|
@ -798,7 +798,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
||||||
connect(&domainHandler, SIGNAL(resetting()), SLOT(resettingDomain()));
|
connect(&domainHandler, SIGNAL(resetting()), SLOT(resettingDomain()));
|
||||||
connect(&domainHandler, SIGNAL(connectedToDomain(const QString&)), SLOT(updateWindowTitle()));
|
connect(&domainHandler, SIGNAL(connectedToDomain(const QString&)), SLOT(updateWindowTitle()));
|
||||||
connect(&domainHandler, SIGNAL(disconnectedFromDomain()), SLOT(updateWindowTitle()));
|
connect(&domainHandler, SIGNAL(disconnectedFromDomain()), SLOT(updateWindowTitle()));
|
||||||
connect(&domainHandler, SIGNAL(disconnectedFromDomain()), SLOT(clearDomainOctreeDetails()));
|
connect(&domainHandler, &DomainHandler::disconnectedFromDomain, this, &Application::clearDomainAvatars);
|
||||||
connect(&domainHandler, &DomainHandler::disconnectedFromDomain, this, [this]() {
|
connect(&domainHandler, &DomainHandler::disconnectedFromDomain, this, [this]() {
|
||||||
getOverlays().deleteOverlay(getTabletScreenID());
|
getOverlays().deleteOverlay(getTabletScreenID());
|
||||||
getOverlays().deleteOverlay(getTabletHomeButtonID());
|
getOverlays().deleteOverlay(getTabletHomeButtonID());
|
||||||
|
@ -5179,7 +5179,6 @@ void Application::clearDomainOctreeDetails() {
|
||||||
qCDebug(interfaceapp) << "Clearing domain octree details...";
|
qCDebug(interfaceapp) << "Clearing domain octree details...";
|
||||||
|
|
||||||
resetPhysicsReadyInformation();
|
resetPhysicsReadyInformation();
|
||||||
getMyAvatar()->setAvatarEntityDataChanged(true); // to recreate worn entities
|
|
||||||
|
|
||||||
// reset our node to stats and node to jurisdiction maps... since these must be changing...
|
// reset our node to stats and node to jurisdiction maps... since these must be changing...
|
||||||
_entityServerJurisdictions.withWriteLock([&] {
|
_entityServerJurisdictions.withWriteLock([&] {
|
||||||
|
@ -5198,14 +5197,18 @@ void Application::clearDomainOctreeDetails() {
|
||||||
skyStage->setBackgroundMode(model::SunSkyStage::SKY_DEFAULT);
|
skyStage->setBackgroundMode(model::SunSkyStage::SKY_DEFAULT);
|
||||||
|
|
||||||
_recentlyClearedDomain = true;
|
_recentlyClearedDomain = true;
|
||||||
|
|
||||||
DependencyManager::get<AvatarManager>()->clearOtherAvatars();
|
|
||||||
DependencyManager::get<AnimationCache>()->clearUnusedResources();
|
DependencyManager::get<AnimationCache>()->clearUnusedResources();
|
||||||
DependencyManager::get<ModelCache>()->clearUnusedResources();
|
DependencyManager::get<ModelCache>()->clearUnusedResources();
|
||||||
DependencyManager::get<SoundCache>()->clearUnusedResources();
|
DependencyManager::get<SoundCache>()->clearUnusedResources();
|
||||||
DependencyManager::get<TextureCache>()->clearUnusedResources();
|
DependencyManager::get<TextureCache>()->clearUnusedResources();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Application::clearDomainAvatars() {
|
||||||
|
getMyAvatar()->setAvatarEntityDataChanged(true); // to recreate worn entities
|
||||||
|
DependencyManager::get<AvatarManager>()->clearOtherAvatars();
|
||||||
|
}
|
||||||
|
|
||||||
void Application::domainChanged(const QString& domainHostname) {
|
void Application::domainChanged(const QString& domainHostname) {
|
||||||
updateWindowTitle();
|
updateWindowTitle();
|
||||||
// disable physics until we have enough information about our new location to not cause craziness.
|
// disable physics until we have enough information about our new location to not cause craziness.
|
||||||
|
@ -5275,33 +5278,8 @@ void Application::nodeKilled(SharedNodePointer node) {
|
||||||
if (node->getType() == NodeType::AudioMixer) {
|
if (node->getType() == NodeType::AudioMixer) {
|
||||||
QMetaObject::invokeMethod(DependencyManager::get<AudioClient>().data(), "audioMixerKilled");
|
QMetaObject::invokeMethod(DependencyManager::get<AudioClient>().data(), "audioMixerKilled");
|
||||||
} else if (node->getType() == NodeType::EntityServer) {
|
} else if (node->getType() == NodeType::EntityServer) {
|
||||||
QUuid nodeUUID = node->getUUID();
|
// we lost an entity server, clear all of the domain octree details
|
||||||
// see if this is the first we've heard of this node...
|
clearDomainOctreeDetails();
|
||||||
_entityServerJurisdictions.withReadLock([&] {
|
|
||||||
if (_entityServerJurisdictions.find(nodeUUID) == _entityServerJurisdictions.end()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto rootCode = _entityServerJurisdictions[nodeUUID].getRootOctalCode();
|
|
||||||
VoxelPositionSize rootDetails;
|
|
||||||
voxelDetailsForCode(rootCode.get(), rootDetails);
|
|
||||||
|
|
||||||
qCDebug(interfaceapp, "model server going away...... v[%f, %f, %f, %f]",
|
|
||||||
(double)rootDetails.x, (double)rootDetails.y, (double)rootDetails.z, (double)rootDetails.s);
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
// If the model server is going away, remove it from our jurisdiction map so we don't send voxels to a dead server
|
|
||||||
_entityServerJurisdictions.withWriteLock([&] {
|
|
||||||
_entityServerJurisdictions.erase(_entityServerJurisdictions.find(nodeUUID));
|
|
||||||
});
|
|
||||||
|
|
||||||
// also clean up scene stats for that server
|
|
||||||
_octreeServerSceneStats.withWriteLock([&] {
|
|
||||||
if (_octreeServerSceneStats.find(nodeUUID) != _octreeServerSceneStats.end()) {
|
|
||||||
_octreeServerSceneStats.erase(nodeUUID);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else if (node->getType() == NodeType::AvatarMixer) {
|
} else if (node->getType() == NodeType::AvatarMixer) {
|
||||||
// our avatar mixer has gone away - clear the hash of avatars
|
// our avatar mixer has gone away - clear the hash of avatars
|
||||||
DependencyManager::get<AvatarManager>()->clearOtherAvatars();
|
DependencyManager::get<AvatarManager>()->clearOtherAvatars();
|
||||||
|
|
|
@ -409,6 +409,7 @@ public slots:
|
||||||
private slots:
|
private slots:
|
||||||
void showDesktop();
|
void showDesktop();
|
||||||
void clearDomainOctreeDetails();
|
void clearDomainOctreeDetails();
|
||||||
|
void clearDomainAvatars();
|
||||||
void aboutToQuit();
|
void aboutToQuit();
|
||||||
|
|
||||||
void resettingDomain();
|
void resettingDomain();
|
||||||
|
|
|
@ -39,7 +39,7 @@ const QSet<PacketType> NON_SOURCED_PACKETS = QSet<PacketType>()
|
||||||
<< PacketType::ICEServerPeerInformation << PacketType::ICEServerQuery << PacketType::ICEServerHeartbeat
|
<< PacketType::ICEServerPeerInformation << PacketType::ICEServerQuery << PacketType::ICEServerHeartbeat
|
||||||
<< PacketType::ICEServerHeartbeatACK << PacketType::ICEPing << PacketType::ICEPingReply
|
<< PacketType::ICEServerHeartbeatACK << PacketType::ICEPing << PacketType::ICEPingReply
|
||||||
<< PacketType::ICEServerHeartbeatDenied << PacketType::AssignmentClientStatus << PacketType::StopNode
|
<< PacketType::ICEServerHeartbeatDenied << PacketType::AssignmentClientStatus << PacketType::StopNode
|
||||||
<< PacketType::DomainServerRemovedNode << PacketType::UsernameFromIDReply;
|
<< PacketType::DomainServerRemovedNode << PacketType::UsernameFromIDReply << PacketType::OctreeFileReplacement;
|
||||||
|
|
||||||
PacketVersion versionForPacketType(PacketType packetType) {
|
PacketVersion versionForPacketType(PacketType packetType) {
|
||||||
switch (packetType) {
|
switch (packetType) {
|
||||||
|
|
|
@ -113,7 +113,8 @@ public:
|
||||||
EntityPhysics,
|
EntityPhysics,
|
||||||
EntityServerScriptLog,
|
EntityServerScriptLog,
|
||||||
AdjustAvatarSorting,
|
AdjustAvatarSorting,
|
||||||
LAST_PACKET_TYPE = AdjustAvatarSorting
|
OctreeFileReplacement,
|
||||||
|
LAST_PACKET_TYPE = OctreeFileReplacement
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -33,6 +33,7 @@
|
||||||
#include "OctreePersistThread.h"
|
#include "OctreePersistThread.h"
|
||||||
|
|
||||||
const int OctreePersistThread::DEFAULT_PERSIST_INTERVAL = 1000 * 30; // every 30 seconds
|
const int OctreePersistThread::DEFAULT_PERSIST_INTERVAL = 1000 * 30; // every 30 seconds
|
||||||
|
const QString OctreePersistThread::REPLACEMENT_FILE_EXTENSION = ".replace";
|
||||||
|
|
||||||
OctreePersistThread::OctreePersistThread(OctreePointer tree, const QString& filename, const QString& backupDirectory, int persistInterval,
|
OctreePersistThread::OctreePersistThread(OctreePointer tree, const QString& filename, const QString& backupDirectory, int persistInterval,
|
||||||
bool wantBackup, const QJsonObject& settings, bool debugTimestampNow,
|
bool wantBackup, const QJsonObject& settings, bool debugTimestampNow,
|
||||||
|
@ -131,10 +132,47 @@ quint64 OctreePersistThread::getMostRecentBackupTimeInUsecs(const QString& forma
|
||||||
return mostRecentBackupInUsecs;
|
return mostRecentBackupInUsecs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void OctreePersistThread::possiblyReplaceContent() {
|
||||||
|
// before we load the normal file, check if there's a pending replacement file
|
||||||
|
auto replacementFileName = _filename + REPLACEMENT_FILE_EXTENSION;
|
||||||
|
|
||||||
|
QFile replacementFile { replacementFileName };
|
||||||
|
if (replacementFile.exists()) {
|
||||||
|
// we have a replacement file to process
|
||||||
|
qDebug() << "Replacing models file with" << replacementFileName;
|
||||||
|
|
||||||
|
// first take the current models file and move it to a different filename, appended with the timestamp
|
||||||
|
QFile currentFile { _filename };
|
||||||
|
if (currentFile.exists()) {
|
||||||
|
static const QString FILENAME_TIMESTAMP_FORMAT = "yyyyMMdd-hhmmss";
|
||||||
|
auto backupFileName = _filename + ".backup." + QDateTime::currentDateTime().toString(FILENAME_TIMESTAMP_FORMAT);
|
||||||
|
|
||||||
|
if (currentFile.rename(backupFileName)) {
|
||||||
|
qDebug() << "Moved previous models file to" << backupFileName;
|
||||||
|
} else {
|
||||||
|
qWarning() << "Could not backup previous models file to" << backupFileName << "- removing replacement models file";
|
||||||
|
|
||||||
|
if (!replacementFile.remove()) {
|
||||||
|
qWarning() << "Could not remove replacement models file from" << replacementFileName
|
||||||
|
<< "- replacement will be re-attempted on next server restart";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// rename the replacement file to match what the persist thread is just about to read
|
||||||
|
if (!replacementFile.rename(_filename)) {
|
||||||
|
qWarning() << "Could not replace models file with" << replacementFileName << "- starting with empty models file";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
bool OctreePersistThread::process() {
|
bool OctreePersistThread::process() {
|
||||||
|
|
||||||
if (!_initialLoadComplete) {
|
if (!_initialLoadComplete) {
|
||||||
|
possiblyReplaceContent();
|
||||||
|
|
||||||
quint64 loadStarted = usecTimestampNow();
|
quint64 loadStarted = usecTimestampNow();
|
||||||
qCDebug(octree) << "loading Octrees from file: " << _filename << "...";
|
qCDebug(octree) << "loading Octrees from file: " << _filename << "...";
|
||||||
|
|
||||||
|
|
|
@ -32,6 +32,7 @@ public:
|
||||||
};
|
};
|
||||||
|
|
||||||
static const int DEFAULT_PERSIST_INTERVAL;
|
static const int DEFAULT_PERSIST_INTERVAL;
|
||||||
|
static const QString REPLACEMENT_FILE_EXTENSION;
|
||||||
|
|
||||||
OctreePersistThread(OctreePointer tree, const QString& filename, const QString& backupDirectory,
|
OctreePersistThread(OctreePointer tree, const QString& filename, const QString& backupDirectory,
|
||||||
int persistInterval = DEFAULT_PERSIST_INTERVAL, bool wantBackup = false,
|
int persistInterval = DEFAULT_PERSIST_INTERVAL, bool wantBackup = false,
|
||||||
|
@ -60,6 +61,7 @@ protected:
|
||||||
bool getMostRecentBackup(const QString& format, QString& mostRecentBackupFileName, QDateTime& mostRecentBackupTime);
|
bool getMostRecentBackup(const QString& format, QString& mostRecentBackupFileName, QDateTime& mostRecentBackupTime);
|
||||||
quint64 getMostRecentBackupTimeInUsecs(const QString& format);
|
quint64 getMostRecentBackupTimeInUsecs(const QString& format);
|
||||||
void parseSettings(const QJsonObject& settings);
|
void parseSettings(const QJsonObject& settings);
|
||||||
|
void possiblyReplaceContent();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
OctreePointer _tree;
|
OctreePointer _tree;
|
||||||
|
|
Loading…
Reference in a new issue