From e3cfcd4cc7da7153b5980a03069b3ec774f2740f Mon Sep 17 00:00:00 2001 From: AMZN-Phil Date: Thu, 21 Oct 2021 16:07:07 -0700 Subject: [PATCH 1/5] Add the ability for Python to pass download progress to Project Manager and to cancel downloads. Signed-off-by: AMZN-Phil --- .../Source/DownloadController.cpp | 29 +++++++--- .../Source/DownloadController.h | 4 +- .../GemCatalog/GemCatalogHeaderWidget.cpp | 11 +++- .../GemCatalog/GemCatalogHeaderWidget.h | 1 + .../ProjectManager/Source/PythonBindings.cpp | 54 +++++++++++++++++++ .../ProjectManager/Source/PythonBindings.h | 1 + .../Source/PythonBindingsInterface.h | 11 ++++ scripts/o3de/o3de/utils.py | 42 ++++++++++++++- 8 files changed, 142 insertions(+), 11 deletions(-) diff --git a/Code/Tools/ProjectManager/Source/DownloadController.cpp b/Code/Tools/ProjectManager/Source/DownloadController.cpp index fa3fdb10d1..3ee800bba8 100644 --- a/Code/Tools/ProjectManager/Source/DownloadController.cpp +++ b/Code/Tools/ProjectManager/Source/DownloadController.cpp @@ -8,10 +8,15 @@ #include #include +#include + +#include #include + + namespace O3DE::ProjectManager { DownloadController::DownloadController(QWidget* parent) @@ -46,6 +51,24 @@ namespace O3DE::ProjectManager } } + void DownloadController::CancelGemDownload(const QString& gemName) + { + auto findResult = AZStd::find(m_gemNames.begin(), m_gemNames.end(), gemName); + + if (findResult != m_gemNames.end()) + { + if (findResult == m_gemNames.begin()) + { + // HandleResults will remove the gem upon cancelling + PythonBindingsInterface::Get()->CancelDownload(); + } + else + { + m_gemNames.erase(findResult); + } + } + } + void DownloadController::UpdateUIProgress(int progress) { m_lastProgress = progress; @@ -75,10 +98,4 @@ namespace O3DE::ProjectManager m_workerThread.wait(); } } - - void DownloadController::HandleCancel() - { - m_workerThread.quit(); - emit Done(false); - } } // namespace O3DE::ProjectManager diff --git a/Code/Tools/ProjectManager/Source/DownloadController.h b/Code/Tools/ProjectManager/Source/DownloadController.h index 608d9b1a2b..11ceaacddb 100644 --- a/Code/Tools/ProjectManager/Source/DownloadController.h +++ b/Code/Tools/ProjectManager/Source/DownloadController.h @@ -27,7 +27,8 @@ namespace O3DE::ProjectManager explicit DownloadController(QWidget* parent = nullptr); ~DownloadController(); - void AddGemDownload(const QString& m_gemName); + void AddGemDownload(const QString& gemName); + void CancelGemDownload(const QString& gemName); bool IsDownloadQueueEmpty() { @@ -54,7 +55,6 @@ namespace O3DE::ProjectManager public slots: void UpdateUIProgress(int progress); void HandleResults(const QString& result); - void HandleCancel(); signals: void StartGemDownload(const QString& gemName); diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.cpp index 79ff7624b7..875d5ada8c 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.cpp @@ -154,6 +154,11 @@ namespace O3DE::ProjectManager update(); } + void CartOverlayWidget::OnCancelDownloadActivated(const QString& gemName) + { + m_downloadController->CancelGemDownload(gemName); + } + void CartOverlayWidget::CreateDownloadSection() { QWidget* widget = new QWidget(); @@ -188,6 +193,8 @@ namespace O3DE::ProjectManager downloadingItemLayout->setAlignment(Qt::AlignTop); downloadingItemWidget->setLayout(downloadingItemLayout); + m_downloadController->AddGemDownload("TestGem"); + auto update = [=](int downloadProgress) { if (m_downloadController->IsDownloadQueueEmpty()) @@ -234,7 +241,9 @@ namespace O3DE::ProjectManager nameProgressLayout->addWidget(progress); QSpacerItem* spacer = new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Minimum); nameProgressLayout->addSpacerItem(spacer); - QLabel* cancelText = new QLabel(tr("Cancel")); + QLabel* cancelText = new QLabel(QString("Cancel").arg(downloadQueue[downloadingGemNumber])); + cancelText->setTextInteractionFlags(Qt::LinksAccessibleByMouse); + connect(cancelText, &QLabel::linkActivated, this, &CartOverlayWidget::OnCancelDownloadActivated); nameProgressLayout->addWidget(cancelText); downloadingItemLayout->addLayout(nameProgressLayout); QProgressBar* downloadProgessBar = new QProgressBar(); diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.h b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.h index 8e0eaa13ba..9f1e592d6e 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.h +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.h @@ -40,6 +40,7 @@ namespace O3DE::ProjectManager using GetTagIndicesCallback = AZStd::function()>; void CreateGemSection(const QString& singularTitle, const QString& pluralTitle, GetTagIndicesCallback getTagIndices); void CreateDownloadSection(); + void OnCancelDownloadActivated(const QString& link); QVBoxLayout* m_layout = nullptr; GemModel* m_gemModel = nullptr; diff --git a/Code/Tools/ProjectManager/Source/PythonBindings.cpp b/Code/Tools/ProjectManager/Source/PythonBindings.cpp index bc8773b0c8..8b716666de 100644 --- a/Code/Tools/ProjectManager/Source/PythonBindings.cpp +++ b/Code/Tools/ProjectManager/Source/PythonBindings.cpp @@ -223,6 +223,50 @@ namespace RedirectOutput } } // namespace RedirectOutput +namespace O3DEProjectManagerPy +{ + using DownloadProgressFunc = AZStd::function; + DownloadProgressFunc currentProgressCallback; + bool requestCancelDownload = false; + + static PyObject* CLICancelDownload(PyObject* /*self*/, PyObject* /*args*/) + { + if (requestCancelDownload) + { + return Py_True; + } + else + { + return Py_False; + } + } + + static PyObject* CLIDownloadProgress(PyObject* /*self*/, PyObject* args) + { + int progress_percentage = 0; + if (!PyArg_ParseTuple(args, "i", &progress_percentage)) + return NULL; + if (currentProgressCallback) + { + currentProgressCallback(progress_percentage); + } + + Py_RETURN_NONE; + } + + static PyMethodDef O3DEPMPyMethods[] = { { "download_progress", CLIDownloadProgress, METH_VARARGS, "Used to call back to the UI to inform of download progress." }, + { "request_cancel_download", CLICancelDownload, METH_NOARGS, "Returns that the UI is requesting that the current download be cancelled." }, + { NULL, NULL, 0, NULL } }; + + static PyModuleDef O3DEPMPyModule = { PyModuleDef_HEAD_INIT, "o3de_projectmanager", NULL, -1, O3DEPMPyMethods, NULL, NULL, NULL, NULL }; + + static PyObject* PyInit_O3DEPMPy(void) + { + return PyModule_Create(&O3DEPMPyModule); + } +} // namespace O3DEProjectManagerPy + + namespace O3DE::ProjectManager { PythonBindings::PythonBindings(const AZ::IO::PathView& enginePath) @@ -270,6 +314,7 @@ namespace O3DE::ProjectManager AZ_TracePrintf("python", "Py_GetProgramFullPath=%ls \n", Py_GetProgramFullPath()); PyImport_AppendInittab("azlmbr_redirect", RedirectOutput::PyInit_RedirectOutput); + PyImport_AppendInittab("o3de_projectmanager", &O3DEProjectManagerPy::PyInit_O3DEPMPy); try { @@ -1080,6 +1125,8 @@ namespace O3DE::ProjectManager AZ::Outcome PythonBindings::DownloadGem(const QString& gemName, std::function gemProgressCallback) { bool downloadSucceeded = false; + O3DEProjectManagerPy::currentProgressCallback = gemProgressCallback; + O3DEProjectManagerPy::requestCancelDownload = false; auto result = ExecuteWithLockErrorHandling( [&] { @@ -1091,6 +1138,8 @@ namespace O3DE::ProjectManager downloadSucceeded = (downloadResult.cast() == 0); }); + O3DEProjectManagerPy::currentProgressCallback = nullptr; + if (!result.IsSuccess()) { return result; @@ -1102,4 +1151,9 @@ namespace O3DE::ProjectManager return AZ::Success(); } + + void PythonBindings::CancelDownload() + { + O3DEProjectManagerPy::requestCancelDownload = true; + } } diff --git a/Code/Tools/ProjectManager/Source/PythonBindings.h b/Code/Tools/ProjectManager/Source/PythonBindings.h index 638ce6b1d4..386268b5b2 100644 --- a/Code/Tools/ProjectManager/Source/PythonBindings.h +++ b/Code/Tools/ProjectManager/Source/PythonBindings.h @@ -62,6 +62,7 @@ namespace O3DE::ProjectManager 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; private: AZ_DISABLE_COPY_MOVE(PythonBindings); diff --git a/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h b/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h index 19442540a4..928e9a8d48 100644 --- a/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h +++ b/Code/Tools/ProjectManager/Source/PythonBindingsInterface.h @@ -188,7 +188,18 @@ namespace O3DE::ProjectManager */ virtual AZ::Outcome, AZStd::string> GetAllGemRepoInfos() = 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 + * @return an outcome with a string error message on failure. + */ virtual AZ::Outcome DownloadGem(const QString& gemName, std::function gemProgressCallback) = 0; + + /** + * Cancels the current download. + */ + virtual void CancelDownload() = 0; }; using PythonBindingsInterface = AZ::Interface; diff --git a/scripts/o3de/o3de/utils.py b/scripts/o3de/o3de/utils.py index c5be21c60f..cce51bbd8f 100755 --- a/scripts/o3de/o3de/utils.py +++ b/scripts/o3de/o3de/utils.py @@ -10,15 +10,44 @@ This file contains utility functions """ import sys import uuid +import os import pathlib import shutil import urllib.request import logging import zipfile +try: + import o3de_projectmanager +except ImportError: + pass logger = logging.getLogger() logging.basicConfig() +COPY_BUFSIZE = 64 * 1024 + +def copyfileobj(fsrc, fdst, callback, length=0): + # This is functionally the same as the python shutil copyfileobj but + # allows for a callback to return the download progress in blocks and allows + # to early out to cancel the copy. + if not length: + length = COPY_BUFSIZE + + fsrc_read = fsrc.read + fdst_write = fdst.write + + copied = 0 + while True: + if o3de_projectmanager and o3de_projectmanager.request_cancel_download(): + return 1 + buf = fsrc_read(length) + if not buf: + break + fdst_write(buf) + copied += len(buf) + callback(copied) + return 0 + def validate_identifier(identifier: str) -> bool: """ Determine if the identifier supplied is valid. @@ -93,7 +122,6 @@ 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) -> int: """ :param parsed_uri: uniform resource identifier to zip file to download @@ -103,8 +131,18 @@ def download_file(parsed_uri, download_path: pathlib.Path) -> int: logger.warn(f'File already downloaded to {download_path}.') elif parsed_uri.scheme in ['http', 'https', 'ftp', 'ftps']: with urllib.request.urlopen(parsed_uri.geturl()) as s: + download_file_size = 0 + try: + download_file_size = s.headers['content-length'] + except KeyError: + pass + def download_progress(blocks): + if o3de_projectmanager and download_file_size: + o3de_projectmanager.download_progress(int(blocks/int(download_file_size) * 100)) with download_path.open('wb') as f: - shutil.copyfileobj(s, f) + download_cancelled = copyfileobj(s, f, download_progress) + if download_cancelled: + return 1 else: origin_file = pathlib.Path(parsed_uri.geturl()).resolve() if not origin_file.is_file(): From 5e6ecddbf98c59c491740f0953f9f7a9e227523d Mon Sep 17 00:00:00 2001 From: AMZN-Phil Date: Thu, 21 Oct 2021 16:11:28 -0700 Subject: [PATCH 2/5] Remove some test code and extra newlines Signed-off-by: AMZN-Phil --- Code/Tools/ProjectManager/Source/DownloadController.cpp | 3 --- .../Source/GemCatalog/GemCatalogHeaderWidget.cpp | 2 -- 2 files changed, 5 deletions(-) diff --git a/Code/Tools/ProjectManager/Source/DownloadController.cpp b/Code/Tools/ProjectManager/Source/DownloadController.cpp index 3ee800bba8..224b90299c 100644 --- a/Code/Tools/ProjectManager/Source/DownloadController.cpp +++ b/Code/Tools/ProjectManager/Source/DownloadController.cpp @@ -14,9 +14,6 @@ #include - - - namespace O3DE::ProjectManager { DownloadController::DownloadController(QWidget* parent) diff --git a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.cpp b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.cpp index 875d5ada8c..e62499d963 100644 --- a/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.cpp +++ b/Code/Tools/ProjectManager/Source/GemCatalog/GemCatalogHeaderWidget.cpp @@ -193,8 +193,6 @@ namespace O3DE::ProjectManager downloadingItemLayout->setAlignment(Qt::AlignTop); downloadingItemWidget->setLayout(downloadingItemLayout); - m_downloadController->AddGemDownload("TestGem"); - auto update = [=](int downloadProgress) { if (m_downloadController->IsDownloadQueueEmpty()) From 993baeac7bf667a1964f343732db8e8e75afd645 Mon Sep 17 00:00:00 2001 From: AMZN-Phil Date: Fri, 22 Oct 2021 09:37:04 -0700 Subject: [PATCH 3/5] Add a comment to inform that the process is limited to a single download Signed-off-by: AMZN-Phil --- Code/Tools/ProjectManager/Source/PythonBindings.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Code/Tools/ProjectManager/Source/PythonBindings.cpp b/Code/Tools/ProjectManager/Source/PythonBindings.cpp index 8b716666de..0e24d46815 100644 --- a/Code/Tools/ProjectManager/Source/PythonBindings.cpp +++ b/Code/Tools/ProjectManager/Source/PythonBindings.cpp @@ -1124,6 +1124,7 @@ namespace O3DE::ProjectManager 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; O3DEProjectManagerPy::currentProgressCallback = gemProgressCallback; O3DEProjectManagerPy::requestCancelDownload = false; From b609dbbbbbb654397a315dc8e28e38703f9853ac Mon Sep 17 00:00:00 2001 From: AMZN-Phil Date: Fri, 22 Oct 2021 14:07:32 -0700 Subject: [PATCH 4/5] Pass a callback instead of weak linking a module Signed-off-by: AMZN-Phil --- .../ProjectManager/Source/PythonBindings.cpp | 60 ++++--------------- .../ProjectManager/Source/PythonBindings.h | 2 + scripts/o3de/o3de/download.py | 30 ++++++---- scripts/o3de/o3de/utils.py | 20 +++---- 4 files changed, 39 insertions(+), 73 deletions(-) diff --git a/Code/Tools/ProjectManager/Source/PythonBindings.cpp b/Code/Tools/ProjectManager/Source/PythonBindings.cpp index 0e24d46815..269ddee445 100644 --- a/Code/Tools/ProjectManager/Source/PythonBindings.cpp +++ b/Code/Tools/ProjectManager/Source/PythonBindings.cpp @@ -223,49 +223,6 @@ namespace RedirectOutput } } // namespace RedirectOutput -namespace O3DEProjectManagerPy -{ - using DownloadProgressFunc = AZStd::function; - DownloadProgressFunc currentProgressCallback; - bool requestCancelDownload = false; - - static PyObject* CLICancelDownload(PyObject* /*self*/, PyObject* /*args*/) - { - if (requestCancelDownload) - { - return Py_True; - } - else - { - return Py_False; - } - } - - static PyObject* CLIDownloadProgress(PyObject* /*self*/, PyObject* args) - { - int progress_percentage = 0; - if (!PyArg_ParseTuple(args, "i", &progress_percentage)) - return NULL; - if (currentProgressCallback) - { - currentProgressCallback(progress_percentage); - } - - Py_RETURN_NONE; - } - - static PyMethodDef O3DEPMPyMethods[] = { { "download_progress", CLIDownloadProgress, METH_VARARGS, "Used to call back to the UI to inform of download progress." }, - { "request_cancel_download", CLICancelDownload, METH_NOARGS, "Returns that the UI is requesting that the current download be cancelled." }, - { NULL, NULL, 0, NULL } }; - - static PyModuleDef O3DEPMPyModule = { PyModuleDef_HEAD_INIT, "o3de_projectmanager", NULL, -1, O3DEPMPyMethods, NULL, NULL, NULL, NULL }; - - static PyObject* PyInit_O3DEPMPy(void) - { - return PyModule_Create(&O3DEPMPyModule); - } -} // namespace O3DEProjectManagerPy - namespace O3DE::ProjectManager { @@ -314,7 +271,6 @@ namespace O3DE::ProjectManager AZ_TracePrintf("python", "Py_GetProgramFullPath=%ls \n", Py_GetProgramFullPath()); PyImport_AppendInittab("azlmbr_redirect", RedirectOutput::PyInit_RedirectOutput); - PyImport_AppendInittab("o3de_projectmanager", &O3DEProjectManagerPy::PyInit_O3DEPMPy); try { @@ -1126,20 +1082,26 @@ namespace O3DE::ProjectManager { // This process is currently limited to download a single gem at a time. bool downloadSucceeded = false; - O3DEProjectManagerPy::currentProgressCallback = gemProgressCallback; - O3DEProjectManagerPy::requestCancelDownload = false; + + m_requestCancelDownload = false; auto result = ExecuteWithLockErrorHandling( [&] { auto downloadResult = m_download.attr("download_gem")( QString_To_Py_String(gemName), // gem name pybind11::none(), // destination path - false// skip auto register + false, // skip auto register + pybind11::cpp_function( + [this, gemProgressCallback](int progress) + { + gemProgressCallback(progress); + + return m_requestCancelDownload; + }) // Callback for download progress and cancelling ); downloadSucceeded = (downloadResult.cast() == 0); }); - O3DEProjectManagerPy::currentProgressCallback = nullptr; if (!result.IsSuccess()) { @@ -1155,6 +1117,6 @@ namespace O3DE::ProjectManager void PythonBindings::CancelDownload() { - O3DEProjectManagerPy::requestCancelDownload = true; + m_requestCancelDownload = true; } } diff --git a/Code/Tools/ProjectManager/Source/PythonBindings.h b/Code/Tools/ProjectManager/Source/PythonBindings.h index 386268b5b2..73970021a6 100644 --- a/Code/Tools/ProjectManager/Source/PythonBindings.h +++ b/Code/Tools/ProjectManager/Source/PythonBindings.h @@ -91,5 +91,7 @@ namespace O3DE::ProjectManager pybind11::handle m_editProjectProperties; pybind11::handle m_download; pybind11::handle m_pathlib; + + bool m_requestCancelDownload = false; }; } diff --git a/scripts/o3de/o3de/download.py b/scripts/o3de/o3de/download.py index f8868dd460..98f5d051a4 100644 --- a/scripts/o3de/o3de/download.py +++ b/scripts/o3de/o3de/download.py @@ -90,7 +90,8 @@ def get_downloadable(engine_name: str = None, def download_o3de_object(object_name: str, default_folder_name: str, dest_path: str or pathlib.Path, - object_type: str, downloadable_kwarg_key, skip_auto_register: bool) -> int: + object_type: str, downloadable_kwarg_key, skip_auto_register: bool, + download_progress_callback = None) -> int: download_path = manifest.get_o3de_cache_folder() / default_folder_name / object_name download_path.mkdir(parents=True, exist_ok=True) @@ -104,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_zip_result = utils.download_zip_file(parsed_uri, download_zip_path, download_progress_callback) if download_zip_result != 0: return download_zip_result @@ -147,33 +148,38 @@ def download_o3de_object(object_name: str, default_folder_name: str, dest_path: def download_engine(engine_name: str, dest_path: str or pathlib.Path, - skip_auto_register: bool) -> int: - return download_o3de_object(engine_name, 'engines', dest_path, 'engine', 'engine_name', skip_auto_register) + skip_auto_register: bool, + download_progress_callback = None) -> int: + return download_o3de_object(engine_name, 'engines', dest_path, 'engine', 'engine_name', skip_auto_register, download_progress_callback) def download_project(project_name: str, dest_path: str or pathlib.Path, - skip_auto_register: bool) -> int: - return download_o3de_object(project_name, 'projects', dest_path, 'project', 'project_name', skip_auto_register) + skip_auto_register: bool, + download_progress_callback = None) -> int: + return download_o3de_object(project_name, 'projects', dest_path, 'project', 'project_name', skip_auto_register, download_progress_callback) def download_gem(gem_name: str, dest_path: str or pathlib.Path, - skip_auto_register: bool) -> int: - return download_o3de_object(gem_name, 'gems', dest_path, 'gem', 'gem_name', skip_auto_register) + skip_auto_register: bool, + download_progress_callback = None) -> int: + return download_o3de_object(gem_name, 'gems', dest_path, 'gem', 'gem_name', skip_auto_register, download_progress_callback) def download_template(template_name: str, dest_path: str or pathlib.Path, - skip_auto_register: bool) -> int: - return download_o3de_object(template_name, 'templates', dest_path, 'template', 'template_name', skip_auto_register) + skip_auto_register: bool, + download_progress_callback = None) -> int: + return download_o3de_object(template_name, 'templates', dest_path, 'template', 'template_name', skip_auto_register, download_progress_callback) def download_restricted(restricted_name: str, dest_path: str or pathlib.Path, - skip_auto_register: bool) -> int: - return download_o3de_object(restricted_name, 'restricted', dest_path, 'restricted', 'restricted_name', skip_auto_register) + skip_auto_register: bool, + download_progress_callback = None) -> int: + return download_o3de_object(restricted_name, 'restricted', dest_path, 'restricted', 'restricted_name', skip_auto_register, download_progress_callback) def _run_download(args: argparse) -> int: diff --git a/scripts/o3de/o3de/utils.py b/scripts/o3de/o3de/utils.py index cce51bbd8f..c07793fd1b 100755 --- a/scripts/o3de/o3de/utils.py +++ b/scripts/o3de/o3de/utils.py @@ -16,10 +16,6 @@ import shutil import urllib.request import logging import zipfile -try: - import o3de_projectmanager -except ImportError: - pass logger = logging.getLogger() logging.basicConfig() @@ -38,14 +34,13 @@ def copyfileobj(fsrc, fdst, callback, length=0): copied = 0 while True: - if o3de_projectmanager and o3de_projectmanager.request_cancel_download(): - return 1 buf = fsrc_read(length) if not buf: break fdst_write(buf) copied += len(buf) - callback(copied) + if callback(copied): + return 1 return 0 def validate_identifier(identifier: str) -> bool: @@ -122,11 +117,12 @@ 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) -> int: +def download_file(parsed_uri, download_path: pathlib.Path, 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 """ + logger.warn(f'File about to downloaded to {download_path}.') if download_path.is_file(): logger.warn(f'File already downloaded to {download_path}.') elif parsed_uri.scheme in ['http', 'https', 'ftp', 'ftps']: @@ -137,8 +133,8 @@ def download_file(parsed_uri, download_path: pathlib.Path) -> int: except KeyError: pass def download_progress(blocks): - if o3de_projectmanager and download_file_size: - o3de_projectmanager.download_progress(int(blocks/int(download_file_size) * 100)) + if download_progress_callback and download_file_size: + return download_progress_callback(int(blocks/int(download_file_size) * 100)) with download_path.open('wb') as f: download_cancelled = copyfileobj(s, f, download_progress) if download_cancelled: @@ -152,12 +148,12 @@ def download_file(parsed_uri, download_path: pathlib.Path) -> int: return 0 -def download_zip_file(parsed_uri, download_zip_path: pathlib.Path) -> int: +def download_zip_file(parsed_uri, download_zip_path: pathlib.Path, 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_file_result = download_file(parsed_uri, download_zip_path, download_progress_callback) if download_file_result != 0: return download_file_result From e1b46b7e5c08bb934c82daf8ec909e8389b923bf Mon Sep 17 00:00:00 2001 From: AMZN-Phil Date: Fri, 22 Oct 2021 14:23:55 -0700 Subject: [PATCH 5/5] Add comment about additional callback function and be explicit on return value. Signed-off-by: AMZN-Phil --- scripts/o3de/o3de/utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/o3de/o3de/utils.py b/scripts/o3de/o3de/utils.py index c07793fd1b..8663502a3f 100755 --- a/scripts/o3de/o3de/utils.py +++ b/scripts/o3de/o3de/utils.py @@ -121,8 +121,8 @@ def download_file(parsed_uri, download_path: pathlib.Path, download_progress_cal """ :param parsed_uri: uniform resource identifier to zip file to download :param download_path: location path on disk to download file + :download_progress_callback: callback called with the download progress as a percentage, returns true to request to cancel the download """ - logger.warn(f'File about to downloaded to {download_path}.') if download_path.is_file(): logger.warn(f'File already downloaded to {download_path}.') elif parsed_uri.scheme in ['http', 'https', 'ftp', 'ftps']: @@ -135,6 +135,7 @@ def download_file(parsed_uri, download_path: pathlib.Path, download_progress_cal def download_progress(blocks): if download_progress_callback and download_file_size: return download_progress_callback(int(blocks/int(download_file_size) * 100)) + return False with download_path.open('wb') as f: download_cancelled = copyfileobj(s, f, download_progress) if download_cancelled: