From 5cee8d18929c10b2bd67cbaaab0554e916360648 Mon Sep 17 00:00:00 2001 From: chcurran <82187351+carlitosan@users.noreply.github.com> Date: Fri, 10 Sep 2021 10:59:40 -0700 Subject: [PATCH] stubbed out refactor of version explorer Signed-off-by: chcurran <82187351+carlitosan@users.noreply.github.com> --- .../Code/Editor/View/Windows/MainWindow.cpp | 71 -- .../Code/Editor/View/Windows/MainWindow.h | 3 - .../Windows/Tools/UpgradeTool/FileSaver.cpp | 1023 +++++++++++++++++ .../Windows/Tools/UpgradeTool/FileSaver.h | 180 +++ .../Windows/Tools/UpgradeTool/Modifier.cpp | 1023 +++++++++++++++++ .../View/Windows/Tools/UpgradeTool/Modifier.h | 167 +++ .../Windows/Tools/UpgradeTool/Scanner.cpp | 1023 +++++++++++++++++ .../View/Windows/Tools/UpgradeTool/Scanner.h | 167 +++ .../Windows/Tools/UpgradeTool/UpgradeTool.cpp | 697 ----------- .../Windows/Tools/UpgradeTool/UpgradeTool.h | 149 --- .../Windows/Tools/UpgradeTool/UpgradeTool.ui | 260 ----- .../Tools/UpgradeTool/VersionExplorer.h | 10 +- .../Tools/UpgradeTool/VersionExplorerLog.cpp | 1023 +++++++++++++++++ .../Tools/UpgradeTool/VersionExplorerLog.h | 167 +++ .../Tools/UpgradeTool/VersionExplorerTraits.h | 19 + .../Code/scriptcanvasgem_editor_files.cmake | 11 +- 16 files changed, 4803 insertions(+), 1190 deletions(-) create mode 100644 Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/FileSaver.cpp create mode 100644 Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/FileSaver.h create mode 100644 Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/Modifier.cpp create mode 100644 Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/Modifier.h create mode 100644 Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/Scanner.cpp create mode 100644 Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/Scanner.h delete mode 100644 Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/UpgradeTool.cpp delete mode 100644 Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/UpgradeTool.h delete mode 100644 Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/UpgradeTool.ui create mode 100644 Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/VersionExplorerLog.cpp create mode 100644 Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/VersionExplorerLog.h create mode 100644 Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/VersionExplorerTraits.h diff --git a/Gems/ScriptCanvas/Code/Editor/View/Windows/MainWindow.cpp b/Gems/ScriptCanvas/Code/Editor/View/Windows/MainWindow.cpp index fbe03ffc0a..856cceb508 100644 --- a/Gems/ScriptCanvas/Code/Editor/View/Windows/MainWindow.cpp +++ b/Gems/ScriptCanvas/Code/Editor/View/Windows/MainWindow.cpp @@ -704,78 +704,7 @@ namespace ScriptCanvasEditor m_autoSaveTimer.setSingleShot(true); connect(&m_autoSaveTimer, &QTimer::timeout, this, &MainWindow::OnAutoSave); - UpdateMenuState(false); - - PromptForUpgrade(); - } - - void MainWindow::PromptForUpgrade() - { - static bool displayUpgradePrompt = false; // Set to true if you need the upgrade dialog to show up on ScriptCanvas editor open - if (displayUpgradePrompt && m_showUpgradeTool) - { - QTimer::singleShot(1.f, this, [this]() - { - UpgradeTool* upgradeTool = aznew UpgradeTool(this); - - QPoint centerPoint = frameGeometry().center(); - - upgradeTool->adjustSize(); - upgradeTool->move(centerPoint.x() - upgradeTool->width() / 2, centerPoint.y() - upgradeTool->height() / 2); - - if (upgradeTool->exec() == QDialog::Accepted) - { - // Manual correction - size_t assetsThatNeedManualInspection = AZ::Interface::Get()->GetGraphsThatNeedManualUpgrade().size(); - QString message = QObject::tr("%1 Graph(s) upgraded
%2 graph(s) did not require upgrade").arg(upgradeTool->UpgradedGraphCount()).arg(upgradeTool->SkippedGraphCount()); - if (assetsThatNeedManualInspection > 0) - { - message.append(QObject::tr("
%1 graph(s) that need manual corrections. You will be prompted to review them after you close this dialog.
").arg(assetsThatNeedManualInspection)); - } - - // Report - char resolvedBuffer[AZ_MAX_PATH_LEN] = { 0 }; - AZStd::string outputFileName = AZStd::string::format("@devroot@/ScriptCanvasUpgradeReport.html"); - AZ::IO::FileIOBase::GetInstance()->ResolvePath(outputFileName.c_str(), resolvedBuffer, AZ_MAX_PATH_LEN); - AZStd::string urlToReport = AZStd::string::format("For more information see the Upgrade Report.", resolvedBuffer); - message.append(QObject::tr("
%1").arg(urlToReport.c_str())); - - // Backup - if (upgradeTool->HasBackup()) - { - AZStd::string outputFileName2 = AZStd::string::format("@devroot@/ScriptCanvas_BACKUP"); - - AZ::IO::FileIOBase::GetInstance()->ResolvePath(outputFileName2.c_str(), resolvedBuffer, AZ_MAX_PATH_LEN); - - AZStd::string backupPath = AZStd::string::format("
Open the Backup Folder.", resolvedBuffer); - message.append(QObject::tr("%1").arg(backupPath.c_str())); - } - - // Done upgrading, show details - QMessageBox mb(QMessageBox::Information, - QObject::tr("Upgrade Complete"), - message, - QMessageBox::Ok, this); - mb.setTextFormat(Qt::RichText); - - centerPoint = frameGeometry().center(); - - mb.adjustSize(); - mb.move(centerPoint.x() - width() / 2, centerPoint.y() - height() / 2); - - mb.exec(); - - // If there are graphs that need manual correction, show the helper - if (assetsThatNeedManualInspection > 0) - { - UpgradeHelper* upgradeHelper = new UpgradeHelper(this); - upgradeHelper->show(); - } - } - - }); - } } MainWindow::~MainWindow() diff --git a/Gems/ScriptCanvas/Code/Editor/View/Windows/MainWindow.h b/Gems/ScriptCanvas/Code/Editor/View/Windows/MainWindow.h index d11d2f0052..ed00254677 100644 --- a/Gems/ScriptCanvas/Code/Editor/View/Windows/MainWindow.h +++ b/Gems/ScriptCanvas/Code/Editor/View/Windows/MainWindow.h @@ -51,7 +51,6 @@ #include -#include #include #if SCRIPTCANVAS_EDITOR @@ -806,7 +805,5 @@ namespace ScriptCanvasEditor Workspace* m_workspace; void OnSaveCallback(bool saveSuccess, AZ::Data::AssetPtr, AZ::Data::AssetId previousFileAssetId); - - void PromptForUpgrade(); }; } diff --git a/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/FileSaver.cpp b/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/FileSaver.cpp new file mode 100644 index 0000000000..eb182ed291 --- /dev/null +++ b/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/FileSaver.cpp @@ -0,0 +1,1023 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace VersionExplorerCpp +{ + class FileEventHandler + : public AZ::IO::FileIOEventBus::Handler + { + public: + int m_errorCode = 0; + AZStd::string m_fileName; + + FileEventHandler() + { + BusConnect(); + } + + ~FileEventHandler() + { + BusDisconnect(); + } + + void OnError(const AZ::IO::SystemFile* /*file*/, const char* fileName, int errorCode) override + { + m_errorCode = errorCode; + + if (fileName) + { + m_fileName = fileName; + } + } + }; +} + +namespace ScriptCanvasEditor +{ + EditorKeepAlive::EditorKeepAlive() + { + ISystem* system = nullptr; + CrySystemRequestBus::BroadcastResult(system, &CrySystemRequestBus::Events::GetCrySystem); + + m_edKeepEditorActive = system->GetIConsole()->GetCVar("ed_KeepEditorActive"); + + if (m_edKeepEditorActive) + { + m_keepEditorActive = m_edKeepEditorActive->GetIVal(); + m_edKeepEditorActive->Set(1); + } + } + + EditorKeepAlive::~EditorKeepAlive() + { + if (m_edKeepEditorActive) + { + m_edKeepEditorActive->Set(m_keepEditorActive); + } + } + + VersionExplorer::VersionExplorer(QWidget* parent /*= nullptr*/) + : AzQtComponents::StyledDialog(parent) + , m_ui(new Ui::VersionExplorer()) + { + m_ui->setupUi(this); + + m_ui->tableWidget->horizontalHeader()->setVisible(false); + m_ui->tableWidget->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch); + m_ui->tableWidget->horizontalHeader()->setSectionResizeMode(3, QHeaderView::Fixed); + m_ui->tableWidget->setColumnWidth(3, 22); + + m_ui->textEdit->setHorizontalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAsNeeded); + m_ui->textEdit->setVerticalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAlwaysOn); + + connect(m_ui->scanButton, &QPushButton::pressed, this, &VersionExplorer::OnScan); + connect(m_ui->closeButton, &QPushButton::pressed, this, &VersionExplorer::OnClose); + connect(m_ui->upgradeAllButton, &QPushButton::pressed, this, &VersionExplorer::OnUpgradeAll); + + m_ui->progressBar->setValue(0); + m_ui->progressBar->setVisible(false); + + m_keepEditorAlive = AZStd::make_unique(); + m_inspectingAsset = m_assetsToInspect.end(); + + } + + VersionExplorer::~VersionExplorer() + { + AZ::SystemTickBus::Handler::BusDisconnect(); + + UpgradeNotifications::Bus::Handler::BusDisconnect(); + AZ::Debug::TraceMessageBus::Handler::BusDisconnect(); + + } + + void VersionExplorer::Log(const char* format, ...) + { + if (m_ui->verbose->isChecked()) + { + char sBuffer[2048]; + va_list ArgList; + va_start(ArgList, format); + azvsnprintf(sBuffer, sizeof(sBuffer), format, ArgList); + sBuffer[sizeof(sBuffer) - 1] = '\0'; + va_end(ArgList); + + AZ_TracePrintf(ScriptCanvas::k_VersionExplorerWindow.data(), "%s\n", sBuffer); + } + } + + void VersionExplorer::OnClose() + { + reject(); + } + + bool VersionExplorer::IsUpgrading() const + { + return m_inProgressAsset != m_assetsToUpgrade.end() && m_inProgress; + } + + void VersionExplorer::OnSystemTick() + { + switch (m_state) + { + case ProcessState::Scan: + + if (!m_inProgress && m_inspectingAsset != m_assetsToInspect.end()) + { + m_inProgress = true; + AZ::Data::AssetInfo& assetToUpgrade = *m_inspectingAsset; + m_currentAsset = AZ::Data::AssetManager::Instance().GetAsset(assetToUpgrade.m_assetId, assetToUpgrade.m_assetType, AZ::Data::AssetLoadBehavior::PreLoad); + Log("SystemTick::ProcessState::Scan: %s pre-blocking load hint", m_currentAsset.GetHint().c_str()); + m_currentAsset.BlockUntilLoadComplete(); + if (m_currentAsset.IsReady()) + { + // The asset is ready, grab its info + m_inProgress = true; + InspectAsset(m_currentAsset, assetToUpgrade); + } + else + { + m_ui->tableWidget->insertRow(static_cast(m_currentAssetRowIndex)); + QTableWidgetItem* rowName = new QTableWidgetItem + ( tr(AZStd::string::format("Error: %s", assetToUpgrade.m_relativePath.c_str()).c_str())); + m_ui->tableWidget->setItem(static_cast(m_currentAssetRowIndex), static_cast(ColumnAsset), rowName); + ++m_currentAssetRowIndex; + + Log("SystemTick::ProcessState::Scan: %s post-blocking load, problem loading asset", assetToUpgrade.m_relativePath.c_str()); + ++m_failedAssets; + ScanComplete(m_currentAsset); + } + } + break; + + case ProcessState::Upgrade: + { + AZStd::lock_guard lock(m_mutex); + if (m_upgradeComplete) + { + ++m_upgradeAssetIndex; + m_inProgress = false; + m_ui->progressBar->setVisible(true); + m_ui->progressBar->setValue(m_upgradeAssetIndex); + + if (m_scriptCanvasEntity) + { + m_scriptCanvasEntity->Deactivate(); + m_scriptCanvasEntity = nullptr; + } + + GraphUpgradeCompleteUIUpdate(m_upgradeAsset, m_upgradeResult, m_upgradeMessage); + + if (!m_isUpgradingSingleGraph) + { + if (m_inProgressAsset != m_assetsToUpgrade.end()) + { + m_inProgressAsset = m_assetsToUpgrade.erase(m_inProgressAsset); + } + + if (m_inProgressAsset == m_assetsToUpgrade.end()) + { + FinalizeUpgrade(); + } + } + else + { + m_inProgressAsset = m_assetsToUpgrade.erase(m_inProgressAsset); + m_inProgress = false; + m_state = ProcessState::Inactive; + m_settingsCache.reset(); + AZ::SystemTickBus::Handler::BusDisconnect(); + AZ::Debug::TraceMessageBus::Handler::BusDisconnect(); + } + + m_isUpgradingSingleGraph = false; + + if (m_assetsToUpgrade.empty()) + { + m_ui->upgradeAllButton->setEnabled(false); + } + + m_upgradeComplete = false; + } + + if (!IsUpgrading() && m_state == ProcessState::Upgrade) + { + AZStd::string errorMessage = BackupGraph(*m_inProgressAsset); + // Make the backup + if (errorMessage.empty()) + { + Log("SystemTick::ProcessState::Upgrade: Backup Success %s ", m_inProgressAsset->GetHint().c_str()); + QList items = m_ui->tableWidget->findItems(m_inProgressAsset->GetHint().c_str(), Qt::MatchFlag::MatchExactly); + if (!items.isEmpty()) + { + for (auto* item : items) + { + int row = item->row(); + AzQtComponents::StyledBusyLabel* spinner = qobject_cast(m_ui->tableWidget->cellWidget(row, ColumnStatus)); + spinner->SetIsBusy(true); + } + } + + // Upgrade the graph + UpgradeGraph(*m_inProgressAsset); + } + else + { + Log("SystemTick::ProcessState::Upgrade: Backup Failed %s ", m_inProgressAsset->GetHint().c_str()); + GraphUpgradeComplete(*m_inProgressAsset, OperationResult::Failure, errorMessage); + } + + } + break; + } + default: + break; + } + + FlushLogs(); + + AZ::Data::AssetManager::Instance().DispatchEvents(); + AZ::SystemTickBus::ExecuteQueuedEvents(); + } + + // Backup + + void VersionExplorer::OnUpgradeAll() + { + m_state = ProcessState::Upgrade; + m_settingsCache = AZStd::make_unique(); + ScriptCanvas::Grammar::g_saveRawTranslationOuputToFile = false; + ScriptCanvas::Grammar::g_printAbstractCodeModel = false; + ScriptCanvas::Grammar::g_saveRawTranslationOuputToFile = false; + AZ::Interface::Get()->SetIsUpgrading(true); + AZ::Interface::Get()->ClearGraphsThatNeedUpgrade(); + m_inProgressAsset = m_assetsToUpgrade.begin(); + AZ::Debug::TraceMessageBus::Handler::BusConnect(); + AZ::SystemTickBus::Handler::BusConnect(); + m_ui->progressBar->setVisible(true); + m_ui->progressBar->setRange(0, aznumeric_cast(m_assetsToUpgrade.size())); + m_ui->progressBar->setValue(m_upgradeAssetIndex); + m_keepEditorAlive = AZStd::make_unique(); + } + + AZStd::string VersionExplorer::BackupGraph(const AZ::Data::Asset& asset) + { + if (!m_ui->makeBackupCheckbox->isChecked()) + { + // considered a success + return ""; + } + + QDateTime theTime = QDateTime::currentDateTime(); + QString subFolder = theTime.toString("yyyy-MM-dd [HH.mm.ss]"); + + AZStd::string backupPath = AZStd::string::format("@devroot@/ScriptCanvas_BACKUP/%s", subFolder.toUtf8().data()); + char backupPathCStr[AZ_MAX_PATH_LEN] = { 0 }; + AZ::IO::FileIOBase::GetInstance()->ResolvePath(backupPath.c_str(), backupPathCStr, AZ_MAX_PATH_LEN); + backupPath = backupPathCStr; + + if (!AZ::IO::FileIOBase::GetInstance()->Exists(backupPath.c_str())) + { + if (AZ::IO::FileIOBase::GetInstance()->CreatePath(backupPath.c_str()) != AZ::IO::ResultCode::Success) + { + AZ_Error(ScriptCanvas::k_VersionExplorerWindow.data(), false, "Failed to create backup folder %s", backupPath.c_str()); + return "Failed to create backup folder"; + } + } + + AZStd::string devRoot = "@devroot@"; + AZStd::string devAssets = "@devassets@"; + + char devRootCStr[AZ_MAX_PATH_LEN] = { 0 }; + AZ::IO::FileIOBase::GetInstance()->ResolvePath(devRoot.c_str(), devRootCStr, AZ_MAX_PATH_LEN); + + char devAssetsCStr[AZ_MAX_PATH_LEN] = { 0 }; + AZ::IO::FileIOBase::GetInstance()->ResolvePath(devAssets.c_str(), devAssetsCStr, AZ_MAX_PATH_LEN); + + AZStd::string relativePath = devAssetsCStr; + AzFramework::StringFunc::Replace(relativePath, devRootCStr, ""); + if (relativePath.starts_with("/")) + { + relativePath = relativePath.substr(1, relativePath.size() - 1); + } + + AZStd::string sourceFilePath; + + AZStd::string watchFolder; + AZ::Data::AssetInfo assetInfo; + bool sourceInfoFound{}; + AzToolsFramework::AssetSystemRequestBus::BroadcastResult(sourceInfoFound, &AzToolsFramework::AssetSystemRequestBus::Events::GetSourceInfoBySourcePath, asset.GetHint().c_str(), assetInfo, watchFolder); + if (sourceInfoFound) + { + AZStd::string assetPath; + AzFramework::StringFunc::Path::Join(watchFolder.c_str(), assetInfo.m_relativePath.c_str(), assetPath); + + sourceFilePath = assetPath; + } + else + { + AZ_Warning(ScriptCanvas::k_VersionExplorerWindow.data(), false, "VersionExplorer::BackupGraph: Failed to find file: %s", asset.GetHint().c_str()); + return "Failed to find source file"; + } + + devRoot = devRootCStr; + AzFramework::StringFunc::Path::Normalize(devRoot); + + relativePath = sourceFilePath; + AzFramework::StringFunc::Replace(relativePath, devRoot.c_str(), ""); + if (relativePath.starts_with("/")) + { + relativePath = relativePath.substr(1, relativePath.size() - 1); + } + + AzFramework::StringFunc::Path::Normalize(relativePath); + AzFramework::StringFunc::Path::Normalize(backupPath); + + AZStd::string targetFilePath = backupPath; + targetFilePath += relativePath; + + if (AZ::IO::FileIOBase::GetInstance()->Copy(sourceFilePath.c_str(), targetFilePath.c_str()) != AZ::IO::ResultCode::Success) + { + AZ_Warning(ScriptCanvas::k_VersionExplorerWindow.data(), false, "VersionExplorer::BackupGraph: Error creating backup: %s ---> %s\n", sourceFilePath.c_str(), targetFilePath.c_str()); + return "Failed to copy source file to backup location"; + } + + Log("VersionExplorer::BackupGraph: Backed up: %s ---> %s\n", sourceFilePath.c_str(), targetFilePath.c_str()); + return ""; + } + + void VersionExplorer::UpgradeGraph(const AZ::Data::Asset& asset) + { + m_inProgress = true; + m_upgradeComplete = false; + Log("UpgradeGraph %s ", m_inProgressAsset->GetHint().c_str()); + m_ui->spinner->SetText(QObject::tr("Upgrading: %1").arg(asset.GetHint().c_str())); + m_scriptCanvasEntity = nullptr; + + UpgradeNotifications::Bus::Handler::BusConnect(); + + if (asset.GetType() == azrtti_typeid()) + { + ScriptCanvasAsset* scriptCanvasAsset = asset.GetAs(); + AZ_Assert(scriptCanvasAsset, "Unable to get the asset of ScriptCanvasAsset, but received type: %s" + , azrtti_typeid().template ToString().c_str()); + + if (!scriptCanvasAsset) + { + return; + } + + AZ::Entity* scriptCanvasEntity = scriptCanvasAsset->GetScriptCanvasEntity(); + AZ_Assert(scriptCanvasEntity, "VersionExplorer::UpgradeGraph The Script Canvas asset must have a valid entity"); + if (!scriptCanvasEntity) + { + return; + } + + AZ::Entity* queryEntity = nullptr; + AZ::ComponentApplicationBus::BroadcastResult(queryEntity, &AZ::ComponentApplicationRequests::FindEntity, scriptCanvasEntity->GetId()); + if (queryEntity) + { + if (queryEntity->GetState() == AZ::Entity::State::Active) + { + queryEntity->Deactivate(); + } + + scriptCanvasEntity = queryEntity; + } + + if (scriptCanvasEntity->GetState() == AZ::Entity::State::Constructed) + { + scriptCanvasEntity->Init(); + } + + if (scriptCanvasEntity->GetState() == AZ::Entity::State::Init) + { + scriptCanvasEntity->Activate(); + } + + AZ_Assert(scriptCanvasEntity->GetState() == AZ::Entity::State::Active, "Graph entity is not active"); + auto graphComponent = scriptCanvasEntity->FindComponent(); + AZ_Assert(graphComponent, "The Script Canvas entity must have a Graph component"); + + if (graphComponent) + { + m_scriptCanvasEntity = scriptCanvasEntity; + + graphComponent->UpgradeGraph + ( asset + , m_ui->forceUpgrade->isChecked() ? Graph::UpgradeRequest::Forced : Graph::UpgradeRequest::IfOutOfDate + , m_ui->verbose->isChecked()); + } + } + + AZ_Assert(m_scriptCanvasEntity, "The ScriptCanvas asset should have an entity"); + } + + void VersionExplorer::OnGraphUpgradeComplete(AZ::Data::Asset& asset, bool /*skipped*/ /*= false*/) + { + AZStd::string relativePath, fullPath; + AZ::Data::AssetCatalogRequestBus::BroadcastResult(relativePath, &AZ::Data::AssetCatalogRequests::GetAssetPathById, asset.GetId()); + bool fullPathFound = false; + AzToolsFramework::AssetSystemRequestBus::BroadcastResult(fullPathFound, &AzToolsFramework::AssetSystemRequestBus::Events::GetFullSourcePathFromRelativeProductPath, relativePath, fullPath); + if (!fullPathFound) + { + AZ_Error(ScriptCanvas::k_VersionExplorerWindow.data(), false, "Full source path not found for %s", relativePath.c_str()); + } + + auto streamer = AZ::Interface::Get(); + AZ::IO::FileRequestPtr flushRequest = streamer->FlushCache(fullPath); + streamer->SetRequestCompleteCallback(flushRequest, [this, asset]([[maybe_unused]] AZ::IO::FileRequestHandle request) + { + this->OnSourceFileReleased(asset); + }); + streamer->QueueRequest(flushRequest); + } + + void VersionExplorer::OnSourceFileReleased(AZ::Data::Asset asset) + { + AZStd::string relativePath, fullPath; + AZ::Data::AssetCatalogRequestBus::BroadcastResult(relativePath, &AZ::Data::AssetCatalogRequests::GetAssetPathById, asset.GetId()); + bool fullPathFound = false; + AzToolsFramework::AssetSystemRequestBus::BroadcastResult(fullPathFound, &AzToolsFramework::AssetSystemRequestBus::Events::GetFullSourcePathFromRelativeProductPath, relativePath, fullPath); + m_tmpFileName.clear(); + AZStd::string tmpFileName; + // here we are saving the graph to a temp file instead of the original file and then copying the temp file to the original file. + // This ensures that AP will not a get a file change notification on an incomplete graph file causing it to fail processing. Temp files are ignored by AP. + if (!AZ::IO::CreateTempFileName(fullPath.c_str(), tmpFileName)) + { + GraphUpgradeComplete(asset, OperationResult::Failure, "Failure to create temporary file name"); + return; + } + + bool tempSavedSucceeded = false; + AZ::IO::FileIOStream fileStream(tmpFileName.c_str(), AZ::IO::OpenMode::ModeWrite | AZ::IO::OpenMode::ModeText); + if (fileStream.IsOpen()) + { + if (asset.GetType() == azrtti_typeid()) + { + ScriptCanvasEditor::ScriptCanvasAssetHandler handler; + tempSavedSucceeded = handler.SaveAssetData(asset, &fileStream); + } + + fileStream.Close(); + } + + // attempt to remove temporary file no matter what + m_tmpFileName = tmpFileName; + if (!tempSavedSucceeded) + { + GraphUpgradeComplete(asset, OperationResult::Failure, "Save asset data to temporary file failed"); + return; + } + + using SCCommandBus = AzToolsFramework::SourceControlCommandBus; + SCCommandBus::Broadcast(&SCCommandBus::Events::RequestEdit, fullPath.c_str(), true, + [this, asset, fullPath, tmpFileName]([[maybe_unused]] bool success, const AzToolsFramework::SourceControlFileInfo& info) + { + constexpr const size_t k_maxAttemps = 10; + + if (!info.IsReadOnly()) + { + PerformMove(asset, tmpFileName, fullPath, k_maxAttemps); + } + else + { + if (m_overwriteAll) + { + AZ::IO::SystemFile::SetWritable(info.m_filePath.c_str(), true); + PerformMove(asset, tmpFileName, fullPath, k_maxAttemps); + } + else + { + int result = QMessageBox::No; + if (!m_overwriteAll) + { + QMessageBox mb(QMessageBox::Warning, + QObject::tr("Failed to Save Upgraded File"), + QObject::tr("The upgraded file could not be saved because the file is read only.\nDo you want to make it writeable and overwrite it?"), + QMessageBox::YesToAll | QMessageBox::Yes | QMessageBox::No, this); + + result = mb.exec(); + if (result == QMessageBox::YesToAll) + { + m_overwriteAll = true; + } + } + + if (result == QMessageBox::Yes || m_overwriteAll) + { + AZ::IO::SystemFile::SetWritable(info.m_filePath.c_str(), true); + PerformMove(asset, tmpFileName, fullPath, k_maxAttemps); + } + } + } + }); + } + + void VersionExplorer::PerformMove(AZ::Data::Asset asset, AZStd::string source, AZStd::string target + , size_t remainingAttempts) + { + VersionExplorerCpp::FileEventHandler fileEventHandler; + + if (remainingAttempts == 0) + { + // all attempts failed, give up + AZ_Warning(ScriptCanvas::k_VersionExplorerWindow.data(), false, "moving converted file to source destination failed: %s. giving up", target.c_str()); + GraphUpgradeComplete(asset, OperationResult::Failure, "Failed to move updated file from backup to source destination"); + } + else if (remainingAttempts == 2) + { + // before the final attempt, flush all caches + AZ_Warning(ScriptCanvas::k_VersionExplorerWindow.data(), false, "moving converted file to source destination failed: %s, trying again", target.c_str()); + auto streamer = AZ::Interface::Get(); + AZ::IO::FileRequestPtr flushRequest = streamer->FlushCaches(); + streamer->SetRequestCompleteCallback(flushRequest + , [this, asset, remainingAttempts, source, target]([[maybe_unused]] AZ::IO::FileRequestHandle request) + { + // Continue saving. + AZ::SystemTickBus::QueueFunction( + [this, asset, remainingAttempts, source, target](){ PerformMove(asset, source, target, remainingAttempts - 1); }); + }); + streamer->QueueRequest(flushRequest); + } + else + { + // the actual move attempt + auto moveResult = AZ::IO::SmartMove(source.c_str(), target.c_str()); + if (moveResult.GetResultCode() == AZ::IO::ResultCode::Success) + { + m_tmpFileName.clear(); + auto streamer = AZ::Interface::Get(); + AZ::IO::FileRequestPtr flushRequest = streamer->FlushCache(target.c_str()); + // Bump the slice asset up in the asset processor's queue. + AzFramework::AssetSystemRequestBus::Broadcast(&AzFramework::AssetSystem::AssetSystemRequests::EscalateAssetBySearchTerm, target.c_str()); + AZ::SystemTickBus::QueueFunction([this, asset]() + { + GraphUpgradeComplete(asset, OperationResult::Success, ""); + }); + } + else + { + AZ_Warning(ScriptCanvas::k_VersionExplorerWindow.data(), false, "moving converted file to source destination failed: %s, trying again", target.c_str()); + auto streamer = AZ::Interface::Get(); + AZ::IO::FileRequestPtr flushRequest = streamer->FlushCache(target.c_str()); + streamer->SetRequestCompleteCallback(flushRequest, [this, asset, source, target, remainingAttempts]([[maybe_unused]] AZ::IO::FileRequestHandle request) + { + // Continue saving. + AZ::SystemTickBus::QueueFunction([this, asset, source, target, remainingAttempts]() { PerformMove(asset, source, target, remainingAttempts - 1); }); + }); + streamer->QueueRequest(flushRequest); + } + } + } + + void VersionExplorer::GraphUpgradeComplete + ( const AZ::Data::Asset asset, OperationResult result, AZStd::string_view message) + { + AZStd::lock_guard lock(m_mutex); + m_upgradeComplete = true; + m_upgradeResult = result; + m_upgradeMessage = message; + m_upgradeAsset = asset; + + if (!m_tmpFileName.empty()) + { + AZ::IO::FileIOBase* fileIO = AZ::IO::FileIOBase::GetInstance(); + AZ_Assert(fileIO, "GraphUpgradeComplete: No FileIO instance"); + + if (fileIO->Exists(m_tmpFileName.c_str()) && !fileIO->Remove(m_tmpFileName.c_str())) + { + AZ_TracePrintf(ScriptCanvas::k_VersionExplorerWindow.data(), "Failed to remove temporary file: %s", m_tmpFileName.c_str()); + } + } + + if (m_upgradeResult == OperationResult::Failure) + { + AZ::Interface::Get()->GraphNeedsManualUpgrade(asset.GetId()); + } + + m_tmpFileName.clear(); + } + + void VersionExplorer::GraphUpgradeCompleteUIUpdate + ( const AZ::Data::Asset asset, OperationResult result, AZStd::string_view message) + { + QString text = asset.GetHint().c_str(); + QList items = m_ui->tableWidget->findItems(text, Qt::MatchFlag::MatchExactly); + + if (!items.isEmpty()) + { + for (auto* item : items) + { + int row = item->row(); + QTableWidgetItem* label = m_ui->tableWidget->item(row, ColumnAsset); + QString assetName = asset.GetHint().c_str(); + + if (label->text().compare(assetName) == 0) + { + m_ui->tableWidget->removeCellWidget(row, ColumnAction); + m_ui->tableWidget->removeCellWidget(row, ColumnStatus); + + QToolButton* doneButton = new QToolButton(this); + doneButton->setToolTip("Upgrade complete"); + if (result == OperationResult::Success) + { + doneButton->setIcon(QIcon(":/stylesheet/img/UI20/checkmark-menu.svg")); + } + else + { + doneButton->setIcon(QIcon(":/stylesheet/img/UI20/titlebar-close.svg")); + doneButton->setToolTip(message.data()); + } + + m_ui->tableWidget->setCellWidget(row, ColumnStatus, doneButton); + } + } + } + } + + void VersionExplorer::FinalizeUpgrade() + { + Log("FinalizeUpgrade!"); + m_inProgress = false; + m_assetsToUpgrade.clear(); + m_ui->upgradeAllButton->setEnabled(false); + m_ui->onlyShowOutdated->setEnabled(true); + m_keepEditorAlive.reset(); + m_ui->progressBar->setVisible(false); + + // Manual correction + size_t assetsThatNeedManualInspection = AZ::Interface::Get()->GetGraphsThatNeedManualUpgrade().size(); + if (assetsThatNeedManualInspection > 0) + { + m_ui->spinner->SetText("Some graphs will require manual corrections, you will be prompted to review them upon closing this dialog"); + } + else + { + m_ui->spinner->SetText("Upgrade complete."); + } + + AZ::SystemTickBus::Handler::BusDisconnect(); + AZ::Debug::TraceMessageBus::Handler::BusDisconnect(); + UpgradeNotifications::Bus::Handler::BusDisconnect(); + AZ::Interface::Get()->SetIsUpgrading(false); + m_settingsCache.reset(); + } + + // Scanning + + void VersionExplorer::OnScan() + { + m_assetsToUpgrade.clear(); + m_assetsToInspect.clear(); + m_ui->tableWidget->setRowCount(0); + m_inspectedAssets = 0; + m_currentAssetRowIndex = 0; + IUpgradeRequests* upgradeRequests = AZ::Interface::Get(); + m_assetsToInspect = upgradeRequests->GetAssetsToUpgrade(); + DoScan(); + } + + void VersionExplorer::DoScan() + { + m_state = ProcessState::Scan; + m_settingsCache = AZStd::make_unique(); + ScriptCanvas::Grammar::g_saveRawTranslationOuputToFile = false; + ScriptCanvas::Grammar::g_printAbstractCodeModel = false; + ScriptCanvas::Grammar::g_saveRawTranslationOuputToFile = false; + + AZ::SystemTickBus::Handler::BusConnect(); + AZ::Debug::TraceMessageBus::Handler::BusConnect(); + + if (!m_assetsToInspect.empty()) + { + m_discoveredAssets = m_assetsToInspect.size(); + m_failedAssets = 0; + m_inspectedAssets = 0; + m_currentAssetRowIndex = 0; + m_ui->progressFrame->setVisible(true); + m_ui->progressBar->setVisible(true); + m_ui->progressBar->setRange(0, aznumeric_cast(m_assetsToInspect.size())); + m_ui->progressBar->setValue(0); + + m_ui->spinner->SetIsBusy(true); + m_ui->spinner->SetBusyIconSize(32); + + m_ui->scanButton->setEnabled(false); + m_ui->upgradeAllButton->setEnabled(false); + m_ui->onlyShowOutdated->setEnabled(false); + + m_inspectingAsset = m_assetsToInspect.begin(); + m_keepEditorAlive = AZStd::make_unique(); + } + } + + void VersionExplorer::BackupComplete() + { + m_currentAssetRowIndex = 0; + m_ui->progressBar->setValue(0); + DoScan(); + } + + void VersionExplorer::InspectAsset(AZ::Data::Asset& asset, AZ::Data::AssetInfo& assetInfo) + { + Log("InspectAsset: %s", asset.GetHint().c_str()); + AZ::Entity* scriptCanvasEntity = nullptr; + if (asset.GetType() == azrtti_typeid()) + { + ScriptCanvasAsset* scriptCanvasAsset = asset.GetAs(); + if (!scriptCanvasAsset) + { + Log("InspectAsset: %s, AsestData failed to return ScriptCanvasAsset", asset.GetHint().c_str()); + return; + } + + scriptCanvasEntity = scriptCanvasAsset->GetScriptCanvasEntity(); + AZ_Assert(scriptCanvasEntity, "The Script Canvas asset must have a valid entity"); + } + + auto graphComponent = scriptCanvasEntity->FindComponent(); + AZ_Assert(graphComponent, "The Script Canvas entity must have a Graph component"); + + bool onlyShowOutdatedGraphs = m_ui->onlyShowOutdated->isChecked(); + bool forceUpgrade = m_ui->forceUpgrade->isChecked(); + ScriptCanvas::VersionData graphVersion = graphComponent->GetVersion(); + + + if (!forceUpgrade && onlyShowOutdatedGraphs && graphVersion.IsLatest()) + { + ScanComplete(asset); + Log("InspectAsset: %s, is at latest", asset.GetHint().c_str()); + return; + } + + m_ui->tableWidget->insertRow(static_cast(m_currentAssetRowIndex)); + QTableWidgetItem* rowName = new QTableWidgetItem(tr(asset.GetHint().c_str())); + m_ui->tableWidget->setItem(static_cast(m_currentAssetRowIndex), static_cast(ColumnAsset), rowName); + + if (forceUpgrade || !graphComponent->GetVersion().IsLatest()) + { + m_assetsToUpgrade.push_back(asset); + + AzQtComponents::StyledBusyLabel* spinner = new AzQtComponents::StyledBusyLabel(this); + spinner->SetBusyIconSize(16); + + QPushButton* rowGoToButton = new QPushButton(this); + rowGoToButton->setText("Upgrade"); + rowGoToButton->setEnabled(false); + + connect(rowGoToButton, &QPushButton::clicked, [this, spinner, rowGoToButton, assetInfo] { + + AZ::SystemTickBus::QueueFunction([this, rowGoToButton, spinner, assetInfo]() { + // Queue the process state change because we can't connect to the SystemTick bus in a Qt lambda + UpgradeSingle(rowGoToButton, spinner, assetInfo); + }); + + AZ::SystemTickBus::ExecuteQueuedEvents(); + + }); + + m_ui->tableWidget->setCellWidget(static_cast(m_currentAssetRowIndex), static_cast(ColumnAction), rowGoToButton); + m_ui->tableWidget->setCellWidget(static_cast(m_currentAssetRowIndex), static_cast(ColumnStatus), spinner); + } + + char resolvedBuffer[AZ_MAX_PATH_LEN] = { 0 }; + AZStd::string path = AZStd::string::format("@devroot@/%s", asset.GetHint().c_str()); + AZ::IO::FileIOBase::GetInstance()->ResolvePath(path.c_str(), resolvedBuffer, AZ_MAX_PATH_LEN); + AZ::StringFunc::Path::GetFullPath(resolvedBuffer, path); + AZ::StringFunc::Path::Normalize(path); + + bool result = false; + AZ::Data::AssetInfo info; + AZStd::string watchFolder; + QByteArray assetNameUtf8 = asset.GetHint().c_str(); + AzToolsFramework::AssetSystemRequestBus::BroadcastResult(result, &AzToolsFramework::AssetSystemRequestBus::Events::GetSourceInfoBySourcePath, assetNameUtf8, info, watchFolder); + + AZ_Error(ScriptCanvas::k_VersionExplorerWindow.data(), result, "Failed to locate asset info for '%s'.", assetNameUtf8.constData()); + + QToolButton* browseButton = new QToolButton(this); + browseButton->setToolTip(AzQtComponents::fileBrowserActionName()); + browseButton->setIcon(QIcon(":/stylesheet/img/UI20/browse-edit.svg")); + + QString absolutePath = QDir(watchFolder.c_str()).absoluteFilePath(info.m_relativePath.c_str()); + connect(browseButton, &QPushButton::clicked, [absolutePath] { + AzQtComponents::ShowFileOnDesktop(absolutePath); + }); + + m_ui->tableWidget->setCellWidget(static_cast(m_currentAssetRowIndex), static_cast(ColumnBrowse), browseButton); + ScanComplete(asset); + ++m_inspectedAssets; + ++m_currentAssetRowIndex; + } + + void VersionExplorer::UpgradeSingle + ( QPushButton* rowGoToButton + , AzQtComponents::StyledBusyLabel* spinner + , AZ::Data::AssetInfo assetInfo) + { + AZ::Data::Asset asset = AZ::Data::AssetManager::Instance().GetAsset + ( assetInfo.m_assetId, assetInfo.m_assetType, AZ::Data::AssetLoadBehavior::PreLoad); + + if (asset) + { + asset.BlockUntilLoadComplete(); + + if (asset.IsReady()) + { + AZ::Interface::Get()->SetIsUpgrading(true); + m_isUpgradingSingleGraph = true; + m_logs.clear(); + m_ui->textEdit->clear(); + spinner->SetIsBusy(true); + rowGoToButton->setEnabled(false); + + m_inProgressAsset = AZStd::find_if(m_assetsToUpgrade.begin(), m_assetsToUpgrade.end() + , [asset](const UpgradeAssets::value_type& assetToUpgrade) + { + return assetToUpgrade.GetId() == asset.GetId(); + }); + + m_state = ProcessState::Upgrade; + AZ::SystemTickBus::Handler::BusConnect(); + } + } + } + + void VersionExplorer::ScanComplete(const AZ::Data::Asset& asset) + { + Log("ScanComplete: %s", asset.GetHint().c_str()); + m_inProgress = false; + m_ui->progressBar->setValue(aznumeric_cast(m_currentAssetRowIndex)); + m_ui->scanButton->setEnabled(true); + + m_inspectingAsset = m_assetsToInspect.erase(m_inspectingAsset); + FlushLogs(); + + if (m_inspectingAsset == m_assetsToInspect.end()) + { + AZ::SystemTickBus::QueueFunction([this]() { FinalizeScan(); }); + + if (!m_assetsToUpgrade.empty()) + { + m_ui->upgradeAllButton->setEnabled(true); + } + } + } + + void VersionExplorer::FinalizeScan() + { + Log("FinalizeScan()"); + + m_ui->spinner->SetIsBusy(false); + m_ui->onlyShowOutdated->setEnabled(true); + + // Enable all the Upgrade buttons + for (int row = 0; row < m_ui->tableWidget->rowCount(); ++row) + { + QPushButton* button = qobject_cast(m_ui->tableWidget->cellWidget(row, ColumnAction)); + if (button) + { + button->setEnabled(true); + } + } + + QString spinnerText = QStringLiteral("Scan Complete"); + if (m_assetsToUpgrade.empty()) + { + spinnerText.append(" - No graphs require upgrade!"); + } + else + { + spinnerText.append(QString::asprintf(" - Discovered: %zu, Inspected: %zu, Failed: %zu, Upgradeable: %zu" + , m_discoveredAssets, m_inspectedAssets, m_failedAssets, m_assetsToUpgrade.size())); + } + + + m_ui->spinner->SetText(spinnerText); + m_ui->progressBar->setVisible(false); + + if (!m_assetsToUpgrade.empty()) + { + m_ui->upgradeAllButton->setEnabled(true); + } + + AZ::SystemTickBus::Handler::BusDisconnect(); + AZ::Debug::TraceMessageBus::Handler::BusDisconnect(); + UpgradeNotifications::Bus::Handler::BusDisconnect(); + + m_keepEditorAlive.reset(); + m_settingsCache.reset(); + m_state = ProcessState::Inactive; + } + + void VersionExplorer::FlushLogs() + { + if (m_logs.empty()) + { + return; + } + + const QTextCursor oldCursor = m_ui->textEdit->textCursor(); + QScrollBar* scrollBar = m_ui->textEdit->verticalScrollBar(); + + m_ui->textEdit->moveCursor(QTextCursor::End); + QTextCursor textCursor = m_ui->textEdit->textCursor(); + + while (!m_logs.empty()) + { + auto line = "\n" + m_logs.front(); + + m_logs.pop_front(); + + textCursor.insertText(line.c_str()); + } + + scrollBar->setValue(scrollBar->maximum()); + m_ui->textEdit->moveCursor(QTextCursor::StartOfLine); + + } + + bool VersionExplorer::CaptureLogFromTraceBus(const char* window, const char* message) + { + if (m_ui->updateReportingOnly->isChecked() && window != ScriptCanvas::k_VersionExplorerWindow) + { + return true; + } + + AZStd::string msg = message; + if (msg.ends_with("\n")) + { + msg = msg.substr(0, msg.size() - 1); + } + + m_logs.push_back(msg); + return m_ui->updateReportingOnly->isChecked(); + } + + bool VersionExplorer::OnPreError(const char* window, const char* /*fileName*/, int /*line*/, const char* /*func*/, const char* message) + { + AZStd::string msg = AZStd::string::format("(Error): %s", message); + return CaptureLogFromTraceBus(window, msg.c_str()); + } + + bool VersionExplorer::OnPreWarning(const char* window, const char* /*fileName*/, int /*line*/, const char* /*func*/, const char* message) + { + AZStd::string msg = AZStd::string::format("(Warning): %s", message); + return CaptureLogFromTraceBus(window, msg.c_str()); + } + + bool VersionExplorer::OnException(const char* message) + { + AZStd::string msg = AZStd::string::format("(Exception): %s", message); + return CaptureLogFromTraceBus("Script Canvas", msg.c_str()); + } + + bool VersionExplorer::OnPrintf(const char* window, const char* message) + { + return CaptureLogFromTraceBus(window, message); + } + + void VersionExplorer::closeEvent(QCloseEvent* event) + { + m_keepEditorAlive.reset(); + + AzQtComponents::StyledDialog::closeEvent(event); + } + +#include + +} diff --git a/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/FileSaver.h b/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/FileSaver.h new file mode 100644 index 0000000000..acb28a7dba --- /dev/null +++ b/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/FileSaver.h @@ -0,0 +1,180 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#if !defined(Q_MOC_RUN) +#include + +AZ_PUSH_DISABLE_WARNING(4244 4251 4800, "-Wunknown-warning-option") +#include +AZ_POP_DISABLE_WARNING + +#include +#include +#include + +#include +#include + +#include +#include +#include +#endif + +class QPushButton; + +namespace Ui +{ + class VersionExplorer; +} + +namespace AzQtComponents +{ + class StyledBusyLabel; +} + +namespace ScriptCanvasEditor +{ + //! Scoped utility to set and restore the "ed_KeepEditorActive" CVar in order to allow + //! the upgrade tool to work even if the editor is not in the foreground + class EditorKeepAlive + { + public: + EditorKeepAlive(); + ~EditorKeepAlive(); + + private: + int m_keepEditorActive; + ICVar* m_edKeepEditorActive; + }; + + //! A tool that collects and upgrades all Script Canvas graphs in the asset catalog + class VersionExplorer + : public AzQtComponents::StyledDialog + , private AZ::SystemTickBus::Handler + , private UpgradeNotifications::Bus::Handler + , private AZ::Debug::TraceMessageBus::Handler + { + Q_OBJECT + + public: + AZ_CLASS_ALLOCATOR(VersionExplorer, AZ::SystemAllocator, 0); + + explicit VersionExplorer(QWidget* parent = nullptr); + ~VersionExplorer(); + + private: + + static constexpr int ColumnAsset = 0; + static constexpr int ColumnAction = 1; + static constexpr int ColumnBrowse = 2; + static constexpr int ColumnStatus = 3; + + void OnScan(); + void OnClose(); + + enum class ProcessState + { + Inactive, + Backup, + Scan, + Upgrade, + }; + ProcessState m_state = ProcessState::Inactive; + + void DoScan(); + void ScanComplete(const AZ::Data::Asset&); + + void InspectAsset(AZ::Data::Asset& asset, AZ::Data::AssetInfo& assetInfo); + + void OnUpgradeAll(); + + // SystemTickBus::Handler + void OnSystemTick() override; + // + + // AZ::Debug::TranceMessageBus::Handler + bool OnException(const char* /*message*/) override; + bool OnPrintf(const char* /*window*/, const char* /*message*/) override; + bool OnPreError(const char* /*window*/, const char* /*fileName*/, int /*line*/, const char* /*func*/, const char* /*message*/) override; + bool OnPreWarning(const char* /*window*/, const char* /*fileName*/, int /*line*/, const char* /*func*/, const char* /*message*/) override; + // + + bool CaptureLogFromTraceBus(const char* window, const char* message); + + enum class OperationResult + { + Success, + Failure, + }; + + void GraphUpgradeComplete(const AZ::Data::Asset, OperationResult result, AZStd::string_view message); + + bool IsUpgrading() const; + + bool m_inProgress = false; + // scan fields + size_t m_currentAssetRowIndex = 0; + size_t m_inspectedAssets = 0; + size_t m_failedAssets = 0; + size_t m_discoveredAssets = 0; + + IUpgradeRequests::AssetList m_assetsToInspect; + IUpgradeRequests::AssetList::iterator m_inspectingAsset; + using UpgradeAssets = AZStd::vector>; + UpgradeAssets m_assetsToUpgrade; + UpgradeAssets::iterator m_inProgressAsset; + + AZ::Data::Asset m_currentAsset; + + AZStd::unique_ptr m_ui; + + AZStd::unique_ptr m_settingsCache; + + // upgrade fields + AZStd::recursive_mutex m_mutex; + bool m_upgradeComplete = false; + AZ::Data::Asset m_upgradeAsset; + int m_upgradeAssetIndex = 0; + OperationResult m_upgradeResult; + AZStd::string m_upgradeMessage; + AZStd::string m_tmpFileName; + + AZStd::unique_ptr m_keepEditorAlive; + + AZStd::deque m_logs; + + AZ::Entity* m_scriptCanvasEntity = nullptr; + + bool m_isUpgradingSingleGraph = false; + + void UpgradeSingle(QPushButton* item, AzQtComponents::StyledBusyLabel* spinner, AZ::Data::AssetInfo assetInfo); + + void FlushLogs(); + + void FinalizeUpgrade(); + void FinalizeScan(); + + void BackupComplete(); + AZStd::string BackupGraph(const AZ::Data::Asset&); + void UpgradeGraph(const AZ::Data::Asset&); + + void GraphUpgradeCompleteUIUpdate(const AZ::Data::Asset asset, OperationResult result, AZStd::string_view message); + void OnGraphUpgradeComplete(AZ::Data::Asset&, bool skipped = false) override; + + void OnSourceFileReleased(AZ::Data::Asset asset); + + void closeEvent(QCloseEvent* event) override; + + bool m_overwriteAll = false; + void PerformMove(AZ::Data::Asset asset, AZStd::string source, AZStd::string target, size_t remainingAttempts); + + void Log(const char* format, ...); + }; +} diff --git a/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/Modifier.cpp b/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/Modifier.cpp new file mode 100644 index 0000000000..b96a5c6d0c --- /dev/null +++ b/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/Modifier.cpp @@ -0,0 +1,1023 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace VersionExplorerCpp +{ + class FileEventHandler + : public AZ::IO::FileIOEventBus::Handler + { + public: + int m_errorCode = 0; + AZStd::string m_fileName; + + FileEventHandler() + { + BusConnect(); + } + + ~FileEventHandler() + { + BusDisconnect(); + } + + void OnError(const AZ::IO::SystemFile* /*file*/, const char* fileName, int errorCode) override + { + m_errorCode = errorCode; + + if (fileName) + { + m_fileName = fileName; + } + } + }; +} + +namespace ScriptCanvasEditor +{ + EditorKeepAlive::EditorKeepAlive() + { + ISystem* system = nullptr; + CrySystemRequestBus::BroadcastResult(system, &CrySystemRequestBus::Events::GetCrySystem); + + m_edKeepEditorActive = system->GetIConsole()->GetCVar("ed_KeepEditorActive"); + + if (m_edKeepEditorActive) + { + m_keepEditorActive = m_edKeepEditorActive->GetIVal(); + m_edKeepEditorActive->Set(1); + } + } + + EditorKeepAlive::~EditorKeepAlive() + { + if (m_edKeepEditorActive) + { + m_edKeepEditorActive->Set(m_keepEditorActive); + } + } + + Modifier::Modifier(QWidget* parent /*= nullptr*/) + : AzQtComponents::StyledDialog(parent) + , m_ui(new Ui::Modifier()) + { + m_ui->setupUi(this); + + m_ui->tableWidget->horizontalHeader()->setVisible(false); + m_ui->tableWidget->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch); + m_ui->tableWidget->horizontalHeader()->setSectionResizeMode(3, QHeaderView::Fixed); + m_ui->tableWidget->setColumnWidth(3, 22); + + m_ui->textEdit->setHorizontalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAsNeeded); + m_ui->textEdit->setVerticalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAlwaysOn); + + connect(m_ui->scanButton, &QPushButton::pressed, this, &Modifier::OnScan); + connect(m_ui->closeButton, &QPushButton::pressed, this, &Modifier::OnClose); + connect(m_ui->upgradeAllButton, &QPushButton::pressed, this, &Modifier::OnUpgradeAll); + + m_ui->progressBar->setValue(0); + m_ui->progressBar->setVisible(false); + + m_keepEditorAlive = AZStd::make_unique(); + m_inspectingAsset = m_assetsToInspect.end(); + + } + + Modifier::~Modifier() + { + AZ::SystemTickBus::Handler::BusDisconnect(); + + UpgradeNotifications::Bus::Handler::BusDisconnect(); + AZ::Debug::TraceMessageBus::Handler::BusDisconnect(); + + } + + void Modifier::Log(const char* format, ...) + { + if (m_ui->verbose->isChecked()) + { + char sBuffer[2048]; + va_list ArgList; + va_start(ArgList, format); + azvsnprintf(sBuffer, sizeof(sBuffer), format, ArgList); + sBuffer[sizeof(sBuffer) - 1] = '\0'; + va_end(ArgList); + + AZ_TracePrintf(ScriptCanvas::k_VersionExplorerWindow.data(), "%s\n", sBuffer); + } + } + + void Modifier::OnClose() + { + reject(); + } + + bool Modifier::IsUpgrading() const + { + return m_inProgressAsset != m_assetsToUpgrade.end() && m_inProgress; + } + + void Modifier::OnSystemTick() + { + switch (m_state) + { + case ProcessState::Scan: + + if (!m_inProgress && m_inspectingAsset != m_assetsToInspect.end()) + { + m_inProgress = true; + AZ::Data::AssetInfo& assetToUpgrade = *m_inspectingAsset; + m_currentAsset = AZ::Data::AssetManager::Instance().GetAsset(assetToUpgrade.m_assetId, assetToUpgrade.m_assetType, AZ::Data::AssetLoadBehavior::PreLoad); + Log("SystemTick::ProcessState::Scan: %s pre-blocking load hint", m_currentAsset.GetHint().c_str()); + m_currentAsset.BlockUntilLoadComplete(); + if (m_currentAsset.IsReady()) + { + // The asset is ready, grab its info + m_inProgress = true; + InspectAsset(m_currentAsset, assetToUpgrade); + } + else + { + m_ui->tableWidget->insertRow(static_cast(m_currentAssetRowIndex)); + QTableWidgetItem* rowName = new QTableWidgetItem + ( tr(AZStd::string::format("Error: %s", assetToUpgrade.m_relativePath.c_str()).c_str())); + m_ui->tableWidget->setItem(static_cast(m_currentAssetRowIndex), static_cast(ColumnAsset), rowName); + ++m_currentAssetRowIndex; + + Log("SystemTick::ProcessState::Scan: %s post-blocking load, problem loading asset", assetToUpgrade.m_relativePath.c_str()); + ++m_failedAssets; + ScanComplete(m_currentAsset); + } + } + break; + + case ProcessState::Upgrade: + { + AZStd::lock_guard lock(m_mutex); + if (m_upgradeComplete) + { + ++m_upgradeAssetIndex; + m_inProgress = false; + m_ui->progressBar->setVisible(true); + m_ui->progressBar->setValue(m_upgradeAssetIndex); + + if (m_scriptCanvasEntity) + { + m_scriptCanvasEntity->Deactivate(); + m_scriptCanvasEntity = nullptr; + } + + GraphUpgradeCompleteUIUpdate(m_upgradeAsset, m_upgradeResult, m_upgradeMessage); + + if (!m_isUpgradingSingleGraph) + { + if (m_inProgressAsset != m_assetsToUpgrade.end()) + { + m_inProgressAsset = m_assetsToUpgrade.erase(m_inProgressAsset); + } + + if (m_inProgressAsset == m_assetsToUpgrade.end()) + { + FinalizeUpgrade(); + } + } + else + { + m_inProgressAsset = m_assetsToUpgrade.erase(m_inProgressAsset); + m_inProgress = false; + m_state = ProcessState::Inactive; + m_settingsCache.reset(); + AZ::SystemTickBus::Handler::BusDisconnect(); + AZ::Debug::TraceMessageBus::Handler::BusDisconnect(); + } + + m_isUpgradingSingleGraph = false; + + if (m_assetsToUpgrade.empty()) + { + m_ui->upgradeAllButton->setEnabled(false); + } + + m_upgradeComplete = false; + } + + if (!IsUpgrading() && m_state == ProcessState::Upgrade) + { + AZStd::string errorMessage = BackupGraph(*m_inProgressAsset); + // Make the backup + if (errorMessage.empty()) + { + Log("SystemTick::ProcessState::Upgrade: Backup Success %s ", m_inProgressAsset->GetHint().c_str()); + QList items = m_ui->tableWidget->findItems(m_inProgressAsset->GetHint().c_str(), Qt::MatchFlag::MatchExactly); + if (!items.isEmpty()) + { + for (auto* item : items) + { + int row = item->row(); + AzQtComponents::StyledBusyLabel* spinner = qobject_cast(m_ui->tableWidget->cellWidget(row, ColumnStatus)); + spinner->SetIsBusy(true); + } + } + + // Upgrade the graph + UpgradeGraph(*m_inProgressAsset); + } + else + { + Log("SystemTick::ProcessState::Upgrade: Backup Failed %s ", m_inProgressAsset->GetHint().c_str()); + GraphUpgradeComplete(*m_inProgressAsset, OperationResult::Failure, errorMessage); + } + + } + break; + } + default: + break; + } + + FlushLogs(); + + AZ::Data::AssetManager::Instance().DispatchEvents(); + AZ::SystemTickBus::ExecuteQueuedEvents(); + } + + // Backup + + void Modifier::OnUpgradeAll() + { + m_state = ProcessState::Upgrade; + m_settingsCache = AZStd::make_unique(); + ScriptCanvas::Grammar::g_saveRawTranslationOuputToFile = false; + ScriptCanvas::Grammar::g_printAbstractCodeModel = false; + ScriptCanvas::Grammar::g_saveRawTranslationOuputToFile = false; + AZ::Interface::Get()->SetIsUpgrading(true); + AZ::Interface::Get()->ClearGraphsThatNeedUpgrade(); + m_inProgressAsset = m_assetsToUpgrade.begin(); + AZ::Debug::TraceMessageBus::Handler::BusConnect(); + AZ::SystemTickBus::Handler::BusConnect(); + m_ui->progressBar->setVisible(true); + m_ui->progressBar->setRange(0, aznumeric_cast(m_assetsToUpgrade.size())); + m_ui->progressBar->setValue(m_upgradeAssetIndex); + m_keepEditorAlive = AZStd::make_unique(); + } + + AZStd::string Modifier::BackupGraph(const AZ::Data::Asset& asset) + { + if (!m_ui->makeBackupCheckbox->isChecked()) + { + // considered a success + return ""; + } + + QDateTime theTime = QDateTime::currentDateTime(); + QString subFolder = theTime.toString("yyyy-MM-dd [HH.mm.ss]"); + + AZStd::string backupPath = AZStd::string::format("@devroot@/ScriptCanvas_BACKUP/%s", subFolder.toUtf8().data()); + char backupPathCStr[AZ_MAX_PATH_LEN] = { 0 }; + AZ::IO::FileIOBase::GetInstance()->ResolvePath(backupPath.c_str(), backupPathCStr, AZ_MAX_PATH_LEN); + backupPath = backupPathCStr; + + if (!AZ::IO::FileIOBase::GetInstance()->Exists(backupPath.c_str())) + { + if (AZ::IO::FileIOBase::GetInstance()->CreatePath(backupPath.c_str()) != AZ::IO::ResultCode::Success) + { + AZ_Error(ScriptCanvas::k_VersionExplorerWindow.data(), false, "Failed to create backup folder %s", backupPath.c_str()); + return "Failed to create backup folder"; + } + } + + AZStd::string devRoot = "@devroot@"; + AZStd::string devAssets = "@devassets@"; + + char devRootCStr[AZ_MAX_PATH_LEN] = { 0 }; + AZ::IO::FileIOBase::GetInstance()->ResolvePath(devRoot.c_str(), devRootCStr, AZ_MAX_PATH_LEN); + + char devAssetsCStr[AZ_MAX_PATH_LEN] = { 0 }; + AZ::IO::FileIOBase::GetInstance()->ResolvePath(devAssets.c_str(), devAssetsCStr, AZ_MAX_PATH_LEN); + + AZStd::string relativePath = devAssetsCStr; + AzFramework::StringFunc::Replace(relativePath, devRootCStr, ""); + if (relativePath.starts_with("/")) + { + relativePath = relativePath.substr(1, relativePath.size() - 1); + } + + AZStd::string sourceFilePath; + + AZStd::string watchFolder; + AZ::Data::AssetInfo assetInfo; + bool sourceInfoFound{}; + AzToolsFramework::AssetSystemRequestBus::BroadcastResult(sourceInfoFound, &AzToolsFramework::AssetSystemRequestBus::Events::GetSourceInfoBySourcePath, asset.GetHint().c_str(), assetInfo, watchFolder); + if (sourceInfoFound) + { + AZStd::string assetPath; + AzFramework::StringFunc::Path::Join(watchFolder.c_str(), assetInfo.m_relativePath.c_str(), assetPath); + + sourceFilePath = assetPath; + } + else + { + AZ_Warning(ScriptCanvas::k_VersionExplorerWindow.data(), false, "Modifier::BackupGraph: Failed to find file: %s", asset.GetHint().c_str()); + return "Failed to find source file"; + } + + devRoot = devRootCStr; + AzFramework::StringFunc::Path::Normalize(devRoot); + + relativePath = sourceFilePath; + AzFramework::StringFunc::Replace(relativePath, devRoot.c_str(), ""); + if (relativePath.starts_with("/")) + { + relativePath = relativePath.substr(1, relativePath.size() - 1); + } + + AzFramework::StringFunc::Path::Normalize(relativePath); + AzFramework::StringFunc::Path::Normalize(backupPath); + + AZStd::string targetFilePath = backupPath; + targetFilePath += relativePath; + + if (AZ::IO::FileIOBase::GetInstance()->Copy(sourceFilePath.c_str(), targetFilePath.c_str()) != AZ::IO::ResultCode::Success) + { + AZ_Warning(ScriptCanvas::k_VersionExplorerWindow.data(), false, "Modifier::BackupGraph: Error creating backup: %s ---> %s\n", sourceFilePath.c_str(), targetFilePath.c_str()); + return "Failed to copy source file to backup location"; + } + + Log("Modifier::BackupGraph: Backed up: %s ---> %s\n", sourceFilePath.c_str(), targetFilePath.c_str()); + return ""; + } + + void Modifier::UpgradeGraph(const AZ::Data::Asset& asset) + { + m_inProgress = true; + m_upgradeComplete = false; + Log("UpgradeGraph %s ", m_inProgressAsset->GetHint().c_str()); + m_ui->spinner->SetText(QObject::tr("Upgrading: %1").arg(asset.GetHint().c_str())); + m_scriptCanvasEntity = nullptr; + + UpgradeNotifications::Bus::Handler::BusConnect(); + + if (asset.GetType() == azrtti_typeid()) + { + ScriptCanvasAsset* scriptCanvasAsset = asset.GetAs(); + AZ_Assert(scriptCanvasAsset, "Unable to get the asset of ScriptCanvasAsset, but received type: %s" + , azrtti_typeid().template ToString().c_str()); + + if (!scriptCanvasAsset) + { + return; + } + + AZ::Entity* scriptCanvasEntity = scriptCanvasAsset->GetScriptCanvasEntity(); + AZ_Assert(scriptCanvasEntity, "Modifier::UpgradeGraph The Script Canvas asset must have a valid entity"); + if (!scriptCanvasEntity) + { + return; + } + + AZ::Entity* queryEntity = nullptr; + AZ::ComponentApplicationBus::BroadcastResult(queryEntity, &AZ::ComponentApplicationRequests::FindEntity, scriptCanvasEntity->GetId()); + if (queryEntity) + { + if (queryEntity->GetState() == AZ::Entity::State::Active) + { + queryEntity->Deactivate(); + } + + scriptCanvasEntity = queryEntity; + } + + if (scriptCanvasEntity->GetState() == AZ::Entity::State::Constructed) + { + scriptCanvasEntity->Init(); + } + + if (scriptCanvasEntity->GetState() == AZ::Entity::State::Init) + { + scriptCanvasEntity->Activate(); + } + + AZ_Assert(scriptCanvasEntity->GetState() == AZ::Entity::State::Active, "Graph entity is not active"); + auto graphComponent = scriptCanvasEntity->FindComponent(); + AZ_Assert(graphComponent, "The Script Canvas entity must have a Graph component"); + + if (graphComponent) + { + m_scriptCanvasEntity = scriptCanvasEntity; + + graphComponent->UpgradeGraph + ( asset + , m_ui->forceUpgrade->isChecked() ? Graph::UpgradeRequest::Forced : Graph::UpgradeRequest::IfOutOfDate + , m_ui->verbose->isChecked()); + } + } + + AZ_Assert(m_scriptCanvasEntity, "The ScriptCanvas asset should have an entity"); + } + + void Modifier::OnGraphUpgradeComplete(AZ::Data::Asset& asset, bool /*skipped*/ /*= false*/) + { + AZStd::string relativePath, fullPath; + AZ::Data::AssetCatalogRequestBus::BroadcastResult(relativePath, &AZ::Data::AssetCatalogRequests::GetAssetPathById, asset.GetId()); + bool fullPathFound = false; + AzToolsFramework::AssetSystemRequestBus::BroadcastResult(fullPathFound, &AzToolsFramework::AssetSystemRequestBus::Events::GetFullSourcePathFromRelativeProductPath, relativePath, fullPath); + if (!fullPathFound) + { + AZ_Error(ScriptCanvas::k_VersionExplorerWindow.data(), false, "Full source path not found for %s", relativePath.c_str()); + } + + auto streamer = AZ::Interface::Get(); + AZ::IO::FileRequestPtr flushRequest = streamer->FlushCache(fullPath); + streamer->SetRequestCompleteCallback(flushRequest, [this, asset]([[maybe_unused]] AZ::IO::FileRequestHandle request) + { + this->OnSourceFileReleased(asset); + }); + streamer->QueueRequest(flushRequest); + } + + void Modifier::OnSourceFileReleased(AZ::Data::Asset asset) + { + AZStd::string relativePath, fullPath; + AZ::Data::AssetCatalogRequestBus::BroadcastResult(relativePath, &AZ::Data::AssetCatalogRequests::GetAssetPathById, asset.GetId()); + bool fullPathFound = false; + AzToolsFramework::AssetSystemRequestBus::BroadcastResult(fullPathFound, &AzToolsFramework::AssetSystemRequestBus::Events::GetFullSourcePathFromRelativeProductPath, relativePath, fullPath); + m_tmpFileName.clear(); + AZStd::string tmpFileName; + // here we are saving the graph to a temp file instead of the original file and then copying the temp file to the original file. + // This ensures that AP will not a get a file change notification on an incomplete graph file causing it to fail processing. Temp files are ignored by AP. + if (!AZ::IO::CreateTempFileName(fullPath.c_str(), tmpFileName)) + { + GraphUpgradeComplete(asset, OperationResult::Failure, "Failure to create temporary file name"); + return; + } + + bool tempSavedSucceeded = false; + AZ::IO::FileIOStream fileStream(tmpFileName.c_str(), AZ::IO::OpenMode::ModeWrite | AZ::IO::OpenMode::ModeText); + if (fileStream.IsOpen()) + { + if (asset.GetType() == azrtti_typeid()) + { + ScriptCanvasEditor::ScriptCanvasAssetHandler handler; + tempSavedSucceeded = handler.SaveAssetData(asset, &fileStream); + } + + fileStream.Close(); + } + + // attempt to remove temporary file no matter what + m_tmpFileName = tmpFileName; + if (!tempSavedSucceeded) + { + GraphUpgradeComplete(asset, OperationResult::Failure, "Save asset data to temporary file failed"); + return; + } + + using SCCommandBus = AzToolsFramework::SourceControlCommandBus; + SCCommandBus::Broadcast(&SCCommandBus::Events::RequestEdit, fullPath.c_str(), true, + [this, asset, fullPath, tmpFileName]([[maybe_unused]] bool success, const AzToolsFramework::SourceControlFileInfo& info) + { + constexpr const size_t k_maxAttemps = 10; + + if (!info.IsReadOnly()) + { + PerformMove(asset, tmpFileName, fullPath, k_maxAttemps); + } + else + { + if (m_overwriteAll) + { + AZ::IO::SystemFile::SetWritable(info.m_filePath.c_str(), true); + PerformMove(asset, tmpFileName, fullPath, k_maxAttemps); + } + else + { + int result = QMessageBox::No; + if (!m_overwriteAll) + { + QMessageBox mb(QMessageBox::Warning, + QObject::tr("Failed to Save Upgraded File"), + QObject::tr("The upgraded file could not be saved because the file is read only.\nDo you want to make it writeable and overwrite it?"), + QMessageBox::YesToAll | QMessageBox::Yes | QMessageBox::No, this); + + result = mb.exec(); + if (result == QMessageBox::YesToAll) + { + m_overwriteAll = true; + } + } + + if (result == QMessageBox::Yes || m_overwriteAll) + { + AZ::IO::SystemFile::SetWritable(info.m_filePath.c_str(), true); + PerformMove(asset, tmpFileName, fullPath, k_maxAttemps); + } + } + } + }); + } + + void Modifier::PerformMove(AZ::Data::Asset asset, AZStd::string source, AZStd::string target + , size_t remainingAttempts) + { + VersionExplorerCpp::FileEventHandler fileEventHandler; + + if (remainingAttempts == 0) + { + // all attempts failed, give up + AZ_Warning(ScriptCanvas::k_VersionExplorerWindow.data(), false, "moving converted file to source destination failed: %s. giving up", target.c_str()); + GraphUpgradeComplete(asset, OperationResult::Failure, "Failed to move updated file from backup to source destination"); + } + else if (remainingAttempts == 2) + { + // before the final attempt, flush all caches + AZ_Warning(ScriptCanvas::k_VersionExplorerWindow.data(), false, "moving converted file to source destination failed: %s, trying again", target.c_str()); + auto streamer = AZ::Interface::Get(); + AZ::IO::FileRequestPtr flushRequest = streamer->FlushCaches(); + streamer->SetRequestCompleteCallback(flushRequest + , [this, asset, remainingAttempts, source, target]([[maybe_unused]] AZ::IO::FileRequestHandle request) + { + // Continue saving. + AZ::SystemTickBus::QueueFunction( + [this, asset, remainingAttempts, source, target](){ PerformMove(asset, source, target, remainingAttempts - 1); }); + }); + streamer->QueueRequest(flushRequest); + } + else + { + // the actual move attempt + auto moveResult = AZ::IO::SmartMove(source.c_str(), target.c_str()); + if (moveResult.GetResultCode() == AZ::IO::ResultCode::Success) + { + m_tmpFileName.clear(); + auto streamer = AZ::Interface::Get(); + AZ::IO::FileRequestPtr flushRequest = streamer->FlushCache(target.c_str()); + // Bump the slice asset up in the asset processor's queue. + AzFramework::AssetSystemRequestBus::Broadcast(&AzFramework::AssetSystem::AssetSystemRequests::EscalateAssetBySearchTerm, target.c_str()); + AZ::SystemTickBus::QueueFunction([this, asset]() + { + GraphUpgradeComplete(asset, OperationResult::Success, ""); + }); + } + else + { + AZ_Warning(ScriptCanvas::k_VersionExplorerWindow.data(), false, "moving converted file to source destination failed: %s, trying again", target.c_str()); + auto streamer = AZ::Interface::Get(); + AZ::IO::FileRequestPtr flushRequest = streamer->FlushCache(target.c_str()); + streamer->SetRequestCompleteCallback(flushRequest, [this, asset, source, target, remainingAttempts]([[maybe_unused]] AZ::IO::FileRequestHandle request) + { + // Continue saving. + AZ::SystemTickBus::QueueFunction([this, asset, source, target, remainingAttempts]() { PerformMove(asset, source, target, remainingAttempts - 1); }); + }); + streamer->QueueRequest(flushRequest); + } + } + } + + void Modifier::GraphUpgradeComplete + ( const AZ::Data::Asset asset, OperationResult result, AZStd::string_view message) + { + AZStd::lock_guard lock(m_mutex); + m_upgradeComplete = true; + m_upgradeResult = result; + m_upgradeMessage = message; + m_upgradeAsset = asset; + + if (!m_tmpFileName.empty()) + { + AZ::IO::FileIOBase* fileIO = AZ::IO::FileIOBase::GetInstance(); + AZ_Assert(fileIO, "GraphUpgradeComplete: No FileIO instance"); + + if (fileIO->Exists(m_tmpFileName.c_str()) && !fileIO->Remove(m_tmpFileName.c_str())) + { + AZ_TracePrintf(ScriptCanvas::k_VersionExplorerWindow.data(), "Failed to remove temporary file: %s", m_tmpFileName.c_str()); + } + } + + if (m_upgradeResult == OperationResult::Failure) + { + AZ::Interface::Get()->GraphNeedsManualUpgrade(asset.GetId()); + } + + m_tmpFileName.clear(); + } + + void Modifier::GraphUpgradeCompleteUIUpdate + ( const AZ::Data::Asset asset, OperationResult result, AZStd::string_view message) + { + QString text = asset.GetHint().c_str(); + QList items = m_ui->tableWidget->findItems(text, Qt::MatchFlag::MatchExactly); + + if (!items.isEmpty()) + { + for (auto* item : items) + { + int row = item->row(); + QTableWidgetItem* label = m_ui->tableWidget->item(row, ColumnAsset); + QString assetName = asset.GetHint().c_str(); + + if (label->text().compare(assetName) == 0) + { + m_ui->tableWidget->removeCellWidget(row, ColumnAction); + m_ui->tableWidget->removeCellWidget(row, ColumnStatus); + + QToolButton* doneButton = new QToolButton(this); + doneButton->setToolTip("Upgrade complete"); + if (result == OperationResult::Success) + { + doneButton->setIcon(QIcon(":/stylesheet/img/UI20/checkmark-menu.svg")); + } + else + { + doneButton->setIcon(QIcon(":/stylesheet/img/UI20/titlebar-close.svg")); + doneButton->setToolTip(message.data()); + } + + m_ui->tableWidget->setCellWidget(row, ColumnStatus, doneButton); + } + } + } + } + + void Modifier::FinalizeUpgrade() + { + Log("FinalizeUpgrade!"); + m_inProgress = false; + m_assetsToUpgrade.clear(); + m_ui->upgradeAllButton->setEnabled(false); + m_ui->onlyShowOutdated->setEnabled(true); + m_keepEditorAlive.reset(); + m_ui->progressBar->setVisible(false); + + // Manual correction + size_t assetsThatNeedManualInspection = AZ::Interface::Get()->GetGraphsThatNeedManualUpgrade().size(); + if (assetsThatNeedManualInspection > 0) + { + m_ui->spinner->SetText("Some graphs will require manual corrections, you will be prompted to review them upon closing this dialog"); + } + else + { + m_ui->spinner->SetText("Upgrade complete."); + } + + AZ::SystemTickBus::Handler::BusDisconnect(); + AZ::Debug::TraceMessageBus::Handler::BusDisconnect(); + UpgradeNotifications::Bus::Handler::BusDisconnect(); + AZ::Interface::Get()->SetIsUpgrading(false); + m_settingsCache.reset(); + } + + // Scanning + + void Modifier::OnScan() + { + m_assetsToUpgrade.clear(); + m_assetsToInspect.clear(); + m_ui->tableWidget->setRowCount(0); + m_inspectedAssets = 0; + m_currentAssetRowIndex = 0; + IUpgradeRequests* upgradeRequests = AZ::Interface::Get(); + m_assetsToInspect = upgradeRequests->GetAssetsToUpgrade(); + DoScan(); + } + + void Modifier::DoScan() + { + m_state = ProcessState::Scan; + m_settingsCache = AZStd::make_unique(); + ScriptCanvas::Grammar::g_saveRawTranslationOuputToFile = false; + ScriptCanvas::Grammar::g_printAbstractCodeModel = false; + ScriptCanvas::Grammar::g_saveRawTranslationOuputToFile = false; + + AZ::SystemTickBus::Handler::BusConnect(); + AZ::Debug::TraceMessageBus::Handler::BusConnect(); + + if (!m_assetsToInspect.empty()) + { + m_discoveredAssets = m_assetsToInspect.size(); + m_failedAssets = 0; + m_inspectedAssets = 0; + m_currentAssetRowIndex = 0; + m_ui->progressFrame->setVisible(true); + m_ui->progressBar->setVisible(true); + m_ui->progressBar->setRange(0, aznumeric_cast(m_assetsToInspect.size())); + m_ui->progressBar->setValue(0); + + m_ui->spinner->SetIsBusy(true); + m_ui->spinner->SetBusyIconSize(32); + + m_ui->scanButton->setEnabled(false); + m_ui->upgradeAllButton->setEnabled(false); + m_ui->onlyShowOutdated->setEnabled(false); + + m_inspectingAsset = m_assetsToInspect.begin(); + m_keepEditorAlive = AZStd::make_unique(); + } + } + + void Modifier::BackupComplete() + { + m_currentAssetRowIndex = 0; + m_ui->progressBar->setValue(0); + DoScan(); + } + + void Modifier::InspectAsset(AZ::Data::Asset& asset, AZ::Data::AssetInfo& assetInfo) + { + Log("InspectAsset: %s", asset.GetHint().c_str()); + AZ::Entity* scriptCanvasEntity = nullptr; + if (asset.GetType() == azrtti_typeid()) + { + ScriptCanvasAsset* scriptCanvasAsset = asset.GetAs(); + if (!scriptCanvasAsset) + { + Log("InspectAsset: %s, AsestData failed to return ScriptCanvasAsset", asset.GetHint().c_str()); + return; + } + + scriptCanvasEntity = scriptCanvasAsset->GetScriptCanvasEntity(); + AZ_Assert(scriptCanvasEntity, "The Script Canvas asset must have a valid entity"); + } + + auto graphComponent = scriptCanvasEntity->FindComponent(); + AZ_Assert(graphComponent, "The Script Canvas entity must have a Graph component"); + + bool onlyShowOutdatedGraphs = m_ui->onlyShowOutdated->isChecked(); + bool forceUpgrade = m_ui->forceUpgrade->isChecked(); + ScriptCanvas::VersionData graphVersion = graphComponent->GetVersion(); + + + if (!forceUpgrade && onlyShowOutdatedGraphs && graphVersion.IsLatest()) + { + ScanComplete(asset); + Log("InspectAsset: %s, is at latest", asset.GetHint().c_str()); + return; + } + + m_ui->tableWidget->insertRow(static_cast(m_currentAssetRowIndex)); + QTableWidgetItem* rowName = new QTableWidgetItem(tr(asset.GetHint().c_str())); + m_ui->tableWidget->setItem(static_cast(m_currentAssetRowIndex), static_cast(ColumnAsset), rowName); + + if (forceUpgrade || !graphComponent->GetVersion().IsLatest()) + { + m_assetsToUpgrade.push_back(asset); + + AzQtComponents::StyledBusyLabel* spinner = new AzQtComponents::StyledBusyLabel(this); + spinner->SetBusyIconSize(16); + + QPushButton* rowGoToButton = new QPushButton(this); + rowGoToButton->setText("Upgrade"); + rowGoToButton->setEnabled(false); + + connect(rowGoToButton, &QPushButton::clicked, [this, spinner, rowGoToButton, assetInfo] { + + AZ::SystemTickBus::QueueFunction([this, rowGoToButton, spinner, assetInfo]() { + // Queue the process state change because we can't connect to the SystemTick bus in a Qt lambda + UpgradeSingle(rowGoToButton, spinner, assetInfo); + }); + + AZ::SystemTickBus::ExecuteQueuedEvents(); + + }); + + m_ui->tableWidget->setCellWidget(static_cast(m_currentAssetRowIndex), static_cast(ColumnAction), rowGoToButton); + m_ui->tableWidget->setCellWidget(static_cast(m_currentAssetRowIndex), static_cast(ColumnStatus), spinner); + } + + char resolvedBuffer[AZ_MAX_PATH_LEN] = { 0 }; + AZStd::string path = AZStd::string::format("@devroot@/%s", asset.GetHint().c_str()); + AZ::IO::FileIOBase::GetInstance()->ResolvePath(path.c_str(), resolvedBuffer, AZ_MAX_PATH_LEN); + AZ::StringFunc::Path::GetFullPath(resolvedBuffer, path); + AZ::StringFunc::Path::Normalize(path); + + bool result = false; + AZ::Data::AssetInfo info; + AZStd::string watchFolder; + QByteArray assetNameUtf8 = asset.GetHint().c_str(); + AzToolsFramework::AssetSystemRequestBus::BroadcastResult(result, &AzToolsFramework::AssetSystemRequestBus::Events::GetSourceInfoBySourcePath, assetNameUtf8, info, watchFolder); + + AZ_Error(ScriptCanvas::k_VersionExplorerWindow.data(), result, "Failed to locate asset info for '%s'.", assetNameUtf8.constData()); + + QToolButton* browseButton = new QToolButton(this); + browseButton->setToolTip(AzQtComponents::fileBrowserActionName()); + browseButton->setIcon(QIcon(":/stylesheet/img/UI20/browse-edit.svg")); + + QString absolutePath = QDir(watchFolder.c_str()).absoluteFilePath(info.m_relativePath.c_str()); + connect(browseButton, &QPushButton::clicked, [absolutePath] { + AzQtComponents::ShowFileOnDesktop(absolutePath); + }); + + m_ui->tableWidget->setCellWidget(static_cast(m_currentAssetRowIndex), static_cast(ColumnBrowse), browseButton); + ScanComplete(asset); + ++m_inspectedAssets; + ++m_currentAssetRowIndex; + } + + void Modifier::UpgradeSingle + ( QPushButton* rowGoToButton + , AzQtComponents::StyledBusyLabel* spinner + , AZ::Data::AssetInfo assetInfo) + { + AZ::Data::Asset asset = AZ::Data::AssetManager::Instance().GetAsset + ( assetInfo.m_assetId, assetInfo.m_assetType, AZ::Data::AssetLoadBehavior::PreLoad); + + if (asset) + { + asset.BlockUntilLoadComplete(); + + if (asset.IsReady()) + { + AZ::Interface::Get()->SetIsUpgrading(true); + m_isUpgradingSingleGraph = true; + m_logs.clear(); + m_ui->textEdit->clear(); + spinner->SetIsBusy(true); + rowGoToButton->setEnabled(false); + + m_inProgressAsset = AZStd::find_if(m_assetsToUpgrade.begin(), m_assetsToUpgrade.end() + , [asset](const UpgradeAssets::value_type& assetToUpgrade) + { + return assetToUpgrade.GetId() == asset.GetId(); + }); + + m_state = ProcessState::Upgrade; + AZ::SystemTickBus::Handler::BusConnect(); + } + } + } + + void Modifier::ScanComplete(const AZ::Data::Asset& asset) + { + Log("ScanComplete: %s", asset.GetHint().c_str()); + m_inProgress = false; + m_ui->progressBar->setValue(aznumeric_cast(m_currentAssetRowIndex)); + m_ui->scanButton->setEnabled(true); + + m_inspectingAsset = m_assetsToInspect.erase(m_inspectingAsset); + FlushLogs(); + + if (m_inspectingAsset == m_assetsToInspect.end()) + { + AZ::SystemTickBus::QueueFunction([this]() { FinalizeScan(); }); + + if (!m_assetsToUpgrade.empty()) + { + m_ui->upgradeAllButton->setEnabled(true); + } + } + } + + void Modifier::FinalizeScan() + { + Log("FinalizeScan()"); + + m_ui->spinner->SetIsBusy(false); + m_ui->onlyShowOutdated->setEnabled(true); + + // Enable all the Upgrade buttons + for (int row = 0; row < m_ui->tableWidget->rowCount(); ++row) + { + QPushButton* button = qobject_cast(m_ui->tableWidget->cellWidget(row, ColumnAction)); + if (button) + { + button->setEnabled(true); + } + } + + QString spinnerText = QStringLiteral("Scan Complete"); + if (m_assetsToUpgrade.empty()) + { + spinnerText.append(" - No graphs require upgrade!"); + } + else + { + spinnerText.append(QString::asprintf(" - Discovered: %zu, Inspected: %zu, Failed: %zu, Upgradeable: %zu" + , m_discoveredAssets, m_inspectedAssets, m_failedAssets, m_assetsToUpgrade.size())); + } + + + m_ui->spinner->SetText(spinnerText); + m_ui->progressBar->setVisible(false); + + if (!m_assetsToUpgrade.empty()) + { + m_ui->upgradeAllButton->setEnabled(true); + } + + AZ::SystemTickBus::Handler::BusDisconnect(); + AZ::Debug::TraceMessageBus::Handler::BusDisconnect(); + UpgradeNotifications::Bus::Handler::BusDisconnect(); + + m_keepEditorAlive.reset(); + m_settingsCache.reset(); + m_state = ProcessState::Inactive; + } + + void Modifier::FlushLogs() + { + if (m_logs.empty()) + { + return; + } + + const QTextCursor oldCursor = m_ui->textEdit->textCursor(); + QScrollBar* scrollBar = m_ui->textEdit->verticalScrollBar(); + + m_ui->textEdit->moveCursor(QTextCursor::End); + QTextCursor textCursor = m_ui->textEdit->textCursor(); + + while (!m_logs.empty()) + { + auto line = "\n" + m_logs.front(); + + m_logs.pop_front(); + + textCursor.insertText(line.c_str()); + } + + scrollBar->setValue(scrollBar->maximum()); + m_ui->textEdit->moveCursor(QTextCursor::StartOfLine); + + } + + bool Modifier::CaptureLogFromTraceBus(const char* window, const char* message) + { + if (m_ui->updateReportingOnly->isChecked() && window != ScriptCanvas::k_VersionExplorerWindow) + { + return true; + } + + AZStd::string msg = message; + if (msg.ends_with("\n")) + { + msg = msg.substr(0, msg.size() - 1); + } + + m_logs.push_back(msg); + return m_ui->updateReportingOnly->isChecked(); + } + + bool Modifier::OnPreError(const char* window, const char* /*fileName*/, int /*line*/, const char* /*func*/, const char* message) + { + AZStd::string msg = AZStd::string::format("(Error): %s", message); + return CaptureLogFromTraceBus(window, msg.c_str()); + } + + bool Modifier::OnPreWarning(const char* window, const char* /*fileName*/, int /*line*/, const char* /*func*/, const char* message) + { + AZStd::string msg = AZStd::string::format("(Warning): %s", message); + return CaptureLogFromTraceBus(window, msg.c_str()); + } + + bool Modifier::OnException(const char* message) + { + AZStd::string msg = AZStd::string::format("(Exception): %s", message); + return CaptureLogFromTraceBus("Script Canvas", msg.c_str()); + } + + bool Modifier::OnPrintf(const char* window, const char* message) + { + return CaptureLogFromTraceBus(window, message); + } + + void Modifier::closeEvent(QCloseEvent* event) + { + m_keepEditorAlive.reset(); + + AzQtComponents::StyledDialog::closeEvent(event); + } + +#include + +} diff --git a/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/Modifier.h b/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/Modifier.h new file mode 100644 index 0000000000..5f379d682a --- /dev/null +++ b/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/Modifier.h @@ -0,0 +1,167 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#if !defined(Q_MOC_RUN) +#include + +AZ_PUSH_DISABLE_WARNING(4244 4251 4800, "-Wunknown-warning-option") +#include +AZ_POP_DISABLE_WARNING + +#include +#include +#include + +#include +#include + +#include +#include +#include +#endif + +class QPushButton; + +namespace Ui +{ + class Modifier; +} + +namespace AzQtComponents +{ + class StyledBusyLabel; +} + +namespace ScriptCanvasEditor +{ + //! A tool that collects and upgrades all Script Canvas graphs in the asset catalog + class Modifier + : public AzQtComponents::StyledDialog + , private AZ::SystemTickBus::Handler + , private UpgradeNotifications::Bus::Handler + , private AZ::Debug::TraceMessageBus::Handler + { + Q_OBJECT + + public: + AZ_CLASS_ALLOCATOR(Modifier, AZ::SystemAllocator, 0); + + explicit Modifier(QWidget* parent = nullptr); + ~Modifier(); + + private: + + static constexpr int ColumnAsset = 0; + static constexpr int ColumnAction = 1; + static constexpr int ColumnBrowse = 2; + static constexpr int ColumnStatus = 3; + + void OnScan(); + void OnClose(); + + enum class ProcessState + { + Inactive, + Backup, + Scan, + Upgrade, + }; + ProcessState m_state = ProcessState::Inactive; + + void DoScan(); + void ScanComplete(const AZ::Data::Asset&); + + void InspectAsset(AZ::Data::Asset& asset, AZ::Data::AssetInfo& assetInfo); + + void OnUpgradeAll(); + + // SystemTickBus::Handler + void OnSystemTick() override; + // + + // AZ::Debug::TranceMessageBus::Handler + bool OnException(const char* /*message*/) override; + bool OnPrintf(const char* /*window*/, const char* /*message*/) override; + bool OnPreError(const char* /*window*/, const char* /*fileName*/, int /*line*/, const char* /*func*/, const char* /*message*/) override; + bool OnPreWarning(const char* /*window*/, const char* /*fileName*/, int /*line*/, const char* /*func*/, const char* /*message*/) override; + // + + bool CaptureLogFromTraceBus(const char* window, const char* message); + + enum class OperationResult + { + Success, + Failure, + }; + + void GraphUpgradeComplete(const AZ::Data::Asset, OperationResult result, AZStd::string_view message); + + bool IsUpgrading() const; + + bool m_inProgress = false; + // scan fields + size_t m_currentAssetRowIndex = 0; + size_t m_inspectedAssets = 0; + size_t m_failedAssets = 0; + size_t m_discoveredAssets = 0; + + IUpgradeRequests::AssetList m_assetsToInspect; + IUpgradeRequests::AssetList::iterator m_inspectingAsset; + using UpgradeAssets = AZStd::vector>; + UpgradeAssets m_assetsToUpgrade; + UpgradeAssets::iterator m_inProgressAsset; + + AZ::Data::Asset m_currentAsset; + + AZStd::unique_ptr m_ui; + + AZStd::unique_ptr m_settingsCache; + + // upgrade fields + AZStd::recursive_mutex m_mutex; + bool m_upgradeComplete = false; + AZ::Data::Asset m_upgradeAsset; + int m_upgradeAssetIndex = 0; + OperationResult m_upgradeResult; + AZStd::string m_upgradeMessage; + AZStd::string m_tmpFileName; + + AZStd::unique_ptr m_keepEditorAlive; + + AZStd::deque m_logs; + + AZ::Entity* m_scriptCanvasEntity = nullptr; + + bool m_isUpgradingSingleGraph = false; + + void UpgradeSingle(QPushButton* item, AzQtComponents::StyledBusyLabel* spinner, AZ::Data::AssetInfo assetInfo); + + void FlushLogs(); + + void FinalizeUpgrade(); + void FinalizeScan(); + + void BackupComplete(); + AZStd::string BackupGraph(const AZ::Data::Asset&); + void UpgradeGraph(const AZ::Data::Asset&); + + void GraphUpgradeCompleteUIUpdate(const AZ::Data::Asset asset, OperationResult result, AZStd::string_view message); + void OnGraphUpgradeComplete(AZ::Data::Asset&, bool skipped = false) override; + + void OnSourceFileReleased(AZ::Data::Asset asset); + + void closeEvent(QCloseEvent* event) override; + + bool m_overwriteAll = false; + void PerformMove(AZ::Data::Asset asset, AZStd::string source, AZStd::string target, size_t remainingAttempts); + + void Log(const char* format, ...); + }; +} diff --git a/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/Scanner.cpp b/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/Scanner.cpp new file mode 100644 index 0000000000..29f5f8f530 --- /dev/null +++ b/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/Scanner.cpp @@ -0,0 +1,1023 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace VersionExplorerCpp +{ + class FileEventHandler + : public AZ::IO::FileIOEventBus::Handler + { + public: + int m_errorCode = 0; + AZStd::string m_fileName; + + FileEventHandler() + { + BusConnect(); + } + + ~FileEventHandler() + { + BusDisconnect(); + } + + void OnError(const AZ::IO::SystemFile* /*file*/, const char* fileName, int errorCode) override + { + m_errorCode = errorCode; + + if (fileName) + { + m_fileName = fileName; + } + } + }; +} + +namespace ScriptCanvasEditor +{ + EditorKeepAlive::EditorKeepAlive() + { + ISystem* system = nullptr; + CrySystemRequestBus::BroadcastResult(system, &CrySystemRequestBus::Events::GetCrySystem); + + m_edKeepEditorActive = system->GetIConsole()->GetCVar("ed_KeepEditorActive"); + + if (m_edKeepEditorActive) + { + m_keepEditorActive = m_edKeepEditorActive->GetIVal(); + m_edKeepEditorActive->Set(1); + } + } + + EditorKeepAlive::~EditorKeepAlive() + { + if (m_edKeepEditorActive) + { + m_edKeepEditorActive->Set(m_keepEditorActive); + } + } + + Scanner::Scanner(QWidget* parent /*= nullptr*/) + : AzQtComponents::StyledDialog(parent) + , m_ui(new Ui::Scanner()) + { + m_ui->setupUi(this); + + m_ui->tableWidget->horizontalHeader()->setVisible(false); + m_ui->tableWidget->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch); + m_ui->tableWidget->horizontalHeader()->setSectionResizeMode(3, QHeaderView::Fixed); + m_ui->tableWidget->setColumnWidth(3, 22); + + m_ui->textEdit->setHorizontalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAsNeeded); + m_ui->textEdit->setVerticalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAlwaysOn); + + connect(m_ui->scanButton, &QPushButton::pressed, this, &Scanner::OnScan); + connect(m_ui->closeButton, &QPushButton::pressed, this, &Scanner::OnClose); + connect(m_ui->upgradeAllButton, &QPushButton::pressed, this, &Scanner::OnUpgradeAll); + + m_ui->progressBar->setValue(0); + m_ui->progressBar->setVisible(false); + + m_keepEditorAlive = AZStd::make_unique(); + m_inspectingAsset = m_assetsToInspect.end(); + + } + + Scanner::~Scanner() + { + AZ::SystemTickBus::Handler::BusDisconnect(); + + UpgradeNotifications::Bus::Handler::BusDisconnect(); + AZ::Debug::TraceMessageBus::Handler::BusDisconnect(); + + } + + void Scanner::Log(const char* format, ...) + { + if (m_ui->verbose->isChecked()) + { + char sBuffer[2048]; + va_list ArgList; + va_start(ArgList, format); + azvsnprintf(sBuffer, sizeof(sBuffer), format, ArgList); + sBuffer[sizeof(sBuffer) - 1] = '\0'; + va_end(ArgList); + + AZ_TracePrintf(ScriptCanvas::k_VersionExplorerWindow.data(), "%s\n", sBuffer); + } + } + + void Scanner::OnClose() + { + reject(); + } + + bool Scanner::IsUpgrading() const + { + return m_inProgressAsset != m_assetsToUpgrade.end() && m_inProgress; + } + + void Scanner::OnSystemTick() + { + switch (m_state) + { + case ProcessState::Scan: + + if (!m_inProgress && m_inspectingAsset != m_assetsToInspect.end()) + { + m_inProgress = true; + AZ::Data::AssetInfo& assetToUpgrade = *m_inspectingAsset; + m_currentAsset = AZ::Data::AssetManager::Instance().GetAsset(assetToUpgrade.m_assetId, assetToUpgrade.m_assetType, AZ::Data::AssetLoadBehavior::PreLoad); + Log("SystemTick::ProcessState::Scan: %s pre-blocking load hint", m_currentAsset.GetHint().c_str()); + m_currentAsset.BlockUntilLoadComplete(); + if (m_currentAsset.IsReady()) + { + // The asset is ready, grab its info + m_inProgress = true; + InspectAsset(m_currentAsset, assetToUpgrade); + } + else + { + m_ui->tableWidget->insertRow(static_cast(m_currentAssetRowIndex)); + QTableWidgetItem* rowName = new QTableWidgetItem + ( tr(AZStd::string::format("Error: %s", assetToUpgrade.m_relativePath.c_str()).c_str())); + m_ui->tableWidget->setItem(static_cast(m_currentAssetRowIndex), static_cast(ColumnAsset), rowName); + ++m_currentAssetRowIndex; + + Log("SystemTick::ProcessState::Scan: %s post-blocking load, problem loading asset", assetToUpgrade.m_relativePath.c_str()); + ++m_failedAssets; + ScanComplete(m_currentAsset); + } + } + break; + + case ProcessState::Upgrade: + { + AZStd::lock_guard lock(m_mutex); + if (m_upgradeComplete) + { + ++m_upgradeAssetIndex; + m_inProgress = false; + m_ui->progressBar->setVisible(true); + m_ui->progressBar->setValue(m_upgradeAssetIndex); + + if (m_scriptCanvasEntity) + { + m_scriptCanvasEntity->Deactivate(); + m_scriptCanvasEntity = nullptr; + } + + GraphUpgradeCompleteUIUpdate(m_upgradeAsset, m_upgradeResult, m_upgradeMessage); + + if (!m_isUpgradingSingleGraph) + { + if (m_inProgressAsset != m_assetsToUpgrade.end()) + { + m_inProgressAsset = m_assetsToUpgrade.erase(m_inProgressAsset); + } + + if (m_inProgressAsset == m_assetsToUpgrade.end()) + { + FinalizeUpgrade(); + } + } + else + { + m_inProgressAsset = m_assetsToUpgrade.erase(m_inProgressAsset); + m_inProgress = false; + m_state = ProcessState::Inactive; + m_settingsCache.reset(); + AZ::SystemTickBus::Handler::BusDisconnect(); + AZ::Debug::TraceMessageBus::Handler::BusDisconnect(); + } + + m_isUpgradingSingleGraph = false; + + if (m_assetsToUpgrade.empty()) + { + m_ui->upgradeAllButton->setEnabled(false); + } + + m_upgradeComplete = false; + } + + if (!IsUpgrading() && m_state == ProcessState::Upgrade) + { + AZStd::string errorMessage = BackupGraph(*m_inProgressAsset); + // Make the backup + if (errorMessage.empty()) + { + Log("SystemTick::ProcessState::Upgrade: Backup Success %s ", m_inProgressAsset->GetHint().c_str()); + QList items = m_ui->tableWidget->findItems(m_inProgressAsset->GetHint().c_str(), Qt::MatchFlag::MatchExactly); + if (!items.isEmpty()) + { + for (auto* item : items) + { + int row = item->row(); + AzQtComponents::StyledBusyLabel* spinner = qobject_cast(m_ui->tableWidget->cellWidget(row, ColumnStatus)); + spinner->SetIsBusy(true); + } + } + + // Upgrade the graph + UpgradeGraph(*m_inProgressAsset); + } + else + { + Log("SystemTick::ProcessState::Upgrade: Backup Failed %s ", m_inProgressAsset->GetHint().c_str()); + GraphUpgradeComplete(*m_inProgressAsset, OperationResult::Failure, errorMessage); + } + + } + break; + } + default: + break; + } + + FlushLogs(); + + AZ::Data::AssetManager::Instance().DispatchEvents(); + AZ::SystemTickBus::ExecuteQueuedEvents(); + } + + // Backup + + void Scanner::OnUpgradeAll() + { + m_state = ProcessState::Upgrade; + m_settingsCache = AZStd::make_unique(); + ScriptCanvas::Grammar::g_saveRawTranslationOuputToFile = false; + ScriptCanvas::Grammar::g_printAbstractCodeModel = false; + ScriptCanvas::Grammar::g_saveRawTranslationOuputToFile = false; + AZ::Interface::Get()->SetIsUpgrading(true); + AZ::Interface::Get()->ClearGraphsThatNeedUpgrade(); + m_inProgressAsset = m_assetsToUpgrade.begin(); + AZ::Debug::TraceMessageBus::Handler::BusConnect(); + AZ::SystemTickBus::Handler::BusConnect(); + m_ui->progressBar->setVisible(true); + m_ui->progressBar->setRange(0, aznumeric_cast(m_assetsToUpgrade.size())); + m_ui->progressBar->setValue(m_upgradeAssetIndex); + m_keepEditorAlive = AZStd::make_unique(); + } + + AZStd::string Scanner::BackupGraph(const AZ::Data::Asset& asset) + { + if (!m_ui->makeBackupCheckbox->isChecked()) + { + // considered a success + return ""; + } + + QDateTime theTime = QDateTime::currentDateTime(); + QString subFolder = theTime.toString("yyyy-MM-dd [HH.mm.ss]"); + + AZStd::string backupPath = AZStd::string::format("@devroot@/ScriptCanvas_BACKUP/%s", subFolder.toUtf8().data()); + char backupPathCStr[AZ_MAX_PATH_LEN] = { 0 }; + AZ::IO::FileIOBase::GetInstance()->ResolvePath(backupPath.c_str(), backupPathCStr, AZ_MAX_PATH_LEN); + backupPath = backupPathCStr; + + if (!AZ::IO::FileIOBase::GetInstance()->Exists(backupPath.c_str())) + { + if (AZ::IO::FileIOBase::GetInstance()->CreatePath(backupPath.c_str()) != AZ::IO::ResultCode::Success) + { + AZ_Error(ScriptCanvas::k_VersionExplorerWindow.data(), false, "Failed to create backup folder %s", backupPath.c_str()); + return "Failed to create backup folder"; + } + } + + AZStd::string devRoot = "@devroot@"; + AZStd::string devAssets = "@devassets@"; + + char devRootCStr[AZ_MAX_PATH_LEN] = { 0 }; + AZ::IO::FileIOBase::GetInstance()->ResolvePath(devRoot.c_str(), devRootCStr, AZ_MAX_PATH_LEN); + + char devAssetsCStr[AZ_MAX_PATH_LEN] = { 0 }; + AZ::IO::FileIOBase::GetInstance()->ResolvePath(devAssets.c_str(), devAssetsCStr, AZ_MAX_PATH_LEN); + + AZStd::string relativePath = devAssetsCStr; + AzFramework::StringFunc::Replace(relativePath, devRootCStr, ""); + if (relativePath.starts_with("/")) + { + relativePath = relativePath.substr(1, relativePath.size() - 1); + } + + AZStd::string sourceFilePath; + + AZStd::string watchFolder; + AZ::Data::AssetInfo assetInfo; + bool sourceInfoFound{}; + AzToolsFramework::AssetSystemRequestBus::BroadcastResult(sourceInfoFound, &AzToolsFramework::AssetSystemRequestBus::Events::GetSourceInfoBySourcePath, asset.GetHint().c_str(), assetInfo, watchFolder); + if (sourceInfoFound) + { + AZStd::string assetPath; + AzFramework::StringFunc::Path::Join(watchFolder.c_str(), assetInfo.m_relativePath.c_str(), assetPath); + + sourceFilePath = assetPath; + } + else + { + AZ_Warning(ScriptCanvas::k_VersionExplorerWindow.data(), false, "Scanner::BackupGraph: Failed to find file: %s", asset.GetHint().c_str()); + return "Failed to find source file"; + } + + devRoot = devRootCStr; + AzFramework::StringFunc::Path::Normalize(devRoot); + + relativePath = sourceFilePath; + AzFramework::StringFunc::Replace(relativePath, devRoot.c_str(), ""); + if (relativePath.starts_with("/")) + { + relativePath = relativePath.substr(1, relativePath.size() - 1); + } + + AzFramework::StringFunc::Path::Normalize(relativePath); + AzFramework::StringFunc::Path::Normalize(backupPath); + + AZStd::string targetFilePath = backupPath; + targetFilePath += relativePath; + + if (AZ::IO::FileIOBase::GetInstance()->Copy(sourceFilePath.c_str(), targetFilePath.c_str()) != AZ::IO::ResultCode::Success) + { + AZ_Warning(ScriptCanvas::k_VersionExplorerWindow.data(), false, "Scanner::BackupGraph: Error creating backup: %s ---> %s\n", sourceFilePath.c_str(), targetFilePath.c_str()); + return "Failed to copy source file to backup location"; + } + + Log("Scanner::BackupGraph: Backed up: %s ---> %s\n", sourceFilePath.c_str(), targetFilePath.c_str()); + return ""; + } + + void Scanner::UpgradeGraph(const AZ::Data::Asset& asset) + { + m_inProgress = true; + m_upgradeComplete = false; + Log("UpgradeGraph %s ", m_inProgressAsset->GetHint().c_str()); + m_ui->spinner->SetText(QObject::tr("Upgrading: %1").arg(asset.GetHint().c_str())); + m_scriptCanvasEntity = nullptr; + + UpgradeNotifications::Bus::Handler::BusConnect(); + + if (asset.GetType() == azrtti_typeid()) + { + ScriptCanvasAsset* scriptCanvasAsset = asset.GetAs(); + AZ_Assert(scriptCanvasAsset, "Unable to get the asset of ScriptCanvasAsset, but received type: %s" + , azrtti_typeid().template ToString().c_str()); + + if (!scriptCanvasAsset) + { + return; + } + + AZ::Entity* scriptCanvasEntity = scriptCanvasAsset->GetScriptCanvasEntity(); + AZ_Assert(scriptCanvasEntity, "Scanner::UpgradeGraph The Script Canvas asset must have a valid entity"); + if (!scriptCanvasEntity) + { + return; + } + + AZ::Entity* queryEntity = nullptr; + AZ::ComponentApplicationBus::BroadcastResult(queryEntity, &AZ::ComponentApplicationRequests::FindEntity, scriptCanvasEntity->GetId()); + if (queryEntity) + { + if (queryEntity->GetState() == AZ::Entity::State::Active) + { + queryEntity->Deactivate(); + } + + scriptCanvasEntity = queryEntity; + } + + if (scriptCanvasEntity->GetState() == AZ::Entity::State::Constructed) + { + scriptCanvasEntity->Init(); + } + + if (scriptCanvasEntity->GetState() == AZ::Entity::State::Init) + { + scriptCanvasEntity->Activate(); + } + + AZ_Assert(scriptCanvasEntity->GetState() == AZ::Entity::State::Active, "Graph entity is not active"); + auto graphComponent = scriptCanvasEntity->FindComponent(); + AZ_Assert(graphComponent, "The Script Canvas entity must have a Graph component"); + + if (graphComponent) + { + m_scriptCanvasEntity = scriptCanvasEntity; + + graphComponent->UpgradeGraph + ( asset + , m_ui->forceUpgrade->isChecked() ? Graph::UpgradeRequest::Forced : Graph::UpgradeRequest::IfOutOfDate + , m_ui->verbose->isChecked()); + } + } + + AZ_Assert(m_scriptCanvasEntity, "The ScriptCanvas asset should have an entity"); + } + + void Scanner::OnGraphUpgradeComplete(AZ::Data::Asset& asset, bool /*skipped*/ /*= false*/) + { + AZStd::string relativePath, fullPath; + AZ::Data::AssetCatalogRequestBus::BroadcastResult(relativePath, &AZ::Data::AssetCatalogRequests::GetAssetPathById, asset.GetId()); + bool fullPathFound = false; + AzToolsFramework::AssetSystemRequestBus::BroadcastResult(fullPathFound, &AzToolsFramework::AssetSystemRequestBus::Events::GetFullSourcePathFromRelativeProductPath, relativePath, fullPath); + if (!fullPathFound) + { + AZ_Error(ScriptCanvas::k_VersionExplorerWindow.data(), false, "Full source path not found for %s", relativePath.c_str()); + } + + auto streamer = AZ::Interface::Get(); + AZ::IO::FileRequestPtr flushRequest = streamer->FlushCache(fullPath); + streamer->SetRequestCompleteCallback(flushRequest, [this, asset]([[maybe_unused]] AZ::IO::FileRequestHandle request) + { + this->OnSourceFileReleased(asset); + }); + streamer->QueueRequest(flushRequest); + } + + void Scanner::OnSourceFileReleased(AZ::Data::Asset asset) + { + AZStd::string relativePath, fullPath; + AZ::Data::AssetCatalogRequestBus::BroadcastResult(relativePath, &AZ::Data::AssetCatalogRequests::GetAssetPathById, asset.GetId()); + bool fullPathFound = false; + AzToolsFramework::AssetSystemRequestBus::BroadcastResult(fullPathFound, &AzToolsFramework::AssetSystemRequestBus::Events::GetFullSourcePathFromRelativeProductPath, relativePath, fullPath); + m_tmpFileName.clear(); + AZStd::string tmpFileName; + // here we are saving the graph to a temp file instead of the original file and then copying the temp file to the original file. + // This ensures that AP will not a get a file change notification on an incomplete graph file causing it to fail processing. Temp files are ignored by AP. + if (!AZ::IO::CreateTempFileName(fullPath.c_str(), tmpFileName)) + { + GraphUpgradeComplete(asset, OperationResult::Failure, "Failure to create temporary file name"); + return; + } + + bool tempSavedSucceeded = false; + AZ::IO::FileIOStream fileStream(tmpFileName.c_str(), AZ::IO::OpenMode::ModeWrite | AZ::IO::OpenMode::ModeText); + if (fileStream.IsOpen()) + { + if (asset.GetType() == azrtti_typeid()) + { + ScriptCanvasEditor::ScriptCanvasAssetHandler handler; + tempSavedSucceeded = handler.SaveAssetData(asset, &fileStream); + } + + fileStream.Close(); + } + + // attempt to remove temporary file no matter what + m_tmpFileName = tmpFileName; + if (!tempSavedSucceeded) + { + GraphUpgradeComplete(asset, OperationResult::Failure, "Save asset data to temporary file failed"); + return; + } + + using SCCommandBus = AzToolsFramework::SourceControlCommandBus; + SCCommandBus::Broadcast(&SCCommandBus::Events::RequestEdit, fullPath.c_str(), true, + [this, asset, fullPath, tmpFileName]([[maybe_unused]] bool success, const AzToolsFramework::SourceControlFileInfo& info) + { + constexpr const size_t k_maxAttemps = 10; + + if (!info.IsReadOnly()) + { + PerformMove(asset, tmpFileName, fullPath, k_maxAttemps); + } + else + { + if (m_overwriteAll) + { + AZ::IO::SystemFile::SetWritable(info.m_filePath.c_str(), true); + PerformMove(asset, tmpFileName, fullPath, k_maxAttemps); + } + else + { + int result = QMessageBox::No; + if (!m_overwriteAll) + { + QMessageBox mb(QMessageBox::Warning, + QObject::tr("Failed to Save Upgraded File"), + QObject::tr("The upgraded file could not be saved because the file is read only.\nDo you want to make it writeable and overwrite it?"), + QMessageBox::YesToAll | QMessageBox::Yes | QMessageBox::No, this); + + result = mb.exec(); + if (result == QMessageBox::YesToAll) + { + m_overwriteAll = true; + } + } + + if (result == QMessageBox::Yes || m_overwriteAll) + { + AZ::IO::SystemFile::SetWritable(info.m_filePath.c_str(), true); + PerformMove(asset, tmpFileName, fullPath, k_maxAttemps); + } + } + } + }); + } + + void Scanner::PerformMove(AZ::Data::Asset asset, AZStd::string source, AZStd::string target + , size_t remainingAttempts) + { + VersionExplorerCpp::FileEventHandler fileEventHandler; + + if (remainingAttempts == 0) + { + // all attempts failed, give up + AZ_Warning(ScriptCanvas::k_VersionExplorerWindow.data(), false, "moving converted file to source destination failed: %s. giving up", target.c_str()); + GraphUpgradeComplete(asset, OperationResult::Failure, "Failed to move updated file from backup to source destination"); + } + else if (remainingAttempts == 2) + { + // before the final attempt, flush all caches + AZ_Warning(ScriptCanvas::k_VersionExplorerWindow.data(), false, "moving converted file to source destination failed: %s, trying again", target.c_str()); + auto streamer = AZ::Interface::Get(); + AZ::IO::FileRequestPtr flushRequest = streamer->FlushCaches(); + streamer->SetRequestCompleteCallback(flushRequest + , [this, asset, remainingAttempts, source, target]([[maybe_unused]] AZ::IO::FileRequestHandle request) + { + // Continue saving. + AZ::SystemTickBus::QueueFunction( + [this, asset, remainingAttempts, source, target](){ PerformMove(asset, source, target, remainingAttempts - 1); }); + }); + streamer->QueueRequest(flushRequest); + } + else + { + // the actual move attempt + auto moveResult = AZ::IO::SmartMove(source.c_str(), target.c_str()); + if (moveResult.GetResultCode() == AZ::IO::ResultCode::Success) + { + m_tmpFileName.clear(); + auto streamer = AZ::Interface::Get(); + AZ::IO::FileRequestPtr flushRequest = streamer->FlushCache(target.c_str()); + // Bump the slice asset up in the asset processor's queue. + AzFramework::AssetSystemRequestBus::Broadcast(&AzFramework::AssetSystem::AssetSystemRequests::EscalateAssetBySearchTerm, target.c_str()); + AZ::SystemTickBus::QueueFunction([this, asset]() + { + GraphUpgradeComplete(asset, OperationResult::Success, ""); + }); + } + else + { + AZ_Warning(ScriptCanvas::k_VersionExplorerWindow.data(), false, "moving converted file to source destination failed: %s, trying again", target.c_str()); + auto streamer = AZ::Interface::Get(); + AZ::IO::FileRequestPtr flushRequest = streamer->FlushCache(target.c_str()); + streamer->SetRequestCompleteCallback(flushRequest, [this, asset, source, target, remainingAttempts]([[maybe_unused]] AZ::IO::FileRequestHandle request) + { + // Continue saving. + AZ::SystemTickBus::QueueFunction([this, asset, source, target, remainingAttempts]() { PerformMove(asset, source, target, remainingAttempts - 1); }); + }); + streamer->QueueRequest(flushRequest); + } + } + } + + void Scanner::GraphUpgradeComplete + ( const AZ::Data::Asset asset, OperationResult result, AZStd::string_view message) + { + AZStd::lock_guard lock(m_mutex); + m_upgradeComplete = true; + m_upgradeResult = result; + m_upgradeMessage = message; + m_upgradeAsset = asset; + + if (!m_tmpFileName.empty()) + { + AZ::IO::FileIOBase* fileIO = AZ::IO::FileIOBase::GetInstance(); + AZ_Assert(fileIO, "GraphUpgradeComplete: No FileIO instance"); + + if (fileIO->Exists(m_tmpFileName.c_str()) && !fileIO->Remove(m_tmpFileName.c_str())) + { + AZ_TracePrintf(ScriptCanvas::k_VersionExplorerWindow.data(), "Failed to remove temporary file: %s", m_tmpFileName.c_str()); + } + } + + if (m_upgradeResult == OperationResult::Failure) + { + AZ::Interface::Get()->GraphNeedsManualUpgrade(asset.GetId()); + } + + m_tmpFileName.clear(); + } + + void Scanner::GraphUpgradeCompleteUIUpdate + ( const AZ::Data::Asset asset, OperationResult result, AZStd::string_view message) + { + QString text = asset.GetHint().c_str(); + QList items = m_ui->tableWidget->findItems(text, Qt::MatchFlag::MatchExactly); + + if (!items.isEmpty()) + { + for (auto* item : items) + { + int row = item->row(); + QTableWidgetItem* label = m_ui->tableWidget->item(row, ColumnAsset); + QString assetName = asset.GetHint().c_str(); + + if (label->text().compare(assetName) == 0) + { + m_ui->tableWidget->removeCellWidget(row, ColumnAction); + m_ui->tableWidget->removeCellWidget(row, ColumnStatus); + + QToolButton* doneButton = new QToolButton(this); + doneButton->setToolTip("Upgrade complete"); + if (result == OperationResult::Success) + { + doneButton->setIcon(QIcon(":/stylesheet/img/UI20/checkmark-menu.svg")); + } + else + { + doneButton->setIcon(QIcon(":/stylesheet/img/UI20/titlebar-close.svg")); + doneButton->setToolTip(message.data()); + } + + m_ui->tableWidget->setCellWidget(row, ColumnStatus, doneButton); + } + } + } + } + + void Scanner::FinalizeUpgrade() + { + Log("FinalizeUpgrade!"); + m_inProgress = false; + m_assetsToUpgrade.clear(); + m_ui->upgradeAllButton->setEnabled(false); + m_ui->onlyShowOutdated->setEnabled(true); + m_keepEditorAlive.reset(); + m_ui->progressBar->setVisible(false); + + // Manual correction + size_t assetsThatNeedManualInspection = AZ::Interface::Get()->GetGraphsThatNeedManualUpgrade().size(); + if (assetsThatNeedManualInspection > 0) + { + m_ui->spinner->SetText("Some graphs will require manual corrections, you will be prompted to review them upon closing this dialog"); + } + else + { + m_ui->spinner->SetText("Upgrade complete."); + } + + AZ::SystemTickBus::Handler::BusDisconnect(); + AZ::Debug::TraceMessageBus::Handler::BusDisconnect(); + UpgradeNotifications::Bus::Handler::BusDisconnect(); + AZ::Interface::Get()->SetIsUpgrading(false); + m_settingsCache.reset(); + } + + // Scanning + + void Scanner::OnScan() + { + m_assetsToUpgrade.clear(); + m_assetsToInspect.clear(); + m_ui->tableWidget->setRowCount(0); + m_inspectedAssets = 0; + m_currentAssetRowIndex = 0; + IUpgradeRequests* upgradeRequests = AZ::Interface::Get(); + m_assetsToInspect = upgradeRequests->GetAssetsToUpgrade(); + DoScan(); + } + + void Scanner::DoScan() + { + m_state = ProcessState::Scan; + m_settingsCache = AZStd::make_unique(); + ScriptCanvas::Grammar::g_saveRawTranslationOuputToFile = false; + ScriptCanvas::Grammar::g_printAbstractCodeModel = false; + ScriptCanvas::Grammar::g_saveRawTranslationOuputToFile = false; + + AZ::SystemTickBus::Handler::BusConnect(); + AZ::Debug::TraceMessageBus::Handler::BusConnect(); + + if (!m_assetsToInspect.empty()) + { + m_discoveredAssets = m_assetsToInspect.size(); + m_failedAssets = 0; + m_inspectedAssets = 0; + m_currentAssetRowIndex = 0; + m_ui->progressFrame->setVisible(true); + m_ui->progressBar->setVisible(true); + m_ui->progressBar->setRange(0, aznumeric_cast(m_assetsToInspect.size())); + m_ui->progressBar->setValue(0); + + m_ui->spinner->SetIsBusy(true); + m_ui->spinner->SetBusyIconSize(32); + + m_ui->scanButton->setEnabled(false); + m_ui->upgradeAllButton->setEnabled(false); + m_ui->onlyShowOutdated->setEnabled(false); + + m_inspectingAsset = m_assetsToInspect.begin(); + m_keepEditorAlive = AZStd::make_unique(); + } + } + + void Scanner::BackupComplete() + { + m_currentAssetRowIndex = 0; + m_ui->progressBar->setValue(0); + DoScan(); + } + + void Scanner::InspectAsset(AZ::Data::Asset& asset, AZ::Data::AssetInfo& assetInfo) + { + Log("InspectAsset: %s", asset.GetHint().c_str()); + AZ::Entity* scriptCanvasEntity = nullptr; + if (asset.GetType() == azrtti_typeid()) + { + ScriptCanvasAsset* scriptCanvasAsset = asset.GetAs(); + if (!scriptCanvasAsset) + { + Log("InspectAsset: %s, AsestData failed to return ScriptCanvasAsset", asset.GetHint().c_str()); + return; + } + + scriptCanvasEntity = scriptCanvasAsset->GetScriptCanvasEntity(); + AZ_Assert(scriptCanvasEntity, "The Script Canvas asset must have a valid entity"); + } + + auto graphComponent = scriptCanvasEntity->FindComponent(); + AZ_Assert(graphComponent, "The Script Canvas entity must have a Graph component"); + + bool onlyShowOutdatedGraphs = m_ui->onlyShowOutdated->isChecked(); + bool forceUpgrade = m_ui->forceUpgrade->isChecked(); + ScriptCanvas::VersionData graphVersion = graphComponent->GetVersion(); + + + if (!forceUpgrade && onlyShowOutdatedGraphs && graphVersion.IsLatest()) + { + ScanComplete(asset); + Log("InspectAsset: %s, is at latest", asset.GetHint().c_str()); + return; + } + + m_ui->tableWidget->insertRow(static_cast(m_currentAssetRowIndex)); + QTableWidgetItem* rowName = new QTableWidgetItem(tr(asset.GetHint().c_str())); + m_ui->tableWidget->setItem(static_cast(m_currentAssetRowIndex), static_cast(ColumnAsset), rowName); + + if (forceUpgrade || !graphComponent->GetVersion().IsLatest()) + { + m_assetsToUpgrade.push_back(asset); + + AzQtComponents::StyledBusyLabel* spinner = new AzQtComponents::StyledBusyLabel(this); + spinner->SetBusyIconSize(16); + + QPushButton* rowGoToButton = new QPushButton(this); + rowGoToButton->setText("Upgrade"); + rowGoToButton->setEnabled(false); + + connect(rowGoToButton, &QPushButton::clicked, [this, spinner, rowGoToButton, assetInfo] { + + AZ::SystemTickBus::QueueFunction([this, rowGoToButton, spinner, assetInfo]() { + // Queue the process state change because we can't connect to the SystemTick bus in a Qt lambda + UpgradeSingle(rowGoToButton, spinner, assetInfo); + }); + + AZ::SystemTickBus::ExecuteQueuedEvents(); + + }); + + m_ui->tableWidget->setCellWidget(static_cast(m_currentAssetRowIndex), static_cast(ColumnAction), rowGoToButton); + m_ui->tableWidget->setCellWidget(static_cast(m_currentAssetRowIndex), static_cast(ColumnStatus), spinner); + } + + char resolvedBuffer[AZ_MAX_PATH_LEN] = { 0 }; + AZStd::string path = AZStd::string::format("@devroot@/%s", asset.GetHint().c_str()); + AZ::IO::FileIOBase::GetInstance()->ResolvePath(path.c_str(), resolvedBuffer, AZ_MAX_PATH_LEN); + AZ::StringFunc::Path::GetFullPath(resolvedBuffer, path); + AZ::StringFunc::Path::Normalize(path); + + bool result = false; + AZ::Data::AssetInfo info; + AZStd::string watchFolder; + QByteArray assetNameUtf8 = asset.GetHint().c_str(); + AzToolsFramework::AssetSystemRequestBus::BroadcastResult(result, &AzToolsFramework::AssetSystemRequestBus::Events::GetSourceInfoBySourcePath, assetNameUtf8, info, watchFolder); + + AZ_Error(ScriptCanvas::k_VersionExplorerWindow.data(), result, "Failed to locate asset info for '%s'.", assetNameUtf8.constData()); + + QToolButton* browseButton = new QToolButton(this); + browseButton->setToolTip(AzQtComponents::fileBrowserActionName()); + browseButton->setIcon(QIcon(":/stylesheet/img/UI20/browse-edit.svg")); + + QString absolutePath = QDir(watchFolder.c_str()).absoluteFilePath(info.m_relativePath.c_str()); + connect(browseButton, &QPushButton::clicked, [absolutePath] { + AzQtComponents::ShowFileOnDesktop(absolutePath); + }); + + m_ui->tableWidget->setCellWidget(static_cast(m_currentAssetRowIndex), static_cast(ColumnBrowse), browseButton); + ScanComplete(asset); + ++m_inspectedAssets; + ++m_currentAssetRowIndex; + } + + void Scanner::UpgradeSingle + ( QPushButton* rowGoToButton + , AzQtComponents::StyledBusyLabel* spinner + , AZ::Data::AssetInfo assetInfo) + { + AZ::Data::Asset asset = AZ::Data::AssetManager::Instance().GetAsset + ( assetInfo.m_assetId, assetInfo.m_assetType, AZ::Data::AssetLoadBehavior::PreLoad); + + if (asset) + { + asset.BlockUntilLoadComplete(); + + if (asset.IsReady()) + { + AZ::Interface::Get()->SetIsUpgrading(true); + m_isUpgradingSingleGraph = true; + m_logs.clear(); + m_ui->textEdit->clear(); + spinner->SetIsBusy(true); + rowGoToButton->setEnabled(false); + + m_inProgressAsset = AZStd::find_if(m_assetsToUpgrade.begin(), m_assetsToUpgrade.end() + , [asset](const UpgradeAssets::value_type& assetToUpgrade) + { + return assetToUpgrade.GetId() == asset.GetId(); + }); + + m_state = ProcessState::Upgrade; + AZ::SystemTickBus::Handler::BusConnect(); + } + } + } + + void Scanner::ScanComplete(const AZ::Data::Asset& asset) + { + Log("ScanComplete: %s", asset.GetHint().c_str()); + m_inProgress = false; + m_ui->progressBar->setValue(aznumeric_cast(m_currentAssetRowIndex)); + m_ui->scanButton->setEnabled(true); + + m_inspectingAsset = m_assetsToInspect.erase(m_inspectingAsset); + FlushLogs(); + + if (m_inspectingAsset == m_assetsToInspect.end()) + { + AZ::SystemTickBus::QueueFunction([this]() { FinalizeScan(); }); + + if (!m_assetsToUpgrade.empty()) + { + m_ui->upgradeAllButton->setEnabled(true); + } + } + } + + void Scanner::FinalizeScan() + { + Log("FinalizeScan()"); + + m_ui->spinner->SetIsBusy(false); + m_ui->onlyShowOutdated->setEnabled(true); + + // Enable all the Upgrade buttons + for (int row = 0; row < m_ui->tableWidget->rowCount(); ++row) + { + QPushButton* button = qobject_cast(m_ui->tableWidget->cellWidget(row, ColumnAction)); + if (button) + { + button->setEnabled(true); + } + } + + QString spinnerText = QStringLiteral("Scan Complete"); + if (m_assetsToUpgrade.empty()) + { + spinnerText.append(" - No graphs require upgrade!"); + } + else + { + spinnerText.append(QString::asprintf(" - Discovered: %zu, Inspected: %zu, Failed: %zu, Upgradeable: %zu" + , m_discoveredAssets, m_inspectedAssets, m_failedAssets, m_assetsToUpgrade.size())); + } + + + m_ui->spinner->SetText(spinnerText); + m_ui->progressBar->setVisible(false); + + if (!m_assetsToUpgrade.empty()) + { + m_ui->upgradeAllButton->setEnabled(true); + } + + AZ::SystemTickBus::Handler::BusDisconnect(); + AZ::Debug::TraceMessageBus::Handler::BusDisconnect(); + UpgradeNotifications::Bus::Handler::BusDisconnect(); + + m_keepEditorAlive.reset(); + m_settingsCache.reset(); + m_state = ProcessState::Inactive; + } + + void Scanner::FlushLogs() + { + if (m_logs.empty()) + { + return; + } + + const QTextCursor oldCursor = m_ui->textEdit->textCursor(); + QScrollBar* scrollBar = m_ui->textEdit->verticalScrollBar(); + + m_ui->textEdit->moveCursor(QTextCursor::End); + QTextCursor textCursor = m_ui->textEdit->textCursor(); + + while (!m_logs.empty()) + { + auto line = "\n" + m_logs.front(); + + m_logs.pop_front(); + + textCursor.insertText(line.c_str()); + } + + scrollBar->setValue(scrollBar->maximum()); + m_ui->textEdit->moveCursor(QTextCursor::StartOfLine); + + } + + bool Scanner::CaptureLogFromTraceBus(const char* window, const char* message) + { + if (m_ui->updateReportingOnly->isChecked() && window != ScriptCanvas::k_VersionExplorerWindow) + { + return true; + } + + AZStd::string msg = message; + if (msg.ends_with("\n")) + { + msg = msg.substr(0, msg.size() - 1); + } + + m_logs.push_back(msg); + return m_ui->updateReportingOnly->isChecked(); + } + + bool Scanner::OnPreError(const char* window, const char* /*fileName*/, int /*line*/, const char* /*func*/, const char* message) + { + AZStd::string msg = AZStd::string::format("(Error): %s", message); + return CaptureLogFromTraceBus(window, msg.c_str()); + } + + bool Scanner::OnPreWarning(const char* window, const char* /*fileName*/, int /*line*/, const char* /*func*/, const char* message) + { + AZStd::string msg = AZStd::string::format("(Warning): %s", message); + return CaptureLogFromTraceBus(window, msg.c_str()); + } + + bool Scanner::OnException(const char* message) + { + AZStd::string msg = AZStd::string::format("(Exception): %s", message); + return CaptureLogFromTraceBus("Script Canvas", msg.c_str()); + } + + bool Scanner::OnPrintf(const char* window, const char* message) + { + return CaptureLogFromTraceBus(window, message); + } + + void Scanner::closeEvent(QCloseEvent* event) + { + m_keepEditorAlive.reset(); + + AzQtComponents::StyledDialog::closeEvent(event); + } + +#include + +} diff --git a/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/Scanner.h b/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/Scanner.h new file mode 100644 index 0000000000..574b2b2503 --- /dev/null +++ b/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/Scanner.h @@ -0,0 +1,167 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#if !defined(Q_MOC_RUN) +#include + +AZ_PUSH_DISABLE_WARNING(4244 4251 4800, "-Wunknown-warning-option") +#include +AZ_POP_DISABLE_WARNING + +#include +#include +#include + +#include +#include + +#include +#include +#include +#endif + +class QPushButton; + +namespace Ui +{ + class Scanner; +} + +namespace AzQtComponents +{ + class StyledBusyLabel; +} + +namespace ScriptCanvasEditor +{ + //! A tool that collects and upgrades all Script Canvas graphs in the asset catalog + class Scanner + : public AzQtComponents::StyledDialog + , private AZ::SystemTickBus::Handler + , private UpgradeNotifications::Bus::Handler + , private AZ::Debug::TraceMessageBus::Handler + { + Q_OBJECT + + public: + AZ_CLASS_ALLOCATOR(Scanner, AZ::SystemAllocator, 0); + + explicit Scanner(QWidget* parent = nullptr); + ~Scanner(); + + private: + + static constexpr int ColumnAsset = 0; + static constexpr int ColumnAction = 1; + static constexpr int ColumnBrowse = 2; + static constexpr int ColumnStatus = 3; + + void OnScan(); + void OnClose(); + + enum class ProcessState + { + Inactive, + Backup, + Scan, + Upgrade, + }; + ProcessState m_state = ProcessState::Inactive; + + void DoScan(); + void ScanComplete(const AZ::Data::Asset&); + + void InspectAsset(AZ::Data::Asset& asset, AZ::Data::AssetInfo& assetInfo); + + void OnUpgradeAll(); + + // SystemTickBus::Handler + void OnSystemTick() override; + // + + // AZ::Debug::TranceMessageBus::Handler + bool OnException(const char* /*message*/) override; + bool OnPrintf(const char* /*window*/, const char* /*message*/) override; + bool OnPreError(const char* /*window*/, const char* /*fileName*/, int /*line*/, const char* /*func*/, const char* /*message*/) override; + bool OnPreWarning(const char* /*window*/, const char* /*fileName*/, int /*line*/, const char* /*func*/, const char* /*message*/) override; + // + + bool CaptureLogFromTraceBus(const char* window, const char* message); + + enum class OperationResult + { + Success, + Failure, + }; + + void GraphUpgradeComplete(const AZ::Data::Asset, OperationResult result, AZStd::string_view message); + + bool IsUpgrading() const; + + bool m_inProgress = false; + // scan fields + size_t m_currentAssetRowIndex = 0; + size_t m_inspectedAssets = 0; + size_t m_failedAssets = 0; + size_t m_discoveredAssets = 0; + + IUpgradeRequests::AssetList m_assetsToInspect; + IUpgradeRequests::AssetList::iterator m_inspectingAsset; + using UpgradeAssets = AZStd::vector>; + UpgradeAssets m_assetsToUpgrade; + UpgradeAssets::iterator m_inProgressAsset; + + AZ::Data::Asset m_currentAsset; + + AZStd::unique_ptr m_ui; + + AZStd::unique_ptr m_settingsCache; + + // upgrade fields + AZStd::recursive_mutex m_mutex; + bool m_upgradeComplete = false; + AZ::Data::Asset m_upgradeAsset; + int m_upgradeAssetIndex = 0; + OperationResult m_upgradeResult; + AZStd::string m_upgradeMessage; + AZStd::string m_tmpFileName; + + AZStd::unique_ptr m_keepEditorAlive; + + AZStd::deque m_logs; + + AZ::Entity* m_scriptCanvasEntity = nullptr; + + bool m_isUpgradingSingleGraph = false; + + void UpgradeSingle(QPushButton* item, AzQtComponents::StyledBusyLabel* spinner, AZ::Data::AssetInfo assetInfo); + + void FlushLogs(); + + void FinalizeUpgrade(); + void FinalizeScan(); + + void BackupComplete(); + AZStd::string BackupGraph(const AZ::Data::Asset&); + void UpgradeGraph(const AZ::Data::Asset&); + + void GraphUpgradeCompleteUIUpdate(const AZ::Data::Asset asset, OperationResult result, AZStd::string_view message); + void OnGraphUpgradeComplete(AZ::Data::Asset&, bool skipped = false) override; + + void OnSourceFileReleased(AZ::Data::Asset asset); + + void closeEvent(QCloseEvent* event) override; + + bool m_overwriteAll = false; + void PerformMove(AZ::Data::Asset asset, AZStd::string source, AZStd::string target, size_t remainingAttempts); + + void Log(const char* format, ...); + }; +} diff --git a/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/UpgradeTool.cpp b/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/UpgradeTool.cpp deleted file mode 100644 index 5e6858c993..0000000000 --- a/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/UpgradeTool.cpp +++ /dev/null @@ -1,697 +0,0 @@ -/* - * Copyright (c) Contributors to the Open 3D Engine Project. - * For complete copyright and license terms please see the LICENSE at the root of this distribution. - * - * SPDX-License-Identifier: Apache-2.0 OR MIT - * - */ - -#include -#include - -#include "UpgradeTool.h" - -#include -#include -#include -#include - -#include -#include - -#include -#include -#include - -#include - -#include -#include -#include - -#include -#include - -#include "UpgradeHelper.h" - -namespace ScriptCanvasEditor -{ - UpgradeTool::UpgradeTool(QWidget* parent /*= nullptr*/) - : AzQtComponents::StyledDialog(parent) - , m_ui(new Ui::UpgradeTool()) - { - m_ui->setupUi(this); - - AzQtComponents::CheckBox::applyToggleSwitchStyle(m_ui->doNotAskCheckbox); - AzQtComponents::CheckBox::applyToggleSwitchStyle(m_ui->makeBackupCheckbox); - - m_ui->progressFrame->setVisible(false); - - connect(m_ui->upgradeButton, &QPushButton::pressed, this, &UpgradeTool::OnUpgrade); - connect(m_ui->notNowButton, &QPushButton::pressed, this, &UpgradeTool::OnNoThanks); - - UpgradeNotifications::Bus::Handler::BusConnect(); - AZ::Debug::TraceMessageBus::Handler::BusConnect(); - - resize(700, 100); - } - - void UpgradeTool::OnNoThanks() - { - DisconnectBuses(); - - UpdateSettings(); - - reject(); - } - - void UpgradeTool::UpdateSettings() - { - auto userSettings = AZ::UserSettings::CreateFind(AZ_CRC("ScriptCanvasPreviewSettings", 0x1c5a2965), AZ::UserSettings::CT_LOCAL); - if (userSettings) - { - userSettings->m_showUpgradeDialog = !m_ui->doNotAskCheckbox->isChecked(); - - AZ::UserSettingsOwnerRequestBus::Event(AZ::UserSettings::CT_LOCAL, &AZ::UserSettingsOwnerRequests::SaveSettings); - } - } - - UpgradeTool::~UpgradeTool() - { - DisconnectBuses(); - } - - void UpgradeTool::DisconnectBuses() - { - UpgradeNotifications::Bus::Handler::BusDisconnect(); - - AZ::SystemTickBus::Handler::BusDisconnect(); - AZ::Data::AssetBus::MultiHandler::BusDisconnect(); - AZ::Debug::TraceMessageBus::Handler::BusDisconnect(); - } - - void UpgradeTool::closeEvent(QCloseEvent* event) - { - // m_keepEditorAlive.reset(); - - DisconnectBuses(); - - UpgradeNotifications::Bus::Broadcast(&UpgradeNotifications::OnUpgradeCancelled); - - AzQtComponents::StyledDialog::closeEvent(event); - } - - bool UpgradeTool::HasBackup() const - { - return m_ui->makeBackupCheckbox->isChecked(); - } - - void UpgradeTool::OnUpgrade() - { - setWindowFlag(Qt::WindowCloseButtonHint, false); - - // m_keepEditorAlive = AZStd::make_unique(); - - UpdateSettings(); - - UpgradeNotifications::Bus::Broadcast(&UpgradeNotifications::OnUpgradeStart); - - m_assetsToUpgrade.clear(); - - IUpgradeRequests* upgradeRequests = AZ::Interface::Get(); - m_assetsToUpgrade = upgradeRequests->GetAssetsToUpgrade(); - - AZ::SystemTickBus::Handler::BusConnect(); - - if (m_ui->makeBackupCheckbox->isChecked()) - { - if (!DoBackup()) - { - // There was a problem, ask if the user wants to keep going or abort - QMessageBox mb(QMessageBox::Warning, - QObject::tr("Backup Failed"), - QObject::tr("Failed to backup your Script Canvas graphs, do you want to proceed with upgrade?"), - QMessageBox::Yes | QMessageBox::No, this); - - if (mb.exec() == QMessageBox::Yes) - { - DoUpgrade(); - } - } - } - else - { - DoUpgrade(); - } - } - - bool UpgradeTool::DoBackup() - { - if (!m_assetsToUpgrade.empty()) - { - m_state = UpgradeState::Backup; - - m_inProgressAsset = m_assetsToUpgrade.begin(); - - m_ui->progressFrame->setVisible(true); - m_ui->progressBar->setRange(0, aznumeric_cast(m_assetsToUpgrade.size())); - - m_ui->spinner->SetIsBusy(true); - m_ui->spinner->SetBusyIconSize(32); - - m_ui->upgradeButton->setEnabled(false); - m_ui->notNowButton->setEnabled(false); - m_ui->doNotAskCheckbox->setEnabled(false); - m_ui->makeBackupCheckbox->setEnabled(false); - - // Make the folder for the backup - - QDateTime theTime = QDateTime::currentDateTime(); - QString subFolder = theTime.toString("yyyy-MM-dd [HH.mm.ss]"); - - m_backupPath = AZStd::string::format("@devroot@/ScriptCanvas_BACKUP/%s", subFolder.toUtf8().data()); - char backupPathCStr[AZ_MAX_PATH_LEN] = { 0 }; - AZ::IO::FileIOBase::GetInstance()->ResolvePath(m_backupPath.c_str(), backupPathCStr, AZ_MAX_PATH_LEN); - m_backupPath = backupPathCStr; - - if (!AZ::IO::FileIOBase::GetInstance()->Exists(m_backupPath.c_str())) - { - if (AZ::IO::FileIOBase::GetInstance()->CreatePath(m_backupPath.c_str()) != AZ::IO::ResultCode::Success) - { - AZ_Error("Script Canvas", false, "Failed to create backup folder %s", m_backupPath.c_str()); - - return false; - } - } - } - - return true; - } - - void UpgradeTool::BackupComplete() - { - m_currentAssetIndex = 0; - m_ui->progressBar->setValue(0); - - DoUpgrade(); - } - - void UpgradeTool::DoUpgrade() - { - m_state = UpgradeState::Upgrade; - - if (!m_assetsToUpgrade.empty()) - { - m_ui->progressFrame->setVisible(true); - m_ui->progressBar->setRange(0, aznumeric_cast(m_assetsToUpgrade.size())); - - m_ui->spinner->SetIsBusy(true); - m_ui->spinner->SetBusyIconSize(32); - - m_ui->upgradeButton->setEnabled(false); - m_ui->notNowButton->setEnabled(false); - m_ui->doNotAskCheckbox->setEnabled(false); - m_ui->makeBackupCheckbox->setEnabled(false); - - m_inProgressAsset = m_assetsToUpgrade.begin(); - } - } - - void UpgradeTool::OnAssetReady(AZ::Data::Asset asset) - { - // Start asset upgrade job when current asset is unassigned only - // If current asset is present, there is ongoing progress handling this asset already - if (IsOnReadyAssetForCurrentProcess(asset.GetId())) - { - m_inProgress = true; - m_currentAsset = asset; - m_scriptCanvasEntity = AssetUpgradeJob(asset); - if (!m_scriptCanvasEntity) - { - ResetUpgradeCurrentAsset(); - } - } - } - - void UpgradeTool::OnAssetError(AZ::Data::Asset asset) - { - // Reset upgrade target when script canvas entity is unassigned only. - // If script canvas entity is present, we should let the conversion progress finish itself. - if (IsCurrentProcessFreeToAbort(asset.GetId())) - { - AZ_TracePrintf("Script Canvas", "Asset fails to get load: %s\n", asset.GetHint().c_str()); - ResetUpgradeCurrentAsset(); - } - } - - void UpgradeTool::OnAssetUnloaded(const AZ::Data::AssetId assetId, const AZ::Data::AssetType) - { - // Reset upgrade target when script canvas entity is unassigned only. - // If script canvas entity is present, we should let the conversion progress finish itself. - if (IsCurrentProcessFreeToAbort(assetId)) - { - AZ_TracePrintf("Script Canvas", "Asset gets unloaded: %s\n", m_inProgressAsset->m_relativePath.c_str()); - ResetUpgradeCurrentAsset(); - } - } - - - void UpgradeTool::OnGraphUpgradeComplete(AZ::Data::Asset& asset, bool skipped /*=false*/) - { - if (!skipped) - { - AZStd::string relativePath, fullPath; - AZ::Data::AssetCatalogRequestBus::BroadcastResult(relativePath, &AZ::Data::AssetCatalogRequests::GetAssetPathById, asset.GetId()); - - bool fullPathFound = false; - AzToolsFramework::AssetSystemRequestBus::BroadcastResult(fullPathFound, &AzToolsFramework::AssetSystemRequestBus::Events::GetFullSourcePathFromRelativeProductPath, relativePath, fullPath); - - AZStd::string tmpFileName; - bool tmpFilesaved = false; - - // here we are saving the graph to a temp file instead of the original file and then copying the temp file to the original file. - // This ensures that AP will not a get a file change notification on an incomplete graph file causing it to fail processing. Temp files are ignored by AP. - if (AZ::IO::CreateTempFileName(fullPath.c_str(), tmpFileName)) - { - AZ::IO::FileIOStream fileStream(tmpFileName.c_str(), AZ::IO::OpenMode::ModeWrite | AZ::IO::OpenMode::ModeText); - - if (fileStream.IsOpen()) - { - if (asset.GetType() == azrtti_typeid()) - { - tmpFilesaved = AZ::Utils::SaveObjectToStream(fileStream, AZ::DataStream::ST_XML, &asset.GetAs()->GetScriptCanvasData()); - } - - fileStream.Close(); - } - - using SCCommandBus = AzToolsFramework::SourceControlCommandBus; - SCCommandBus::Broadcast(&SCCommandBus::Events::RequestEdit, fullPath.c_str(), true, - [this, &asset, fullPath, tmpFileName, tmpFilesaved](bool /*success*/, const AzToolsFramework::SourceControlFileInfo& info) - { - if (!info.IsReadOnly()) - { - if (tmpFilesaved) - { - PerformMove(asset, tmpFileName, fullPath); - } - } - else - { - if (m_overwriteAll) - { - MakeWriteable(info); - - if (tmpFilesaved) - { - PerformMove(asset, tmpFileName, fullPath); - } - } - else - { - int result = QMessageBox::No; - if (!m_overwriteAll) - { - QMessageBox mb(QMessageBox::Warning, - QObject::tr("Failed to Save Upgraded File"), - QObject::tr("The upgraded file could not be saved because the file is read only.\nDo you want to make it writeable and overwrite it?"), - QMessageBox::YesToAll | QMessageBox::Yes | QMessageBox::No, this); - - result = mb.exec(); - if (result == QMessageBox::YesToAll) - { - m_overwriteAll = true; - } - } - - if (result == QMessageBox::Yes || m_overwriteAll) - { - MakeWriteable(info); - - if (tmpFilesaved) - { - PerformMove(asset, tmpFileName, fullPath); - } - } - - } - } - }); - } - } - else - { - // We skipped the upgrade (it's up to date), just mark it complete - AZ::SystemTickBus::QueueFunction([this, asset]() { UpgradeComplete(asset, true); }); - - } - - } - - void UpgradeTool::MakeWriteable(const AzToolsFramework::SourceControlFileInfo& info) - { - AZ::IO::SystemFile::SetWritable(info.m_filePath.c_str(), true); - } - - void UpgradeTool::PerformMove(AZ::Data::Asset& asset, const AZStd::string& source, const AZStd::string& target) - { - auto moveResult = AZ::IO::SmartMove(source.c_str(), target.c_str()); - if (moveResult.GetResultCode() == AZ::IO::ResultCode::Success) - { - // Bump the slice asset up in the asset processor's queue. - AzFramework::AssetSystemRequestBus::Broadcast(&AzFramework::AssetSystem::AssetSystemRequests::EscalateAssetBySearchTerm, target.c_str()); - - AZ::SystemTickBus::QueueFunction([this, &asset]() { UpgradeComplete(asset); }); - } - else - { - auto streamer = AZ::Interface::Get(); - AZ::IO::FileRequestPtr flushRequest = streamer->FlushCache(target.c_str()); - streamer->SetRequestCompleteCallback(flushRequest, [this, &asset, source, target]([[maybe_unused]] AZ::IO::FileRequestHandle request) - { - // Continue saving. - AZ::SystemTickBus::QueueFunction([this, &asset, source, target]() { RetryMove(asset, source, target); }); - }); - streamer->QueueRequest(flushRequest); - } - } - - bool UpgradeTool::IsOnReadyAssetForCurrentProcess(const AZ::Data::AssetId& assetId) - { - return !m_currentAsset && m_inProgressAsset != m_assetsToUpgrade.end() && m_inProgressAsset->m_assetId == assetId; - } - - bool UpgradeTool::IsCurrentProcessFreeToAbort(const AZ::Data::AssetId& assetId) - { - return !m_scriptCanvasEntity && m_inProgressAsset != m_assetsToUpgrade.end() && m_inProgressAsset->m_assetId == assetId; - } - - bool UpgradeTool::IsUpgradeCompleteForAllAssets() - { - return !m_inProgress && !m_currentAsset && !m_scriptCanvasEntity && m_inProgressAsset == m_assetsToUpgrade.end(); - } - - bool UpgradeTool::IsUpgradeCompleteForCurrentAsset() - { - return !m_inProgress && !m_currentAsset && !m_scriptCanvasEntity && m_inProgressAsset != m_assetsToUpgrade.end(); - } - - void UpgradeTool::ResetUpgradeCurrentAsset() - { - AZ::Data::AssetBus::MultiHandler::BusDisconnect(m_currentAsset.GetId()); - - if (m_scriptCanvasEntity) - { - m_scriptCanvasEntity->Deactivate(); - m_scriptCanvasEntity = nullptr; - } - - if (m_inProgressAsset != m_assetsToUpgrade.end()) - { - m_inProgressAsset = m_assetsToUpgrade.erase(m_inProgressAsset); - } - - m_currentAsset.Release(); - m_currentAsset = {}; - m_inProgress = false; - } - - void UpgradeTool::OnSystemTick() - { - switch (m_state) - { - case UpgradeTool::UpgradeState::Upgrade: - - if (IsUpgradeCompleteForCurrentAsset()) - { - m_inProgress = true; - AZ::Data::AssetInfo& assetToUpgrade = *m_inProgressAsset; - - if (!AZ::Data::AssetBus::MultiHandler::BusIsConnectedId(assetToUpgrade.m_assetId)) - { - AZ::Data::AssetBus::MultiHandler::BusConnect(assetToUpgrade.m_assetId); - } - - auto asset = AZ::Data::AssetManager::Instance().GetAsset(assetToUpgrade.m_assetId, assetToUpgrade.m_assetType, AZ::Data::AssetLoadBehavior::Default); - asset.BlockUntilLoadComplete(); - - auto streamer = AZ::Interface::Get(); - AZ::IO::FileRequestPtr flushRequest = streamer->FlushCache(assetToUpgrade.m_relativePath); - streamer->SetRequestCompleteCallback(flushRequest, []([[maybe_unused]] AZ::IO::FileRequestHandle request) - { - }); - streamer->QueueRequest(flushRequest); - - if (asset.IsReady() || asset.GetStatus() == AZ::Data::AssetData::AssetStatus::ReadyPreNotify) - { - m_currentAsset = asset; - m_scriptCanvasEntity = AssetUpgradeJob(asset); - if (!m_scriptCanvasEntity) - { - ResetUpgradeCurrentAsset(); - } - } - - m_ui->spinner->SetText(QObject::tr("%1").arg(assetToUpgrade.m_relativePath.c_str())); - } - else if (IsUpgradeCompleteForAllAssets()) - { - FinalizeUpgrade(); - } - break; - - case UpgradeTool::UpgradeState::Backup: - - if (m_inProgressAsset != m_assetsToUpgrade.end()) - { - AZ::Data::AssetInfo& assetToBackup = *m_inProgressAsset; - - m_ui->spinner->SetText(QObject::tr("%1").arg(assetToBackup.m_relativePath.c_str())); - - BackupAsset(assetToBackup); - - m_ui->progressBar->setValue(aznumeric_cast(++m_currentAssetIndex)); - } - else - { - BackupComplete(); - } - break; - - default: - break; - } - - AZ::Data::AssetManager::Instance().DispatchEvents(); - AZ::SystemTickBus::ExecuteQueuedEvents(); - - } - - void UpgradeTool::BackupAsset(const AZ::Data::AssetInfo& assetInfo) - { - - AZStd::string devRoot = "@devroot@"; - AZStd::string devAssets = "@devassets@"; - - char devRootCStr[AZ_MAX_PATH_LEN] = { 0 }; - AZ::IO::FileIOBase::GetInstance()->ResolvePath(devRoot.c_str(), devRootCStr, AZ_MAX_PATH_LEN); - - char devAssetsCStr[AZ_MAX_PATH_LEN] = { 0 }; - AZ::IO::FileIOBase::GetInstance()->ResolvePath(devAssets.c_str(), devAssetsCStr, AZ_MAX_PATH_LEN); - - AZStd::string relativePath = devAssetsCStr; - AzFramework::StringFunc::Replace(relativePath, devRootCStr, ""); - if (relativePath.starts_with("/")) - { - relativePath = relativePath.substr(1, relativePath.size() - 1); - } - - AZStd::string sourceFilePath; - - // Using this to get the watch folder - AZStd::string watchFolder; - AZ::Data::AssetInfo assetInfo2; - bool sourceInfoFound{}; - AzToolsFramework::AssetSystemRequestBus::BroadcastResult(sourceInfoFound, &AzToolsFramework::AssetSystemRequestBus::Events::GetSourceInfoBySourcePath, assetInfo.m_relativePath.c_str(), assetInfo2, watchFolder); - if (sourceInfoFound) - { - AZStd::string assetPath; - AzFramework::StringFunc::Path::Join(watchFolder.c_str(), assetInfo.m_relativePath.c_str(), assetPath); - - sourceFilePath = assetPath; - } - - devRoot = devRootCStr; - AzFramework::StringFunc::Path::Normalize(devRoot); - - relativePath = sourceFilePath; - AzFramework::StringFunc::Replace(relativePath, devRoot.c_str(), ""); - if (relativePath.starts_with("/")) - { - relativePath = relativePath.substr(1, relativePath.size() - 1); - } - - AZStd::string targetFilePath; - AzFramework::StringFunc::Path::Join(m_backupPath.c_str(), relativePath.c_str(), targetFilePath); - - if (AZ::IO::FileIOBase::GetInstance()->Copy(sourceFilePath.c_str(), targetFilePath.c_str()) != AZ::IO::ResultCode::Error) - { - AZStd::string filename; - AzFramework::StringFunc::Path::GetFileName(sourceFilePath.c_str(), filename); - AZ_TracePrintf("Script Canvas", "Backup: %s -> %s\n", filename.c_str(), targetFilePath.c_str()); - } - else - { - AZ_TracePrintf("Script Canvas", "(Error) Failed to create backup: %s -> %s\n", sourceFilePath.c_str(), targetFilePath.c_str()); - } - - ++m_inProgressAsset; - } - - void UpgradeTool::UpgradeComplete(const AZ::Data::Asset& asset, bool skipped /*= false*/) - { - m_ui->progressBar->setValue(aznumeric_cast(++m_currentAssetIndex)); - - ResetUpgradeCurrentAsset(); - - if (!skipped) - { - AZStd::string filename; - AzFramework::StringFunc::Path::GetFileName(asset.GetHint().c_str(), filename); - AZ_TracePrintf("Script Canvas", "%s -> Upgraded and Saved!\n", filename.c_str()); - } - } - - void UpgradeTool::FinalizeUpgrade() - { - setWindowFlag(Qt::WindowCloseButtonHint, true); - - AZ::SystemTickBus::Handler::BusDisconnect(); - - m_currentAsset = {}; - - SaveLog(); - - UpgradeNotifications::Bus::Broadcast(&UpgradeNotifications::OnUpgradeComplete); - - AZ_TracePrintf("Script Canvas", "\nUpgrade Complete!\n"); - - DisconnectBuses(); - - accept(); - } - - void UpgradeTool::SaveLog() - { - AZStd::string outputFileName = AZStd::string::format("@devroot@/ScriptCanvasUpgradeReport.html"); - - char resolvedBuffer[AZ_MAX_PATH_LEN] = { 0 }; - AZ::IO::FileIOBase::GetInstance()->ResolvePath(outputFileName.c_str(), resolvedBuffer, AZ_MAX_PATH_LEN); - - AZStd::string endPath = resolvedBuffer; - AZ::StringFunc::Path::Normalize(endPath); - - AZ::IO::SystemFile outputFile; - if (!outputFile.Open(endPath.c_str(), - AZ::IO::SystemFile::SF_OPEN_CREATE | AZ::IO::SystemFile::SF_OPEN_WRITE_ONLY)) - { - AZ_Error("Script Canvas", false, "Failed to open file for writing: %s", endPath.c_str()); - return; - } - - QDateTime theTime = QDateTime::currentDateTime(); - AZStd::string timeStamp = theTime.toString("yyyy-MM-dd [HH.mm.ss]").toUtf8().data(); - - AZStd::string header = "\n\n\n\n\n"; - header.append(AZStd::string::format("Log captured: %s
\n", timeStamp.c_str()).c_str()); - - outputFile.Write(header.c_str(), header.size()); - for (auto& log : m_logs) - { - AZStd::string logText = AZStd::string::format("%s
", log.c_str()); - AzFramework::StringFunc::Replace(logText, "\n", "
\n"); - outputFile.Write(logText.data(), logText.size()); - } - AZStd::string footer = "\n\n"; - outputFile.Write(footer.c_str(), footer.size()); - - - - outputFile.Close(); - } - - AZ::Entity* UpgradeTool::AssetUpgradeJob(AZ::Data::Asset&) - { - return nullptr; - } - - void UpgradeTool::RetryMove(AZ::Data::Asset& asset, const AZStd::string& source, const AZStd::string& target) - { - auto moveResult = AZ::IO::SmartMove(source.c_str(), target.c_str()); - if (moveResult.GetResultCode() == AZ::IO::ResultCode::Success) - { - // Bump the slice asset up in the asset processor's queue. - AzFramework::AssetSystemRequestBus::Broadcast(&AzFramework::AssetSystem::AssetSystemRequests::EscalateAssetBySearchTerm, target.c_str()); - - AZ::SystemTickBus::QueueFunction([this, &asset]() { UpgradeComplete(asset); }); - } - else - { - auto streamer = AZ::Interface::Get(); - AZ::IO::FileRequestPtr flushRequest = streamer->FlushCache(target.c_str()); - streamer->SetRequestCompleteCallback(flushRequest, [this, &asset, source, target]([[maybe_unused]] AZ::IO::FileRequestHandle request) - { - // Continue saving. - AZ::SystemTickBus::QueueFunction([this, &asset, source, target]() { RetryMove(asset, source, target); }); - }); - streamer->QueueRequest(flushRequest); - - - //AZ::SystemTickBus::QueueFunction([this, &asset, source, target]() { RetryMove(asset, source, target); }); - } - } - - void UpgradeTool::CaptureLogFromTraceBus(const char* /*window*/, const char* message) - { - AZStd::string msg = message; - if (msg.ends_with("\n")) - { - msg = msg.substr(0, msg.size() - 1); - } - - m_logs.push_back(msg); - } - - bool UpgradeTool::OnPreError(const char* window, const char* /*fileName*/, int /*line*/, const char* /*func*/, const char* message) - { - AZStd::string msg = AZStd::string::format("(Error): %s
", message); - CaptureLogFromTraceBus(window, msg.c_str()); - - return false; - } - - bool UpgradeTool::OnPreWarning(const char* window, const char* /*fileName*/, int /*line*/, const char* /*func*/, const char* message) - { - AZStd::string msg = AZStd::string::format("(Warning): %s
", message); - CaptureLogFromTraceBus(window, msg.c_str()); - - return false; - } - - bool UpgradeTool::OnException(const char* message) - { - AZStd::string msg = AZStd::string::format("(Exception): %s
", message); - CaptureLogFromTraceBus("Script Canvas", msg.c_str()); - - return false; - } - - bool UpgradeTool::OnPrintf(const char* window, const char* message) - { - CaptureLogFromTraceBus(window, message); - return false; - } - -#include - -} diff --git a/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/UpgradeTool.h b/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/UpgradeTool.h deleted file mode 100644 index be00198457..0000000000 --- a/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/UpgradeTool.h +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Copyright (c) Contributors to the Open 3D Engine Project. - * For complete copyright and license terms please see the LICENSE at the root of this distribution. - * - * SPDX-License-Identifier: Apache-2.0 OR MIT - * - */ - -#pragma once - -#if !defined(Q_MOC_RUN) -#include - -AZ_PUSH_DISABLE_WARNING(4244 4251 4800, "-Wunknown-warning-option") -#include -AZ_POP_DISABLE_WARNING - -#include -#include - -#include - -#include - -#include - -#include -#include -#include -#endif - -class QPushButton; - -namespace Ui -{ - class UpgradeTool; -} - -namespace ScriptCanvasEditor -{ - class KeepEditorAlive; - - //! A tool that collects and upgrades all Script Canvas graphs in the asset catalog - class UpgradeTool - : public AzQtComponents::StyledDialog - , private AZ::SystemTickBus::Handler - , private AZ::Data::AssetBus::MultiHandler - , private UpgradeNotifications::Bus::Handler - , private AZ::Debug::TraceMessageBus::Handler - { - Q_OBJECT - - public: - AZ_CLASS_ALLOCATOR(UpgradeTool, AZ::SystemAllocator, 0); - - UpgradeTool(QWidget* parent = nullptr); - ~UpgradeTool(); - - size_t& UpgradedGraphCount() { return m_upgradedAssets; } - size_t& SkippedGraphCount() { return m_skippedAssets; } - - bool HasBackup() const; - - private: - - bool IsOnReadyAssetForCurrentProcess(const AZ::Data::AssetId& assetId); - bool IsCurrentProcessFreeToAbort(const AZ::Data::AssetId& assetId); - bool IsUpgradeCompleteForAllAssets(); - bool IsUpgradeCompleteForCurrentAsset(); - void ResetUpgradeCurrentAsset(); - - void OnUpgrade(); - void OnNoThanks(); - void UpdateSettings(); - - enum class UpgradeState - { - Inactive, - Backup, - Upgrade - }; - UpgradeState m_state = UpgradeState::Inactive; - - bool DoBackup(); - void BackupAsset(const AZ::Data::AssetInfo& assetInfo); - void BackupComplete(); - - void DoUpgrade(); - void UpgradeComplete(const AZ::Data::Asset&, bool skipped = false); - - AZ::Entity* AssetUpgradeJob(AZ::Data::Asset& asset); - - // SystemTickBus::Handler - void OnSystemTick() override; - // - - // AssetBus::Handler - void OnAssetReady(AZ::Data::Asset asset) override; - void OnAssetError(AZ::Data::Asset asset) override; - void OnAssetUnloaded(const AZ::Data::AssetId assetId, const AZ::Data::AssetType assetType) override; - // - - // AZ::Debug::TranceMessageBus::Handler - bool OnException(const char* /*message*/) override; - bool OnPrintf(const char* /*window*/, const char* /*message*/) override; - bool OnPreError(const char* /*window*/, const char* /*fileName*/, int /*line*/, const char* /*func*/, const char* /*message*/) override; - bool OnPreWarning(const char* /*window*/, const char* /*fileName*/, int /*line*/, const char* /*func*/, const char* /*message*/) override; - // - - void CaptureLogFromTraceBus(const char* window, const char* message); - - void OnGraphUpgradeComplete(AZ::Data::Asset&, bool skipped = false) override; - - void RetryMove(AZ::Data::Asset& asset, const AZStd::string& source, const AZStd::string& target); - - void SaveLog(); - - bool m_inProgress = false; - size_t m_currentAssetIndex = 0; - - size_t m_upgradedAssets = 0; - size_t m_skippedAssets = 0; - - IUpgradeRequests::AssetList m_assetsToUpgrade; - IUpgradeRequests::AssetList::iterator m_inProgressAsset; - - AZ::Data::Asset m_currentAsset; - - AZStd::unique_ptr m_ui; - AZStd::recursive_mutex m_mutex; - - // AZStd::unique_ptr m_keepEditorAlive; - - AZStd::vector m_logs; - - AZ::Entity* m_scriptCanvasEntity = nullptr; - - AZStd::string m_backupPath; - - void FinalizeUpgrade(); - void DisconnectBuses(); - - void closeEvent(QCloseEvent* event) override; - - bool m_overwriteAll = false; - void MakeWriteable(const AzToolsFramework::SourceControlFileInfo& info); - void PerformMove(AZ::Data::Asset& asset, const AZStd::string& source, const AZStd::string& target); - }; -} diff --git a/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/UpgradeTool.ui b/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/UpgradeTool.ui deleted file mode 100644 index 78a63ce727..0000000000 --- a/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/UpgradeTool.ui +++ /dev/null @@ -1,260 +0,0 @@ - - - UpgradeTool - - - Qt::WindowModal - - - - 0 - 0 - 801 - 328 - - - - - 0 - 0 - - - - Script Canvas Upgrade - - - - 5 - - - 5 - - - 5 - - - 5 - - - 5 - - - - - - - - - - 0 - 0 - - - - <html><head/><body><p>Script Canvas graphs are now compiled into Lua by the Asset Processor! In order to complete this upgrade, all graphs in your current project must be updated.</p><p>This upgrading is making changes to the underlying Script Canvas graph format. Depending on the number of scripts you've created this process could take a few minutes to complete. </p><p>This upgrade is required for any projects saved on 1.26 or earlier.</p><p>If you choose &quot;Not now&quot;, your project may not work correctly.</p></body></html> - - - false - - - true - - - - - - - - 0 - 80 - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - true - - - - 0 - 0 - - - - - 0 - 0 - - - - false - - - background-color: rgb(47, 47, 47); - - - QFrame::NoFrame - - - QFrame::Raised - - - - - - - 0 - 0 - - - - - 32 - 32 - - - - false - - - - - - - - 0 - 0 - - - - - - - 24 - - - true - - - - - - - - - - - - - Qt::Vertical - - - QSizePolicy::MinimumExpanding - - - - 20 - 0 - - - - - - - - - - - - Backup my source graphs - - - true - - - - - - - Do not ask me again - - - - - - - - 0 - 0 - - - - Qt::LeftToRight - - - Upgrade - - - - - - - - 0 - 0 - - - - Qt::LeftToRight - - - Not now - - - false - - - - - - - - - - - - - - - - AzQtComponents::StyledBusyLabel - QWidget -
AzQtComponents/Components/StyledBusyLabel.h
- 1 -
-
- - -
diff --git a/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/VersionExplorer.h b/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/VersionExplorer.h index acb28a7dba..7ce1920dbb 100644 --- a/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/VersionExplorer.h +++ b/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/VersionExplorer.h @@ -10,21 +10,17 @@ #if !defined(Q_MOC_RUN) #include - AZ_PUSH_DISABLE_WARNING(4244 4251 4800, "-Wunknown-warning-option") #include AZ_POP_DISABLE_WARNING - #include #include +#include #include - +#include +#include #include #include - -#include -#include -#include #endif class QPushButton; diff --git a/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/VersionExplorerLog.cpp b/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/VersionExplorerLog.cpp new file mode 100644 index 0000000000..6c9acea25c --- /dev/null +++ b/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/VersionExplorerLog.cpp @@ -0,0 +1,1023 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace VersionExplorerCpp +{ + class FileEventHandler + : public AZ::IO::FileIOEventBus::Handler + { + public: + int m_errorCode = 0; + AZStd::string m_fileName; + + FileEventHandler() + { + BusConnect(); + } + + ~FileEventHandler() + { + BusDisconnect(); + } + + void OnError(const AZ::IO::SystemFile* /*file*/, const char* fileName, int errorCode) override + { + m_errorCode = errorCode; + + if (fileName) + { + m_fileName = fileName; + } + } + }; +} + +namespace ScriptCanvasEditor +{ + EditorKeepAlive::EditorKeepAlive() + { + ISystem* system = nullptr; + CrySystemRequestBus::BroadcastResult(system, &CrySystemRequestBus::Events::GetCrySystem); + + m_edKeepEditorActive = system->GetIConsole()->GetCVar("ed_KeepEditorActive"); + + if (m_edKeepEditorActive) + { + m_keepEditorActive = m_edKeepEditorActive->GetIVal(); + m_edKeepEditorActive->Set(1); + } + } + + EditorKeepAlive::~EditorKeepAlive() + { + if (m_edKeepEditorActive) + { + m_edKeepEditorActive->Set(m_keepEditorActive); + } + } + + VersionExplorerLog::VersionExplorerLog(QWidget* parent /*= nullptr*/) + : AzQtComponents::StyledDialog(parent) + , m_ui(new Ui::VersionExplorerLog()) + { + m_ui->setupUi(this); + + m_ui->tableWidget->horizontalHeader()->setVisible(false); + m_ui->tableWidget->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch); + m_ui->tableWidget->horizontalHeader()->setSectionResizeMode(3, QHeaderView::Fixed); + m_ui->tableWidget->setColumnWidth(3, 22); + + m_ui->textEdit->setHorizontalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAsNeeded); + m_ui->textEdit->setVerticalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAlwaysOn); + + connect(m_ui->scanButton, &QPushButton::pressed, this, &VersionExplorerLog::OnScan); + connect(m_ui->closeButton, &QPushButton::pressed, this, &VersionExplorerLog::OnClose); + connect(m_ui->upgradeAllButton, &QPushButton::pressed, this, &VersionExplorerLog::OnUpgradeAll); + + m_ui->progressBar->setValue(0); + m_ui->progressBar->setVisible(false); + + m_keepEditorAlive = AZStd::make_unique(); + m_inspectingAsset = m_assetsToInspect.end(); + + } + + VersionExplorerLog::~VersionExplorerLog() + { + AZ::SystemTickBus::Handler::BusDisconnect(); + + UpgradeNotifications::Bus::Handler::BusDisconnect(); + AZ::Debug::TraceMessageBus::Handler::BusDisconnect(); + + } + + void VersionExplorerLog::Log(const char* format, ...) + { + if (m_ui->verbose->isChecked()) + { + char sBuffer[2048]; + va_list ArgList; + va_start(ArgList, format); + azvsnprintf(sBuffer, sizeof(sBuffer), format, ArgList); + sBuffer[sizeof(sBuffer) - 1] = '\0'; + va_end(ArgList); + + AZ_TracePrintf(ScriptCanvas::k_VersionExplorerWindow.data(), "%s\n", sBuffer); + } + } + + void VersionExplorerLog::OnClose() + { + reject(); + } + + bool VersionExplorerLog::IsUpgrading() const + { + return m_inProgressAsset != m_assetsToUpgrade.end() && m_inProgress; + } + + void VersionExplorerLog::OnSystemTick() + { + switch (m_state) + { + case ProcessState::Scan: + + if (!m_inProgress && m_inspectingAsset != m_assetsToInspect.end()) + { + m_inProgress = true; + AZ::Data::AssetInfo& assetToUpgrade = *m_inspectingAsset; + m_currentAsset = AZ::Data::AssetManager::Instance().GetAsset(assetToUpgrade.m_assetId, assetToUpgrade.m_assetType, AZ::Data::AssetLoadBehavior::PreLoad); + Log("SystemTick::ProcessState::Scan: %s pre-blocking load hint", m_currentAsset.GetHint().c_str()); + m_currentAsset.BlockUntilLoadComplete(); + if (m_currentAsset.IsReady()) + { + // The asset is ready, grab its info + m_inProgress = true; + InspectAsset(m_currentAsset, assetToUpgrade); + } + else + { + m_ui->tableWidget->insertRow(static_cast(m_currentAssetRowIndex)); + QTableWidgetItem* rowName = new QTableWidgetItem + ( tr(AZStd::string::format("Error: %s", assetToUpgrade.m_relativePath.c_str()).c_str())); + m_ui->tableWidget->setItem(static_cast(m_currentAssetRowIndex), static_cast(ColumnAsset), rowName); + ++m_currentAssetRowIndex; + + Log("SystemTick::ProcessState::Scan: %s post-blocking load, problem loading asset", assetToUpgrade.m_relativePath.c_str()); + ++m_failedAssets; + ScanComplete(m_currentAsset); + } + } + break; + + case ProcessState::Upgrade: + { + AZStd::lock_guard lock(m_mutex); + if (m_upgradeComplete) + { + ++m_upgradeAssetIndex; + m_inProgress = false; + m_ui->progressBar->setVisible(true); + m_ui->progressBar->setValue(m_upgradeAssetIndex); + + if (m_scriptCanvasEntity) + { + m_scriptCanvasEntity->Deactivate(); + m_scriptCanvasEntity = nullptr; + } + + GraphUpgradeCompleteUIUpdate(m_upgradeAsset, m_upgradeResult, m_upgradeMessage); + + if (!m_isUpgradingSingleGraph) + { + if (m_inProgressAsset != m_assetsToUpgrade.end()) + { + m_inProgressAsset = m_assetsToUpgrade.erase(m_inProgressAsset); + } + + if (m_inProgressAsset == m_assetsToUpgrade.end()) + { + FinalizeUpgrade(); + } + } + else + { + m_inProgressAsset = m_assetsToUpgrade.erase(m_inProgressAsset); + m_inProgress = false; + m_state = ProcessState::Inactive; + m_settingsCache.reset(); + AZ::SystemTickBus::Handler::BusDisconnect(); + AZ::Debug::TraceMessageBus::Handler::BusDisconnect(); + } + + m_isUpgradingSingleGraph = false; + + if (m_assetsToUpgrade.empty()) + { + m_ui->upgradeAllButton->setEnabled(false); + } + + m_upgradeComplete = false; + } + + if (!IsUpgrading() && m_state == ProcessState::Upgrade) + { + AZStd::string errorMessage = BackupGraph(*m_inProgressAsset); + // Make the backup + if (errorMessage.empty()) + { + Log("SystemTick::ProcessState::Upgrade: Backup Success %s ", m_inProgressAsset->GetHint().c_str()); + QList items = m_ui->tableWidget->findItems(m_inProgressAsset->GetHint().c_str(), Qt::MatchFlag::MatchExactly); + if (!items.isEmpty()) + { + for (auto* item : items) + { + int row = item->row(); + AzQtComponents::StyledBusyLabel* spinner = qobject_cast(m_ui->tableWidget->cellWidget(row, ColumnStatus)); + spinner->SetIsBusy(true); + } + } + + // Upgrade the graph + UpgradeGraph(*m_inProgressAsset); + } + else + { + Log("SystemTick::ProcessState::Upgrade: Backup Failed %s ", m_inProgressAsset->GetHint().c_str()); + GraphUpgradeComplete(*m_inProgressAsset, OperationResult::Failure, errorMessage); + } + + } + break; + } + default: + break; + } + + FlushLogs(); + + AZ::Data::AssetManager::Instance().DispatchEvents(); + AZ::SystemTickBus::ExecuteQueuedEvents(); + } + + // Backup + + void VersionExplorerLog::OnUpgradeAll() + { + m_state = ProcessState::Upgrade; + m_settingsCache = AZStd::make_unique(); + ScriptCanvas::Grammar::g_saveRawTranslationOuputToFile = false; + ScriptCanvas::Grammar::g_printAbstractCodeModel = false; + ScriptCanvas::Grammar::g_saveRawTranslationOuputToFile = false; + AZ::Interface::Get()->SetIsUpgrading(true); + AZ::Interface::Get()->ClearGraphsThatNeedUpgrade(); + m_inProgressAsset = m_assetsToUpgrade.begin(); + AZ::Debug::TraceMessageBus::Handler::BusConnect(); + AZ::SystemTickBus::Handler::BusConnect(); + m_ui->progressBar->setVisible(true); + m_ui->progressBar->setRange(0, aznumeric_cast(m_assetsToUpgrade.size())); + m_ui->progressBar->setValue(m_upgradeAssetIndex); + m_keepEditorAlive = AZStd::make_unique(); + } + + AZStd::string VersionExplorerLog::BackupGraph(const AZ::Data::Asset& asset) + { + if (!m_ui->makeBackupCheckbox->isChecked()) + { + // considered a success + return ""; + } + + QDateTime theTime = QDateTime::currentDateTime(); + QString subFolder = theTime.toString("yyyy-MM-dd [HH.mm.ss]"); + + AZStd::string backupPath = AZStd::string::format("@devroot@/ScriptCanvas_BACKUP/%s", subFolder.toUtf8().data()); + char backupPathCStr[AZ_MAX_PATH_LEN] = { 0 }; + AZ::IO::FileIOBase::GetInstance()->ResolvePath(backupPath.c_str(), backupPathCStr, AZ_MAX_PATH_LEN); + backupPath = backupPathCStr; + + if (!AZ::IO::FileIOBase::GetInstance()->Exists(backupPath.c_str())) + { + if (AZ::IO::FileIOBase::GetInstance()->CreatePath(backupPath.c_str()) != AZ::IO::ResultCode::Success) + { + AZ_Error(ScriptCanvas::k_VersionExplorerWindow.data(), false, "Failed to create backup folder %s", backupPath.c_str()); + return "Failed to create backup folder"; + } + } + + AZStd::string devRoot = "@devroot@"; + AZStd::string devAssets = "@devassets@"; + + char devRootCStr[AZ_MAX_PATH_LEN] = { 0 }; + AZ::IO::FileIOBase::GetInstance()->ResolvePath(devRoot.c_str(), devRootCStr, AZ_MAX_PATH_LEN); + + char devAssetsCStr[AZ_MAX_PATH_LEN] = { 0 }; + AZ::IO::FileIOBase::GetInstance()->ResolvePath(devAssets.c_str(), devAssetsCStr, AZ_MAX_PATH_LEN); + + AZStd::string relativePath = devAssetsCStr; + AzFramework::StringFunc::Replace(relativePath, devRootCStr, ""); + if (relativePath.starts_with("/")) + { + relativePath = relativePath.substr(1, relativePath.size() - 1); + } + + AZStd::string sourceFilePath; + + AZStd::string watchFolder; + AZ::Data::AssetInfo assetInfo; + bool sourceInfoFound{}; + AzToolsFramework::AssetSystemRequestBus::BroadcastResult(sourceInfoFound, &AzToolsFramework::AssetSystemRequestBus::Events::GetSourceInfoBySourcePath, asset.GetHint().c_str(), assetInfo, watchFolder); + if (sourceInfoFound) + { + AZStd::string assetPath; + AzFramework::StringFunc::Path::Join(watchFolder.c_str(), assetInfo.m_relativePath.c_str(), assetPath); + + sourceFilePath = assetPath; + } + else + { + AZ_Warning(ScriptCanvas::k_VersionExplorerWindow.data(), false, "VersionExplorerLog::BackupGraph: Failed to find file: %s", asset.GetHint().c_str()); + return "Failed to find source file"; + } + + devRoot = devRootCStr; + AzFramework::StringFunc::Path::Normalize(devRoot); + + relativePath = sourceFilePath; + AzFramework::StringFunc::Replace(relativePath, devRoot.c_str(), ""); + if (relativePath.starts_with("/")) + { + relativePath = relativePath.substr(1, relativePath.size() - 1); + } + + AzFramework::StringFunc::Path::Normalize(relativePath); + AzFramework::StringFunc::Path::Normalize(backupPath); + + AZStd::string targetFilePath = backupPath; + targetFilePath += relativePath; + + if (AZ::IO::FileIOBase::GetInstance()->Copy(sourceFilePath.c_str(), targetFilePath.c_str()) != AZ::IO::ResultCode::Success) + { + AZ_Warning(ScriptCanvas::k_VersionExplorerWindow.data(), false, "VersionExplorerLog::BackupGraph: Error creating backup: %s ---> %s\n", sourceFilePath.c_str(), targetFilePath.c_str()); + return "Failed to copy source file to backup location"; + } + + Log("VersionExplorerLog::BackupGraph: Backed up: %s ---> %s\n", sourceFilePath.c_str(), targetFilePath.c_str()); + return ""; + } + + void VersionExplorerLog::UpgradeGraph(const AZ::Data::Asset& asset) + { + m_inProgress = true; + m_upgradeComplete = false; + Log("UpgradeGraph %s ", m_inProgressAsset->GetHint().c_str()); + m_ui->spinner->SetText(QObject::tr("Upgrading: %1").arg(asset.GetHint().c_str())); + m_scriptCanvasEntity = nullptr; + + UpgradeNotifications::Bus::Handler::BusConnect(); + + if (asset.GetType() == azrtti_typeid()) + { + ScriptCanvasAsset* scriptCanvasAsset = asset.GetAs(); + AZ_Assert(scriptCanvasAsset, "Unable to get the asset of ScriptCanvasAsset, but received type: %s" + , azrtti_typeid().template ToString().c_str()); + + if (!scriptCanvasAsset) + { + return; + } + + AZ::Entity* scriptCanvasEntity = scriptCanvasAsset->GetScriptCanvasEntity(); + AZ_Assert(scriptCanvasEntity, "VersionExplorerLog::UpgradeGraph The Script Canvas asset must have a valid entity"); + if (!scriptCanvasEntity) + { + return; + } + + AZ::Entity* queryEntity = nullptr; + AZ::ComponentApplicationBus::BroadcastResult(queryEntity, &AZ::ComponentApplicationRequests::FindEntity, scriptCanvasEntity->GetId()); + if (queryEntity) + { + if (queryEntity->GetState() == AZ::Entity::State::Active) + { + queryEntity->Deactivate(); + } + + scriptCanvasEntity = queryEntity; + } + + if (scriptCanvasEntity->GetState() == AZ::Entity::State::Constructed) + { + scriptCanvasEntity->Init(); + } + + if (scriptCanvasEntity->GetState() == AZ::Entity::State::Init) + { + scriptCanvasEntity->Activate(); + } + + AZ_Assert(scriptCanvasEntity->GetState() == AZ::Entity::State::Active, "Graph entity is not active"); + auto graphComponent = scriptCanvasEntity->FindComponent(); + AZ_Assert(graphComponent, "The Script Canvas entity must have a Graph component"); + + if (graphComponent) + { + m_scriptCanvasEntity = scriptCanvasEntity; + + graphComponent->UpgradeGraph + ( asset + , m_ui->forceUpgrade->isChecked() ? Graph::UpgradeRequest::Forced : Graph::UpgradeRequest::IfOutOfDate + , m_ui->verbose->isChecked()); + } + } + + AZ_Assert(m_scriptCanvasEntity, "The ScriptCanvas asset should have an entity"); + } + + void VersionExplorerLog::OnGraphUpgradeComplete(AZ::Data::Asset& asset, bool /*skipped*/ /*= false*/) + { + AZStd::string relativePath, fullPath; + AZ::Data::AssetCatalogRequestBus::BroadcastResult(relativePath, &AZ::Data::AssetCatalogRequests::GetAssetPathById, asset.GetId()); + bool fullPathFound = false; + AzToolsFramework::AssetSystemRequestBus::BroadcastResult(fullPathFound, &AzToolsFramework::AssetSystemRequestBus::Events::GetFullSourcePathFromRelativeProductPath, relativePath, fullPath); + if (!fullPathFound) + { + AZ_Error(ScriptCanvas::k_VersionExplorerWindow.data(), false, "Full source path not found for %s", relativePath.c_str()); + } + + auto streamer = AZ::Interface::Get(); + AZ::IO::FileRequestPtr flushRequest = streamer->FlushCache(fullPath); + streamer->SetRequestCompleteCallback(flushRequest, [this, asset]([[maybe_unused]] AZ::IO::FileRequestHandle request) + { + this->OnSourceFileReleased(asset); + }); + streamer->QueueRequest(flushRequest); + } + + void VersionExplorerLog::OnSourceFileReleased(AZ::Data::Asset asset) + { + AZStd::string relativePath, fullPath; + AZ::Data::AssetCatalogRequestBus::BroadcastResult(relativePath, &AZ::Data::AssetCatalogRequests::GetAssetPathById, asset.GetId()); + bool fullPathFound = false; + AzToolsFramework::AssetSystemRequestBus::BroadcastResult(fullPathFound, &AzToolsFramework::AssetSystemRequestBus::Events::GetFullSourcePathFromRelativeProductPath, relativePath, fullPath); + m_tmpFileName.clear(); + AZStd::string tmpFileName; + // here we are saving the graph to a temp file instead of the original file and then copying the temp file to the original file. + // This ensures that AP will not a get a file change notification on an incomplete graph file causing it to fail processing. Temp files are ignored by AP. + if (!AZ::IO::CreateTempFileName(fullPath.c_str(), tmpFileName)) + { + GraphUpgradeComplete(asset, OperationResult::Failure, "Failure to create temporary file name"); + return; + } + + bool tempSavedSucceeded = false; + AZ::IO::FileIOStream fileStream(tmpFileName.c_str(), AZ::IO::OpenMode::ModeWrite | AZ::IO::OpenMode::ModeText); + if (fileStream.IsOpen()) + { + if (asset.GetType() == azrtti_typeid()) + { + ScriptCanvasEditor::ScriptCanvasAssetHandler handler; + tempSavedSucceeded = handler.SaveAssetData(asset, &fileStream); + } + + fileStream.Close(); + } + + // attempt to remove temporary file no matter what + m_tmpFileName = tmpFileName; + if (!tempSavedSucceeded) + { + GraphUpgradeComplete(asset, OperationResult::Failure, "Save asset data to temporary file failed"); + return; + } + + using SCCommandBus = AzToolsFramework::SourceControlCommandBus; + SCCommandBus::Broadcast(&SCCommandBus::Events::RequestEdit, fullPath.c_str(), true, + [this, asset, fullPath, tmpFileName]([[maybe_unused]] bool success, const AzToolsFramework::SourceControlFileInfo& info) + { + constexpr const size_t k_maxAttemps = 10; + + if (!info.IsReadOnly()) + { + PerformMove(asset, tmpFileName, fullPath, k_maxAttemps); + } + else + { + if (m_overwriteAll) + { + AZ::IO::SystemFile::SetWritable(info.m_filePath.c_str(), true); + PerformMove(asset, tmpFileName, fullPath, k_maxAttemps); + } + else + { + int result = QMessageBox::No; + if (!m_overwriteAll) + { + QMessageBox mb(QMessageBox::Warning, + QObject::tr("Failed to Save Upgraded File"), + QObject::tr("The upgraded file could not be saved because the file is read only.\nDo you want to make it writeable and overwrite it?"), + QMessageBox::YesToAll | QMessageBox::Yes | QMessageBox::No, this); + + result = mb.exec(); + if (result == QMessageBox::YesToAll) + { + m_overwriteAll = true; + } + } + + if (result == QMessageBox::Yes || m_overwriteAll) + { + AZ::IO::SystemFile::SetWritable(info.m_filePath.c_str(), true); + PerformMove(asset, tmpFileName, fullPath, k_maxAttemps); + } + } + } + }); + } + + void VersionExplorerLog::PerformMove(AZ::Data::Asset asset, AZStd::string source, AZStd::string target + , size_t remainingAttempts) + { + VersionExplorerCpp::FileEventHandler fileEventHandler; + + if (remainingAttempts == 0) + { + // all attempts failed, give up + AZ_Warning(ScriptCanvas::k_VersionExplorerWindow.data(), false, "moving converted file to source destination failed: %s. giving up", target.c_str()); + GraphUpgradeComplete(asset, OperationResult::Failure, "Failed to move updated file from backup to source destination"); + } + else if (remainingAttempts == 2) + { + // before the final attempt, flush all caches + AZ_Warning(ScriptCanvas::k_VersionExplorerWindow.data(), false, "moving converted file to source destination failed: %s, trying again", target.c_str()); + auto streamer = AZ::Interface::Get(); + AZ::IO::FileRequestPtr flushRequest = streamer->FlushCaches(); + streamer->SetRequestCompleteCallback(flushRequest + , [this, asset, remainingAttempts, source, target]([[maybe_unused]] AZ::IO::FileRequestHandle request) + { + // Continue saving. + AZ::SystemTickBus::QueueFunction( + [this, asset, remainingAttempts, source, target](){ PerformMove(asset, source, target, remainingAttempts - 1); }); + }); + streamer->QueueRequest(flushRequest); + } + else + { + // the actual move attempt + auto moveResult = AZ::IO::SmartMove(source.c_str(), target.c_str()); + if (moveResult.GetResultCode() == AZ::IO::ResultCode::Success) + { + m_tmpFileName.clear(); + auto streamer = AZ::Interface::Get(); + AZ::IO::FileRequestPtr flushRequest = streamer->FlushCache(target.c_str()); + // Bump the slice asset up in the asset processor's queue. + AzFramework::AssetSystemRequestBus::Broadcast(&AzFramework::AssetSystem::AssetSystemRequests::EscalateAssetBySearchTerm, target.c_str()); + AZ::SystemTickBus::QueueFunction([this, asset]() + { + GraphUpgradeComplete(asset, OperationResult::Success, ""); + }); + } + else + { + AZ_Warning(ScriptCanvas::k_VersionExplorerWindow.data(), false, "moving converted file to source destination failed: %s, trying again", target.c_str()); + auto streamer = AZ::Interface::Get(); + AZ::IO::FileRequestPtr flushRequest = streamer->FlushCache(target.c_str()); + streamer->SetRequestCompleteCallback(flushRequest, [this, asset, source, target, remainingAttempts]([[maybe_unused]] AZ::IO::FileRequestHandle request) + { + // Continue saving. + AZ::SystemTickBus::QueueFunction([this, asset, source, target, remainingAttempts]() { PerformMove(asset, source, target, remainingAttempts - 1); }); + }); + streamer->QueueRequest(flushRequest); + } + } + } + + void VersionExplorerLog::GraphUpgradeComplete + ( const AZ::Data::Asset asset, OperationResult result, AZStd::string_view message) + { + AZStd::lock_guard lock(m_mutex); + m_upgradeComplete = true; + m_upgradeResult = result; + m_upgradeMessage = message; + m_upgradeAsset = asset; + + if (!m_tmpFileName.empty()) + { + AZ::IO::FileIOBase* fileIO = AZ::IO::FileIOBase::GetInstance(); + AZ_Assert(fileIO, "GraphUpgradeComplete: No FileIO instance"); + + if (fileIO->Exists(m_tmpFileName.c_str()) && !fileIO->Remove(m_tmpFileName.c_str())) + { + AZ_TracePrintf(ScriptCanvas::k_VersionExplorerWindow.data(), "Failed to remove temporary file: %s", m_tmpFileName.c_str()); + } + } + + if (m_upgradeResult == OperationResult::Failure) + { + AZ::Interface::Get()->GraphNeedsManualUpgrade(asset.GetId()); + } + + m_tmpFileName.clear(); + } + + void VersionExplorerLog::GraphUpgradeCompleteUIUpdate + ( const AZ::Data::Asset asset, OperationResult result, AZStd::string_view message) + { + QString text = asset.GetHint().c_str(); + QList items = m_ui->tableWidget->findItems(text, Qt::MatchFlag::MatchExactly); + + if (!items.isEmpty()) + { + for (auto* item : items) + { + int row = item->row(); + QTableWidgetItem* label = m_ui->tableWidget->item(row, ColumnAsset); + QString assetName = asset.GetHint().c_str(); + + if (label->text().compare(assetName) == 0) + { + m_ui->tableWidget->removeCellWidget(row, ColumnAction); + m_ui->tableWidget->removeCellWidget(row, ColumnStatus); + + QToolButton* doneButton = new QToolButton(this); + doneButton->setToolTip("Upgrade complete"); + if (result == OperationResult::Success) + { + doneButton->setIcon(QIcon(":/stylesheet/img/UI20/checkmark-menu.svg")); + } + else + { + doneButton->setIcon(QIcon(":/stylesheet/img/UI20/titlebar-close.svg")); + doneButton->setToolTip(message.data()); + } + + m_ui->tableWidget->setCellWidget(row, ColumnStatus, doneButton); + } + } + } + } + + void VersionExplorerLog::FinalizeUpgrade() + { + Log("FinalizeUpgrade!"); + m_inProgress = false; + m_assetsToUpgrade.clear(); + m_ui->upgradeAllButton->setEnabled(false); + m_ui->onlyShowOutdated->setEnabled(true); + m_keepEditorAlive.reset(); + m_ui->progressBar->setVisible(false); + + // Manual correction + size_t assetsThatNeedManualInspection = AZ::Interface::Get()->GetGraphsThatNeedManualUpgrade().size(); + if (assetsThatNeedManualInspection > 0) + { + m_ui->spinner->SetText("Some graphs will require manual corrections, you will be prompted to review them upon closing this dialog"); + } + else + { + m_ui->spinner->SetText("Upgrade complete."); + } + + AZ::SystemTickBus::Handler::BusDisconnect(); + AZ::Debug::TraceMessageBus::Handler::BusDisconnect(); + UpgradeNotifications::Bus::Handler::BusDisconnect(); + AZ::Interface::Get()->SetIsUpgrading(false); + m_settingsCache.reset(); + } + + // Scanning + + void VersionExplorerLog::OnScan() + { + m_assetsToUpgrade.clear(); + m_assetsToInspect.clear(); + m_ui->tableWidget->setRowCount(0); + m_inspectedAssets = 0; + m_currentAssetRowIndex = 0; + IUpgradeRequests* upgradeRequests = AZ::Interface::Get(); + m_assetsToInspect = upgradeRequests->GetAssetsToUpgrade(); + DoScan(); + } + + void VersionExplorerLog::DoScan() + { + m_state = ProcessState::Scan; + m_settingsCache = AZStd::make_unique(); + ScriptCanvas::Grammar::g_saveRawTranslationOuputToFile = false; + ScriptCanvas::Grammar::g_printAbstractCodeModel = false; + ScriptCanvas::Grammar::g_saveRawTranslationOuputToFile = false; + + AZ::SystemTickBus::Handler::BusConnect(); + AZ::Debug::TraceMessageBus::Handler::BusConnect(); + + if (!m_assetsToInspect.empty()) + { + m_discoveredAssets = m_assetsToInspect.size(); + m_failedAssets = 0; + m_inspectedAssets = 0; + m_currentAssetRowIndex = 0; + m_ui->progressFrame->setVisible(true); + m_ui->progressBar->setVisible(true); + m_ui->progressBar->setRange(0, aznumeric_cast(m_assetsToInspect.size())); + m_ui->progressBar->setValue(0); + + m_ui->spinner->SetIsBusy(true); + m_ui->spinner->SetBusyIconSize(32); + + m_ui->scanButton->setEnabled(false); + m_ui->upgradeAllButton->setEnabled(false); + m_ui->onlyShowOutdated->setEnabled(false); + + m_inspectingAsset = m_assetsToInspect.begin(); + m_keepEditorAlive = AZStd::make_unique(); + } + } + + void VersionExplorerLog::BackupComplete() + { + m_currentAssetRowIndex = 0; + m_ui->progressBar->setValue(0); + DoScan(); + } + + void VersionExplorerLog::InspectAsset(AZ::Data::Asset& asset, AZ::Data::AssetInfo& assetInfo) + { + Log("InspectAsset: %s", asset.GetHint().c_str()); + AZ::Entity* scriptCanvasEntity = nullptr; + if (asset.GetType() == azrtti_typeid()) + { + ScriptCanvasAsset* scriptCanvasAsset = asset.GetAs(); + if (!scriptCanvasAsset) + { + Log("InspectAsset: %s, AsestData failed to return ScriptCanvasAsset", asset.GetHint().c_str()); + return; + } + + scriptCanvasEntity = scriptCanvasAsset->GetScriptCanvasEntity(); + AZ_Assert(scriptCanvasEntity, "The Script Canvas asset must have a valid entity"); + } + + auto graphComponent = scriptCanvasEntity->FindComponent(); + AZ_Assert(graphComponent, "The Script Canvas entity must have a Graph component"); + + bool onlyShowOutdatedGraphs = m_ui->onlyShowOutdated->isChecked(); + bool forceUpgrade = m_ui->forceUpgrade->isChecked(); + ScriptCanvas::VersionData graphVersion = graphComponent->GetVersion(); + + + if (!forceUpgrade && onlyShowOutdatedGraphs && graphVersion.IsLatest()) + { + ScanComplete(asset); + Log("InspectAsset: %s, is at latest", asset.GetHint().c_str()); + return; + } + + m_ui->tableWidget->insertRow(static_cast(m_currentAssetRowIndex)); + QTableWidgetItem* rowName = new QTableWidgetItem(tr(asset.GetHint().c_str())); + m_ui->tableWidget->setItem(static_cast(m_currentAssetRowIndex), static_cast(ColumnAsset), rowName); + + if (forceUpgrade || !graphComponent->GetVersion().IsLatest()) + { + m_assetsToUpgrade.push_back(asset); + + AzQtComponents::StyledBusyLabel* spinner = new AzQtComponents::StyledBusyLabel(this); + spinner->SetBusyIconSize(16); + + QPushButton* rowGoToButton = new QPushButton(this); + rowGoToButton->setText("Upgrade"); + rowGoToButton->setEnabled(false); + + connect(rowGoToButton, &QPushButton::clicked, [this, spinner, rowGoToButton, assetInfo] { + + AZ::SystemTickBus::QueueFunction([this, rowGoToButton, spinner, assetInfo]() { + // Queue the process state change because we can't connect to the SystemTick bus in a Qt lambda + UpgradeSingle(rowGoToButton, spinner, assetInfo); + }); + + AZ::SystemTickBus::ExecuteQueuedEvents(); + + }); + + m_ui->tableWidget->setCellWidget(static_cast(m_currentAssetRowIndex), static_cast(ColumnAction), rowGoToButton); + m_ui->tableWidget->setCellWidget(static_cast(m_currentAssetRowIndex), static_cast(ColumnStatus), spinner); + } + + char resolvedBuffer[AZ_MAX_PATH_LEN] = { 0 }; + AZStd::string path = AZStd::string::format("@devroot@/%s", asset.GetHint().c_str()); + AZ::IO::FileIOBase::GetInstance()->ResolvePath(path.c_str(), resolvedBuffer, AZ_MAX_PATH_LEN); + AZ::StringFunc::Path::GetFullPath(resolvedBuffer, path); + AZ::StringFunc::Path::Normalize(path); + + bool result = false; + AZ::Data::AssetInfo info; + AZStd::string watchFolder; + QByteArray assetNameUtf8 = asset.GetHint().c_str(); + AzToolsFramework::AssetSystemRequestBus::BroadcastResult(result, &AzToolsFramework::AssetSystemRequestBus::Events::GetSourceInfoBySourcePath, assetNameUtf8, info, watchFolder); + + AZ_Error(ScriptCanvas::k_VersionExplorerWindow.data(), result, "Failed to locate asset info for '%s'.", assetNameUtf8.constData()); + + QToolButton* browseButton = new QToolButton(this); + browseButton->setToolTip(AzQtComponents::fileBrowserActionName()); + browseButton->setIcon(QIcon(":/stylesheet/img/UI20/browse-edit.svg")); + + QString absolutePath = QDir(watchFolder.c_str()).absoluteFilePath(info.m_relativePath.c_str()); + connect(browseButton, &QPushButton::clicked, [absolutePath] { + AzQtComponents::ShowFileOnDesktop(absolutePath); + }); + + m_ui->tableWidget->setCellWidget(static_cast(m_currentAssetRowIndex), static_cast(ColumnBrowse), browseButton); + ScanComplete(asset); + ++m_inspectedAssets; + ++m_currentAssetRowIndex; + } + + void VersionExplorerLog::UpgradeSingle + ( QPushButton* rowGoToButton + , AzQtComponents::StyledBusyLabel* spinner + , AZ::Data::AssetInfo assetInfo) + { + AZ::Data::Asset asset = AZ::Data::AssetManager::Instance().GetAsset + ( assetInfo.m_assetId, assetInfo.m_assetType, AZ::Data::AssetLoadBehavior::PreLoad); + + if (asset) + { + asset.BlockUntilLoadComplete(); + + if (asset.IsReady()) + { + AZ::Interface::Get()->SetIsUpgrading(true); + m_isUpgradingSingleGraph = true; + m_logs.clear(); + m_ui->textEdit->clear(); + spinner->SetIsBusy(true); + rowGoToButton->setEnabled(false); + + m_inProgressAsset = AZStd::find_if(m_assetsToUpgrade.begin(), m_assetsToUpgrade.end() + , [asset](const UpgradeAssets::value_type& assetToUpgrade) + { + return assetToUpgrade.GetId() == asset.GetId(); + }); + + m_state = ProcessState::Upgrade; + AZ::SystemTickBus::Handler::BusConnect(); + } + } + } + + void VersionExplorerLog::ScanComplete(const AZ::Data::Asset& asset) + { + Log("ScanComplete: %s", asset.GetHint().c_str()); + m_inProgress = false; + m_ui->progressBar->setValue(aznumeric_cast(m_currentAssetRowIndex)); + m_ui->scanButton->setEnabled(true); + + m_inspectingAsset = m_assetsToInspect.erase(m_inspectingAsset); + FlushLogs(); + + if (m_inspectingAsset == m_assetsToInspect.end()) + { + AZ::SystemTickBus::QueueFunction([this]() { FinalizeScan(); }); + + if (!m_assetsToUpgrade.empty()) + { + m_ui->upgradeAllButton->setEnabled(true); + } + } + } + + void VersionExplorerLog::FinalizeScan() + { + Log("FinalizeScan()"); + + m_ui->spinner->SetIsBusy(false); + m_ui->onlyShowOutdated->setEnabled(true); + + // Enable all the Upgrade buttons + for (int row = 0; row < m_ui->tableWidget->rowCount(); ++row) + { + QPushButton* button = qobject_cast(m_ui->tableWidget->cellWidget(row, ColumnAction)); + if (button) + { + button->setEnabled(true); + } + } + + QString spinnerText = QStringLiteral("Scan Complete"); + if (m_assetsToUpgrade.empty()) + { + spinnerText.append(" - No graphs require upgrade!"); + } + else + { + spinnerText.append(QString::asprintf(" - Discovered: %zu, Inspected: %zu, Failed: %zu, Upgradeable: %zu" + , m_discoveredAssets, m_inspectedAssets, m_failedAssets, m_assetsToUpgrade.size())); + } + + + m_ui->spinner->SetText(spinnerText); + m_ui->progressBar->setVisible(false); + + if (!m_assetsToUpgrade.empty()) + { + m_ui->upgradeAllButton->setEnabled(true); + } + + AZ::SystemTickBus::Handler::BusDisconnect(); + AZ::Debug::TraceMessageBus::Handler::BusDisconnect(); + UpgradeNotifications::Bus::Handler::BusDisconnect(); + + m_keepEditorAlive.reset(); + m_settingsCache.reset(); + m_state = ProcessState::Inactive; + } + + void VersionExplorerLog::FlushLogs() + { + if (m_logs.empty()) + { + return; + } + + const QTextCursor oldCursor = m_ui->textEdit->textCursor(); + QScrollBar* scrollBar = m_ui->textEdit->verticalScrollBar(); + + m_ui->textEdit->moveCursor(QTextCursor::End); + QTextCursor textCursor = m_ui->textEdit->textCursor(); + + while (!m_logs.empty()) + { + auto line = "\n" + m_logs.front(); + + m_logs.pop_front(); + + textCursor.insertText(line.c_str()); + } + + scrollBar->setValue(scrollBar->maximum()); + m_ui->textEdit->moveCursor(QTextCursor::StartOfLine); + + } + + bool VersionExplorerLog::CaptureLogFromTraceBus(const char* window, const char* message) + { + if (m_ui->updateReportingOnly->isChecked() && window != ScriptCanvas::k_VersionExplorerWindow) + { + return true; + } + + AZStd::string msg = message; + if (msg.ends_with("\n")) + { + msg = msg.substr(0, msg.size() - 1); + } + + m_logs.push_back(msg); + return m_ui->updateReportingOnly->isChecked(); + } + + bool VersionExplorerLog::OnPreError(const char* window, const char* /*fileName*/, int /*line*/, const char* /*func*/, const char* message) + { + AZStd::string msg = AZStd::string::format("(Error): %s", message); + return CaptureLogFromTraceBus(window, msg.c_str()); + } + + bool VersionExplorerLog::OnPreWarning(const char* window, const char* /*fileName*/, int /*line*/, const char* /*func*/, const char* message) + { + AZStd::string msg = AZStd::string::format("(Warning): %s", message); + return CaptureLogFromTraceBus(window, msg.c_str()); + } + + bool VersionExplorerLog::OnException(const char* message) + { + AZStd::string msg = AZStd::string::format("(Exception): %s", message); + return CaptureLogFromTraceBus("Script Canvas", msg.c_str()); + } + + bool VersionExplorerLog::OnPrintf(const char* window, const char* message) + { + return CaptureLogFromTraceBus(window, message); + } + + void VersionExplorerLog::closeEvent(QCloseEvent* event) + { + m_keepEditorAlive.reset(); + + AzQtComponents::StyledDialog::closeEvent(event); + } + +#include + +} diff --git a/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/VersionExplorerLog.h b/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/VersionExplorerLog.h new file mode 100644 index 0000000000..498b2f7745 --- /dev/null +++ b/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/VersionExplorerLog.h @@ -0,0 +1,167 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#if !defined(Q_MOC_RUN) +#include + +AZ_PUSH_DISABLE_WARNING(4244 4251 4800, "-Wunknown-warning-option") +#include +AZ_POP_DISABLE_WARNING + +#include +#include +#include + +#include +#include + +#include +#include +#include +#endif + +class QPushButton; + +namespace Ui +{ + class VersionExplorerLog; +} + +namespace AzQtComponents +{ + class StyledBusyLabel; +} + +namespace ScriptCanvasEditor +{ + //! A tool that collects and upgrades all Script Canvas graphs in the asset catalog + class VersionExplorerLog + : public AzQtComponents::StyledDialog + , private AZ::SystemTickBus::Handler + , private UpgradeNotifications::Bus::Handler + , private AZ::Debug::TraceMessageBus::Handler + { + Q_OBJECT + + public: + AZ_CLASS_ALLOCATOR(VersionExplorerLog, AZ::SystemAllocator, 0); + + explicit VersionExplorerLog(QWidget* parent = nullptr); + ~VersionExplorerLog(); + + private: + + static constexpr int ColumnAsset = 0; + static constexpr int ColumnAction = 1; + static constexpr int ColumnBrowse = 2; + static constexpr int ColumnStatus = 3; + + void OnScan(); + void OnClose(); + + enum class ProcessState + { + Inactive, + Backup, + Scan, + Upgrade, + }; + ProcessState m_state = ProcessState::Inactive; + + void DoScan(); + void ScanComplete(const AZ::Data::Asset&); + + void InspectAsset(AZ::Data::Asset& asset, AZ::Data::AssetInfo& assetInfo); + + void OnUpgradeAll(); + + // SystemTickBus::Handler + void OnSystemTick() override; + // + + // AZ::Debug::TranceMessageBus::Handler + bool OnException(const char* /*message*/) override; + bool OnPrintf(const char* /*window*/, const char* /*message*/) override; + bool OnPreError(const char* /*window*/, const char* /*fileName*/, int /*line*/, const char* /*func*/, const char* /*message*/) override; + bool OnPreWarning(const char* /*window*/, const char* /*fileName*/, int /*line*/, const char* /*func*/, const char* /*message*/) override; + // + + bool CaptureLogFromTraceBus(const char* window, const char* message); + + enum class OperationResult + { + Success, + Failure, + }; + + void GraphUpgradeComplete(const AZ::Data::Asset, OperationResult result, AZStd::string_view message); + + bool IsUpgrading() const; + + bool m_inProgress = false; + // scan fields + size_t m_currentAssetRowIndex = 0; + size_t m_inspectedAssets = 0; + size_t m_failedAssets = 0; + size_t m_discoveredAssets = 0; + + IUpgradeRequests::AssetList m_assetsToInspect; + IUpgradeRequests::AssetList::iterator m_inspectingAsset; + using UpgradeAssets = AZStd::vector>; + UpgradeAssets m_assetsToUpgrade; + UpgradeAssets::iterator m_inProgressAsset; + + AZ::Data::Asset m_currentAsset; + + AZStd::unique_ptr m_ui; + + AZStd::unique_ptr m_settingsCache; + + // upgrade fields + AZStd::recursive_mutex m_mutex; + bool m_upgradeComplete = false; + AZ::Data::Asset m_upgradeAsset; + int m_upgradeAssetIndex = 0; + OperationResult m_upgradeResult; + AZStd::string m_upgradeMessage; + AZStd::string m_tmpFileName; + + AZStd::unique_ptr m_keepEditorAlive; + + AZStd::deque m_logs; + + AZ::Entity* m_scriptCanvasEntity = nullptr; + + bool m_isUpgradingSingleGraph = false; + + void UpgradeSingle(QPushButton* item, AzQtComponents::StyledBusyLabel* spinner, AZ::Data::AssetInfo assetInfo); + + void FlushLogs(); + + void FinalizeUpgrade(); + void FinalizeScan(); + + void BackupComplete(); + AZStd::string BackupGraph(const AZ::Data::Asset&); + void UpgradeGraph(const AZ::Data::Asset&); + + void GraphUpgradeCompleteUIUpdate(const AZ::Data::Asset asset, OperationResult result, AZStd::string_view message); + void OnGraphUpgradeComplete(AZ::Data::Asset&, bool skipped = false) override; + + void OnSourceFileReleased(AZ::Data::Asset asset); + + void closeEvent(QCloseEvent* event) override; + + bool m_overwriteAll = false; + void PerformMove(AZ::Data::Asset asset, AZStd::string source, AZStd::string target, size_t remainingAttempts); + + void Log(const char* format, ...); + }; +} diff --git a/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/VersionExplorerTraits.h b/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/VersionExplorerTraits.h new file mode 100644 index 0000000000..4bed23c74e --- /dev/null +++ b/Gems/ScriptCanvas/Code/Editor/View/Windows/Tools/UpgradeTool/VersionExplorerTraits.h @@ -0,0 +1,19 @@ +#include + +namespace ScriptCanvasEditor +{ + class VersionExplorerNotificationsTraits : public AZ::EBusTraits + { + public: + static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusHandlerPolicy::Multiple; + using BusIdType = VersionExplorerNotificationsTraits*; + }; + using VersionExplorerNotificationsBus = AZ:EBus; + + class VersionExplorerRequestsTraits : public AZ::EBusTraits + { + public: + static const AZ::EBusHandlerPolicy = AZ::EBusHandlerPolicy::Single; + }; + using VersionExplorerRequestsBus = AZ:EBus; +} diff --git a/Gems/ScriptCanvas/Code/scriptcanvasgem_editor_files.cmake b/Gems/ScriptCanvas/Code/scriptcanvasgem_editor_files.cmake index 52b1cc4772..dbffe99afd 100644 --- a/Gems/ScriptCanvas/Code/scriptcanvasgem_editor_files.cmake +++ b/Gems/ScriptCanvas/Code/scriptcanvasgem_editor_files.cmake @@ -262,12 +262,17 @@ set(FILES Editor/View/Windows/Tools/UpgradeTool/UpgradeHelper.h Editor/View/Windows/Tools/UpgradeTool/UpgradeHelper.cpp Editor/View/Windows/Tools/UpgradeTool/UpgradeHelper.ui - Editor/View/Windows/Tools/UpgradeTool/UpgradeTool.cpp - Editor/View/Windows/Tools/UpgradeTool/UpgradeTool.h - Editor/View/Windows/Tools/UpgradeTool/UpgradeTool.ui + Editor/View/Windows/Tools/UpgradeTool/Scanner.h + Editor/View/Windows/Tools/UpgradeTool/Scanner.cpp + Editor/View/Windows/Tools/UpgradeTool/Modifier.h + Editor/View/Windows/Tools/UpgradeTool/Modifier.cpp + Editor/View/Windows/Tools/UpgradeTool/FileSaver.h + Editor/View/Windows/Tools/UpgradeTool/FileSaver.cpp Editor/View/Windows/Tools/UpgradeTool/VersionExplorer.h Editor/View/Windows/Tools/UpgradeTool/VersionExplorer.cpp Editor/View/Windows/Tools/UpgradeTool/VersionExplorer.ui + Editor/View/Windows/Tools/UpgradeTool/VersionExplorerLog.h + Editor/View/Windows/Tools/UpgradeTool/VersionExplorerLog.cpp Editor/Framework/ScriptCanvasGraphUtilities.inl Editor/Framework/ScriptCanvasGraphUtilities.h Editor/Framework/ScriptCanvasTraceUtilities.h