Merge pull request #4911 from aws-lumberyard-dev/Prism/RefreshGemRepos
Gem Repos can be Refreshed through Project Manager
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -28,5 +28,6 @@ namespace O3DE::ProjectManager
|
||||
|
||||
signals:
|
||||
void RemoveRepo(const QModelIndex& modelIndex);
|
||||
void RefreshRepo(const QModelIndex& modelIndex);
|
||||
};
|
||||
} // namespace O3DE::ProjectManager
|
||||
|
||||
@@ -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<void, AZStd::string> refreshResult = PythonBindingsInterface::Get()->RefreshGemRepo(repoUri);
|
||||
if (refreshResult.IsSuccess())
|
||||
{
|
||||
Reinit();
|
||||
}
|
||||
else
|
||||
{
|
||||
QMessageBox::critical(
|
||||
this, tr("Operation failed"),
|
||||
QString("Failed to refresh gem repo %1<br>Error:<br>%2")
|
||||
.arg(m_gemRepoModel->GetName(modelIndex), refreshResult.GetError().c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
void GemRepoScreen::FillModel()
|
||||
{
|
||||
AZ::Outcome<QVector<GemRepoInfo>, 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);
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -303,6 +303,7 @@ namespace O3DE::ProjectManager
|
||||
m_disableGemProject = pybind11::module::import("o3de.disable_gem");
|
||||
m_editProjectProperties = pybind11::module::import("o3de.project_properties");
|
||||
m_download = pybind11::module::import("o3de.download");
|
||||
m_repo = pybind11::module::import("o3de.repo");
|
||||
m_pathlib = pybind11::module::import("pathlib");
|
||||
|
||||
// make sure the engine is registered
|
||||
@@ -982,6 +983,46 @@ namespace O3DE::ProjectManager
|
||||
}
|
||||
}
|
||||
|
||||
AZ::Outcome<void, AZStd::string> PythonBindings::RefreshGemRepo(const QString& repoUri)
|
||||
{
|
||||
bool refreshResult = false;
|
||||
AZ::Outcome<void, AZStd::string> 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<bool>();
|
||||
});
|
||||
|
||||
if (!result.IsSuccess())
|
||||
{
|
||||
return result;
|
||||
}
|
||||
else if (!refreshResult)
|
||||
{
|
||||
return AZ::Failure<AZStd::string>("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<bool>();
|
||||
});
|
||||
|
||||
return result && refreshResult;
|
||||
}
|
||||
|
||||
bool PythonBindings::AddGemRepo(const QString& repoUri)
|
||||
{
|
||||
bool registrationResult = false;
|
||||
|
||||
@@ -59,6 +59,8 @@ namespace O3DE::ProjectManager
|
||||
AZ::Outcome<QVector<ProjectTemplateInfo>> GetProjectTemplates(const QString& projectPath = {}) override;
|
||||
|
||||
// Gem Repos
|
||||
AZ::Outcome<void, AZStd::string> RefreshGemRepo(const QString& repoUri) override;
|
||||
bool RefreshAllGemRepos() override;
|
||||
bool AddGemRepo(const QString& repoUri) override;
|
||||
bool RemoveGemRepo(const QString& repoUri) override;
|
||||
AZ::Outcome<QVector<GemRepoInfo>, AZStd::string> GetAllGemRepoInfos() override;
|
||||
@@ -91,6 +93,7 @@ namespace O3DE::ProjectManager
|
||||
pybind11::handle m_disableGemProject;
|
||||
pybind11::handle m_editProjectProperties;
|
||||
pybind11::handle m_download;
|
||||
pybind11::handle m_repo;
|
||||
pybind11::handle m_pathlib;
|
||||
|
||||
bool m_requestCancelDownload = false;
|
||||
|
||||
@@ -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<void, AZStd::string> 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.
|
||||
|
||||
+89
-67
@@ -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 = []
|
||||
with file_name.open('w') as f:
|
||||
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
|
||||
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
|
||||
|
||||
for o3de_object_uris, manifest_json in manifest_download_list:
|
||||
# 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
|
||||
|
||||
# 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
|
||||
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
|
||||
|
||||
# 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:
|
||||
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
|
||||
|
||||
|
||||
@@ -130,6 +142,29 @@ def get_gem_json_paths_from_cached_repo(repo_uri: str) -> list:
|
||||
|
||||
return gem_list
|
||||
|
||||
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()
|
||||
|
||||
@@ -147,22 +182,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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user