mirror of
https://thingvellir.net/git/overte
synced 2025-03-27 23:52:03 +01:00
add entities file replacement to DS and ES
This commit is contained in:
parent
0435749f82
commit
a21a34a4a4
13 changed files with 248 additions and 16 deletions
|
@ -11,12 +11,14 @@
|
|||
|
||||
#include "OctreeServer.h"
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QTimer>
|
||||
|
||||
#include <time.h>
|
||||
|
||||
#include <AccountManager.h>
|
||||
#include <Gzip.h>
|
||||
#include <HTTPConnection.h>
|
||||
#include <LogHandler.h>
|
||||
#include <shared/NetworkUtils.h>
|
||||
|
@ -924,6 +926,57 @@ void OctreeServer::handleJurisdictionRequestPacket(QSharedPointer<ReceivedMessag
|
|||
_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) {
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -136,6 +136,7 @@ private slots:
|
|||
void handleOctreeQueryPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
|
||||
void handleOctreeDataNackPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
|
||||
void handleJurisdictionRequestPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
|
||||
void handleOctreeFileReplacement(QSharedPointer<ReceivedMessage> message);
|
||||
void removeSendThread();
|
||||
|
||||
protected:
|
||||
|
@ -172,6 +173,7 @@ protected:
|
|||
QString _statusHost;
|
||||
|
||||
QString _persistFilePath;
|
||||
QString _persistAbsoluteFilePath;
|
||||
QString _persistAsFileType;
|
||||
QString _backupDirectoryPath;
|
||||
int _packetsPerClientPerInterval;
|
||||
|
|
41
domain-server/resources/web/content/index.shtml
Normal file
41
domain-server/resources/web/content/index.shtml
Normal file
|
@ -0,0 +1,41 @@
|
|||
<!--#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 in <i>C:\Users\[user_name]\AppData\Roaming\High Fidelity\assignment-client</i>.
|
||||
</p>
|
||||
|
||||
<input type="file" name="entities-file" class="form-control-file" accept=".json, .gz">
|
||||
</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>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a href="/content/">Content</a></li>
|
||||
<li><a href="/settings/">Settings</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
|
|
@ -99,7 +99,7 @@
|
|||
<script src='/js/underscore-keypath.min.js'></script>
|
||||
<script src='/js/bootbox.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/form2js.min.js'></script>
|
||||
<script src='js/sha256.js'></script>
|
||||
|
|
|
@ -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> 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>();
|
||||
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 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;
|
||||
|
|
|
@ -39,7 +39,7 @@ const QSet<PacketType> NON_SOURCED_PACKETS = QSet<PacketType>()
|
|||
<< 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) {
|
||||
|
|
|
@ -113,7 +113,8 @@ public:
|
|||
EntityPhysics,
|
||||
EntityServerScriptLog,
|
||||
AdjustAvatarSorting,
|
||||
LAST_PACKET_TYPE = AdjustAvatarSorting
|
||||
OctreeFileReplacement,
|
||||
LAST_PACKET_TYPE = OctreeFileReplacement
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -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 << "...";
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue