Merge pull request #109 from birarda/asset-migrator

tweaks to the ATP asset migrator for compound URLs and mappings
This commit is contained in:
Clément Brisset 2016-03-15 09:25:11 -07:00
commit bbfbbf6565
4 changed files with 162 additions and 275 deletions

View file

@ -24,9 +24,9 @@
#include <AssetClient.h>
#include <AssetUpload.h>
#include <ResourceManager.h>
#include <MappingRequest.h>
#include "OffscreenUi.h"
#include "../ui/AssetUploadDialogFactory.h"
Q_DECLARE_LOGGING_CATEGORY(asset_migrator);
Q_LOGGING_CATEGORY(asset_migrator, "hf.asset_migrator");
@ -38,18 +38,18 @@ ATPAssetMigrator& ATPAssetMigrator::getInstance() {
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() {
auto filename = QFileDialog::getOpenFileName(_dialogParent, "Select an entity-server content file to migrate",
QString(), QString("Entity-Server Content (*.gz)"));
auto filename = OffscreenUi::getOpenFileName(_dialogParent, tr("Select an entity-server content file to migrate"), QString(), tr("Entity-Server Content (*.gz)"));
if (!filename.isEmpty()) {
qCDebug(asset_migrator) << "Selected filename for ATP asset migration: " << filename;
static const QString MIGRATION_CONFIRMATION_TEXT {
"The ATP Asset Migration process will scan the selected entity-server file, upload discovered resources to the"\
" current asset-server and then save a new entity-server file with the ATP URLs.\n\nAre you ready to"\
"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."
};
@ -77,57 +77,75 @@ void ATPAssetMigrator::loadEntityServerFile() {
for (auto jsonValue : _entitiesArray) {
QJsonObject entityObject = jsonValue.toObject();
QString modelURLString = entityObject.value(MODEL_URL_KEY).toString();
if (!modelURLString.isEmpty()) {
QUrl modelURL = QUrl(modelURLString);
if (!_ignoredUrls.contains(modelURL)
&& (modelURL.scheme() == URL_SCHEME_HTTP || modelURL.scheme() == URL_SCHEME_HTTPS
|| modelURL.scheme() == URL_SCHEME_FILE || modelURL.scheme() == URL_SCHEME_FTP)) {
if (_pendingReplacements.contains(modelURL)) {
// 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(modelURL, jsonValue);
} else if (_uploadedAssets.contains(modelURL)) {
// we already have a hash for this asset
// so just do the replacement immediately
entityObject[MODEL_URL_KEY] = _uploadedAssets.value(modelURL).toString();
jsonValue = entityObject;
} else if (wantsToMigrateResource(modelURL)) {
auto request = ResourceManager::createResourceRequest(this, modelURL);
if (request) {
qCDebug(asset_migrator) << "Requesting" << modelURL << "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(modelURL, jsonValue);
connect(request, &ResourceRequest::finished, this, [=]() {
if (request->getResult() == ResourceRequest::Success) {
migrateResource(request);
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() == URL_SCHEME_HTTP || migrationURL.scheme() == URL_SCHEME_HTTPS
|| migrationURL.scheme() == URL_SCHEME_FILE || migrationURL.scheme() == 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 {
OffscreenUi::warning(_dialogParent, "Error",
QString("Could not retrieve asset at %1").arg(modelURL.toString()));
entityObject[COMPOUND_SHAPE_URL_KEY] = _uploadedAssets.value(migrationURL).toString();
}
request->deleteLater();
});
request->send();
} else {
OffscreenUi::warning(_dialogParent, "Error",
QString("Could not create request for asset at %1").arg(modelURL.toString()));
jsonValue = entityObject;
} else if (wantsToMigrateResource(migrationURL)) {
auto request = ResourceManager::createResourceRequest(this, migrationURL);
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, [=]() {
if (request->getResult() == ResourceRequest::Success) {
migrateResource(request);
} else {
++_errorCount;
_pendingReplacements.remove(migrationURL);
qWarning() << "Could not retrieve asset at" << migrationURL.toString();
checkIfFinished();
}
request->deleteLater();
});
request->send();
} else {
++_errorCount;
qWarning() << "Count not create request for asset at" << migrationURL.toString();
}
} else {
_ignoredUrls.insert(migrationURL);
}
}
} else {
_ignoredUrls.insert(modelURL);
}
}
}
}
_doneReading = true;
checkIfFinished();
} else {
OffscreenUi::warning(_dialogParent, "Error",
@ -140,8 +158,6 @@ void ATPAssetMigrator::migrateResource(ResourceRequest* request) {
// use an asset client to upload the asset
auto assetClient = DependencyManager::get<AssetClient>();
QFileInfo assetInfo { request->getUrl().fileName() };
auto upload = assetClient->createUpload(request->getData());
// add this URL to our hash of AssetUpload to original URL
@ -157,51 +173,94 @@ void ATPAssetMigrator::migrateResource(ResourceRequest* request) {
}
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) {
const auto& modelURL = _originalURLs[upload];
// successfully uploaded asset - make any required replacements found in the pending replacements
auto values = _pendingReplacements.values(modelURL);
QString atpURL = getATPUrl(hash).toString();
for (auto value : values) {
// replace the modelURL in this QJsonValueRef with the hash
QJsonObject valueObject = value.toObject();
valueObject[MODEL_URL_KEY] = atpURL;
value = valueObject;
}
// add this URL to our list of uploaded assets
_uploadedAssets.insert(modelURL, atpURL);
// pull the replaced models from _pendingReplacements
_pendingReplacements.remove(modelURL);
// are we out of pending replacements? if so it is time to save the entity-server file
if (_doneReading && _pendingReplacements.empty()) {
saveEntityServerFile();
// reset after the attempted save, success or fail
reset();
}
// 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 {
AssetUploadDialogFactory::showErrorDialog(upload, _dialogParent);
// this is a fail for this modelURL, remove it from pending replacements
_pendingReplacements.remove(migrationURL);
++_errorCount;
qWarning() << "Failed to upload" << migrationURL << "- 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" << migrationURL << "- 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();
// reset after the attempted save, success or fail
reset();
}
}
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\n"\
"Select \"Yes\" to upload all discovered assets to the current asset-server immediately.\n\n"\
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."
};
@ -228,7 +287,7 @@ bool ATPAssetMigrator::wantsToMigrateResource(const QUrl& url) {
void ATPAssetMigrator::saveEntityServerFile() {
// show a dialog to ask the user where they want to save the file
QString saveName = QFileDialog::getSaveFileName(_dialogParent, "Save Migrated Entities File");
QString saveName = OffscreenUi::getSaveFileName(_dialogParent, "Save Migrated Entities File");
QFile saveFile { saveName };
@ -243,9 +302,16 @@ void ATPAssetMigrator::saveEntityServerFile() {
saveFile.write(jsonDataForFile);
saveFile.close();
QMessageBox::information(_dialogParent, "Success",
QString("Your new entities file has been saved at %1").arg(saveName));
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::information(_dialogParent, "Success", infoMessage);
} else {
OffscreenUi::warning(_dialogParent, "Error", "Could not gzip JSON data for new entities file.");
}
@ -263,4 +329,5 @@ void ATPAssetMigrator::reset() {
_uploadedAssets.clear();
_originalURLs.clear();
_ignoredUrls.clear();
_errorCount = 0;
}

View file

@ -21,6 +21,7 @@
class AssetUpload;
class ResourceRequest;
class SetMappingRequest;
class ATPAssetMigrator : public QObject {
Q_OBJECT
@ -32,8 +33,11 @@ public slots:
void loadEntityServerFile();
private slots:
void assetUploadFinished(AssetUpload* upload, const QString& hash);
void setMappingFinished(SetMappingRequest* request);
private:
void migrateResource(ResourceRequest* request);
void checkIfFinished();
void saveEntityServerFile();
void reset();
@ -44,11 +48,14 @@ private:
QJsonArray _entitiesArray;
bool _doneReading { false };
QMultiHash<QUrl, QJsonValueRef> _pendingReplacements;
using JSONTypePair = std::pair<QJsonValueRef, quint8>;
QMultiHash<QUrl, JSONTypePair> _pendingReplacements;
QHash<QUrl, QUrl> _uploadedAssets;
QHash<AssetUpload*, QUrl> _originalURLs;
QHash<QObject*, QUrl> _originalURLs;
QSet<QUrl> _ignoredUrls;
int _errorCount { 0 };
};

View file

@ -1,143 +0,0 @@
//
// AssetUploadDialogFactory.cpp
// interface/src/ui
//
// Created by Stephen Birarda on 2015-08-26.
// 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 "AssetUploadDialogFactory.h"
#include <QtCore/QDebug>
#include <QtWidgets/QDialogButtonBox>
#include <QtWidgets/QFileDialog>
#include <QtWidgets/QLabel>
#include <QtWidgets/QMessageBox>
#include <QtWidgets/QLineEdit>
#include <QtWidgets/QVBoxLayout>
#include <AssetClient.h>
#include <AssetUpload.h>
#include <AssetUtils.h>
#include <NodeList.h>
#include <OffscreenUi.h>
#include <ResourceManager.h>
AssetUploadDialogFactory& AssetUploadDialogFactory::getInstance() {
static AssetUploadDialogFactory staticInstance;
return staticInstance;
}
void AssetUploadDialogFactory::showDialog() {
auto nodeList = DependencyManager::get<NodeList>();
if (nodeList->getThisNodeCanRez()) {
auto filename = QFileDialog::getOpenFileName(_dialogParent, "Select a file to upload");
if (!filename.isEmpty()) {
qDebug() << "Selected filename for upload to asset-server: " << filename;
auto assetClient = DependencyManager::get<AssetClient>();
auto upload = assetClient->createUpload(filename);
// connect to the finished signal so we know when the AssetUpload is done
QObject::connect(upload, &AssetUpload::finished, this, &AssetUploadDialogFactory::handleUploadFinished);
// start the upload now
upload->start();
}
} else {
// we don't have permission to upload to asset server in this domain - show the permission denied error
showErrorDialog(nullptr, _dialogParent, AssetUpload::PERMISSION_DENIED_ERROR);
}
}
void AssetUploadDialogFactory::handleUploadFinished(AssetUpload* upload, const QString& hash) {
if (upload->getError() == AssetUpload::NoError) {
// show message box for successful upload, with copiable text for ATP hash
QDialog* hashCopyDialog = new QDialog(_dialogParent);
// delete the dialog on close
hashCopyDialog->setAttribute(Qt::WA_DeleteOnClose);
// set the window title
hashCopyDialog->setWindowTitle(tr("Successful Asset Upload"));
// setup a layout for the contents of the dialog
QVBoxLayout* boxLayout = new QVBoxLayout;
// set the label text (this shows above the text box)
QLabel* lineEditLabel = new QLabel;
lineEditLabel->setText(QString("ATP URL for %1").arg(QFileInfo(upload->getFilename()).fileName()));
// setup the line edit to hold the copiable text
QLineEdit* lineEdit = new QLineEdit;
QString atpURL = QString("%1:%2").arg(URL_SCHEME_ATP).arg(hash);
// set the ATP URL as the text value so it's copiable
lineEdit->insert(atpURL);
// figure out what size this line edit should be using font metrics
QFontMetrics textMetrics { lineEdit->font() };
// set the fixed width on the line edit
// pad it by 10 to cover the border and some extra space on the right side (for clicking)
static const int LINE_EDIT_RIGHT_PADDING { 10 };
lineEdit->setFixedWidth(textMetrics.width(atpURL) + LINE_EDIT_RIGHT_PADDING );
// left align the ATP URL line edit
lineEdit->home(true);
// add the label and line edit to the dialog
boxLayout->addWidget(lineEditLabel);
boxLayout->addWidget(lineEdit);
// setup an OK button to close the dialog
QDialogButtonBox* buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok);
connect(buttonBox, &QDialogButtonBox::accepted, hashCopyDialog, &QDialog::close);
boxLayout->addWidget(buttonBox);
// set the new layout on the dialog
hashCopyDialog->setLayout(boxLayout);
// show the new dialog
hashCopyDialog->show();
} else {
// display a message box with the error
showErrorDialog(upload, _dialogParent);
}
upload->deleteLater();
}
void AssetUploadDialogFactory::showErrorDialog(AssetUpload* upload, QWidget* dialogParent, const QString& overrideMessage) {
QString filename;
if (upload) {
filename = QFileInfo { upload->getFilename() }.fileName();
}
QString errorMessage = overrideMessage;
if (errorMessage.isEmpty() && upload) {
errorMessage = upload->getErrorString();
}
QString dialogMessage;
if (upload) {
dialogMessage += QString("Failed to upload %1.\n\n").arg(filename);
}
dialogMessage += errorMessage;
OffscreenUi::warning(dialogParent, "Failed Upload", dialogMessage);
}

View file

@ -1,44 +0,0 @@
//
// AssetUploadDialogFactory.h
// interface/src/ui
//
// Created by Stephen Birarda on 2015-08-26.
// 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
//
#pragma once
#ifndef hifi_AssetUploadDialogFactory_h
#define hifi_AssetUploadDialogFactory_h
#include <QtCore/QObject>
class AssetUpload;
class AssetUploadDialogFactory : public QObject {
Q_OBJECT
public:
AssetUploadDialogFactory(const AssetUploadDialogFactory& other) = delete;
AssetUploadDialogFactory& operator=(const AssetUploadDialogFactory& rhs) = delete;
static AssetUploadDialogFactory& getInstance();
static void showErrorDialog(AssetUpload* upload, QWidget* dialogParent, const QString& overrideMessage = QString());
void setDialogParent(QWidget* dialogParent) { _dialogParent = dialogParent; }
public slots:
void showDialog();
void handleUploadFinished(AssetUpload* upload, const QString& hash);
private:
AssetUploadDialogFactory() = default;
QWidget* _dialogParent { nullptr };
};
#endif // hifi_AssetUploadDialogFactory_h