From a21a34a4a46b0c10ee87203f36f057962b846654 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 27 Apr 2017 16:51:24 -0700 Subject: [PATCH 1/7] add entities file replacement to DS and ES --- assignment-client/src/octree/OctreeServer.cpp | 76 ++++++++++++++++--- assignment-client/src/octree/OctreeServer.h | 2 + .../resources/web/content/index.shtml | 41 ++++++++++ .../resources/web/content/js/content.js | 45 +++++++++++ domain-server/resources/web/header.html | 1 + .../web/{settings => }/js/sweetalert.min.js | 0 .../resources/web/settings/index.shtml | 2 +- domain-server/src/DomainServer.cpp | 49 +++++++++++- domain-server/src/DomainServer.h | 4 + .../networking/src/udt/PacketHeaders.cpp | 2 +- libraries/networking/src/udt/PacketHeaders.h | 3 +- libraries/octree/src/OctreePersistThread.cpp | 37 +++++++++ libraries/octree/src/OctreePersistThread.h | 2 + 13 files changed, 248 insertions(+), 16 deletions(-) create mode 100644 domain-server/resources/web/content/index.shtml create mode 100644 domain-server/resources/web/content/js/content.js rename domain-server/resources/web/{settings => }/js/sweetalert.min.js (100%) diff --git a/assignment-client/src/octree/OctreeServer.cpp b/assignment-client/src/octree/OctreeServer.cpp index f2dbe5d1d2..af5f2c904e 100644 --- a/assignment-client/src/octree/OctreeServer.cpp +++ b/assignment-client/src/octree/OctreeServer.cpp @@ -11,12 +11,14 @@ #include "OctreeServer.h" +#include #include #include #include #include +#include #include #include #include @@ -924,6 +926,57 @@ void OctreeServer::handleJurisdictionRequestPacket(QSharedPointerqueueReceivedPacket(message, senderNode); } +void OctreeServer::handleOctreeFileReplacement(QSharedPointer 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(); + 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) { result = false; // assume it doesn't exist bool optionAvailable = false; @@ -1148,6 +1201,7 @@ void OctreeServer::domainSettingsRequestComplete() { packetReceiver.registerListener(getMyQueryMessageType(), this, "handleOctreeQueryPacket"); packetReceiver.registerListener(PacketType::OctreeDataNack, this, "handleOctreeDataNackPacket"); packetReceiver.registerListener(PacketType::JurisdictionRequest, this, "handleJurisdictionRequestPacket"); + packetReceiver.registerListener(PacketType::OctreeFileReplacement, this, "handleOctreeFileReplacement"); 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 there is, let's copy it over to our target persist directory QDir persistPath { _persistFilePath }; - QString persistAbsoluteFilePath = persistPath.absolutePath(); + _persistAbsoluteFilePath = persistPath.absolutePath(); if (persistPath.isRelative()) { // if the domain settings passed us a relative path, make an absolute path that is relative to the // 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"; // force the persist file to end with .json.gz - if (!persistAbsoluteFilePath.endsWith(ENTITY_PERSIST_EXTENSION, Qt::CaseInsensitive)) { - persistAbsoluteFilePath += ENTITY_PERSIST_EXTENSION; + if (!_persistAbsoluteFilePath.endsWith(ENTITY_PERSIST_EXTENSION, Qt::CaseInsensitive)) { + _persistAbsoluteFilePath += ENTITY_PERSIST_EXTENSION; } else { // 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"; static const QString OLD_DEFAULT_PERSIST_FILENAME = "resources/models.json.gz"; @@ -1217,7 +1271,7 @@ void OctreeServer::domainSettingsRequestComplete() { pathToCopyFrom = oldDefaultPersistPath; } - QDir persistFileDirectory { QDir::cleanPath(persistAbsoluteFilePath + "/..") }; + QDir persistFileDirectory { QDir::cleanPath(_persistAbsoluteFilePath + "/..") }; if (!persistFileDirectory.exists()) { qDebug() << "Creating data directory " << persistFileDirectory.absolutePath(); @@ -1225,15 +1279,15 @@ void OctreeServer::domainSettingsRequestComplete() { } 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 { qDebug() << "No existing persist file found"; } } - auto persistFileDirectory = QFileInfo(persistAbsoluteFilePath).absolutePath(); + auto persistFileDirectory = QFileInfo(_persistAbsoluteFilePath).absolutePath(); if (_backupDirectoryPath.isEmpty()) { // Use the persist file's directory to store backups _backupDirectoryPath = persistFileDirectory; @@ -1264,7 +1318,7 @@ void OctreeServer::domainSettingsRequestComplete() { qDebug() << "Backups will be stored in: " << _backupDirectoryPath; // now set up PersistThread - _persistThread = new OctreePersistThread(_tree, persistAbsoluteFilePath, _backupDirectoryPath, _persistInterval, + _persistThread = new OctreePersistThread(_tree, _persistAbsoluteFilePath, _backupDirectoryPath, _persistInterval, _wantBackup, _settings, _debugTimestampNow, _persistAsFileType); _persistThread->initialize(true); } diff --git a/assignment-client/src/octree/OctreeServer.h b/assignment-client/src/octree/OctreeServer.h index 3bb327eb06..8db8d845de 100644 --- a/assignment-client/src/octree/OctreeServer.h +++ b/assignment-client/src/octree/OctreeServer.h @@ -136,6 +136,7 @@ private slots: void handleOctreeQueryPacket(QSharedPointer message, SharedNodePointer senderNode); void handleOctreeDataNackPacket(QSharedPointer message, SharedNodePointer senderNode); void handleJurisdictionRequestPacket(QSharedPointer message, SharedNodePointer senderNode); + void handleOctreeFileReplacement(QSharedPointer message); void removeSendThread(); protected: @@ -172,6 +173,7 @@ protected: QString _statusHost; QString _persistFilePath; + QString _persistAbsoluteFilePath; QString _persistAsFileType; QString _backupDirectoryPath; int _packetsPerClientPerInterval; diff --git a/domain-server/resources/web/content/index.shtml b/domain-server/resources/web/content/index.shtml new file mode 100644 index 0000000000..1d8d3fe1ab --- /dev/null +++ b/domain-server/resources/web/content/index.shtml @@ -0,0 +1,41 @@ + + +
+
+
+ +
+
+ +
+
+
+
+

Upload Entities File

+
+
+
+

+ Upload an entities file (e.g.: models.json.gz) to replace the content of this domain.
+ Note: Your domain's content will be replaced by the content you upload, but the backup files of your domain's content will not immediately be changed. +

+

+ 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 in C:\Users\[user_name]\AppData\Roaming\High Fidelity\assignment-client. +

+ + +
+ +
+ +
+
+
+
+ + + + + diff --git a/domain-server/resources/web/content/js/content.js b/domain-server/resources/web/content/js/content.js new file mode 100644 index 0000000000..2e6e084164 --- /dev/null +++ b/domain-server/resources/web/content/js/content.js @@ -0,0 +1,45 @@ +$(document).ready(function(){ + + function showSpinnerAlert(title) { + swal({ + title: title, + text: '
', + 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.
Verify that the file is a .json or .json.gz entities file and try again.', + html: true, + confirmButtonText: 'OK', + }); + } + }); + + ev.preventDefault(); + + showSpinnerAlert("Uploading Entities File"); + }); +}); diff --git a/domain-server/resources/web/header.html b/domain-server/resources/web/header.html index b4eee406f2..965f86b0a1 100644 --- a/domain-server/resources/web/header.html +++ b/domain-server/resources/web/header.html @@ -36,6 +36,7 @@
  • New Assignment
  • +
  • Content
  • Settings
  • diff --git a/domain-server/resources/web/settings/js/sweetalert.min.js b/domain-server/resources/web/js/sweetalert.min.js similarity index 100% rename from domain-server/resources/web/settings/js/sweetalert.min.js rename to domain-server/resources/web/js/sweetalert.min.js diff --git a/domain-server/resources/web/settings/index.shtml b/domain-server/resources/web/settings/index.shtml index 3eb7a53726..1812c52dad 100644 --- a/domain-server/resources/web/settings/index.shtml +++ b/domain-server/resources/web/settings/index.shtml @@ -99,7 +99,7 @@ - + diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 620b11d8ad..233cb02dff 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -1633,6 +1633,15 @@ QString pathForAssignmentScript(const QUuid& 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"; bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler) { 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_NODES = "/nodes"; 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}"; @@ -1869,6 +1879,25 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url // respond with a 200 code for successful upload 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 = 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; } } else if (connection->requestOperation() == QNetworkAccessManager::DeleteOperation) { @@ -2159,8 +2188,7 @@ Headers DomainServer::setupCookieHeadersFromProfileReply(QNetworkReply* profileR cookieHeaders.insert("Set-Cookie", cookieString.toUtf8()); // 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", redirectString.toUtf8()); + cookieHeaders.insert("Location", pathForRedirect().toUtf8()); return cookieHeaders; } @@ -2560,3 +2588,20 @@ void DomainServer::setupGroupCacheRefresh() { _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->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); + }); +} diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index 4c5c42acee..63b82cb37d 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -100,6 +100,8 @@ private slots: void handleSuccessfulICEServerAddressUpdate(QNetworkReply& requestReply); void handleFailedICEServerAddressUpdate(QNetworkReply& requestReply); + void handleOctreeFileReplacement(QByteArray octreeFile); + signals: void iceServerChanged(); void userConnected(); @@ -161,6 +163,8 @@ private: void setupGroupCacheRefresh(); + QString pathForRedirect(QString path = QString()) const; + SubnetList _acSubnetWhitelist; DomainGatekeeper _gatekeeper; diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index bd30cdd29c..adaa7a848c 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -39,7 +39,7 @@ const QSet NON_SOURCED_PACKETS = QSet() << PacketType::ICEServerPeerInformation << PacketType::ICEServerQuery << PacketType::ICEServerHeartbeat << PacketType::ICEServerHeartbeatACK << PacketType::ICEPing << PacketType::ICEPingReply << PacketType::ICEServerHeartbeatDenied << PacketType::AssignmentClientStatus << PacketType::StopNode - << PacketType::DomainServerRemovedNode << PacketType::UsernameFromIDReply; + << PacketType::DomainServerRemovedNode << PacketType::UsernameFromIDReply << PacketType::OctreeFileReplacement; PacketVersion versionForPacketType(PacketType packetType) { switch (packetType) { diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index d312427ca7..f803b83887 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -113,7 +113,8 @@ public: EntityPhysics, EntityServerScriptLog, AdjustAvatarSorting, - LAST_PACKET_TYPE = AdjustAvatarSorting + OctreeFileReplacement, + LAST_PACKET_TYPE = OctreeFileReplacement }; }; diff --git a/libraries/octree/src/OctreePersistThread.cpp b/libraries/octree/src/OctreePersistThread.cpp index 7034790eaf..b79ce5537f 100644 --- a/libraries/octree/src/OctreePersistThread.cpp +++ b/libraries/octree/src/OctreePersistThread.cpp @@ -33,6 +33,7 @@ #include "OctreePersistThread.h" 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, bool wantBackup, const QJsonObject& settings, bool debugTimestampNow, @@ -131,10 +132,46 @@ quint64 OctreePersistThread::getMostRecentBackupTimeInUsecs(const QString& forma 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 (!QFile::remove(replacementFileName)) { + qWarning() << "Could not remove replacement models file from" << replacementFileName + << "- replacement will be re-attempted on next server restart"; + } + } + } + + // 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() { if (!_initialLoadComplete) { + possiblyReplaceContent(); + quint64 loadStarted = usecTimestampNow(); qCDebug(octree) << "loading Octrees from file: " << _filename << "..."; diff --git a/libraries/octree/src/OctreePersistThread.h b/libraries/octree/src/OctreePersistThread.h index 927304e862..2441223467 100644 --- a/libraries/octree/src/OctreePersistThread.h +++ b/libraries/octree/src/OctreePersistThread.h @@ -32,6 +32,7 @@ public: }; static const int DEFAULT_PERSIST_INTERVAL; + static const QString REPLACEMENT_FILE_EXTENSION; OctreePersistThread(OctreePointer tree, const QString& filename, const QString& backupDirectory, int persistInterval = DEFAULT_PERSIST_INTERVAL, bool wantBackup = false, @@ -60,6 +61,7 @@ protected: bool getMostRecentBackup(const QString& format, QString& mostRecentBackupFileName, QDateTime& mostRecentBackupTime); quint64 getMostRecentBackupTimeInUsecs(const QString& format); void parseSettings(const QJsonObject& settings); + void possiblyReplaceContent(); private: OctreePointer _tree; From 038ac745c8af5fe8ced7d1bd4e3bc093d71fff50 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 27 Apr 2017 17:47:18 -0700 Subject: [PATCH 2/7] clear domain octree details if ES killed --- interface/src/Application.cpp | 38 ++++++++--------------------------- interface/src/Application.h | 1 + 2 files changed, 9 insertions(+), 30 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 39a4b8ee7c..c8e61d87dc 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -797,7 +797,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo connect(&domainHandler, SIGNAL(resetting()), SLOT(resettingDomain())); connect(&domainHandler, SIGNAL(connectedToDomain(const QString&)), 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]() { getOverlays().deleteOverlay(getTabletScreenID()); getOverlays().deleteOverlay(getTabletHomeButtonID()); @@ -5196,14 +5196,17 @@ void Application::clearDomainOctreeDetails() { skyStage->setBackgroundMode(model::SunSkyStage::SKY_DEFAULT); _recentlyClearedDomain = true; - - DependencyManager::get()->clearOtherAvatars(); + DependencyManager::get()->clearUnusedResources(); DependencyManager::get()->clearUnusedResources(); DependencyManager::get()->clearUnusedResources(); DependencyManager::get()->clearUnusedResources(); } +void Application::clearDomainAvatars() { + DependencyManager::get()->clearOtherAvatars(); +} + void Application::domainChanged(const QString& domainHostname) { updateWindowTitle(); // disable physics until we have enough information about our new location to not cause craziness. @@ -5273,33 +5276,8 @@ void Application::nodeKilled(SharedNodePointer node) { if (node->getType() == NodeType::AudioMixer) { QMetaObject::invokeMethod(DependencyManager::get().data(), "audioMixerKilled"); } else if (node->getType() == NodeType::EntityServer) { - QUuid nodeUUID = node->getUUID(); - // see if this is the first we've heard of this node... - _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); - } - }); + // we lost an entity server, clear all of the domain octree details + clearDomainOctreeDetails(); } else if (node->getType() == NodeType::AvatarMixer) { // our avatar mixer has gone away - clear the hash of avatars DependencyManager::get()->clearOtherAvatars(); diff --git a/interface/src/Application.h b/interface/src/Application.h index dff1de2860..041f1f8930 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -409,6 +409,7 @@ public slots: private slots: void showDesktop(); void clearDomainOctreeDetails(); + void clearDomainAvatars(); void aboutToQuit(); void resettingDomain(); From 9bc0609b1e2e6534d510354b234c330cbc72a1a5 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 28 Apr 2017 10:17:48 -0700 Subject: [PATCH 3/7] address code review comments --- domain-server/resources/web/content/index.shtml | 11 +++++++---- domain-server/src/DomainServer.cpp | 4 ++-- interface/src/Application.cpp | 2 +- libraries/octree/src/OctreePersistThread.cpp | 3 ++- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/domain-server/resources/web/content/index.shtml b/domain-server/resources/web/content/index.shtml index 1d8d3fe1ab..c7eb765878 100644 --- a/domain-server/resources/web/content/index.shtml +++ b/domain-server/resources/web/content/index.shtml @@ -16,20 +16,23 @@

    - Upload an entities file (e.g.: models.json.gz) to replace the content of this domain.
    + Upload an entities file (e.g.: models.json.gz) to replace the content of this domain.
    Note: Your domain's content will be replaced by the content you upload, but the backup files of your domain's content will not immediately be changed.

    - 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 in C:\Users\[user_name]\AppData\Roaming\High Fidelity\assignment-client. + 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:
    +

    C:\Users\[username]\AppData\Roaming\High Fidelity\assignment-client/entities/models.json.gz
    +
    /Users/[username]/Library/Application Support/High Fidelity/assignment-client/entities/models.json.gz
    +
    /home/[username]/.local/share/High Fidelity/assignment-client/entities/models.json.gz

    - +
    +
    - diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 233cb02dff..782c54419d 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -2593,9 +2593,9 @@ void DomainServer::handleOctreeFileReplacement(QByteArray octreeFile) { // enumerate the nodes and find any octree type servers with active sockets auto limitedNodeList = DependencyManager::get(); - limitedNodeList->eachMatchingNode([](const SharedNodePointer& node){ + limitedNodeList->eachMatchingNode([](const SharedNodePointer& node) { return node->getType() == NodeType::EntityServer && node->getActiveSocket(); - }, [&octreeFile, limitedNodeList](const SharedNodePointer& octreeNode){ + }, [&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); diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index c8e61d87dc..9fa66262cc 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -5177,7 +5177,6 @@ void Application::clearDomainOctreeDetails() { qCDebug(interfaceapp) << "Clearing domain octree details..."; resetPhysicsReadyInformation(); - getMyAvatar()->setAvatarEntityDataChanged(true); // to recreate worn entities // reset our node to stats and node to jurisdiction maps... since these must be changing... _entityServerJurisdictions.withWriteLock([&] { @@ -5204,6 +5203,7 @@ void Application::clearDomainOctreeDetails() { } void Application::clearDomainAvatars() { + getMyAvatar()->setAvatarEntityDataChanged(true); // to recreate worn entities DependencyManager::get()->clearOtherAvatars(); } diff --git a/libraries/octree/src/OctreePersistThread.cpp b/libraries/octree/src/OctreePersistThread.cpp index b79ce5537f..ea6bd28fc4 100644 --- a/libraries/octree/src/OctreePersistThread.cpp +++ b/libraries/octree/src/OctreePersistThread.cpp @@ -152,9 +152,10 @@ void OctreePersistThread::possiblyReplaceContent() { } else { qWarning() << "Could not backup previous models file to" << backupFileName << "- removing replacement models file"; - if (!QFile::remove(replacementFileName)) { + if (!replacementFile.remove()) { qWarning() << "Could not remove replacement models file from" << replacementFileName << "- replacement will be re-attempted on next server restart"; + return; } } } From b44a56db803889ac12ce93b01a70107434a60de3 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 28 Apr 2017 10:32:06 -0700 Subject: [PATCH 4/7] don't save ATP requests to cache if they are range requests --- libraries/networking/src/AssetRequest.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/libraries/networking/src/AssetRequest.cpp b/libraries/networking/src/AssetRequest.cpp index 341c3b45da..9c756b0060 100644 --- a/libraries/networking/src/AssetRequest.cpp +++ b/libraries/networking/src/AssetRequest.cpp @@ -113,8 +113,10 @@ void AssetRequest::start() { _data = data; _totalReceived += data.size(); emit progress(_totalReceived, data.size()); - - saveToCache(getUrl(), data); + + if (!_byteRange.isSet()) { + saveToCache(getUrl(), data); + } } } From 765a06108621338507a0ea136b209e22f9029704 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Fri, 28 Apr 2017 16:26:03 -0700 Subject: [PATCH 5/7] Fix stuck ATP downloads Occasionally ATP requests would get stuck because of a race inside AssetClient::handleAssetReply. This was more likely to happen to small resources. --- libraries/networking/src/AssetClient.cpp | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/libraries/networking/src/AssetClient.cpp b/libraries/networking/src/AssetClient.cpp index 15e0b8c9b5..054557e920 100644 --- a/libraries/networking/src/AssetClient.cpp +++ b/libraries/networking/src/AssetClient.cpp @@ -348,18 +348,19 @@ void AssetClient::handleAssetGetReply(QSharedPointer message, S // Store message in case we need to disconnect from it later. callbacks.message = message; + + auto weakNode = senderNode.toWeakRef(); + connect(message.data(), &ReceivedMessage::progress, this, [this, weakNode, messageID, length](qint64 size) { + handleProgressCallback(weakNode, messageID, size, length); + }); + connect(message.data(), &ReceivedMessage::completed, this, [this, weakNode, messageID]() { + handleCompleteCallback(weakNode, messageID); + }); + if (message->isComplete()) { + disconnect(message.data(), nullptr, this, nullptr); callbacks.completeCallback(true, error, message->readAll()); messageCallbackMap.erase(requestIt); - } else { - auto weakNode = senderNode.toWeakRef(); - - connect(message.data(), &ReceivedMessage::progress, this, [this, weakNode, messageID, length](qint64 size) { - handleProgressCallback(weakNode, messageID, size, length); - }); - connect(message.data(), &ReceivedMessage::completed, this, [this, weakNode, messageID]() { - handleCompleteCallback(weakNode, messageID); - }); } } From 89fb0a917b469592ed0d4587dbe70d0a738e5b3e Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Fri, 28 Apr 2017 16:27:44 -0700 Subject: [PATCH 6/7] Fix poorly indented if in AssetRequest --- libraries/networking/src/AssetRequest.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/networking/src/AssetRequest.cpp b/libraries/networking/src/AssetRequest.cpp index 9c756b0060..920c7ae036 100644 --- a/libraries/networking/src/AssetRequest.cpp +++ b/libraries/networking/src/AssetRequest.cpp @@ -77,7 +77,7 @@ void AssetRequest::start() { _assetRequestID = assetClient->getAsset(_hash, _byteRange.fromInclusive, _byteRange.toExclusive, [this, that, hash](bool responseReceived, AssetServerError serverError, const QByteArray& data) { - if (!that) { + if (!that) { qCWarning(asset_client) << "Got reply for dead asset request " << hash << "- error code" << _error; // If the request is dead, return return; From 953a3f7a6f07140396cbeb08d8ab5936e35188f5 Mon Sep 17 00:00:00 2001 From: 1st-BrainStormer <1st-BrainStormer@users.noreply.github.com> Date: Fri, 28 Apr 2017 20:14:01 -0700 Subject: [PATCH 7/7] Update EntityDynamicInterface.cpp Grammar correction. --- libraries/entities/src/EntityDynamicInterface.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libraries/entities/src/EntityDynamicInterface.cpp b/libraries/entities/src/EntityDynamicInterface.cpp index bed3185b8f..2ab9a60397 100644 --- a/libraries/entities/src/EntityDynamicInterface.cpp +++ b/libraries/entities/src/EntityDynamicInterface.cpp @@ -49,20 +49,20 @@ -An dynamic is a callback which is registered with bullet. An dynamic is called-back every physics +A dynamic is a callback which is registered with bullet. A dynamic is called-back every physics simulation step and can do whatever it wants with the various datastructures it has available. An dynamic, for example, can pull an EntityItem toward a point as if that EntityItem were connected to that point by a spring. -In this system, an dynamic is a property of an EntityItem (rather, an EntityItem has a property which +In this system, a dynamic is a property of an EntityItem (rather, an EntityItem has a property which encodes a list of dynamics). Each dynamic has a type and some arguments. Dynamics can be created by a script or when receiving information via an EntityTree data-stream (either over the network or from an svo file). In the interface, if an EntityItem has dynamics, this EntityItem will have pointers to ObjectDynamic -subclass (like ObjectDynamicSpring) instantiations. Code in the entities library affects an dynamic-object +subclass (like ObjectDynamicSpring) instantiations. Code in the entities library affects a dynamic-object via the EntityDynamicInterface (which knows nothing about bullet). When the ObjectDynamic subclass -instance is created, it is registered as an dynamic with bullet. Bullet will call into code in this +instance is created, it is registered as a dynamic with bullet. Bullet will call into code in this instance with the btDynamicInterface every physics-simulation step. Because the dynamic can exist next to the interface's EntityTree or the entity-server's EntityTree,