diff --git a/Code/Tools/ProjectManager/Resources/ProjectManager.qrc b/Code/Tools/ProjectManager/Resources/ProjectManager.qrc
index 8dd7e4c9b5..2260ae62b9 100644
--- a/Code/Tools/ProjectManager/Resources/ProjectManager.qrc
+++ b/Code/Tools/ProjectManager/Resources/ProjectManager.qrc
@@ -41,5 +41,6 @@
Download.svg
in_progress.gif
gem.svg
+ checkmark.svg
diff --git a/Code/Tools/ProjectManager/Resources/checkmark.svg b/Code/Tools/ProjectManager/Resources/checkmark.svg
new file mode 100644
index 0000000000..d612b35370
--- /dev/null
+++ b/Code/Tools/ProjectManager/Resources/checkmark.svg
@@ -0,0 +1,12 @@
+
+
diff --git a/Code/Tools/ProjectManager/Source/DownloadController.cpp b/Code/Tools/ProjectManager/Source/DownloadController.cpp
index 6326b2fc11..06e51b5116 100644
--- a/Code/Tools/ProjectManager/Source/DownloadController.cpp
+++ b/Code/Tools/ProjectManager/Source/DownloadController.cpp
@@ -18,7 +18,6 @@ namespace O3DE::ProjectManager
{
DownloadController::DownloadController(QWidget* parent)
: QObject()
- , m_lastProgress(0)
, m_parent(parent)
{
m_worker = new DownloadWorker();
@@ -69,10 +68,9 @@ namespace O3DE::ProjectManager
}
}
- void DownloadController::UpdateUIProgress(int progress)
+ void DownloadController::UpdateUIProgress(int bytesDownloaded, int totalBytes)
{
- m_lastProgress = progress;
- emit GemDownloadProgress(m_gemNames.front(), progress);
+ emit GemDownloadProgress(m_gemNames.front(), bytesDownloaded, totalBytes);
}
void DownloadController::HandleResults(const QString& result)
@@ -88,6 +86,7 @@ namespace O3DE::ProjectManager
QString gemName = m_gemNames.front();
m_gemNames.erase(m_gemNames.begin());
emit Done(gemName, succeeded);
+ emit GemDownloadRemoved(gemName);
if (!m_gemNames.empty())
{
diff --git a/Code/Tools/ProjectManager/Source/DownloadController.h b/Code/Tools/ProjectManager/Source/DownloadController.h
index 0bf0ae473c..211d9b48bc 100644
--- a/Code/Tools/ProjectManager/Source/DownloadController.h
+++ b/Code/Tools/ProjectManager/Source/DownloadController.h
@@ -53,7 +53,7 @@ namespace O3DE::ProjectManager
}
}
public slots:
- void UpdateUIProgress(int progress);
+ void UpdateUIProgress(int bytesDownloaded, int totalBytes);
void HandleResults(const QString& result);
signals:
@@ -61,14 +61,12 @@ namespace O3DE::ProjectManager
void Done(const QString& gemName, bool success = true);
void GemDownloadAdded(const QString& gemName);
void GemDownloadRemoved(const QString& gemName);
- void GemDownloadProgress(const QString& gemName, int percentage);
+ void GemDownloadProgress(const QString& gemName, int bytesDownloaded, int totalBytes);
private:
DownloadWorker* m_worker;
QThread m_workerThread;
QWidget* m_parent;
AZStd::vector m_gemNames;
-
- int m_lastProgress;
};
} // namespace O3DE::ProjectManager
diff --git a/Code/Tools/ProjectManager/Source/DownloadWorker.cpp b/Code/Tools/ProjectManager/Source/DownloadWorker.cpp
index 9bda1b34cc..169b276284 100644
--- a/Code/Tools/ProjectManager/Source/DownloadWorker.cpp
+++ b/Code/Tools/ProjectManager/Source/DownloadWorker.cpp
@@ -20,10 +20,9 @@ namespace O3DE::ProjectManager
void DownloadWorker::StartDownload()
{
- auto gemDownloadProgress = [=](int downloadProgress)
+ auto gemDownloadProgress = [=](int bytesDownloaded, int totalBytes)
{
- m_downloadProgress = downloadProgress;
- emit UpdateProgress(downloadProgress);
+ emit UpdateProgress(bytesDownloaded, totalBytes);
};
AZ::Outcome gemInfoResult = PythonBindingsInterface::Get()->DownloadGem(m_gemName, gemDownloadProgress);
if (gemInfoResult.IsSuccess())
diff --git a/Code/Tools/ProjectManager/Source/DownloadWorker.h b/Code/Tools/ProjectManager/Source/DownloadWorker.h
index 316a730a78..4084080ff7 100644
--- a/Code/Tools/ProjectManager/Source/DownloadWorker.h
+++ b/Code/Tools/ProjectManager/Source/DownloadWorker.h
@@ -31,12 +31,11 @@ namespace O3DE::ProjectManager
void SetGemToDownload(const QString& gemName, bool downloadNow = true);
signals:
- void UpdateProgress(int progress);
+ void UpdateProgress(int bytesDownloaded, int totalBytes);
void Done(QString result = "");
private:
QString m_gemName;
- int m_downloadProgress;
};
} // namespace O3DE::ProjectManager
diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.cpp
index bd0a6e9bc3..4e3296a91f 100644
--- a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.cpp
+++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.cpp
@@ -15,6 +15,8 @@
#include
#include
#include
+#include
+#include
namespace O3DE::ProjectManager
{
@@ -224,7 +226,6 @@ namespace O3DE::ProjectManager
connect(m_downloadController, &DownloadController::GemDownloadAdded, this, &CartOverlayWidget::GemDownloadAdded);
connect(m_downloadController, &DownloadController::GemDownloadRemoved, this, &CartOverlayWidget::GemDownloadRemoved);
connect(m_downloadController, &DownloadController::GemDownloadProgress, this, &CartOverlayWidget::GemDownloadProgress);
- connect(m_downloadController, &DownloadController::Done, this, &CartOverlayWidget::GemDownloadComplete);
}
void CartOverlayWidget::GemDownloadAdded(const QString& gemName)
@@ -288,29 +289,41 @@ namespace O3DE::ProjectManager
}
}
- void CartOverlayWidget::GemDownloadProgress(const QString& gemName, int percentage)
+ void CartOverlayWidget::GemDownloadProgress(const QString& gemName, int bytesDownloaded, int totalBytes)
{
QWidget* gemToUpdate = m_downloadingListWidget->findChild(gemName);
if (gemToUpdate)
{
QLabel* progressLabel = gemToUpdate->findChild("DownloadProgressLabel");
- if (progressLabel)
- {
- progressLabel->setText(QString("%1%").arg(percentage));
- }
QProgressBar* progressBar = gemToUpdate->findChild("DownloadProgressBar");
- if (progressBar)
+
+ // totalBytes can be 0 if the server does not return a content-length for the object
+ if (totalBytes != 0)
{
- progressBar->setValue(percentage);
+ int downloadPercentage = static_cast((bytesDownloaded / static_cast(totalBytes)) * 100);
+ if (progressLabel)
+ {
+ progressLabel->setText(QString("%1%").arg(downloadPercentage));
+ }
+ if (progressBar)
+ {
+ progressBar->setValue(downloadPercentage);
+ }
+ }
+ else
+ {
+ if (progressLabel)
+ {
+ progressLabel->setText(QLocale::system().formattedDataSize(bytesDownloaded));
+ }
+ if (progressBar)
+ {
+ progressBar->setRange(0, 0);
+ }
}
}
}
- void CartOverlayWidget::GemDownloadComplete(const QString& gemName, bool /*success*/)
- {
- GemDownloadRemoved(gemName); // update the list to remove the gem that has finished
- }
-
QVector CartOverlayWidget::GetTagsFromModelIndices(const QVector& gems) const
{
QVector tags;
@@ -389,7 +402,7 @@ namespace O3DE::ProjectManager
{
const QVector toBeAdded = m_gemModel->GatherGemsToBeAdded(/*includeDependencies=*/true);
const QVector toBeRemoved = m_gemModel->GatherGemsToBeRemoved(/*includeDependencies=*/true);
- if (toBeAdded.isEmpty() && toBeRemoved.isEmpty())
+ if (toBeAdded.isEmpty() && toBeRemoved.isEmpty() && m_downloadController->IsDownloadQueueEmpty())
{
return;
}
@@ -430,6 +443,7 @@ namespace O3DE::ProjectManager
GemCatalogHeaderWidget::GemCatalogHeaderWidget(GemModel* gemModel, GemSortFilterProxyModel* filterProxyModel, DownloadController* downloadController, QWidget* parent)
: QFrame(parent)
+ , m_downloadController(downloadController)
{
QHBoxLayout* hLayout = new QHBoxLayout();
hLayout->setAlignment(Qt::AlignLeft);
@@ -456,8 +470,25 @@ namespace O3DE::ProjectManager
hLayout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Expanding));
hLayout->addSpacerItem(new QSpacerItem(75, 0, QSizePolicy::Fixed));
- CartButton* cartButton = new CartButton(gemModel, downloadController);
- hLayout->addWidget(cartButton);
+ // spinner
+ m_downloadSpinnerMovie = new QMovie(":/in_progress.gif");
+ m_downloadSpinner = new QLabel(this);
+ m_downloadSpinner->setScaledContents(true);
+ m_downloadSpinner->setMaximumSize(16, 16);
+ m_downloadSpinner->setMovie(m_downloadSpinnerMovie);
+ hLayout->addWidget(m_downloadSpinner);
+ hLayout->addSpacing(8);
+
+ // downloading label
+ m_downloadLabel = new QLabel(tr("Downloading"));
+ hLayout->addWidget(m_downloadLabel);
+ m_downloadSpinner->hide();
+ m_downloadLabel->hide();
+
+ hLayout->addSpacing(16);
+
+ m_cartButton = new CartButton(gemModel, downloadController);
+ hLayout->addWidget(m_cartButton);
hLayout->addSpacing(16);
// Separating line
@@ -479,6 +510,27 @@ namespace O3DE::ProjectManager
gemMenuButton->setIcon(QIcon(":/menu.svg"));
gemMenuButton->setIconSize(QSize(36, 24));
hLayout->addWidget(gemMenuButton);
+
+ connect(m_downloadController, &DownloadController::GemDownloadAdded, this, &GemCatalogHeaderWidget::GemDownloadAdded);
+ connect(m_downloadController, &DownloadController::GemDownloadRemoved, this, &GemCatalogHeaderWidget::GemDownloadRemoved);
+ }
+
+ void GemCatalogHeaderWidget::GemDownloadAdded(const QString& /*gemName*/)
+ {
+ m_downloadSpinner->show();
+ m_downloadLabel->show();
+ m_downloadSpinnerMovie->start();
+ m_cartButton->ShowOverlay();
+ }
+
+ void GemCatalogHeaderWidget::GemDownloadRemoved(const QString& /*gemName*/)
+ {
+ if (m_downloadController->IsDownloadQueueEmpty())
+ {
+ m_downloadSpinner->hide();
+ m_downloadLabel->hide();
+ m_downloadSpinnerMovie->stop();
+ }
}
void GemCatalogHeaderWidget::ReinitForProject()
diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.h
index f3242d6db7..788e67a545 100644
--- a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.h
+++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.h
@@ -24,6 +24,7 @@ QT_FORWARD_DECLARE_CLASS(QVBoxLayout)
QT_FORWARD_DECLARE_CLASS(QHBoxLayout)
QT_FORWARD_DECLARE_CLASS(QHideEvent)
QT_FORWARD_DECLARE_CLASS(QMoveEvent)
+QT_FORWARD_DECLARE_CLASS(QMovie)
namespace O3DE::ProjectManager
{
@@ -39,8 +40,7 @@ namespace O3DE::ProjectManager
public slots:
void GemDownloadAdded(const QString& gemName);
void GemDownloadRemoved(const QString& gemName);
- void GemDownloadProgress(const QString& gemName, int percentage);
- void GemDownloadComplete(const QString& gemName, bool success);
+ void GemDownloadProgress(const QString& gemName, int bytesDownloaded, int totalBytes);
private:
QVector GetTagsFromModelIndices(const QVector& gems) const;
@@ -96,6 +96,10 @@ namespace O3DE::ProjectManager
void ReinitForProject();
+ public slots:
+ void GemDownloadAdded(const QString& gemName);
+ void GemDownloadRemoved(const QString& gemName);
+
signals:
void AddGem();
void OpenGemsRepo();
@@ -103,5 +107,10 @@ namespace O3DE::ProjectManager
private:
AzQtComponents::SearchLineEdit* m_filterLineEdit = nullptr;
inline constexpr static int s_height = 60;
+ DownloadController* m_downloadController = nullptr;
+ QLabel* m_downloadSpinner = nullptr;
+ QLabel* m_downloadLabel = nullptr;
+ QMovie* m_downloadSpinnerMovie = nullptr;
+ CartButton* m_cartButton = nullptr;
};
} // namespace O3DE::ProjectManager
diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp
index 732f4813a2..0c342e12b3 100644
--- a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp
+++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogScreen.cpp
@@ -483,7 +483,7 @@ namespace O3DE::ProjectManager
QModelIndex index = m_gemModel->FindIndexByNameString(gemName);
if (index.isValid())
{
- m_gemModel->setData(index, GemInfo::Downloaded, GemModel::RoleDownloadStatus);
+ m_proxyModel->setData(m_proxyModel->mapFromSource(index), GemInfo::DownloadSuccessful, GemModel::RoleDownloadStatus);
m_gemModel->setData(index, gemInfo.m_path, GemModel::RolePath);
m_gemModel->setData(index, gemInfo.m_path, GemModel::RoleDirectoryLink);
}
@@ -493,6 +493,14 @@ namespace O3DE::ProjectManager
}
}
}
+ else
+ {
+ QModelIndex index = m_gemModel->FindIndexByNameString(gemName);
+ if (index.isValid())
+ {
+ m_proxyModel->setData(m_proxyModel->mapFromSource(index), GemInfo::DownloadFailed, GemModel::RoleDownloadStatus);
+ }
+ }
}
ProjectManagerScreen GemCatalogScreen::GetScreenEnum()
diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.h
index 12cce5a4ea..abd0062749 100644
--- a/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.h
+++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemInfo.h
@@ -57,7 +57,9 @@ namespace O3DE::ProjectManager
UnknownDownloadStatus = -1,
NotDownloaded,
Downloading,
- Downloaded,
+ DownloadSuccessful,
+ DownloadFailed,
+ Downloaded
};
static QString GetDownloadStatusString(DownloadStatus status);
diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemItemDelegate.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemItemDelegate.cpp
index e15c4b3b39..dd94e42fc4 100644
--- a/Code/Tools/ProjectManager/Source/GemCatalog/GemItemDelegate.cpp
+++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemItemDelegate.cpp
@@ -37,6 +37,8 @@ namespace O3DE::ProjectManager
SetStatusIcon(m_notDownloadedPixmap, ":/Download.svg");
SetStatusIcon(m_unknownStatusPixmap, ":/X.svg");
+ SetStatusIcon(m_downloadSuccessfulPixmap, ":/checkmark.svg");
+ SetStatusIcon(m_downloadFailedPixmap, ":/Warning.svg");
m_downloadingMovie = new QMovie(":/in_progress.gif");
}
@@ -480,6 +482,14 @@ namespace O3DE::ProjectManager
currentFrame = currentFrame.scaled(s_statusIconSize, s_statusIconSize);
statusPixmap = ¤tFrame;
}
+ else if (downloadStatus == GemInfo::DownloadStatus::DownloadSuccessful)
+ {
+ statusPixmap = &m_downloadSuccessfulPixmap;
+ }
+ else if (downloadStatus == GemInfo::DownloadStatus::DownloadFailed)
+ {
+ statusPixmap = &m_downloadFailedPixmap;
+ }
else if (downloadStatus == GemInfo::DownloadStatus::NotDownloaded)
{
statusPixmap = &m_notDownloadedPixmap;
diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemItemDelegate.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemItemDelegate.h
index c013be0d9e..107de6de15 100644
--- a/Code/Tools/ProjectManager/Source/GemCatalog/GemItemDelegate.h
+++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemItemDelegate.h
@@ -97,6 +97,8 @@ namespace O3DE::ProjectManager
QPixmap m_unknownStatusPixmap;
QPixmap m_notDownloadedPixmap;
+ QPixmap m_downloadSuccessfulPixmap;
+ QPixmap m_downloadFailedPixmap;
QMovie* m_downloadingMovie = nullptr;
};
} // namespace O3DE::ProjectManager
diff --git a/Code/Tools/ProjectManager/Source/PythonBindings.cpp b/Code/Tools/ProjectManager/Source/PythonBindings.cpp
index 021066e1c7..b1cbc1a2a3 100644
--- a/Code/Tools/ProjectManager/Source/PythonBindings.cpp
+++ b/Code/Tools/ProjectManager/Source/PythonBindings.cpp
@@ -1166,7 +1166,7 @@ namespace O3DE::ProjectManager
return AZ::Success(AZStd::move(gemRepos));
}
- AZ::Outcome PythonBindings::DownloadGem(const QString& gemName, std::function gemProgressCallback)
+ AZ::Outcome PythonBindings::DownloadGem(const QString& gemName, std::function gemProgressCallback)
{
// This process is currently limited to download a single gem at a time.
bool downloadSucceeded = false;
@@ -1181,9 +1181,9 @@ namespace O3DE::ProjectManager
false, // skip auto register
false, // force
pybind11::cpp_function(
- [this, gemProgressCallback](int progress)
+ [this, gemProgressCallback](int bytesDownloaded, int totalBytes)
{
- gemProgressCallback(progress);
+ gemProgressCallback(bytesDownloaded, totalBytes);
return m_requestCancelDownload;
}) // Callback for download progress and cancelling
diff --git a/Code/Tools/ProjectManager/Source/PythonBindings.h b/Code/Tools/ProjectManager/Source/PythonBindings.h
index 4375d56d02..4aae1c2e95 100644
--- a/Code/Tools/ProjectManager/Source/PythonBindings.h
+++ b/Code/Tools/ProjectManager/Source/PythonBindings.h
@@ -64,7 +64,7 @@ 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;
+ AZ::Outcome DownloadGem(const QString& gemName, std::function gemProgressCallback) override;
void CancelDownload() override;
AZ::Outcome, AZStd::string> GetAllGemRepoGemsInfos() override;
diff --git a/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h b/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h
index 1134804f1f..b5ff86af2d 100644
--- a/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h
+++ b/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h
@@ -215,7 +215,7 @@ namespace O3DE::ProjectManager
* @param gemProgressCallback a callback function that is called with an int percentage download value
* @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) = 0;
/**
* Cancels the current download.
diff --git a/scripts/o3de/o3de/download.py b/scripts/o3de/o3de/download.py
index 9e576d64b5..9b7c8cc782 100644
--- a/scripts/o3de/o3de/download.py
+++ b/scripts/o3de/o3de/download.py
@@ -105,7 +105,7 @@ def download_o3de_object(object_name: str, default_folder_name: str, dest_path:
origin_uri = downloadable_object_data['originuri']
parsed_uri = urllib.parse.urlparse(origin_uri)
- download_zip_result = utils.download_zip_file(parsed_uri, download_zip_path, download_progress_callback)
+ download_zip_result = utils.download_zip_file(parsed_uri, download_zip_path, force_overwrite, download_progress_callback)
if download_zip_result != 0:
return download_zip_result
diff --git a/scripts/o3de/o3de/utils.py b/scripts/o3de/o3de/utils.py
index 9872c0779b..88f84ae75e 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
@@ -140,9 +140,9 @@ def download_file(parsed_uri, download_path: pathlib.Path, force_overwrite, down
download_file_size = s.headers['content-length']
except KeyError:
pass
- def download_progress(blocks):
- if download_progress_callback and download_file_size:
- return download_progress_callback(int(blocks/int(download_file_size) * 100))
+ def download_progress(downloaded_bytes):
+ if download_progress_callback:
+ return download_progress_callback(int(downloaded_bytes), int(download_file_size))
return False
with download_path.open('wb') as f:
download_cancelled = copyfileobj(s, f, download_progress)
@@ -157,12 +157,12 @@ def download_file(parsed_uri, download_path: pathlib.Path, force_overwrite, down
return 0
-def download_zip_file(parsed_uri, download_zip_path: pathlib.Path, download_progress_callback = None) -> int:
+def download_zip_file(parsed_uri, download_zip_path: pathlib.Path, force_overwrite: bool, download_progress_callback = None) -> int:
"""
: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, force_overwrite, download_progress_callback)
if download_file_result != 0:
return download_file_result