380 lines · 18.5 KB · C++
RawDownload
////  ATPAssetMigrator.cpp//  interface/src/assets////  Created by Stephen Birarda on 2015-10-12.//  Copyright 2015 High Fidelity, Inc.////  Distributed under the Apache License, Version 2.0.//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html// #include "ATPAssetMigrator.h" #include <QtCore/QDebug>#include <QtCore/QJsonDocument>#include <QtCore/QJsonObject>#include <QtCore/QLoggingCategory>#include <QtCore/QTemporaryFile>#include <QtWidgets/QFileDialog>#include <QtWidgets/QMessageBox> #include <Gzip.h> #include <AssetClient.h>#include <AssetUpload.h>#include <ResourceManager.h>#include <MappingRequest.h> #include "OffscreenUi.h" Q_DECLARE_LOGGING_CATEGORY(asset_migrator);Q_LOGGING_CATEGORY(asset_migrator, "hf.asset_migrator"); ATPAssetMigrator& ATPAssetMigrator::getInstance() {    static ATPAssetMigrator instance;    return instance;} static const QString ENTITIES_OBJECT_KEY = "Entities";static const QString MODEL_URL_KEY = "modelURL";static const QString COMPOUND_SHAPE_URL_KEY = "compoundShapeURL";static const QString MESSAGE_BOX_TITLE = "ATP Asset Migration"; void ATPAssetMigrator::loadEntityServerFile() {    ModalDialogListener* dlg = OffscreenUi::getOpenFileNameAsync(_dialogParent, tr("Select an entity-server content file to migrate"),                                      QString(), tr("Entity-Server Content (*.gz)"));     connect(dlg, &ModalDialogListener::response, this, [=, this] (QVariant response) {        const QString& filename = response.toString();        disconnect(dlg, &ModalDialogListener::response, this, nullptr);        if (!filename.isEmpty()) {            qCDebug(asset_migrator) << "Selected filename for ATP asset migration: " << filename;             auto migrateResources = [=, this](QUrl migrationURL, QJsonValueRef jsonValue, bool isModelURL) {                auto request =                        DependencyManager::get<ResourceManager>()->createResourceRequest(                            this, migrationURL, true, -1, "ATPAssetMigrator::loadEntityServerFile");                 if (request) {                    qCDebug(asset_migrator) << "Requesting" << migrationURL << "for ATP asset migration";                     // add this combination of QUrl and QJsonValueRef to our multi hash so we can change the URL                    // to an ATP one once ready                    _pendingReplacements.insert(migrationURL, { jsonValue, (isModelURL ? 0 : 1)});                     connect(request, &ResourceRequest::finished, this, [=, this]() {                        if (request->getResult() == ResourceRequest::Success) {                            migrateResource(request);                        } else {                            ++_errorCount;                            _pendingReplacements.remove(migrationURL);                            qWarning() << "Could not retrieve asset";                             checkIfFinished();                        }                        request->deleteLater();                    });                     request->send();                } else {                    ++_errorCount;                    qWarning() << "Could not create request for asset";                }             };            static const QString MIGRATION_CONFIRMATION_TEXT {                "The ATP Asset Migration process will scan the selected entity-server file,\nupload discovered resources to the"\                " current asset-server\nand then save a new entity-server file with the ATP URLs.\n\nAre you ready to"\                " continue?\n\nMake sure you are connected to the right domain."            };            ModalDialogListener* migrationConfirmDialog = OffscreenUi::asyncQuestion(_dialogParent, MESSAGE_BOX_TITLE, MIGRATION_CONFIRMATION_TEXT,                                       QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);            QObject::connect(migrationConfirmDialog, &ModalDialogListener::response, this, [=, this] (QVariant answer) {                QObject::disconnect(migrationConfirmDialog, &ModalDialogListener::response, this, nullptr);                 if (QMessageBox::Yes == static_cast<QMessageBox::StandardButton>(answer.toInt())) {                    // try to open the file at the given filename                    QFile modelsFile { filename };                     if (modelsFile.open(QIODevice::ReadOnly)) {                        QByteArray compressedJsonData = modelsFile.readAll();                        QByteArray jsonData;                         if (!gunzip(compressedJsonData, jsonData)) {                            OffscreenUi::asyncWarning(_dialogParent, "Error", "The file at" + filename + "was not in gzip format.");                        }                         QJsonDocument modelsJSON = QJsonDocument::fromJson(jsonData);                        _entitiesArray = modelsJSON.object()["Entities"].toArray();                         for (auto jsonValue : _entitiesArray) {                            QJsonObject entityObject = jsonValue.toObject();                            QString modelURLString = entityObject.value(MODEL_URL_KEY).toString();                            QString compoundURLString = entityObject.value(COMPOUND_SHAPE_URL_KEY).toString();                             for (int i = 0; i < 2; ++i) {                                bool isModelURL = (i == 0);                                quint8 replacementType = i;                                auto migrationURLString = (isModelURL) ? modelURLString : compoundURLString;                                 if (!migrationURLString.isEmpty()) {                                    QUrl migrationURL = QUrl(migrationURLString);                                     if (!_ignoredUrls.contains(migrationURL)                                            && (migrationURL.scheme() == HIFI_URL_SCHEME_HTTP || migrationURL.scheme() == HIFI_URL_SCHEME_HTTPS                                                || migrationURL.scheme() == HIFI_URL_SCHEME_FILE || migrationURL.scheme() == HIFI_URL_SCHEME_FTP)) {                                         if (_pendingReplacements.contains(migrationURL)) {                                            // we already have a request out for this asset, just store the QJsonValueRef                                            // so we can do the hash replacement when the request comes back                                            _pendingReplacements.insert(migrationURL, { jsonValue, replacementType });                                        } else if (_uploadedAssets.contains(migrationURL)) {                                            // we already have a hash for this asset                                            // so just do the replacement immediately                                            if (isModelURL) {                                                entityObject[MODEL_URL_KEY] = _uploadedAssets.value(migrationURL).toString();                                            } else {                                                entityObject[COMPOUND_SHAPE_URL_KEY] = _uploadedAssets.value(migrationURL).toString();                                            }                                             jsonValue = entityObject;                                        } else {                                             static bool hasAskedForCompleteMigration { false };                                            static bool wantsCompleteMigration { false };                                             if (!hasAskedForCompleteMigration) {                                                // this is the first resource migration - ask the user if they just want to migrate everything                                                static const QString COMPLETE_MIGRATION_TEXT { "Do you want to migrate all assets found in this entity-server file?\n"\                                                                                               "Select \"Yes\" to upload all discovered assets to the current asset-server immediately.\n"\                                                                                               "Select \"No\" to be prompted for each discovered asset."                                                                                             };                                                ModalDialogListener* migrationConfirmDialog1 = OffscreenUi::asyncQuestion(_dialogParent, MESSAGE_BOX_TITLE,                                                                           "Would you like to migrate the following resource?\n" + migrationURL.toString(),                                                                           QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);                                                 QObject::connect(migrationConfirmDialog1, &ModalDialogListener::response, this, [=, this] (QVariant answer) {                                                    QObject::disconnect(migrationConfirmDialog1, &ModalDialogListener::response, this, nullptr);                                                    if (static_cast<QMessageBox::StandardButton>(answer.toInt()) ==                                                            QMessageBox::Yes) {                                                        wantsCompleteMigration = true;                                                        migrateResources(migrationURL, jsonValue, isModelURL);                                                    } else {                                                        ModalDialogListener* migrationConfirmDialog2 = OffscreenUi::asyncQuestion(_dialogParent, MESSAGE_BOX_TITLE, COMPLETE_MIGRATION_TEXT,                                                                                   QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);                                                         QObject::connect(migrationConfirmDialog2, &ModalDialogListener::response, this, [=, this] (QVariant answer) {                                                            QObject::disconnect(migrationConfirmDialog2, &ModalDialogListener::response, this, nullptr);                                                            if (static_cast<QMessageBox::StandardButton>(answer.toInt()) ==                                                                    QMessageBox::Yes) {                                                                migrateResources(migrationURL, jsonValue, isModelURL);                                                            } else {                                                                _ignoredUrls.insert(migrationURL);                                                            }                                                        });                                                    }                                                });                                                hasAskedForCompleteMigration = true;                                            }                                            if (wantsCompleteMigration) {                                                migrateResources(migrationURL, jsonValue, isModelURL);                                            }                                        }                                    }                                }                            }                        }                         _doneReading = true;                         checkIfFinished();                     } else {                        OffscreenUi::asyncWarning(_dialogParent, "Error",                                                  "There was a problem loading that entity-server file for ATP asset migration. Please try again");                    }                }            });        }    });} void ATPAssetMigrator::migrateResource(ResourceRequest* request) {    // use an asset client to upload the asset    auto assetClient = DependencyManager::get<AssetClient>();     auto upload = assetClient->createUpload(request->getData());     // add this URL to our hash of AssetUpload to original URL    _originalURLs.insert(upload, request->getUrl());     qCDebug(asset_migrator) << "Starting upload of asset from" << request->getUrl();     // connect to the finished signal so we know when the AssetUpload is done    QObject::connect(upload, &AssetUpload::finished, this, &ATPAssetMigrator::assetUploadFinished);     // start the upload now    upload->start();} void ATPAssetMigrator::assetUploadFinished(AssetUpload *upload, const QString& hash) {    // remove this migrationURL from the key for the AssetUpload pointer    auto migrationURL = _originalURLs.take(upload);     if (upload->getError() == AssetUpload::NoError) {         // use the path of the migrationURL to add a mapping in the Asset Server        auto assetClient = DependencyManager::get<AssetClient>();        auto setMappingRequest = assetClient->createSetMappingRequest(migrationURL.path(), hash);         connect(setMappingRequest, &SetMappingRequest::finished, this, &ATPAssetMigrator::setMappingFinished);         // add this migrationURL with the key for the SetMappingRequest pointer        _originalURLs[setMappingRequest] = migrationURL;         setMappingRequest->start();    } else {        // this is a fail for this modelURL, remove it from pending replacements        _pendingReplacements.remove(migrationURL);         ++_errorCount;        qWarning() << "Failed to upload" << "- error was" << upload->getErrorString();    }     checkIfFinished();     upload->deleteLater();} void ATPAssetMigrator::setMappingFinished(SetMappingRequest* request) {    // take the migrationURL for this SetMappingRequest    auto migrationURL = _originalURLs.take(request);     if (request->getError() == MappingRequest::NoError) {         // successfully uploaded asset - make any required replacements found in the pending replacements        auto values = _pendingReplacements.values(migrationURL);         QString atpURL = QString("atp:%1").arg(request->getPath());         for (auto value : values) {            // replace the modelURL in this QJsonValueRef with the hash            QJsonObject valueObject = value.first.toObject();             if (value.second == 0) {                valueObject[MODEL_URL_KEY] = atpURL;            } else {                valueObject[COMPOUND_SHAPE_URL_KEY] = atpURL;            }             value.first = valueObject;        }         // add this URL to our list of uploaded assets        _uploadedAssets.insert(migrationURL, atpURL);         // pull the replaced urls from _pendingReplacements        _pendingReplacements.remove(migrationURL);    } else {        // this is a fail for this migrationURL, remove it from pending replacements        _pendingReplacements.remove(migrationURL);         ++_errorCount;        qWarning() << "Error setting mapping for" << "- error was " << request->getErrorString();    }     checkIfFinished();     request->deleteLater();} void ATPAssetMigrator::checkIfFinished() {    // are we out of pending replacements? if so it is time to save the entity-server file    if (_doneReading && _pendingReplacements.empty()) {        saveEntityServerFile();    }} bool ATPAssetMigrator::wantsToMigrateResource(const QUrl& url) {    static bool hasAskedForCompleteMigration { false };    static bool wantsCompleteMigration { false };     if (!hasAskedForCompleteMigration) {        // this is the first resource migration - ask the user if they just want to migrate everything        static const QString COMPLETE_MIGRATION_TEXT { "Do you want to migrate all assets found in this entity-server file?\n"\            "Select \"Yes\" to upload all discovered assets to the current asset-server immediately.\n"\            "Select \"No\" to be prompted for each discovered asset."        };         auto button = OffscreenUi::question(_dialogParent, MESSAGE_BOX_TITLE, COMPLETE_MIGRATION_TEXT,                                            QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);         if (button == QMessageBox::Yes) {            wantsCompleteMigration = true;        }         hasAskedForCompleteMigration = true;    }     if (wantsCompleteMigration) {        return true;    } else {        // present a dialog asking the user if they want to migrate this specific resource        auto button = OffscreenUi::question(_dialogParent, MESSAGE_BOX_TITLE,                                            "Would you like to migrate the following resource?\n" + url.toString(),                                            QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);        return button == QMessageBox::Yes;    }} void ATPAssetMigrator::saveEntityServerFile() {    // show a dialog to ask the user where they want to save the file    ModalDialogListener* dlg = OffscreenUi::getSaveFileNameAsync(_dialogParent, "Save Migrated Entities File");    connect(dlg, &ModalDialogListener::response, this, [=, this] (QVariant response) {        const QString& saveName = response.toString();        QFile saveFile { saveName };         if (saveFile.open(QIODevice::WriteOnly)) {            QJsonObject rootObject;            rootObject[ENTITIES_OBJECT_KEY] = _entitiesArray;             QJsonDocument newDocument { rootObject };            QByteArray jsonDataForFile;             if (gzip(newDocument.toJson(), jsonDataForFile, -1)) {                 saveFile.write(jsonDataForFile);                saveFile.close();                 QString infoMessage = QString("Your new entities file has been saved at\n%1.").arg(saveName);                 if (_errorCount > 0) {                    infoMessage += QString("\nThere were %1 models that could not be migrated.\n").arg(_errorCount);                    infoMessage += "Check the warnings in your log for details.\n";                    infoMessage += "You can re-attempt migration on those models\nby restarting this process with the newly saved file.";                }                 OffscreenUi::asyncInformation(_dialogParent, "Success", infoMessage);            } else {                OffscreenUi::asyncWarning(_dialogParent, "Error", "Could not gzip JSON data for new entities file.");            }         } else {            OffscreenUi::asyncWarning(_dialogParent, "Error",                                 QString("Could not open file at %1 to write new entities file to.").arg(saveName));        }        // reset after the attempted save, success or fail        reset();    });} void ATPAssetMigrator::reset() {    _entitiesArray = QJsonArray();    _doneReading = false;    _pendingReplacements.clear();    _uploadedAssets.clear();    _originalURLs.clear();    _ignoredUrls.clear();    _errorCount = 0;}