diff --git a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoItemDelegate.cpp b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoItemDelegate.cpp index 8589337b87..576a4aff6e 100644 --- a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoItemDelegate.cpp +++ b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoItemDelegate.cpp @@ -108,7 +108,7 @@ namespace O3DE::ProjectManager // Draw refresh button painter->drawPixmap( - repoUpdatedDateRect.left() + repoUpdatedDateRect.width() + s_refreshIconSpacing, + repoUpdatedDateRect.left() + s_updatedMaxWidth + s_refreshIconSpacing, contentRect.center().y() - s_refreshIconSize / 3, // Dividing size by 3 centers much better m_refreshIcon); @@ -150,6 +150,11 @@ namespace O3DE::ProjectManager emit RemoveRepo(modelIndex); return true; } + else if (keyEvent->key() == Qt::Key_R || keyEvent->key() == Qt::Key_F5) + { + emit RefreshRepo(modelIndex); + return true; + } } if (event->type() == QEvent::MouseButtonPress) @@ -160,6 +165,7 @@ namespace O3DE::ProjectManager CalcRects(option, fullRect, itemRect, contentRect); const QRect buttonRect = CalcButtonRect(contentRect); const QRect deleteButtonRect = CalcDeleteButtonRect(contentRect); + const QRect refreshButtonRect = CalcRefreshButtonRect(contentRect, buttonRect); if (buttonRect.contains(mouseEvent->pos())) { @@ -172,6 +178,11 @@ namespace O3DE::ProjectManager emit RemoveRepo(modelIndex); return true; } + else if (refreshButtonRect.contains(mouseEvent->pos())) + { + emit RefreshRepo(modelIndex); + return true; + } } return QStyledItemDelegate::editorEvent(event, model, option, modelIndex); @@ -231,6 +242,13 @@ namespace O3DE::ProjectManager return QRect(topLeft, QSize(s_iconSize, s_iconSize)); } + QRect GemRepoItemDelegate::CalcRefreshButtonRect(const QRect& contentRect, const QRect& buttonRect) const + { + const int topLeftX = buttonRect.left() + s_buttonWidth + s_buttonSpacing + s_nameMaxWidth + s_creatorMaxWidth + s_updatedMaxWidth + s_contentSpacing * 2 + s_refreshIconSpacing; + const QPoint topLeft = QPoint(topLeftX, contentRect.center().y() - s_refreshIconSize / 3); + return QRect(topLeft, QSize(s_refreshIconSize, s_refreshIconSize)); + } + void GemRepoItemDelegate::DrawEditButtons(QPainter* painter, const QRect& contentRect) const { painter->drawPixmap(contentRect.right() - s_iconSize, contentRect.center().y() - s_iconSize / 2, m_deleteIcon); diff --git a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoItemDelegate.h b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoItemDelegate.h index 560616bf9d..69f2eb582d 100644 --- a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoItemDelegate.h +++ b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoItemDelegate.h @@ -68,12 +68,14 @@ namespace O3DE::ProjectManager signals: void RemoveRepo(const QModelIndex& modelIndex); + void RefreshRepo(const QModelIndex& modelIndex); protected: void CalcRects(const QStyleOptionViewItem& option, QRect& outFullRect, QRect& outItemRect, QRect& outContentRect) const; QRect GetTextRect(QFont& font, const QString& text, qreal fontSize) const; QRect CalcButtonRect(const QRect& contentRect) const; QRect CalcDeleteButtonRect(const QRect& contentRect) const; + QRect CalcRefreshButtonRect(const QRect& contentRect, const QRect& buttonRect) const; void DrawButton(QPainter* painter, const QRect& contentRect, const QModelIndex& modelIndex) const; void DrawEditButtons(QPainter* painter, const QRect& contentRect) const; diff --git a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoListView.cpp b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoListView.cpp index 53d6478954..9adf3e6e3f 100644 --- a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoListView.cpp +++ b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoListView.cpp @@ -24,6 +24,7 @@ namespace O3DE::ProjectManager GemRepoItemDelegate* itemDelegate = new GemRepoItemDelegate(model, this); connect(itemDelegate, &GemRepoItemDelegate::RemoveRepo, this, &GemRepoListView::RemoveRepo); + connect(itemDelegate, &GemRepoItemDelegate::RefreshRepo, this, &GemRepoListView::RefreshRepo); setItemDelegate(itemDelegate); } } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoListView.h b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoListView.h index be2af4b5a6..50bcf8daa6 100644 --- a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoListView.h +++ b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoListView.h @@ -28,5 +28,6 @@ namespace O3DE::ProjectManager signals: void RemoveRepo(const QModelIndex& modelIndex); + void RefreshRepo(const QModelIndex& modelIndex); }; } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoScreen.cpp b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoScreen.cpp index 82fa13505a..490b509474 100644 --- a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoScreen.cpp +++ b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoScreen.cpp @@ -126,6 +126,36 @@ namespace O3DE::ProjectManager } } + void GemRepoScreen::HandleRefreshAllButton() + { + bool refreshResult = PythonBindingsInterface::Get()->RefreshAllGemRepos(); + Reinit(); + + if (!refreshResult) + { + QMessageBox::critical( + this, tr("Operation failed"), QString("Some repos failed to refresh.")); + } + } + + void GemRepoScreen::HandleRefreshRepoButton(const QModelIndex& modelIndex) + { + const QString repoUri = m_gemRepoModel->GetRepoUri(modelIndex); + + AZ::Outcome refreshResult = PythonBindingsInterface::Get()->RefreshGemRepo(repoUri); + if (refreshResult.IsSuccess()) + { + Reinit(); + } + else + { + QMessageBox::critical( + this, tr("Operation failed"), + QString("Failed to refresh gem repo %1
Error:
%2") + .arg(m_gemRepoModel->GetName(modelIndex), refreshResult.GetError().c_str())); + } + } + void GemRepoScreen::FillModel() { AZ::Outcome, AZStd::string> allGemRepoInfosResult = PythonBindingsInterface::Get()->GetAllGemRepoInfos(); @@ -237,6 +267,8 @@ namespace O3DE::ProjectManager m_AllUpdateButton->setObjectName("gemRepoHeaderRefreshButton"); topMiddleHLayout->addWidget(m_AllUpdateButton); + connect(m_AllUpdateButton, &QPushButton::clicked, this, &GemRepoScreen::HandleRefreshAllButton); + topMiddleHLayout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Minimum)); QPushButton* addRepoButton = new QPushButton(tr("Add Repository"), this); @@ -280,6 +312,7 @@ namespace O3DE::ProjectManager middleVLayout->addWidget(m_gemRepoListView); connect(m_gemRepoListView, &GemRepoListView::RemoveRepo, this, &GemRepoScreen::HandleRemoveRepoButton); + connect(m_gemRepoListView, &GemRepoListView::RefreshRepo, this, &GemRepoScreen::HandleRefreshRepoButton); hLayout->addLayout(middleVLayout); diff --git a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoScreen.h b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoScreen.h index 3ac89f6813..46a733362a 100644 --- a/Code/Tools/ProjectManager/Source/GemRepo/GemRepoScreen.h +++ b/Code/Tools/ProjectManager/Source/GemRepo/GemRepoScreen.h @@ -40,6 +40,8 @@ namespace O3DE::ProjectManager public slots: void HandleAddRepoButton(); void HandleRemoveRepoButton(const QModelIndex& modelIndex); + void HandleRefreshAllButton(); + void HandleRefreshRepoButton(const QModelIndex& modelIndex); private: void FillModel(); diff --git a/Code/Tools/ProjectManager/Source/PythonBindings.cpp b/Code/Tools/ProjectManager/Source/PythonBindings.cpp index 003e3dbd3c..740393fec0 100644 --- a/Code/Tools/ProjectManager/Source/PythonBindings.cpp +++ b/Code/Tools/ProjectManager/Source/PythonBindings.cpp @@ -983,6 +983,46 @@ namespace O3DE::ProjectManager } } + AZ::Outcome PythonBindings::RefreshGemRepo(const QString& repoUri) + { + bool refreshResult = false; + AZ::Outcome result = ExecuteWithLockErrorHandling( + [&] + { + auto pyUri = QString_To_Py_String(repoUri); + auto pythonRefreshResult = m_repo.attr("refresh_repo")(pyUri); + + // Returns an exit code so boolify it then invert result + refreshResult = !pythonRefreshResult.cast(); + }); + + if (!result.IsSuccess()) + { + return result; + } + else if (!refreshResult) + { + return AZ::Failure("Failed to refresh repo."); + } + + return AZ::Success(); + } + + bool PythonBindings::RefreshAllGemRepos() + { + bool refreshResult = false; + bool result = ExecuteWithLock( + [&] + { + auto pythonRefreshResult = m_repo.attr("refresh_repos")(); + + // Returns an exit code so boolify it then invert result + refreshResult = !pythonRefreshResult.cast(); + }); + + return result && refreshResult; + } + bool PythonBindings::AddGemRepo(const QString& repoUri) { bool registrationResult = false; diff --git a/Code/Tools/ProjectManager/Source/PythonBindings.h b/Code/Tools/ProjectManager/Source/PythonBindings.h index fdc692d3f4..4375d56d02 100644 --- a/Code/Tools/ProjectManager/Source/PythonBindings.h +++ b/Code/Tools/ProjectManager/Source/PythonBindings.h @@ -59,6 +59,8 @@ namespace O3DE::ProjectManager AZ::Outcome> GetProjectTemplates(const QString& projectPath = {}) override; // Gem Repos + AZ::Outcome RefreshGemRepo(const QString& repoUri) override; + bool RefreshAllGemRepos() override; bool AddGemRepo(const QString& repoUri) override; bool RemoveGemRepo(const QString& repoUri) override; AZ::Outcome, AZStd::string> GetAllGemRepoInfos() override; diff --git a/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h b/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h index 1670ed7411..1134804f1f 100644 --- a/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h +++ b/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h @@ -176,6 +176,19 @@ namespace O3DE::ProjectManager // Gem Repos + /** + * Refresh gem repo in the current engine. + * @param repoUri the absolute filesystem path or url to the gem repo. + * @return An outcome with the success flag as well as an error message in case of a failure. + */ + virtual AZ::Outcome RefreshGemRepo(const QString& repoUri) = 0; + + /** + * Refresh all gem repos in the current engine. + * @return true on success, false on failure. + */ + virtual bool RefreshAllGemRepos() = 0; + /** * Registers this gem repo with the current engine. * @param repoUri the absolute filesystem path or url to the gem repo. diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/NewDepthOfFieldComposite.shader b/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/NewDepthOfFieldComposite.shader index f17837b330..42130d9ba8 100644 --- a/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/NewDepthOfFieldComposite.shader +++ b/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/NewDepthOfFieldComposite.shader @@ -1,5 +1,5 @@ { - "Source" : "NewDepthOfFieldComposite", + "Source" : "NewDepthOfFieldComposite.azsl", "DepthStencilState" : { diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/NewDepthOfFieldDownsample.shader b/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/NewDepthOfFieldDownsample.shader index 00aa89e6d8..8a9fe62fd7 100644 --- a/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/NewDepthOfFieldDownsample.shader +++ b/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/NewDepthOfFieldDownsample.shader @@ -1,5 +1,5 @@ { - "Source" : "NewDepthOfFieldDownsample", + "Source" : "NewDepthOfFieldDownsample.azsl", "DepthStencilState" : { "Depth" : { "Enable" : false } diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/NewDepthOfFieldFilterLarge.shader b/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/NewDepthOfFieldFilterLarge.shader index 8f6faedb76..f11ac01e5c 100644 --- a/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/NewDepthOfFieldFilterLarge.shader +++ b/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/NewDepthOfFieldFilterLarge.shader @@ -1,5 +1,5 @@ { - "Source" : "NewDepthOfFieldFilterLarge", + "Source" : "NewDepthOfFieldFilterLarge.azsl", "DepthStencilState" : { "Depth" : { "Enable" : false } diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/NewDepthOfFieldFilterSmall.shader b/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/NewDepthOfFieldFilterSmall.shader index d05e09be8f..17387a4390 100644 --- a/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/NewDepthOfFieldFilterSmall.shader +++ b/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/NewDepthOfFieldFilterSmall.shader @@ -1,5 +1,5 @@ { - "Source" : "NewDepthOfFieldFilterSmall", + "Source" : "NewDepthOfFieldFilterSmall.azsl", "DepthStencilState" : { "Depth" : { "Enable" : false } diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/NewDepthOfFieldTile3x3.shader b/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/NewDepthOfFieldTile3x3.shader index f7d9cc160e..6ee990c5a7 100644 --- a/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/NewDepthOfFieldTile3x3.shader +++ b/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/NewDepthOfFieldTile3x3.shader @@ -1,5 +1,5 @@ { - "Source" : "NewDepthOfFieldTile3x3", + "Source" : "NewDepthOfFieldTile3x3.azsl", "DepthStencilState" : { "Depth" : { "Enable" : false } diff --git a/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/NewDepthOfFieldTileReduce.shader b/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/NewDepthOfFieldTileReduce.shader index f2da5305e5..06313cdbf2 100644 --- a/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/NewDepthOfFieldTileReduce.shader +++ b/Gems/Atom/Feature/Common/Assets/Shaders/PostProcessing/NewDepthOfFieldTileReduce.shader @@ -1,5 +1,5 @@ { - "Source" : "NewDepthOfFieldTileReduce", + "Source" : "NewDepthOfFieldTileReduce.azsl", "ProgramSettings" : { diff --git a/scripts/o3de/o3de/repo.py b/scripts/o3de/o3de/repo.py index 6b4441d996..9c26658a30 100644 --- a/scripts/o3de/o3de/repo.py +++ b/scripts/o3de/o3de/repo.py @@ -13,7 +13,7 @@ import shutil import urllib.parse import urllib.request import hashlib - +from datetime import datetime from o3de import manifest, utils, validation logger = logging.getLogger() @@ -27,6 +27,7 @@ def process_add_o3de_repo(file_name: str or pathlib.Path, return 1 cache_folder = manifest.get_o3de_cache_folder() + repo_data = {} with file_name.open('r') as f: try: repo_data = json.load(f) @@ -34,61 +35,72 @@ def process_add_o3de_repo(file_name: str or pathlib.Path, logger.error(f'{file_name} failed to load: {str(e)}') return 1 - # A repo may not contain all types of object. - manifest_download_list = [] - try: - manifest_download_list.append((repo_data['engines'], 'engine.json')) - except KeyError: - pass + with file_name.open('w') as f: try: - manifest_download_list.append((repo_data['projects'], 'project.json')) - except KeyError: - pass - try: - manifest_download_list.append((repo_data['gems'], 'gem.json')) - except KeyError: - pass - try: - manifest_download_list.append((repo_data['templates'], 'template.json')) - except KeyError: - pass - try: - manifest_download_list.append((repo_data['restricted'], 'restricted.json')) - except KeyError: - pass + time_now = datetime.now() + # Convert to lower case because AM/PM is capitalized + time_str = time_now.strftime('%d/%m/%Y %I:%M%p').lower() + repo_data.update({'last_updated': time_str}) + f.write(json.dumps(repo_data, indent=4) + '\n') + except Exception as e: + logger.error(f'{file_name} failed to save: {str(e)}') + return 1 + + # A repo may not contain all types of object. + manifest_download_list = [] + try: + manifest_download_list.append((repo_data['engines'], 'engine.json')) + except KeyError: + pass + try: + manifest_download_list.append((repo_data['projects'], 'project.json')) + except KeyError: + pass + try: + manifest_download_list.append((repo_data['gems'], 'gem.json')) + except KeyError: + pass + try: + manifest_download_list.append((repo_data['templates'], 'template.json')) + except KeyError: + pass + try: + manifest_download_list.append((repo_data['restricted'], 'restricted.json')) + except KeyError: + pass + + for o3de_object_uris, manifest_json in manifest_download_list: + for o3de_object_uri in o3de_object_uris: + 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(): + parsed_uri = urllib.parse.urlparse(manifest_json_uri) + download_file_result = utils.download_file(parsed_uri, cache_file) + if download_file_result != 0: + return download_file_result - for o3de_object_uris, manifest_json in manifest_download_list: + # Having a repo is also optional + repo_list = [] + try: + repo_list.append(repo_data['repos']) + except KeyError: + pass + + for repo in repo_list: + if repo not in repo_set: + repo_set.add(repo) for o3de_object_uri in o3de_object_uris: - manifest_json_uri = f'{o3de_object_uri}/{manifest_json}' - manifest_json_sha256 = hashlib.sha256(manifest_json_uri.encode()) + parsed_uri = urllib.parse.urlparse(f'{repo}/repo.json') + manifest_json_sha256 = hashlib.sha256(parsed_uri.geturl().encode()) cache_file = cache_folder / str(manifest_json_sha256.hexdigest() + '.json') - if not cache_file.is_file(): - parsed_uri = urllib.parse.urlparse(manifest_json_uri) - download_file_result = utils.download_file(parsed_uri, cache_file) - if download_file_result != 0: - return download_file_result - - # Having a repo is also optional - repo_list = [] - try: - repo_list.append(repo_data['repos']) - except KeyError: - pass + if cache_file.is_file(): + cache_file.unlink() + download_file_result = utils.download_file(parsed_uri, cache_file) + if download_file_result != 0: + return download_file_result - for repo in repo_list: - if repo not in repo_set: - repo_set.add(repo) - for o3de_object_uri in o3de_object_uris: - parsed_uri = urllib.parse.urlparse(f'{repo}/repo.json') - manifest_json_sha256 = hashlib.sha256(parsed_uri.geturl().encode()) - 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) - if download_file_result != 0: - return download_file_result - - return process_add_o3de_repo(parsed_uri.geturl(), repo_set) + return process_add_o3de_repo(parsed_uri.geturl(), repo_set) return 0 @@ -141,6 +153,29 @@ def get_gem_json_paths_from_all_cached_repos() -> set: return gem_set +def refresh_repo(repo_uri: str, + cache_folder: str = None, + repo_set: set = None) -> int: + if not cache_folder: + cache_folder = manifest.get_o3de_cache_folder() + if not repo_set: + repo_set = set() + + parsed_uri = urllib.parse.urlparse(f'{repo_uri}/repo.json') + 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) + if download_file_result != 0: + return download_file_result + + if not validation.valid_o3de_repo_json(cache_file): + logger.error(f'Repo json {repo_uri} is not valid.') + cache_file.unlink() + return 1 + + return process_add_o3de_repo(cache_file, repo_set) + def refresh_repos() -> int: json_data = manifest.load_o3de_manifest() @@ -158,22 +193,9 @@ def refresh_repos() -> int: if repo_uri not in repo_set: repo_set.add(repo_uri) - parsed_uri = urllib.parse.urlparse(f'{repo_uri}/repo.json') - repo_sha256 = hashlib.sha256(parsed_uri.geturl().encode()) - cache_file = cache_folder / str(repo_sha256.hexdigest() + '.json') - if not cache_file.is_file(): - download_file_result = utils.download_file(parsed_uri, cache_file) - if download_file_result != 0: - return download_file_result - - if not validation.valid_o3de_repo_json(cache_file): - logger.error(f'Repo json {repo_uri} is not valid.') - cache_file.unlink() - return 1 - - last_failure = process_add_o3de_repo(cache_file, repo_set) - if last_failure: - result = last_failure + last_failure = refresh_repo(repo_uri, cache_folder, repo_set) + if last_failure: + result = last_failure return result