mirror of
https://github.com/overte-org/overte.git
synced 2025-08-08 23:36:44 +02:00
Add backup DS APIs
Add backup apis
This commit is contained in:
parent
e63b692d80
commit
8b07e7e28f
8 changed files with 270 additions and 96 deletions
|
@ -95,7 +95,7 @@ public:
|
||||||
zipFile.write(entitiesFile.readAll());
|
zipFile.write(entitiesFile.readAll());
|
||||||
zipFile.close();
|
zipFile.close();
|
||||||
if (zipFile.getZipError() != UNZ_OK) {
|
if (zipFile.getZipError() != UNZ_OK) {
|
||||||
qDebug() << "testCreate(): outFile.close(): " << zipFile.getZipError();
|
qDebug() << "Failed to zip models.json.gz: " << zipFile.getZipError();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -107,7 +107,10 @@ public:
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
QuaZipFile zipFile { &zip };
|
QuaZipFile zipFile { &zip };
|
||||||
zipFile.open(QIODevice::ReadOnly);
|
if (!zipFile.open(QIODevice::ReadOnly)) {
|
||||||
|
qWarning() << "Failed to open models.json.gz in backup";
|
||||||
|
return;
|
||||||
|
}
|
||||||
auto data = zipFile.readAll();
|
auto data = zipFile.readAll();
|
||||||
|
|
||||||
QFile entitiesFile { _entitiesFilePath };
|
QFile entitiesFile { _entitiesFilePath };
|
||||||
|
@ -117,6 +120,10 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
zipFile.close();
|
zipFile.close();
|
||||||
|
|
||||||
|
if (zipFile.getZipError() != UNZ_OK) {
|
||||||
|
qDebug() << "Failed to zip models.json.gz: " << zipFile.getZipError();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete a skeleton backup
|
// Delete a skeleton backup
|
||||||
|
|
|
@ -91,51 +91,4 @@ private:
|
||||||
int _mappingRequestsInFlight { 0 };
|
int _mappingRequestsInFlight { 0 };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
#include <quazip5/quazipfile.h>
|
|
||||||
class AssetsBackupHandler {
|
|
||||||
public:
|
|
||||||
AssetsBackupHandler(BackupSupervisor* backupSupervisor) : _backupSupervisor(backupSupervisor) {}
|
|
||||||
|
|
||||||
void loadBackup(QuaZip& zip) {}
|
|
||||||
|
|
||||||
void createBackup(QuaZip& zip) {
|
|
||||||
quint64 lastRefreshTimestamp = _backupSupervisor->getLastRefreshTimestamp();
|
|
||||||
AssetUtils::Mappings mappings = _backupSupervisor->getCurrentMappings();
|
|
||||||
|
|
||||||
if (lastRefreshTimestamp == 0) {
|
|
||||||
qWarning() << "Current mappings not yet loaded, ";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
static constexpr quint64 MAX_REFRESH_TIME = 15 * 60 * 1000 * 1000;
|
|
||||||
if (usecTimestampNow() - lastRefreshTimestamp > MAX_REFRESH_TIME) {
|
|
||||||
qWarning() << "Backing up asset mappings that appear old.";
|
|
||||||
}
|
|
||||||
|
|
||||||
QJsonObject jsonObject;
|
|
||||||
for (const auto& mapping : mappings) {
|
|
||||||
jsonObject.insert(mapping.first, mapping.second);
|
|
||||||
}
|
|
||||||
QJsonDocument document(jsonObject);
|
|
||||||
|
|
||||||
QuaZipFile zipFile { &zip };
|
|
||||||
if (!zipFile.open(QIODevice::WriteOnly, QuaZipNewInfo("mappings.json"))) {
|
|
||||||
qDebug() << "testCreate(): outFile.open()";
|
|
||||||
}
|
|
||||||
zipFile.write(document.toJson());
|
|
||||||
zipFile.close();
|
|
||||||
if (zipFile.getZipError() != UNZ_OK) {
|
|
||||||
qDebug() << "testCreate(): outFile.close(): " << zipFile.getZipError();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void recoverBackup(QuaZip& zip) {}
|
|
||||||
void deleteBackup(QuaZip& zip) {}
|
|
||||||
void consolidateBackup(QuaZip& zip) {}
|
|
||||||
|
|
||||||
private:
|
|
||||||
BackupSupervisor* _backupSupervisor;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif /* hifi_BackupSupervisor_h */
|
#endif /* hifi_BackupSupervisor_h */
|
||||||
|
|
|
@ -28,6 +28,7 @@
|
||||||
#include <NumericalConstants.h>
|
#include <NumericalConstants.h>
|
||||||
#include <PerfStat.h>
|
#include <PerfStat.h>
|
||||||
#include <PathUtils.h>
|
#include <PathUtils.h>
|
||||||
|
#include <shared/QtHelpers.h>
|
||||||
|
|
||||||
#include "DomainServer.h"
|
#include "DomainServer.h"
|
||||||
#include "DomainContentBackupManager.h"
|
#include "DomainContentBackupManager.h"
|
||||||
|
@ -36,7 +37,8 @@ const int DomainContentBackupManager::DEFAULT_PERSIST_INTERVAL = 1000 * 30; //
|
||||||
// Backup format looks like: daily_backup-TIMESTAMP.zip
|
// Backup format looks like: daily_backup-TIMESTAMP.zip
|
||||||
const static QString DATETIME_FORMAT { "yyyy-MM-dd_HH-mm-ss" };
|
const static QString DATETIME_FORMAT { "yyyy-MM-dd_HH-mm-ss" };
|
||||||
const static QString DATETIME_FORMAT_RE("\\d{4}-\\d{2}-\\d{2}_\\d{2}-\\d{2}-\\d{2}");
|
const static 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-" };
|
||||||
void DomainContentBackupManager::addBackupHandler(BackupHandler handler) {
|
void DomainContentBackupManager::addBackupHandler(BackupHandler handler) {
|
||||||
_backupHandlers.push_back(std::move(handler));
|
_backupHandlers.push_back(std::move(handler));
|
||||||
}
|
}
|
||||||
|
@ -83,7 +85,7 @@ void DomainContentBackupManager::parseSettings(const QJsonObject& settings) {
|
||||||
|
|
||||||
auto name = obj["Name"].toString();
|
auto name = obj["Name"].toString();
|
||||||
auto format = obj["format"].toString();
|
auto format = obj["format"].toString();
|
||||||
format = name.replace(" ", "_").toLower() + "-";
|
format = name.replace(" ", "_").toLower();
|
||||||
|
|
||||||
qCDebug(domain_server) << " Name:" << name;
|
qCDebug(domain_server) << " Name:" << name;
|
||||||
qCDebug(domain_server) << " format:" << format;
|
qCDebug(domain_server) << " format:" << format;
|
||||||
|
@ -129,6 +131,14 @@ void DomainContentBackupManager::setup() {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DomainContentBackupManager::process() {
|
bool DomainContentBackupManager::process() {
|
||||||
|
if (!_initialLoadComplete) {
|
||||||
|
QDir backupDir { _backupDirectory };
|
||||||
|
if (!backupDir.exists()) {
|
||||||
|
backupDir.mkpath(".");
|
||||||
|
}
|
||||||
|
_initialLoadComplete = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (isStillRunning()) {
|
if (isStillRunning()) {
|
||||||
constexpr int64_t MSECS_TO_USECS = 1000;
|
constexpr int64_t MSECS_TO_USECS = 1000;
|
||||||
constexpr int64_t USECS_TO_SLEEP = 10 * MSECS_TO_USECS; // every 10ms
|
constexpr int64_t USECS_TO_SLEEP = 10 * MSECS_TO_USECS; // every 10ms
|
||||||
|
@ -140,7 +150,7 @@ bool DomainContentBackupManager::process() {
|
||||||
|
|
||||||
if (sinceLastSave > intervalToCheck) {
|
if (sinceLastSave > intervalToCheck) {
|
||||||
_lastCheck = now;
|
_lastCheck = now;
|
||||||
persist();
|
backup();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -149,32 +159,18 @@ bool DomainContentBackupManager::process() {
|
||||||
|
|
||||||
void DomainContentBackupManager::aboutToFinish() {
|
void DomainContentBackupManager::aboutToFinish() {
|
||||||
qCDebug(domain_server) << "Persist thread about to finish...";
|
qCDebug(domain_server) << "Persist thread about to finish...";
|
||||||
persist();
|
backup();
|
||||||
}
|
qCDebug(domain_server) << "Persist thread done with about to finish...";
|
||||||
|
_stopThread = true;
|
||||||
void DomainContentBackupManager::persist() {
|
|
||||||
QDir backupDir { _backupDirectory };
|
|
||||||
backupDir.mkpath(".");
|
|
||||||
|
|
||||||
// create our "lock" file to indicate we're saving.
|
|
||||||
QString lockFileName = _backupDirectory + "/running.lock";
|
|
||||||
|
|
||||||
std::ofstream lockFile(qPrintable(lockFileName), std::ios::out | std::ios::binary);
|
|
||||||
if (lockFile.is_open()) {
|
|
||||||
backup();
|
|
||||||
|
|
||||||
lockFile.close();
|
|
||||||
remove(qPrintable(lockFileName));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DomainContentBackupManager::getMostRecentBackup(const QString& format,
|
bool DomainContentBackupManager::getMostRecentBackup(const QString& format,
|
||||||
QString& mostRecentBackupFileName,
|
QString& mostRecentBackupFileName,
|
||||||
QDateTime& mostRecentBackupTime) {
|
QDateTime& mostRecentBackupTime) {
|
||||||
QRegExp formatRE { QRegExp::escape(format) + "(" + DATETIME_FORMAT_RE + ")" + "\\.zip" };
|
QRegExp formatRE { AUTOMATIC_BACKUP_PREFIX + QRegExp::escape(format) + "\\-(" + DATETIME_FORMAT_RE + ")" + "\\.zip" };
|
||||||
|
|
||||||
QStringList filters;
|
QStringList filters;
|
||||||
filters << format + "*.zip";
|
filters << AUTOMATIC_BACKUP_PREFIX + format + "*.zip";
|
||||||
|
|
||||||
bool bestBackupFound = false;
|
bool bestBackupFound = false;
|
||||||
QString bestBackupFile;
|
QString bestBackupFile;
|
||||||
|
@ -216,7 +212,32 @@ bool DomainContentBackupManager::getMostRecentBackup(const QString& format,
|
||||||
return bestBackupFound;
|
return bestBackupFound;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool DomainContentBackupManager::deleteBackup(const QString& backupName) {
|
||||||
|
if (QThread::currentThread() != thread()) {
|
||||||
|
bool result{ false };
|
||||||
|
BLOCKING_INVOKE_METHOD(this, "deleteBackup",
|
||||||
|
Q_RETURN_ARG(bool, result),
|
||||||
|
Q_ARG(const QString&, backupName));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
QDir backupDir { _backupDirectory };
|
||||||
|
QFile backupFile { backupDir.filePath(backupName) };
|
||||||
|
if (backupFile.remove()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
bool DomainContentBackupManager::recoverFromBackup(const QString& backupName) {
|
bool DomainContentBackupManager::recoverFromBackup(const QString& backupName) {
|
||||||
|
if (QThread::currentThread() != thread()) {
|
||||||
|
bool result{ false };
|
||||||
|
BLOCKING_INVOKE_METHOD(this, "recoverFromBackup",
|
||||||
|
Q_RETURN_ARG(bool, result),
|
||||||
|
Q_ARG(const QString&, backupName));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
qDebug() << "Recoving from" << backupName;
|
qDebug() << "Recoving from" << backupName;
|
||||||
|
|
||||||
QDir backupDir { _backupDirectory };
|
QDir backupDir { _backupDirectory };
|
||||||
|
@ -226,7 +247,6 @@ bool DomainContentBackupManager::recoverFromBackup(const QString& backupName) {
|
||||||
if (!zip.open(QuaZip::Mode::mdUnzip)) {
|
if (!zip.open(QuaZip::Mode::mdUnzip)) {
|
||||||
qWarning() << "Failed to unzip file: " << backupName;
|
qWarning() << "Failed to unzip file: " << backupName;
|
||||||
backupFile.close();
|
backupFile.close();
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto& handler : _backupHandlers) {
|
for (auto& handler : _backupHandlers) {
|
||||||
|
@ -234,11 +254,43 @@ bool DomainContentBackupManager::recoverFromBackup(const QString& backupName) {
|
||||||
}
|
}
|
||||||
|
|
||||||
backupFile.close();
|
backupFile.close();
|
||||||
|
qDebug() << "Successfully recovered from " << backupName;
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
qWarning() << "Invalid id: " << backupName;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<BackupItemInfo> DomainContentBackupManager::getAllBackups() {
|
||||||
|
std::vector<BackupItemInfo> backups;
|
||||||
|
|
||||||
|
QDir backupDir { _backupDirectory };
|
||||||
|
auto matchingFiles =
|
||||||
|
backupDir.entryInfoList({ AUTOMATIC_BACKUP_PREFIX + "*.zip", MANUAL_BACKUP_PREFIX + "*.zip" },
|
||||||
|
QDir::Files | QDir::NoSymLinks, QDir::Name);
|
||||||
|
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" };
|
||||||
|
|
||||||
|
for (const auto& fileInfo : matchingFiles) {
|
||||||
|
auto fileName = fileInfo.fileName();
|
||||||
|
if (backupNameFormat.exactMatch(fileName)) {
|
||||||
|
auto type = backupNameFormat.cap(1);
|
||||||
|
auto name = backupNameFormat.cap(2);
|
||||||
|
auto dateTime = backupNameFormat.cap(3);
|
||||||
|
auto createdAt = QDateTime::fromString(dateTime, DATETIME_FORMAT);
|
||||||
|
if (!createdAt.isValid()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
BackupItemInfo backup { fileInfo.fileName(), name, fileInfo.absoluteFilePath(), createdAt, type == MANUAL_BACKUP_PREFIX };
|
||||||
|
backups.push_back(backup);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
qDebug() << "Successfully recovered from " << backupName;
|
return backups;
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DomainContentBackupManager::removeOldBackupVersions(const BackupRule& rule) {
|
void DomainContentBackupManager::removeOldBackupVersions(const BackupRule& rule) {
|
||||||
|
@ -247,9 +299,10 @@ void DomainContentBackupManager::removeOldBackupVersions(const BackupRule& rule)
|
||||||
qCDebug(domain_server) << "Rolling old backup versions for rule" << rule.name;
|
qCDebug(domain_server) << "Rolling old backup versions for rule" << rule.name;
|
||||||
|
|
||||||
auto matchingFiles =
|
auto matchingFiles =
|
||||||
backupDir.entryInfoList({ rule.extensionFormat + "*.zip" }, QDir::Files | QDir::NoSymLinks, QDir::Name);
|
backupDir.entryInfoList({ AUTOMATIC_BACKUP_PREFIX + rule.extensionFormat + "*.zip" }, QDir::Files | QDir::NoSymLinks, QDir::Name);
|
||||||
|
|
||||||
int backupsToDelete = matchingFiles.length() - rule.maxBackupVersions;
|
int backupsToDelete = matchingFiles.length() - rule.maxBackupVersions;
|
||||||
|
qCDebug(domain_server) << "Found" << matchingFiles.length() << "backups, deleting " << backupsToDelete << "backup(s)";
|
||||||
for (int i = 0; i < backupsToDelete; ++i) {
|
for (int i = 0; i < backupsToDelete; ++i) {
|
||||||
auto fileInfo = matchingFiles[i].absoluteFilePath();
|
auto fileInfo = matchingFiles[i].absoluteFilePath();
|
||||||
QFile backupFile(fileInfo);
|
QFile backupFile(fileInfo);
|
||||||
|
@ -313,6 +366,7 @@ void DomainContentBackupManager::backup() {
|
||||||
qCDebug(domain_server) << "Time since last backup [" << secondsSinceLastBackup << "] for rule [" << rule.name
|
qCDebug(domain_server) << "Time since last backup [" << secondsSinceLastBackup << "] for rule [" << rule.name
|
||||||
<< "] exceeds backup interval [" << rule.intervalSeconds << "] doing backup now...";
|
<< "] exceeds backup interval [" << rule.intervalSeconds << "] doing backup now...";
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
auto timestamp = QDateTime::currentDateTime().toString(DATETIME_FORMAT);
|
auto timestamp = QDateTime::currentDateTime().toString(DATETIME_FORMAT);
|
||||||
auto fileName = "backup-" + rule.extensionFormat + timestamp + ".zip";
|
auto fileName = "backup-" + rule.extensionFormat + timestamp + ".zip";
|
||||||
QuaZip zip(_backupDirectory + "/" + fileName);
|
QuaZip zip(_backupDirectory + "/" + fileName);
|
||||||
|
@ -323,11 +377,17 @@ void DomainContentBackupManager::backup() {
|
||||||
|
|
||||||
for (auto& handler : _backupHandlers) {
|
for (auto& handler : _backupHandlers) {
|
||||||
handler.createBackup(zip);
|
handler.createBackup(zip);
|
||||||
|
=======
|
||||||
|
bool success;
|
||||||
|
QString path;
|
||||||
|
std::tie(success, path) = createBackup(AUTOMATIC_BACKUP_PREFIX, rule.extensionFormat);
|
||||||
|
if (!success) {
|
||||||
|
qCWarning(domain_server) << "Failed to create backup for" << rule.name << "at" << path;
|
||||||
|
continue;
|
||||||
|
>>>>>>> dd86471a42... Add backup DS APIs
|
||||||
}
|
}
|
||||||
|
|
||||||
zip.close();
|
qDebug() << "Created backup: " << path;
|
||||||
|
|
||||||
qDebug() << "Created backup: " << fileName;
|
|
||||||
|
|
||||||
rule.lastBackupSeconds = nowSeconds;
|
rule.lastBackupSeconds = nowSeconds;
|
||||||
|
|
||||||
|
@ -365,3 +425,27 @@ void DomainContentBackupManager::consolidate(QString fileName) {
|
||||||
zip.close();
|
zip.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DomainContentBackupManager::createManualBackup(const QString& name) {
|
||||||
|
createBackup(MANUAL_BACKUP_PREFIX, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<bool, QString> DomainContentBackupManager::createBackup(const QString& prefix, const QString& name) {
|
||||||
|
auto timestamp = QDateTime::currentDateTime().toString(DATETIME_FORMAT);
|
||||||
|
auto fileName = prefix + name + "-" + timestamp + ".zip";
|
||||||
|
auto path = _backupDirectory + "/" + fileName;
|
||||||
|
QuaZip zip(path);
|
||||||
|
if (!zip.open(QuaZip::mdAdd)) {
|
||||||
|
qCWarning(domain_server) << "Failed to open zip file at " << path;
|
||||||
|
qCWarning(domain_server) << " ERROR:" << zip.getZipError();
|
||||||
|
return { false, path };
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& handler : _backupHandlers) {
|
||||||
|
handler.createBackup(zip);
|
||||||
|
}
|
||||||
|
|
||||||
|
zip.close();
|
||||||
|
|
||||||
|
return { true, path };
|
||||||
|
}
|
||||||
|
|
|
@ -21,6 +21,14 @@
|
||||||
|
|
||||||
#include "BackupHandler.h"
|
#include "BackupHandler.h"
|
||||||
|
|
||||||
|
struct BackupItemInfo {
|
||||||
|
QString id;
|
||||||
|
QString name;
|
||||||
|
QString absolutePath;
|
||||||
|
QDateTime createdAt;
|
||||||
|
bool isManualBackup;
|
||||||
|
};
|
||||||
|
|
||||||
class DomainContentBackupManager : public GenericThread {
|
class DomainContentBackupManager : public GenericThread {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
|
@ -41,12 +49,18 @@ public:
|
||||||
bool debugTimestampNow = false);
|
bool debugTimestampNow = false);
|
||||||
|
|
||||||
void addBackupHandler(BackupHandler handler);
|
void addBackupHandler(BackupHandler handler);
|
||||||
|
bool isInitialLoadComplete() const { return _initialLoadComplete; }
|
||||||
|
std::vector<BackupItemInfo> getAllBackups();
|
||||||
|
|
||||||
void aboutToFinish(); /// call this to inform the persist thread that the owner is about to finish to support final persist
|
void aboutToFinish(); /// call this to inform the persist thread that the owner is about to finish to support final persist
|
||||||
|
|
||||||
void replaceData(QByteArray data);
|
void replaceData(QByteArray data);
|
||||||
|
|
||||||
|
void createManualBackup(const QString& name);
|
||||||
|
|
||||||
|
public slots:
|
||||||
bool recoverFromBackup(const QString& backupName);
|
bool recoverFromBackup(const QString& backupName);
|
||||||
|
bool deleteBackup(const QString& backupName);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void loadCompleted();
|
void loadCompleted();
|
||||||
|
@ -56,7 +70,6 @@ protected:
|
||||||
virtual void setup() override;
|
virtual void setup() override;
|
||||||
virtual bool process() override;
|
virtual bool process() override;
|
||||||
|
|
||||||
void persist();
|
|
||||||
void load();
|
void load();
|
||||||
void backup();
|
void backup();
|
||||||
void consolidate(QString fileName);
|
void consolidate(QString fileName);
|
||||||
|
@ -65,10 +78,13 @@ protected:
|
||||||
int64_t getMostRecentBackupTimeInSecs(const QString& format);
|
int64_t getMostRecentBackupTimeInSecs(const QString& format);
|
||||||
void parseSettings(const QJsonObject& settings);
|
void parseSettings(const QJsonObject& settings);
|
||||||
|
|
||||||
|
std::pair<bool, QString> createBackup(const QString& prefix, const QString& name);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QString _backupDirectory;
|
QString _backupDirectory;
|
||||||
std::vector<BackupHandler> _backupHandlers;
|
std::vector<BackupHandler> _backupHandlers;
|
||||||
int _persistInterval { 0 };
|
int _persistInterval { 0 };
|
||||||
|
bool _initialLoadComplete { false };
|
||||||
|
|
||||||
int64_t _lastCheck { 0 };
|
int64_t _lastCheck { 0 };
|
||||||
std::vector<BackupRule> _backupRules;
|
std::vector<BackupRule> _backupRules;
|
||||||
|
|
|
@ -300,7 +300,10 @@ DomainServer::DomainServer(int argc, char* argv[]) :
|
||||||
_contentManager->addBackupHandler(new BackupSupervisor(getContentBackupDir()));
|
_contentManager->addBackupHandler(new BackupSupervisor(getContentBackupDir()));
|
||||||
_contentManager->initialize(true);
|
_contentManager->initialize(true);
|
||||||
|
|
||||||
_contentManager->recoverFromBackup("backup-daily_rolling-2018-02-06_15-13-50.zip");
|
qDebug() << "Existing backups:";
|
||||||
|
for (auto& backup : _contentManager->getAllBackups()) {
|
||||||
|
qDebug() << " Backup: " << backup.name << backup.createdAt;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DomainServer::parseCommandLine() {
|
void DomainServer::parseCommandLine() {
|
||||||
|
@ -1736,6 +1739,12 @@ void DomainServer::processOctreeDataPersistMessage(QSharedPointer<ReceivedMessag
|
||||||
auto data = message->readAll();
|
auto data = message->readAll();
|
||||||
auto filePath = getEntitiesFilePath();
|
auto filePath = getEntitiesFilePath();
|
||||||
|
|
||||||
|
QDir dir(getEntitiesDirPath());
|
||||||
|
if (!dir.exists()) {
|
||||||
|
qCDebug(domain_server) << "Creating entities content directory:" << dir.absolutePath();
|
||||||
|
dir.mkpath(".");
|
||||||
|
}
|
||||||
|
|
||||||
QFile f(filePath);
|
QFile f(filePath);
|
||||||
if (f.open(QIODevice::WriteOnly)) {
|
if (f.open(QIODevice::WriteOnly)) {
|
||||||
f.write(data);
|
f.write(data);
|
||||||
|
@ -1746,12 +1755,12 @@ void DomainServer::processOctreeDataPersistMessage(QSharedPointer<ReceivedMessag
|
||||||
qCDebug(domain_server) << "Failed to read new octree data info";
|
qCDebug(domain_server) << "Failed to read new octree data info";
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
qCDebug(domain_server) << "Failed to write new entities file";
|
qCDebug(domain_server) << "Failed to write new entities file:" << filePath;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QString DomainServer::getContentBackupDir() {
|
QString DomainServer::getContentBackupDir() {
|
||||||
return PathUtils::getAppDataFilePath("backup");
|
return PathUtils::getAppDataFilePath("backups");
|
||||||
}
|
}
|
||||||
|
|
||||||
QString DomainServer::getEntitiesDirPath() {
|
QString DomainServer::getEntitiesDirPath() {
|
||||||
|
@ -1924,6 +1933,10 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
|
||||||
const QString URI_API_PLACES = "/api/places";
|
const QString URI_API_PLACES = "/api/places";
|
||||||
const QString URI_API_DOMAINS = "/api/domains";
|
const QString URI_API_DOMAINS = "/api/domains";
|
||||||
const QString URI_API_DOMAINS_ID = "/api/domains/";
|
const QString URI_API_DOMAINS_ID = "/api/domains/";
|
||||||
|
const QString URI_API_BACKUPS = "/api/backups";
|
||||||
|
const QString URI_API_BACKUPS_ID = "/api/backups/";
|
||||||
|
const QString URI_API_BACKUPS_RECOVER = "/api/backups/recover/";
|
||||||
|
//const QString URI_API_BACKUPS_CREATE = "/api/backups";
|
||||||
|
|
||||||
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}";
|
||||||
|
|
||||||
|
@ -2108,6 +2121,34 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
|
||||||
// send the response
|
// send the response
|
||||||
connection->respond(HTTPConnection::StatusCode200, nodesDocument.toJson(), qPrintable(JSON_MIME_TYPE));
|
connection->respond(HTTPConnection::StatusCode200, nodesDocument.toJson(), qPrintable(JSON_MIME_TYPE));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} else if (url.path().startsWith(URI_API_BACKUPS_RECOVER)) {
|
||||||
|
auto id = url.path().mid(QString(URI_API_BACKUPS_RECOVER).length());
|
||||||
|
_contentManager->recoverFromBackup(id);
|
||||||
|
QJsonObject rootJSON;
|
||||||
|
rootJSON["success"] = true;
|
||||||
|
QJsonDocument docJSON(rootJSON);
|
||||||
|
connection->respond(HTTPConnection::StatusCode200, docJSON.toJson(), JSON_MIME_TYPE.toUtf8());
|
||||||
|
return true;
|
||||||
|
} else if (url.path() == URI_API_BACKUPS) {
|
||||||
|
QJsonObject rootJSON;
|
||||||
|
QJsonArray backupsJSON;
|
||||||
|
|
||||||
|
auto backups = _contentManager->getAllBackups();
|
||||||
|
|
||||||
|
for (const auto& backup : backups) {
|
||||||
|
QJsonObject obj;
|
||||||
|
obj["id"] = backup.id;
|
||||||
|
obj["name"] = backup.name;
|
||||||
|
obj["createdAtMillis"] = backup.createdAt.toMSecsSinceEpoch();
|
||||||
|
obj["isManualBackup"] = backup.isManualBackup;
|
||||||
|
backupsJSON.push_back(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
rootJSON["backups"] = backupsJSON;
|
||||||
|
QJsonDocument docJSON(rootJSON);
|
||||||
|
|
||||||
|
connection->respond(HTTPConnection::StatusCode200, docJSON.toJson(), JSON_MIME_TYPE.toUtf8());
|
||||||
return true;
|
return true;
|
||||||
} else if (url.path() == URI_RESTART) {
|
} else if (url.path() == URI_RESTART) {
|
||||||
connection->respond(HTTPConnection::StatusCode200);
|
connection->respond(HTTPConnection::StatusCode200);
|
||||||
|
@ -2213,6 +2254,20 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
} else if (url.path() == URI_API_BACKUPS) {
|
||||||
|
qDebug() << "GOt request to create a backup:";
|
||||||
|
auto params = connection->parseUrlEncodedForm();
|
||||||
|
auto it = params.find("name");
|
||||||
|
if (it == params.end()) {
|
||||||
|
connection->respond(HTTPConnection::StatusCode400, "Bad request, missing `name`");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
_contentManager->createManualBackup(it.value());
|
||||||
|
|
||||||
|
connection->respond(HTTPConnection::StatusCode200);
|
||||||
|
return true;
|
||||||
|
|
||||||
} else if (url.path() == "/domain_settings") {
|
} else if (url.path() == "/domain_settings") {
|
||||||
auto accessTokenVariant = valueForKeyPath(_settingsManager.getSettingsMap(), ACCESS_TOKEN_KEY_PATH);
|
auto accessTokenVariant = valueForKeyPath(_settingsManager.getSettingsMap(), ACCESS_TOKEN_KEY_PATH);
|
||||||
if (!accessTokenVariant) {
|
if (!accessTokenVariant) {
|
||||||
|
@ -2311,7 +2366,16 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
|
||||||
QRegExp allNodesDeleteRegex(ALL_NODE_DELETE_REGEX_STRING);
|
QRegExp allNodesDeleteRegex(ALL_NODE_DELETE_REGEX_STRING);
|
||||||
QRegExp nodeDeleteRegex(NODE_DELETE_REGEX_STRING);
|
QRegExp nodeDeleteRegex(NODE_DELETE_REGEX_STRING);
|
||||||
|
|
||||||
if (nodeDeleteRegex.indexIn(url.path()) != -1) {
|
if (url.path().startsWith(URI_API_BACKUPS_ID)) {
|
||||||
|
auto id = url.path().mid(QString(URI_API_BACKUPS_ID).length());
|
||||||
|
auto success = _contentManager->deleteBackup(id);
|
||||||
|
QJsonObject rootJSON;
|
||||||
|
rootJSON["success"] = success;
|
||||||
|
QJsonDocument docJSON(rootJSON);
|
||||||
|
connection->respond(HTTPConnection::StatusCode200, docJSON.toJson(), JSON_MIME_TYPE.toUtf8());
|
||||||
|
return true;
|
||||||
|
|
||||||
|
} else if (nodeDeleteRegex.indexIn(url.path()) != -1) {
|
||||||
// this is a request to DELETE one node by UUID
|
// this is a request to DELETE one node by UUID
|
||||||
|
|
||||||
// pull the captured string, if it exists
|
// pull the captured string, if it exists
|
||||||
|
|
|
@ -133,12 +133,33 @@ QList<FormData> HTTPConnection::parseFormData() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
void HTTPConnection::respond(const char* code, const QByteArray& content, const char* contentType, const Headers& headers) {
|
void HTTPConnection::respond(const char* code, const QByteArray& content, const char* contentType, const Headers& headers) {
|
||||||
|
QByteArray data(content);
|
||||||
|
auto device { std::unique_ptr<QBuffer>(new QBuffer()) };
|
||||||
|
device->setBuffer(new QByteArray(content));
|
||||||
|
if (device->open(QIODevice::ReadOnly)) {
|
||||||
|
respond(code, std::move(device), contentType, headers);
|
||||||
|
} else {
|
||||||
|
qCritical() << "Error opening QBuffer to respond to " << _requestUrl.path();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HTTPConnection::respond(const char* code, std::unique_ptr<QIODevice> device, const char* contentType, const Headers& headers) {
|
||||||
|
_responseDevice = std::move(device);
|
||||||
|
|
||||||
_socket->write("HTTP/1.1 ");
|
_socket->write("HTTP/1.1 ");
|
||||||
|
|
||||||
|
if (_responseDevice->isSequential()) {
|
||||||
|
qWarning() << "Error responding to HTTPConnection: sequential IO devices not supported";
|
||||||
|
_socket->write(StatusCode500);
|
||||||
|
_socket->write("\r\n");
|
||||||
|
_socket->disconnect(SIGNAL(readyRead()), this);
|
||||||
|
_socket->disconnectFromHost();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
_socket->write(code);
|
_socket->write(code);
|
||||||
_socket->write("\r\n");
|
_socket->write("\r\n");
|
||||||
|
|
||||||
int csize = content.size();
|
|
||||||
|
|
||||||
for (Headers::const_iterator it = headers.constBegin(), end = headers.constEnd();
|
for (Headers::const_iterator it = headers.constBegin(), end = headers.constEnd();
|
||||||
it != end; it++) {
|
it != end; it++) {
|
||||||
_socket->write(it.key());
|
_socket->write(it.key());
|
||||||
|
@ -146,6 +167,8 @@ void HTTPConnection::respond(const char* code, const QByteArray& content, const
|
||||||
_socket->write(it.value());
|
_socket->write(it.value());
|
||||||
_socket->write("\r\n");
|
_socket->write("\r\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int csize = _responseDevice->size();
|
||||||
if (csize > 0) {
|
if (csize > 0) {
|
||||||
_socket->write("Content-Length: ");
|
_socket->write("Content-Length: ");
|
||||||
_socket->write(QByteArray::number(csize));
|
_socket->write(QByteArray::number(csize));
|
||||||
|
@ -157,20 +180,35 @@ void HTTPConnection::respond(const char* code, const QByteArray& content, const
|
||||||
}
|
}
|
||||||
_socket->write("Connection: close\r\n\r\n");
|
_socket->write("Connection: close\r\n\r\n");
|
||||||
|
|
||||||
if (csize > 0) {
|
if (_responseDevice->atEnd()) {
|
||||||
_socket->write(content);
|
_socket->disconnectFromHost();
|
||||||
|
} else {
|
||||||
|
constexpr size_t HTTP_RESPONSE_CHUNK_SIZE = 1024 * 10;
|
||||||
|
int totalToBeWritten = csize;
|
||||||
|
connect(_socket, &QTcpSocket::bytesWritten, this, [this, totalToBeWritten](size_t bytes) mutable {
|
||||||
|
if (!_responseDevice->atEnd()) {
|
||||||
|
totalToBeWritten -= _socket->write(_responseDevice->read(HTTP_RESPONSE_CHUNK_SIZE));
|
||||||
|
if (_responseDevice->atEnd()) {
|
||||||
|
_socket->disconnectFromHost();
|
||||||
|
disconnect(_socket, &QTcpSocket::bytesWritten, this, nullptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// make sure we receive no further read notifications
|
// make sure we receive no further read notifications
|
||||||
_socket->disconnect(SIGNAL(readyRead()), this);
|
disconnect(_socket, &QTcpSocket::readyRead, this, nullptr);
|
||||||
|
|
||||||
_socket->disconnectFromHost();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void HTTPConnection::readRequest() {
|
void HTTPConnection::readRequest() {
|
||||||
if (!_socket->canReadLine()) {
|
if (!_socket->canReadLine()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (!_requestUrl.isEmpty()) {
|
||||||
|
qDebug() << "Request URL was already set";
|
||||||
|
return;
|
||||||
|
}
|
||||||
// parse out the method and resource
|
// parse out the method and resource
|
||||||
QByteArray line = _socket->readLine().trimmed();
|
QByteArray line = _socket->readLine().trimmed();
|
||||||
if (line.startsWith("HEAD")) {
|
if (line.startsWith("HEAD")) {
|
||||||
|
@ -249,6 +287,7 @@ void HTTPConnection::readContent() {
|
||||||
if (_socket->bytesAvailable() < size) {
|
if (_socket->bytesAvailable() < size) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
qDebug() << "Reading content";
|
||||||
_socket->read(_requestContent.data(), size);
|
_socket->read(_requestContent.data(), size);
|
||||||
_socket->disconnect(this, SLOT(readContent()));
|
_socket->disconnect(this, SLOT(readContent()));
|
||||||
|
|
||||||
|
|
|
@ -87,6 +87,9 @@ public:
|
||||||
void respond (const char* code, const QByteArray& content = QByteArray(),
|
void respond (const char* code, const QByteArray& content = QByteArray(),
|
||||||
const char* contentType = DefaultContentType,
|
const char* contentType = DefaultContentType,
|
||||||
const Headers& headers = Headers());
|
const Headers& headers = Headers());
|
||||||
|
void respond (const char* code, std::unique_ptr<QIODevice> device,
|
||||||
|
const char* contentType = DefaultContentType,
|
||||||
|
const Headers& headers = Headers());
|
||||||
|
|
||||||
protected slots:
|
protected slots:
|
||||||
|
|
||||||
|
@ -127,6 +130,9 @@ protected:
|
||||||
|
|
||||||
/// The content of the request.
|
/// The content of the request.
|
||||||
QByteArray _requestContent;
|
QByteArray _requestContent;
|
||||||
|
|
||||||
|
/// Response content
|
||||||
|
std::unique_ptr<QIODevice> _responseDevice;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_HTTPConnection_h
|
#endif // hifi_HTTPConnection_h
|
||||||
|
|
|
@ -98,13 +98,14 @@ bool HTTPManager::handleHTTPRequest(HTTPConnection* connection, const QUrl& url,
|
||||||
// file exists, serve it
|
// file exists, serve it
|
||||||
static QMimeDatabase mimeDatabase;
|
static QMimeDatabase mimeDatabase;
|
||||||
|
|
||||||
QFile localFile(filePath);
|
auto localFile = std::unique_ptr<QFile>(new QFile(filePath));
|
||||||
localFile.open(QIODevice::ReadOnly);
|
localFile->open(QIODevice::ReadOnly);
|
||||||
QByteArray localFileData = localFile.readAll();
|
QByteArray localFileData;
|
||||||
|
|
||||||
QFileInfo localFileInfo(filePath);
|
QFileInfo localFileInfo(filePath);
|
||||||
|
|
||||||
if (localFileInfo.completeSuffix() == "shtml") {
|
if (localFileInfo.completeSuffix() == "shtml") {
|
||||||
|
localFileData = localFile->readAll();
|
||||||
// this is a file that may have some SSI statements
|
// this is a file that may have some SSI statements
|
||||||
// the only thing we support is the include directive, but check the contents for that
|
// the only thing we support is the include directive, but check the contents for that
|
||||||
|
|
||||||
|
@ -153,8 +154,12 @@ bool HTTPManager::handleHTTPRequest(HTTPConnection* connection, const QUrl& url,
|
||||||
? QString { "text/html" }
|
? QString { "text/html" }
|
||||||
: mimeDatabase.mimeTypeForFile(filePath).name();
|
: mimeDatabase.mimeTypeForFile(filePath).name();
|
||||||
|
|
||||||
connection->respond(HTTPConnection::StatusCode200, localFileData, qPrintable(mimeType));
|
if (localFileData.isNull()) {
|
||||||
|
connection->respond(HTTPConnection::StatusCode200, std::move(localFile), qPrintable(mimeType));
|
||||||
|
} else {
|
||||||
|
connection->respond(HTTPConnection::StatusCode200, localFileData, qPrintable(mimeType));
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue