Write assets to disk when recovering full backup

This commit is contained in:
Atlante45 2018-02-15 11:25:07 -08:00
parent 9fca92facd
commit d6e2814081
2 changed files with 99 additions and 54 deletions

View file

@ -16,6 +16,7 @@
#include <QtCore/QLoggingCategory>
#include <quazip5/quazipfile.h>
#include <quazip5/quazipdir.h>
#include <AssetClient.h>
#include <AssetRequest.h>
@ -23,13 +24,14 @@
#include <MappingRequest.h>
#include <PathUtils.h>
const QString ASSETS_DIR = "/assets/";
const QString MAPPINGS_FILE = "mappings.json";
static const QString ASSETS_DIR = "/assets/";
static const QString MAPPINGS_FILE = "mappings.json";
static const QString ZIP_ASSETS_FOLDER = "files";
using namespace std;
Q_DECLARE_LOGGING_CATEGORY(backup_supervisor)
Q_LOGGING_CATEGORY(backup_supervisor, "hifi.backup-supervisor");
Q_DECLARE_LOGGING_CATEGORY(asset_backup)
Q_LOGGING_CATEGORY(asset_backup, "hifi.asset-backup");
AssetsBackupHandler::AssetsBackupHandler(const QString& backupDirectory) :
_assetsDirectory(backupDirectory + ASSETS_DIR)
@ -39,6 +41,10 @@ AssetsBackupHandler::AssetsBackupHandler(const QString& backupDirectory) :
refreshAssetsOnDisk();
setupRefreshTimer();
}
void AssetsBackupHandler::setupRefreshTimer() {
_mappingsRefreshTimer.setTimerType(Qt::CoarseTimer);
_mappingsRefreshTimer.setSingleShot(true);
QObject::connect(&_mappingsRefreshTimer, &QTimer::timeout, this, &AssetsBackupHandler::refreshMappings);
@ -50,9 +56,14 @@ AssetsBackupHandler::AssetsBackupHandler(const QString& backupDirectory) :
_mappingsRefreshTimer.start(0);
}
});
QObject::connect(nodeList.data(), &LimitedNodeList::nodeKilled, this, [this](SharedNodePointer node) {
if (node->getType() == NodeType::AssetServer) {
// run immediately for the first time.
_mappingsRefreshTimer.stop();
}
});
}
void AssetsBackupHandler::refreshAssetsOnDisk() {
QDir assetsDir { _assetsDirectory };
auto assetNames = assetsDir.entryList(QDir::Files);
@ -79,7 +90,7 @@ void AssetsBackupHandler::checkForMissingAssets() {
begin(_assetsOnDisk), end(_assetsOnDisk),
back_inserter(missingAssets));
if (missingAssets.size() > 0) {
qCWarning(backup_supervisor) << "Found" << missingAssets.size() << "assets missing.";
qCWarning(asset_backup) << "Found" << missingAssets.size() << "backup assets missing from disk.";
}
}
@ -90,24 +101,26 @@ void AssetsBackupHandler::checkForAssetsToDelete() {
back_inserter(deprecatedAssets));
if (deprecatedAssets.size() > 0) {
qCDebug(backup_supervisor) << "Found" << deprecatedAssets.size() << "assets to delete.";
qCDebug(asset_backup) << "Found" << deprecatedAssets.size() << "backup assets to delete from disk.";
if (_allBackupsLoadedSuccessfully) {
for (const auto& hash : deprecatedAssets) {
QFile::remove(_assetsDirectory + hash);
}
} else {
qCWarning(backup_supervisor) << "Some backups did not load properly, aborting deleting for safety.";
qCWarning(asset_backup) << "Some backups did not load properly, aborting delete operation for safety.";
}
}
}
void AssetsBackupHandler::loadBackup(QuaZip& zip) {
Q_ASSERT(QThread::currentThread() == thread());
_backups.push_back({ zip.getZipName(), {}, false });
auto& backup = _backups.back();
if (!zip.setCurrentFile(MAPPINGS_FILE)) {
qCCritical(backup_supervisor) << "Failed to find" << MAPPINGS_FILE << "while recovering backup";
qCCritical(backup_supervisor) << " Error:" << zip.getZipError();
qCCritical(asset_backup) << "Failed to find" << MAPPINGS_FILE << "while loading backup";
qCCritical(asset_backup) << " Error:" << zip.getZipError();
backup.corruptedBackup = true;
_allBackupsLoadedSuccessfully = false;
return;
@ -115,8 +128,8 @@ void AssetsBackupHandler::loadBackup(QuaZip& zip) {
QuaZipFile zipFile { &zip };
if (!zipFile.open(QFile::ReadOnly)) {
qCCritical(backup_supervisor) << "Could not open backup file:" << zip.getZipName();
qCCritical(backup_supervisor) << " Error:" << zip.getZipError();
qCCritical(asset_backup) << "Could not unzip backup file for load:" << zip.getZipName();
qCCritical(asset_backup) << " Error:" << zip.getZipError();
backup.corruptedBackup = true;
_allBackupsLoadedSuccessfully = false;
return;
@ -125,8 +138,8 @@ void AssetsBackupHandler::loadBackup(QuaZip& zip) {
QJsonParseError error;
auto document = QJsonDocument::fromJson(zipFile.readAll(), &error);
if (document.isNull() || !document.isObject()) {
qCCritical(backup_supervisor) << "Could not parse backup file to JSON object:" << zip.getZipName();
qCCritical(backup_supervisor) << " Error:" << error.errorString();
qCCritical(asset_backup) << "Could not parse backup file to JSON object for load:" << zip.getZipName();
qCCritical(asset_backup) << " Error:" << error.errorString();
backup.corruptedBackup = true;
_allBackupsLoadedSuccessfully = false;
return;
@ -138,33 +151,37 @@ void AssetsBackupHandler::loadBackup(QuaZip& zip) {
const auto& assetHash = it.value().toString();
if (!AssetUtils::isValidHash(assetHash)) {
qCCritical(backup_supervisor) << "Corrupted mapping in backup file" << zip.getZipName() << ":" << it.key();
qCCritical(asset_backup) << "Corrupted mapping in loading backup file" << zip.getZipName() << ":" << it.key();
backup.corruptedBackup = true;
_allBackupsLoadedSuccessfully = false;
return;
continue;
}
backup.mappings[assetPath] = assetHash;
_assetsInBackups.insert(assetHash);
}
checkForMissingAssets();
checkForAssetsToDelete();
return;
}
void AssetsBackupHandler::createBackup(QuaZip& zip) {
Q_ASSERT(QThread::currentThread() == thread());
if (operationInProgress()) {
qCWarning(backup_supervisor) << "There is already an operation in progress.";
qCWarning(asset_backup) << "There is already an operation in progress.";
return;
}
if (_lastMappingsRefresh == 0) {
qCWarning(backup_supervisor) << "Current mappings not yet loaded.";
qCWarning(asset_backup) << "Current mappings not yet loaded.";
return;
}
static constexpr quint64 MAX_REFRESH_TIME = 15 * 60 * 1000 * 1000;
if (usecTimestampNow() - _lastMappingsRefresh > MAX_REFRESH_TIME) {
qCWarning(backup_supervisor) << "Backing up asset mappings that might be stale.";
qCWarning(asset_backup) << "Backing up asset mappings that might be stale.";
}
AssetServerBackup backup;
@ -180,43 +197,65 @@ void AssetsBackupHandler::createBackup(QuaZip& zip) {
QuaZipFile zipFile { &zip };
if (!zipFile.open(QIODevice::WriteOnly, QuaZipNewInfo(MAPPINGS_FILE))) {
qCDebug(backup_supervisor) << "Could not open zip file:" << zipFile.getZipError();
qCDebug(asset_backup) << "Could not open zip file:" << zipFile.getZipError();
return;
}
zipFile.write(document.toJson());
zipFile.close();
if (zipFile.getZipError() != UNZ_OK) {
qCDebug(backup_supervisor) << "Could not close zip file: " << zipFile.getZipError();
qCDebug(asset_backup) << "Could not close zip file: " << zipFile.getZipError();
return;
}
_backups.push_back(backup);
}
void AssetsBackupHandler::recoverBackup(QuaZip& zip) {
Q_ASSERT(QThread::currentThread() == thread());
if (operationInProgress()) {
qCWarning(backup_supervisor) << "There is already a backup/restore in progress.";
qCWarning(asset_backup) << "There is already a backup/restore in progress.";
return;
}
if (_lastMappingsRefresh == 0) {
qCWarning(backup_supervisor) << "Current mappings not yet loaded.";
qCWarning(asset_backup) << "Current mappings not yet loaded.";
return;
}
static constexpr quint64 MAX_REFRESH_TIME = 15 * 60 * 1000 * 1000;
if (usecTimestampNow() - _lastMappingsRefresh > MAX_REFRESH_TIME) {
qCWarning(backup_supervisor) << "Current asset mappings that might be stale.";
qCWarning(asset_backup) << "Current asset mappings that might be stale.";
}
startOperation();
auto it = find_if(begin(_backups), end(_backups), [&](const std::vector<AssetServerBackup>::value_type& value) {
return value.filePath == zip.getZipName();
});
if (it == end(_backups)) {
qCDebug(backup_supervisor) << "Could not find backup" << zip.getZipName() << "to restore.";
stopOperation();
return;
qCDebug(asset_backup) << "Could not find backup" << zip.getZipName() << "to restore.";
loadBackup(zip);
QuaZipDir zipDir { &zip, ZIP_ASSETS_FOLDER };
auto assetNames = zipDir.entryList(QDir::Files);
for (const auto& asset : assetNames) {
if (AssetUtils::isValidHash(asset)) {
if (!zip.setCurrentFile(MAPPINGS_FILE)) {
qCCritical(asset_backup) << "Failed to find" << asset << "while recovering backup";
qCCritical(asset_backup) << " Error:" << zip.getZipError();
continue;
}
QuaZipFile zipFile { &zip };
if (!zipFile.open(QFile::ReadOnly)) {
qCCritical(asset_backup) << "Could not unzip asset file:" << asset;
qCCritical(asset_backup) << " Error:" << zip.getZipError();
continue;
}
writeAssetFile(asset, zipFile.readAll());
}
}
}
const auto& newMappings = it->mappings;
@ -226,8 +265,10 @@ void AssetsBackupHandler::recoverBackup(QuaZip& zip) {
}
void AssetsBackupHandler::deleteBackup(QuaZip& zip) {
Q_ASSERT(QThread::currentThread() == thread());
if (operationInProgress()) {
qCWarning(backup_supervisor) << "There is a backup/restore in progress.";
qCWarning(asset_backup) << "There is a backup/restore in progress.";
return;
}
@ -235,7 +276,7 @@ void AssetsBackupHandler::deleteBackup(QuaZip& zip) {
return value.filePath == zip.getZipName();
});
if (it == end(_backups)) {
qCDebug(backup_supervisor) << "Could not find backup" << zip.getZipName() << "to delete.";
qCDebug(asset_backup) << "Could not find backup" << zip.getZipName() << "to delete.";
return;
}
@ -244,8 +285,10 @@ void AssetsBackupHandler::deleteBackup(QuaZip& zip) {
}
void AssetsBackupHandler::consolidateBackup(QuaZip& zip) {
Q_ASSERT(QThread::currentThread() == thread());
if (operationInProgress()) {
qCWarning(backup_supervisor) << "There is a backup/restore in progress.";
qCWarning(asset_backup) << "There is a backup/restore in progress.";
return;
}
QFileInfo zipInfo(zip.getZipName());
@ -255,7 +298,7 @@ void AssetsBackupHandler::consolidateBackup(QuaZip& zip) {
return info.fileName() == zipInfo.fileName();
});
if (it == end(_backups)) {
qCDebug(backup_supervisor) << "Could not find backup" << zip.getZipName() << "to consolidate.";
qCDebug(asset_backup) << "Could not find backup" << zip.getZipName() << "to consolidate.";
return;
}
@ -265,20 +308,19 @@ void AssetsBackupHandler::consolidateBackup(QuaZip& zip) {
QDir assetsDir { _assetsDirectory };
QFile file { assetsDir.filePath(hash) };
if (!file.open(QFile::ReadOnly)) {
qCCritical(backup_supervisor) << "Could not open asset file" << file.fileName();
qCCritical(asset_backup) << "Could not open asset file" << file.fileName();
continue;
}
QuaZipFile zipFile { &zip };
static const QString ZIP_ASSETS_FOLDER = "files/";
if (!zipFile.open(QIODevice::WriteOnly, QuaZipNewInfo(ZIP_ASSETS_FOLDER + hash))) {
qCDebug(backup_supervisor) << "Could not open zip file:" << zipFile.getZipError();
if (!zipFile.open(QIODevice::WriteOnly, QuaZipNewInfo(ZIP_ASSETS_FOLDER + "/" + hash))) {
qCDebug(asset_backup) << "Could not open zip file:" << zipFile.getZipError();
continue;
}
zipFile.write(file.readAll());
zipFile.close();
if (zipFile.getZipError() != UNZ_OK) {
qCDebug(backup_supervisor) << "Could not close zip file: " << zipFile.getZipError();
qCDebug(asset_backup) << "Could not close zip file: " << zipFile.getZipError();
continue;
}
}
@ -300,8 +342,8 @@ void AssetsBackupHandler::refreshMappings() {
downloadMissingFiles(_currentMappings);
} else {
qCCritical(backup_supervisor) << "Could not refresh asset server mappings.";
qCCritical(backup_supervisor) << " Error:" << request->getErrorString();
qCCritical(asset_backup) << "Could not refresh asset server mappings.";
qCCritical(asset_backup) << " Error:" << request->getErrorString();
}
request->deleteLater();
@ -341,14 +383,14 @@ void AssetsBackupHandler::downloadNextMissingFile() {
QObject::connect(assetRequest, &AssetRequest::finished, this, [this](AssetRequest* request) {
if (request->getError() == AssetRequest::NoError) {
qCDebug(backup_supervisor) << "Backing up asset" << request->getHash();
qCDebug(asset_backup) << "Backing up asset" << request->getHash();
bool success = writeAssetFile(request->getHash(), request->getData());
if (!success) {
qCCritical(backup_supervisor) << "Failed to write asset file" << request->getHash();
qCCritical(asset_backup) << "Failed to write asset file" << request->getHash();
}
} else {
qCCritical(backup_supervisor) << "Failed to backup asset" << request->getHash();
qCCritical(asset_backup) << "Failed to backup asset" << request->getHash();
}
_assetsLeftToRequest.erase(request->getHash());
@ -364,13 +406,13 @@ bool AssetsBackupHandler::writeAssetFile(const AssetUtils::AssetHash& hash, cons
QDir assetsDir { _assetsDirectory };
QFile file { assetsDir.filePath(hash) };
if (!file.open(QFile::WriteOnly)) {
qCCritical(backup_supervisor) << "Could not open backup file" << file.fileName();
qCCritical(asset_backup) << "Could not open asset file for write:" << file.fileName();
return false;
}
auto bytesWritten = file.write(data);
if (bytesWritten != data.size()) {
qCCritical(backup_supervisor) << "Could not write data to file" << file.fileName();
qCCritical(asset_backup) << "Could not write data to file" << file.fileName();
file.remove();
return false;
}
@ -410,9 +452,9 @@ void AssetsBackupHandler::computeServerStateDifference(const AssetUtils::Mapping
}
}
qCDebug(backup_supervisor) << "Mappings to set:" << _mappingsLeftToSet.size();
qCDebug(backup_supervisor) << "Mappings to del:" << _mappingsLeftToDelete.size();
qCDebug(backup_supervisor) << "Assets to upload:" << _assetsLeftToUpload.size();
qCDebug(asset_backup) << "Mappings to set:" << _mappingsLeftToSet.size();
qCDebug(asset_backup) << "Mappings to del:" << _mappingsLeftToDelete.size();
qCDebug(asset_backup) << "Assets to upload:" << _assetsLeftToUpload.size();
}
void AssetsBackupHandler::restoreAllAssets() {
@ -420,6 +462,8 @@ void AssetsBackupHandler::restoreAllAssets() {
}
void AssetsBackupHandler::restoreNextAsset() {
startOperation();
if (_assetsLeftToUpload.empty()) {
updateMappings();
return;
@ -435,8 +479,8 @@ void AssetsBackupHandler::restoreNextAsset() {
QObject::connect(request, &AssetUpload::finished, this, [this](AssetUpload* request) {
if (request->getError() != AssetUpload::NoError) {
qCCritical(backup_supervisor) << "Failed to restore asset:" << request->getFilename();
qCCritical(backup_supervisor) << " Error:" << request->getErrorString();
qCCritical(asset_backup) << "Failed to restore asset:" << request->getFilename();
qCCritical(asset_backup) << " Error:" << request->getErrorString();
}
restoreNextAsset();
@ -453,8 +497,8 @@ void AssetsBackupHandler::updateMappings() {
auto request = assetClient->createSetMappingRequest(mapping.first, mapping.second);
QObject::connect(request, &SetMappingRequest::finished, this, [this](SetMappingRequest* request) {
if (request->getError() != MappingRequest::NoError) {
qCCritical(backup_supervisor) << "Failed to set mapping:" << request->getPath();
qCCritical(backup_supervisor) << " Error:" << request->getErrorString();
qCCritical(asset_backup) << "Failed to set mapping:" << request->getPath();
qCCritical(asset_backup) << " Error:" << request->getErrorString();
}
if (--_mappingRequestsInFlight == 0) {
@ -472,8 +516,8 @@ void AssetsBackupHandler::updateMappings() {
auto request = assetClient->createDeleteMappingsRequest(_mappingsLeftToDelete);
QObject::connect(request, &DeleteMappingsRequest::finished, this, [this](DeleteMappingsRequest* request) {
if (request->getError() != MappingRequest::NoError) {
qCCritical(backup_supervisor) << "Failed to delete mappings";
qCCritical(backup_supervisor) << " Error:" << request->getErrorString();
qCCritical(asset_backup) << "Failed to delete mappings";
qCCritical(asset_backup) << " Error:" << request->getErrorString();
}
if (--_mappingRequestsInFlight == 0) {

View file

@ -40,6 +40,7 @@ public:
bool operationInProgress() const { return _operationInProgress; }
private:
void setupRefreshTimer();
void refreshMappings();
void refreshAssetsInBackups();