diff --git a/Code/Tools/ProjectManager/Source/DownloadWorker.cpp b/Code/Tools/ProjectManager/Source/DownloadWorker.cpp index 9bda1b34cc..5f5c64987b 100644 --- a/Code/Tools/ProjectManager/Source/DownloadWorker.cpp +++ b/Code/Tools/ProjectManager/Source/DownloadWorker.cpp @@ -25,7 +25,7 @@ namespace O3DE::ProjectManager m_downloadProgress = downloadProgress; emit UpdateProgress(downloadProgress); }; - AZ::Outcome gemInfoResult = PythonBindingsInterface::Get()->DownloadGem(m_gemName, gemDownloadProgress); + AZ::Outcome gemInfoResult = PythonBindingsInterface::Get()->DownloadGem(m_gemName, gemDownloadProgress, true); if (gemInfoResult.IsSuccess()) { emit Done(""); diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.cpp index 8c875e4846..def02ea9ba 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.cpp @@ -429,6 +429,7 @@ namespace O3DE::ProjectManager hLayout->addSpacing(16); QMenu* gemMenu = new QMenu(this); + gemMenu->addAction( tr("Refresh"), [this]() { emit RefreshGems(); }); gemMenu->addAction( tr("Show Gem Repos"), [this]() { emit OpenGemsRepo(); }); gemMenu->addSeparator(); gemMenu->addAction( tr("Add Existing Gem"), [this]() { emit AddGem(); }); diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.h index 6da78cce7a..a0ed7c70f9 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.h +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.h @@ -89,6 +89,7 @@ namespace O3DE::ProjectManager signals: void AddGem(); void OpenGemsRepo(); + void RefreshGems(); private: AzQtComponents::SearchLineEdit* m_filterLineEdit = nullptr; diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp index 29f73e5651..6758010cf5 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include @@ -50,6 +51,7 @@ namespace O3DE::ProjectManager vLayout->addWidget(m_headerWidget); connect(m_gemModel, &GemModel::gemStatusChanged, this, &GemCatalogScreen::OnGemStatusChanged); + connect(m_headerWidget, &GemCatalogHeaderWidget::RefreshGems, this, &GemCatalogScreen::Refresh); connect(m_headerWidget, &GemCatalogHeaderWidget::OpenGemsRepo, this, &GemCatalogScreen::HandleOpenGemRepo); connect(m_headerWidget, &GemCatalogHeaderWidget::AddGem, this, &GemCatalogScreen::OnAddGemClicked); connect(m_downloadController, &DownloadController::Done, this, &GemCatalogScreen::OnGemDownloadResult); @@ -235,6 +237,9 @@ namespace O3DE::ProjectManager // temporary, until we can refresh filter counts m_proxyModel->ResetFilters(); m_filterWidget->ResetAllFilters(); + + // Reselect the same selection to proc UI updates + m_proxyModel->GetSelectionModel()->select(m_proxyModel->GetSelectionModel()->selection(), QItemSelectionModel::Select); } void GemCatalogScreen::OnGemStatusChanged(const QString& gemName, uint32_t numChangedDependencies) @@ -300,21 +305,83 @@ namespace O3DE::ProjectManager void GemCatalogScreen::UpdateGem(const QModelIndex& modelIndex) { - const QString selectedGemName = m_gemModel->GetDisplayName(modelIndex); - GemUpdateDialog* confirmUpdateDialog = new GemUpdateDialog(selectedGemName, this); + const QString selectedGemName = m_gemModel->GetName(modelIndex); + const QString selectedGemLastUpdate = m_gemModel->GetLastUpdated(modelIndex); + const QString selectedDisplayGemName = m_gemModel->GetDisplayName(modelIndex); + const QString selectedGemRepoUri = m_gemModel->GetRepoUri(modelIndex); + + // Refresh gem repo + if (!selectedGemRepoUri.isEmpty()) + { + AZ::Outcome refreshResult = PythonBindingsInterface::Get()->RefreshGemRepo(selectedGemRepoUri); + if (refreshResult.IsSuccess()) + { + Refresh(); + } + else + { + QMessageBox::critical( + this, tr("Operation failed"), + tr("Failed to refresh gem repo %1
Error:
%2").arg(selectedGemRepoUri, refreshResult.GetError().c_str())); + } + } + // If repo uri isn't specified warn user that repo might not be refreshed + else + { + int result = QMessageBox::warning( + this, tr("Gem Repo Unspecified"), + tr("The repo for %1 is unspecfied. Repo cannot be automatically refreshed. " + "Please ensure this gem's repo is refreshed before attempting to update.") + .arg(selectedDisplayGemName), + QMessageBox::Cancel, QMessageBox::Ok); + + // Allow user to cancel update to manually refresh repo + if (result != QMessageBox::Ok) + { + return; + } + } + + // Check if there is an update avaliable now that repo is refreshed + bool updateAvaliable = PythonBindingsInterface::Get()->IsGemUpdateAvaliable(selectedGemName, selectedGemLastUpdate); + + GemUpdateDialog* confirmUpdateDialog = new GemUpdateDialog(selectedGemName, updateAvaliable, this); if (confirmUpdateDialog->exec() == QDialog::Accepted) { - // Update Gem + m_downloadController->AddGemDownload(selectedGemName); } } void GemCatalogScreen::UninstallGem(const QModelIndex& modelIndex) { - const QString selectedGemName = m_gemModel->GetDisplayName(modelIndex); - GemUninstallDialog* confirmUninstallDialog = new GemUninstallDialog(selectedGemName, this); + const QString selectedDisplayGemName = m_gemModel->GetDisplayName(modelIndex); + + GemUninstallDialog* confirmUninstallDialog = new GemUninstallDialog(selectedDisplayGemName, this); if (confirmUninstallDialog->exec() == QDialog::Accepted) { - // Uninstall Gem + const QString selectedGemPath = m_gemModel->GetPath(modelIndex); + + // Unregister the gem + auto unregisterResult = PythonBindingsInterface::Get()->RegisterGem(selectedGemPath, {}, /*remove*/true); + if (!unregisterResult) + { + QMessageBox::critical(this, tr("Failed to unregister gem"), unregisterResult.GetError().c_str()); + } + else + { + // Remove gem from model + m_gemModel->removeRow(modelIndex.row()); + + // Delete uninstalled gem directory + if (!ProjectUtils::DeleteProjectFiles(selectedGemPath, /*force*/true)) + { + QMessageBox::critical( + this, tr("Failed to remove gem directory"), tr("Could not delete gem directory at:
%1").arg(selectedGemPath)); + } + + // Show undownloaded remote gem again + Refresh(); + } } } @@ -351,8 +418,10 @@ namespace O3DE::ProjectManager { // Add all available gems to the model. const QVector& allGemInfos = allGemInfosResult.GetValue(); - for (const GemInfo& gemInfo : allGemInfos) + for (GemInfo gemInfo : allGemInfos) { + // Mark as downloaded because this gem was registered with an existing directory + gemInfo.m_downloadStatus = GemInfo::DownloadStatus::Downloaded; m_gemModel->AddGem(gemInfo); } diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.h index bef9f6cd99..b3b29d6ee6 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.h +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.h @@ -85,6 +85,7 @@ namespace O3DE::ProjectManager QString m_licenseLink; QString m_directoryLink; QString m_documentationLink; + QString m_repoUri; QString m_version = "Unknown Version"; QString m_lastUpdatedDate = "Unknown Date"; int m_binarySizeInKB = 0; diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemInspector.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemInspector.cpp index f0f5adebaa..52e078eacd 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemInspector.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemInspector.cpp @@ -238,7 +238,7 @@ namespace O3DE::ProjectManager // Depending gems m_dependingGems = new GemsSubWidget(); - connect(m_dependingGems, &GemsSubWidget::TagClicked, this, [this](const QString& tag){ emit TagClicked(tag); }); + connect(m_dependingGems, &GemsSubWidget::TagClicked, this, [this](const Tag& tag){ emit TagClicked(tag); }); m_mainLayout->addWidget(m_dependingGems); m_mainLayout->addSpacing(20); diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.cpp index 88c54de0b3..886b9e57c7 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.cpp @@ -61,6 +61,7 @@ namespace O3DE::ProjectManager item->setData(gemInfo.m_downloadStatus, RoleDownloadStatus); item->setData(gemInfo.m_licenseText, RoleLicenseText); item->setData(gemInfo.m_licenseLink, RoleLicenseLink); + item->setData(gemInfo.m_repoUri, RoleRepoUri); appendRow(item); @@ -255,6 +256,11 @@ namespace O3DE::ProjectManager return modelIndex.data(RoleLicenseLink).toString(); } + QString GemModel::GetRepoUri(const QModelIndex& modelIndex) + { + return modelIndex.data(RoleRepoUri).toString(); + } + GemModel* GemModel::GetSourceModel(QAbstractItemModel* model) { GemSortFilterProxyModel* proxyModel = qobject_cast(model); @@ -369,11 +375,30 @@ namespace O3DE::ProjectManager void GemModel::OnRowsAboutToBeRemoved(const QModelIndex& parent, int first, int last) { + bool selectedRowRemoved = false; for (int i = first; i <= last; ++i) { QModelIndex modelIndex = index(i, 0, parent); const QString& gemName = GetName(modelIndex); m_nameToIndexMap.remove(gemName); + + if (GetSelectionModel()->isRowSelected(i)) + { + selectedRowRemoved = true; + } + } + + // Select a valid row if currently selected row was removed + if (selectedRowRemoved) + { + for (const QModelIndex& index : m_nameToIndexMap) + { + if (index.isValid()) + { + GetSelectionModel()->select(index, QItemSelectionModel::ClearAndSelect); + break; + } + } } } diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.h index e25a1c7703..87a718d8c7 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.h +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemModel.h @@ -51,7 +51,8 @@ namespace O3DE::ProjectManager RoleRequirement, RoleDownloadStatus, RoleLicenseText, - RoleLicenseLink + RoleLicenseLink, + RoleRepoUri }; void AddGem(const GemInfo& gemInfo); @@ -80,6 +81,7 @@ namespace O3DE::ProjectManager static QString GetRequirement(const QModelIndex& modelIndex); static QString GetLicenseText(const QModelIndex& modelIndex); static QString GetLicenseLink(const QModelIndex& modelIndex); + static QString GetRepoUri(const QModelIndex& modelIndex); static GemModel* GetSourceModel(QAbstractItemModel* model); static const GemModel* GetSourceModel(const QAbstractItemModel* model); diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemUninstallDialog.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemUninstallDialog.cpp index 6838741417..6b2f14e551 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemUninstallDialog.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemUninstallDialog.cpp @@ -39,10 +39,10 @@ namespace O3DE::ProjectManager QLabel* bodyLabel = new QLabel(tr("The Gem and its related files will be uninstalled. This does not affect the Gem’s repository. " "You can reinstall this Gem from the Catalog, but its contents may be subject to change.")); bodyLabel->setWordWrap(true); - bodyLabel->setFixedWidth(440); + bodyLabel->setFixedSize(QSize(440, 80)); layout->addWidget(bodyLabel); - layout->addSpacing(60); + layout->addSpacing(40); // Buttons QDialogButtonBox* dialogButtons = new QDialogButtonBox(); diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemUpdateDialog.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemUpdateDialog.cpp index 8d8490ab40..dab4a2a0fe 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemUpdateDialog.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemUpdateDialog.cpp @@ -16,7 +16,7 @@ namespace O3DE::ProjectManager { - GemUpdateDialog::GemUpdateDialog(const QString& gemName, QWidget* parent) + GemUpdateDialog::GemUpdateDialog(const QString& gemName, bool updateAvaliable, QWidget* parent) : QDialog(parent) { setWindowTitle(tr("Update Remote Gem")); @@ -30,20 +30,23 @@ namespace O3DE::ProjectManager setLayout(layout); // Body - QLabel* subTitleLabel = new QLabel(tr("Update to the latest version of %1?").arg(gemName)); + QLabel* subTitleLabel = new QLabel(tr("%1Update to the latest version of %2?").arg( + updateAvaliable ? "" : tr("Force "), gemName)); subTitleLabel->setObjectName("gemCatalogDialogSubTitle"); layout->addWidget(subTitleLabel); layout->addSpacing(10); - QLabel* bodyLabel = new QLabel(tr("The latest version of this Gem may not be compatible with your engine. " + QLabel* bodyLabel = new QLabel(tr("%1The latest version of this Gem may not be compatible with your engine. " "Updating this Gem will remove any local changes made to this Gem, " - "and may remove old features that are in use.")); + "and may remove old features that are in use.").arg( + updateAvaliable ? "" : tr("No update detected for Gem. " + "This will force a redownload of the gem anyways. "))); bodyLabel->setWordWrap(true); - bodyLabel->setFixedWidth(440); + bodyLabel->setFixedSize(QSize(440, 80)); layout->addWidget(bodyLabel); - layout->addSpacing(60); + layout->addSpacing(40); // Buttons QDialogButtonBox* dialogButtons = new QDialogButtonBox(); @@ -52,7 +55,8 @@ namespace O3DE::ProjectManager QPushButton* cancelButton = dialogButtons->addButton(tr("Cancel"), QDialogButtonBox::RejectRole); cancelButton->setProperty("secondary", true); - QPushButton* updateButton = dialogButtons->addButton(tr("Update Gem"), QDialogButtonBox::ApplyRole); + QPushButton* updateButton = + dialogButtons->addButton(tr("%1Update Gem").arg(updateAvaliable ? "" : tr("Force ")), QDialogButtonBox::ApplyRole); connect(cancelButton, &QPushButton::clicked, this, &QDialog::reject); connect(updateButton, &QPushButton::clicked, this, &QDialog::accept); diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemUpdateDialog.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemUpdateDialog.h index 1a2813d1a2..cf34abfb3d 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemUpdateDialog.h +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemUpdateDialog.h @@ -19,7 +19,7 @@ namespace O3DE::ProjectManager { Q_OBJECT // AUTOMOC public : - explicit GemUpdateDialog(const QString& gemName, QWidget* parent = nullptr); + explicit GemUpdateDialog(const QString& gemName, bool updateAvaliable = true, QWidget* parent = nullptr); ~GemUpdateDialog() = default; }; } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/PythonBindings.cpp b/Code/Tools/ProjectManager/Source/PythonBindings.cpp index 021066e1c7..ef7daa761e 100644 --- a/Code/Tools/ProjectManager/Source/PythonBindings.cpp +++ b/Code/Tools/ProjectManager/Source/PythonBindings.cpp @@ -560,7 +560,7 @@ namespace O3DE::ProjectManager return AZ::Success(AZStd::move(gemNames)); } - AZ::Outcome PythonBindings::RegisterGem(const QString& gemPath, const QString& projectPath) + AZ::Outcome PythonBindings::RegisterGem(const QString& gemPath, const QString& projectPath, bool remove) { bool registrationResult = false; auto result = ExecuteWithLockErrorHandling( @@ -582,7 +582,8 @@ namespace O3DE::ProjectManager pybind11::none(), // default_restricted_folder pybind11::none(), // default_third_party_folder pybind11::none(), // external_subdir_engine_path - externalProjectPath // external_subdir_project_path + externalProjectPath, // external_subdir_project_path + remove // remove ); // Returns an exit code so boolify it then invert result @@ -715,6 +716,7 @@ namespace O3DE::ProjectManager gemInfo.m_documentationLink = Py_To_String_Optional(data, "documentation_url", ""); gemInfo.m_licenseText = Py_To_String_Optional(data, "license", "Unspecified License"); gemInfo.m_licenseLink = Py_To_String_Optional(data, "license_url", ""); + gemInfo.m_repoUri = Py_To_String_Optional(data, "repo_uri", ""); if (gemInfo.m_creator.contains("Open 3D Engine")) { @@ -728,6 +730,11 @@ namespace O3DE::ProjectManager { gemInfo.m_gemOrigin = GemInfo::GemOrigin::Remote; } + // If no origin was provided this cannot be remote and would be specified if O3DE so it should be local + else + { + gemInfo.m_gemOrigin = GemInfo::GemOrigin::Local; + } // As long Base Open3DEngine gems are installed before first startup non-remote gems will be downloaded if (gemInfo.m_gemOrigin != GemInfo::GemOrigin::Remote) @@ -1166,7 +1173,34 @@ namespace O3DE::ProjectManager return AZ::Success(AZStd::move(gemRepos)); } - AZ::Outcome PythonBindings::DownloadGem(const QString& gemName, std::function gemProgressCallback) + AZ::Outcome, AZStd::string> PythonBindings::GetAllGemRepoGemsInfos() + { + QVector gemInfos; + AZ::Outcome result = ExecuteWithLockErrorHandling( + [&] + { + auto gemPaths = m_repo.attr("get_gem_json_paths_from_all_cached_repos")(); + + if (pybind11::isinstance(gemPaths)) + { + for (auto path : gemPaths) + { + GemInfo gemInfo = GemInfoFromPath(path, pybind11::none()); + gemInfo.m_downloadStatus = GemInfo::DownloadStatus::NotDownloaded; + gemInfos.push_back(gemInfo); + } + } + }); + + if (!result.IsSuccess()) + { + return AZ::Failure(result.GetError()); + } + + return AZ::Success(AZStd::move(gemInfos)); + } + + AZ::Outcome PythonBindings::DownloadGem(const QString& gemName, std::function gemProgressCallback, bool force) { // This process is currently limited to download a single gem at a time. bool downloadSucceeded = false; @@ -1179,7 +1213,7 @@ namespace O3DE::ProjectManager QString_To_Py_String(gemName), // gem name pybind11::none(), // destination path false, // skip auto register - false, // force + force, // force overwrite pybind11::cpp_function( [this, gemProgressCallback](int progress) { @@ -1209,30 +1243,19 @@ namespace O3DE::ProjectManager m_requestCancelDownload = true; } - AZ::Outcome, AZStd::string> PythonBindings::GetAllGemRepoGemsInfos() + bool PythonBindings::IsGemUpdateAvaliable(const QString& gemName, const QString& lastUpdated) { - QVector gemInfos; - AZ::Outcome result = ExecuteWithLockErrorHandling( + bool updateAvaliableResult = false; + bool result = ExecuteWithLock( [&] { - auto gemPaths = m_repo.attr("get_gem_json_paths_from_all_cached_repos")(); + auto pyGemName = QString_To_Py_String(gemName); + auto pyLastUpdated = QString_To_Py_String(lastUpdated); + auto pythonUpdateAvaliableResult = m_download.attr("is_o3de_gem_update_available")(pyGemName, pyLastUpdated); - if (pybind11::isinstance(gemPaths)) - { - for (auto path : gemPaths) - { - GemInfo gemInfo = GemInfoFromPath(path, pybind11::none()); - gemInfo.m_downloadStatus = GemInfo::DownloadStatus::NotDownloaded; - gemInfos.push_back(gemInfo); - } - } + updateAvaliableResult = pythonUpdateAvaliableResult.cast(); }); - if (!result.IsSuccess()) - { - return AZ::Failure(result.GetError()); - } - - return AZ::Success(AZStd::move(gemInfos)); + return result && updateAvaliableResult; } } diff --git a/Code/Tools/ProjectManager/Source/PythonBindings.h b/Code/Tools/ProjectManager/Source/PythonBindings.h index 4375d56d02..8a22344596 100644 --- a/Code/Tools/ProjectManager/Source/PythonBindings.h +++ b/Code/Tools/ProjectManager/Source/PythonBindings.h @@ -42,7 +42,7 @@ namespace O3DE::ProjectManager AZ::Outcome, AZStd::string> GetEngineGemInfos() override; AZ::Outcome, AZStd::string> GetAllGemInfos(const QString& projectPath) override; AZ::Outcome, AZStd::string> GetEnabledGemNames(const QString& projectPath) override; - AZ::Outcome RegisterGem(const QString& gemPath, const QString& projectPath = {}) override; + AZ::Outcome RegisterGem(const QString& gemPath, const QString& projectPath = {}, bool remove = false) override; // Project AZ::Outcome CreateProject(const QString& projectTemplatePath, const ProjectInfo& projectInfo) override; @@ -64,9 +64,10 @@ namespace O3DE::ProjectManager bool AddGemRepo(const QString& repoUri) override; bool RemoveGemRepo(const QString& repoUri) override; AZ::Outcome, AZStd::string> GetAllGemRepoInfos() override; - AZ::Outcome DownloadGem(const QString& gemName, std::function gemProgressCallback) override; - void CancelDownload() override; AZ::Outcome, AZStd::string> GetAllGemRepoGemsInfos() override; + AZ::Outcome DownloadGem(const QString& gemName, std::function gemProgressCallback, bool force = false) override; + void CancelDownload() override; + bool IsGemUpdateAvaliable(const QString& gemName, const QString& lastUpdated) override; private: AZ_DISABLE_COPY_MOVE(PythonBindings); diff --git a/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h b/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h index 1134804f1f..07d4551c60 100644 --- a/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h +++ b/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h @@ -94,10 +94,11 @@ namespace O3DE::ProjectManager /** * Registers the gem to the specified project, or to the o3de_manifest.json if no project path is given * @param gemPath the path to the gem - * @param projectPath the path to the project. If empty, will register the external path in o3de_manifest.json + * @param projectPath the path to the project. If empty, will register the external path in o3de_manifest.json + * @param remove Unregister instead of registering this gem * @return An outcome with the success flag as well as an error message in case of a failure. */ - virtual AZ::Outcome RegisterGem(const QString& gemPath, const QString& projectPath = {}) = 0; + virtual AZ::Outcome RegisterGem(const QString& gemPath, const QString& projectPath = {}, bool remove = false) = 0; // Projects @@ -209,24 +210,34 @@ namespace O3DE::ProjectManager */ virtual AZ::Outcome, AZStd::string> GetAllGemRepoInfos() = 0; + /** + * Gathers all gem infos for all gems registered from repos. + * @return A list of gem infos. + */ + virtual AZ::Outcome, AZStd::string> GetAllGemRepoGemsInfos() = 0; + /** * Downloads and registers a Gem. - * @param gemName the name of the Gem to download - * @param gemProgressCallback a callback function that is called with an int percentage download value + * @param gemName the name of the Gem to download. + * @param gemProgressCallback a callback function that is called with an int percentage download value. + * @param force should we forcibly overwrite the old version of the gem. * @return an outcome with a string error message on failure. */ - virtual AZ::Outcome DownloadGem(const QString& gemName, std::function gemProgressCallback) = 0; + virtual AZ::Outcome DownloadGem( + const QString& gemName, std::function gemProgressCallback, bool force = false) = 0; /** - * Cancels the current download. - */ + * Cancels the current download. + */ virtual void CancelDownload() = 0; /** - * Gathers all gem infos for all gems registered from repos. - * @return A list of gem infos. + * Checks if there is an update avaliable for a gem on a repo. + * @param gemName the name of the gem to check. + * @param lastUpdated last time the gem was update. + * @return true if update is avaliable, false if not. */ - virtual AZ::Outcome, AZStd::string> GetAllGemRepoGemsInfos() = 0; + virtual bool IsGemUpdateAvaliable(const QString& gemName, const QString& lastUpdated) = 0; }; using PythonBindingsInterface = AZ::Interface; diff --git a/scripts/o3de/o3de/repo.py b/scripts/o3de/o3de/repo.py index a6b1505761..1462015630 100644 --- a/scripts/o3de/o3de/repo.py +++ b/scripts/o3de/o3de/repo.py @@ -74,9 +74,9 @@ def process_add_o3de_repo(file_name: str or pathlib.Path, manifest_json_uri = f'{o3de_object_uri}/{manifest_json}' manifest_json_sha256 = hashlib.sha256(manifest_json_uri.encode()) cache_file = cache_folder / str(manifest_json_sha256.hexdigest() + '.json') - if not cache_file.is_file(): + if cache_file.is_file(): parsed_uri = urllib.parse.urlparse(manifest_json_uri) - download_file_result = utils.download_file(parsed_uri, cache_file) + download_file_result = utils.download_file(parsed_uri, cache_file, True) if download_file_result != 0: return download_file_result @@ -96,7 +96,7 @@ def process_add_o3de_repo(file_name: str or pathlib.Path, cache_file = cache_folder / str(manifest_json_sha256.hexdigest() + '.json') if cache_file.is_file(): cache_file.unlink() - download_file_result = utils.download_file(parsed_uri, cache_file) + download_file_result = utils.download_file(parsed_uri, cache_file, True) if download_file_result != 0: return download_file_result @@ -165,7 +165,7 @@ def refresh_repo(repo_uri: str, repo_sha256 = hashlib.sha256(parsed_uri.geturl().encode()) cache_file = cache_folder / str(repo_sha256.hexdigest() + '.json') - download_file_result = utils.download_file(parsed_uri, cache_file) + download_file_result = utils.download_file(parsed_uri, cache_file, True) if download_file_result != 0: return download_file_result @@ -178,12 +178,7 @@ def refresh_repo(repo_uri: str, def refresh_repos() -> int: json_data = manifest.load_o3de_manifest() - - # clear the cache cache_folder = manifest.get_o3de_cache_folder() - shutil.rmtree(cache_folder) - cache_folder = manifest.get_o3de_cache_folder() # will recreate it - result = 0 # set will stop circular references diff --git a/scripts/o3de/o3de/utils.py b/scripts/o3de/o3de/utils.py index 56f9ae4bcd..71929af7b5 100755 --- a/scripts/o3de/o3de/utils.py +++ b/scripts/o3de/o3de/utils.py @@ -117,7 +117,7 @@ def backup_folder(folder: str or pathlib.Path) -> None: if backup_folder_name.is_dir(): renamed = True -def download_file(parsed_uri, download_path: pathlib.Path, force_overwrite, download_progress_callback = None) -> int: +def download_file(parsed_uri, download_path: pathlib.Path, force_overwrite: bool = False, download_progress_callback = None) -> int: """ :param parsed_uri: uniform resource identifier to zip file to download :param download_path: location path on disk to download file @@ -128,7 +128,7 @@ def download_file(parsed_uri, download_path: pathlib.Path, force_overwrite, down logger.warn(f'File already downloaded to {download_path}.') else: try: - shutil.rmtree(download_path) + os.unlink(download_path) except OSError: logger.error(f'Could not remove existing download path {download_path}.') return 1 @@ -162,7 +162,7 @@ def download_zip_file(parsed_uri, download_zip_path: pathlib.Path, download_prog :param parsed_uri: uniform resource identifier to zip file to download :param download_zip_path: path to output zip file """ - download_file_result = download_file(parsed_uri, download_zip_path, download_progress_callback) + download_file_result = download_file(parsed_uri, download_zip_path, True, download_progress_callback) if download_file_result != 0: return download_file_result