From 2cbe72aea52a962402779e49eaeefa8a8a19ffb2 Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Sun, 9 Jan 2022 00:46:07 -0600 Subject: [PATCH] Addon Manager: Display download progress --- .../AddonManager/addonmanager_utilities.py | 12 ++ src/Mod/AddonManager/addonmanager_workers.py | 133 +++++++++++------- 2 files changed, 95 insertions(+), 50 deletions(-) diff --git a/src/Mod/AddonManager/addonmanager_utilities.py b/src/Mod/AddonManager/addonmanager_utilities.py index 1bd520f8d7..5a5c5cc492 100644 --- a/src/Mod/AddonManager/addonmanager_utilities.py +++ b/src/Mod/AddonManager/addonmanager_utilities.py @@ -297,6 +297,18 @@ def fix_relative_links(text, base_url): def repair_git_repo(repo_url: str, clone_dir: str) -> None: # Repair addon installed with raw download by adding the .git # directory to it + + try: + import git + + # If GitPython is not installed, but the user has a directory named "git" in their Python path, they + # may have the import succeed, but it will not be a real GitPython installation + have_git = hasattr(git, "Repo") + if not have_git: + return + except ImportError: + return + try: bare_repo = git.Repo.clone_from( repo_url, clone_dir + os.sep + ".git", bare=True diff --git a/src/Mod/AddonManager/addonmanager_workers.py b/src/Mod/AddonManager/addonmanager_workers.py index 2f5c9b0838..53e8977a9f 100644 --- a/src/Mod/AddonManager/addonmanager_workers.py +++ b/src/Mod/AddonManager/addonmanager_workers.py @@ -59,7 +59,9 @@ try: # 'git' has no attribute 'Repo' have_git = hasattr(git, "Repo") if not have_git: - FreeCAD.Console.PrintMessage("'import git' gave strange results (no Repo attribute)...") + FreeCAD.Console.PrintMessage( + "'import git' gave strange results (no Repo attribute)..." + ) except ImportError: pass @@ -1063,11 +1065,10 @@ class InstallWorkbenchWorker(QtCore.QThread): QtCore.QThread.__init__(self) self.repo = repo - if have_git and not NOGIT: - self.git_progress = GitProgressMonitor() - # TODO: What is wrong with these? - # self.git_progress.progress_made.connect(self.progress_made.emit) - # self.git_progress.info_message.connect(self.status_message.emit) + self.update_timer = QtCore.QTimer() + self.update_timer.setInterval(100) + self.update_timer.timeout.connect(self.update_status) + self.update_timer.start() def run(self): "installs or updates the selected addon" @@ -1087,7 +1088,7 @@ class InstallWorkbenchWorker(QtCore.QThread): FreeCAD.Console.PrintError( translate( "AddonsInstaller", - "Your version of python doesn't appear to support ZIP " + "Your version of Python doesn't appear to support ZIP " "files. Unable to proceed.", ) + "\n" @@ -1101,10 +1102,18 @@ class InstallWorkbenchWorker(QtCore.QThread): target_dir = moddir + os.sep + self.repo.name if have_git and not NOGIT: + self.git_progress = GitProgressMonitor() + # Do the git process... self.run_git(target_dir) + self.update_timer.stop() else: self.run_zip(target_dir) + def update_status(self) -> None: + if hasattr(self, "git_progress"): + self.progress_made.emit(self.git_progress.current, self.git_progress.total) + self.status_message.emit(self.git_progress.message) + def run_git(self, clonedir: str) -> None: if NOGIT or not have_git: @@ -1138,7 +1147,7 @@ class InstallWorkbenchWorker(QtCore.QThread): utils.repair_git_repo(self.repo.url, clonedir) repo = git.Git(clonedir) try: - repo.pull() + repo.pull(progress=self.git_progress) answer = translate( "AddonsInstaller", "Workbench successfully updated. " @@ -1158,7 +1167,7 @@ class InstallWorkbenchWorker(QtCore.QThread): repo_sms = git.Repo(clonedir) self.status_message.emit("Updating submodules...") for submodule in repo_sms.submodules: - submodule.update(init=True, recursive=True) + submodule.update(init=True, recursive=True, progress=self.git_progress) self.update_metadata() self.success.emit(self.repo, answer) @@ -1175,11 +1184,11 @@ class InstallWorkbenchWorker(QtCore.QThread): + "\n" ) self.status_message.emit("Cloning module...") - repo = git.Repo.clone_from(self.repo.url, clonedir) + repo = git.Repo.clone_from(self.repo.url, clonedir, progress=self.git_progress) # Make sure to clone all the submodules as well if repo.submodules: - repo.submodule_update(recursive=True) + repo.submodule_update(recursive=True, progress=self.git_progress) if self.repo.branch in repo.heads: repo.heads[self.repo.branch].checkout() @@ -1265,42 +1274,65 @@ class InstallWorkbenchWorker(QtCore.QThread): pass current_thread = QtCore.QThread.currentThread() - if data_size and data_size > 5 * 1024 * 1024: - # Use an on-disk file and track download progress, if the zipfile - # is over 5mb - with tempfile.NamedTemporaryFile(delete=False) as temp: - bytes_to_read = 16 * 1024 - bytes_read = 0 - while True: - if current_thread.isInterruptionRequested(): - return - chunk = u.read(bytes_to_read) - if not chunk: - break - bytes_read += bytes_to_read - temp.write(chunk) + + # Use an on-disk file and track download progress + locale = QtCore.QLocale() + with tempfile.NamedTemporaryFile(delete=False) as temp: + bytes_to_read = 1024 * 1024 + bytes_read = 0 + while True: + if current_thread.isInterruptionRequested(): + return + chunk = u.read(bytes_to_read) + if not chunk: + break + bytes_read += bytes_to_read + temp.write(chunk) + if data_size: self.progress_made.emit(bytes_read, data_size) + percent = int(100 * float(bytes_read / data_size)) + MB_read = bytes_read / 1024 / 1024 + MB_total = data_size / 1024 / 1024 + bytes_str = locale.toString(MB_read) + bytes_total_str = locale.toString(MB_total) + self.status_message.emit( + translate( + "AddonsInstaller", + f"Downloading: {bytes_str}MB of {bytes_total_str}MB ({percent}%)", + ) + ) + else: + MB_read = bytes_read / 1024 / 1024 + bytes_str = locale.toString(MB_read) + self.status_message.emit( + translate( + "AddonsInstaller", + f"Downloading: {bytes_str}MB of unknown total", + ) + ) + QtCore.QCoreApplication.processEvents(QtCore.QEventLoop.AllEvents) zfile = zipfile.ZipFile(temp) - else: - zfile = io.BytesIO() - zfile.write(u.read()) - zfile = zipfile.ZipFile(zfile) - master = zfile.namelist()[0] # github will put everything in a subfolder - zfile.extractall(zipdir) - u.close() - zfile.close() - for filename in os.listdir(zipdir + os.sep + master): - shutil.move( - zipdir + os.sep + master + os.sep + filename, zipdir + os.sep + filename + master = zfile.namelist()[0] # github will put everything in a subfolder + self.status_message.emit( + translate("AddonsInstaller", f"Download complete. Unzipping file...") + ) + QtCore.QCoreApplication.processEvents(QtCore.QEventLoop.AllEvents) + zfile.extractall(zipdir) + u.close() + zfile.close() + for filename in os.listdir(zipdir + os.sep + master): + shutil.move( + zipdir + os.sep + master + os.sep + filename, + zipdir + os.sep + filename, + ) + os.rmdir(zipdir + os.sep + master) + if bakdir: + shutil.rmtree(bakdir) + self.update_metadata() + self.success.emit( + self.repo, + translate("AddonsInstaller", "Successfully installed") + " " + zipurl, ) - os.rmdir(zipdir + os.sep + master) - if bakdir: - shutil.rmtree(bakdir) - self.update_metadata() - self.success.emit( - self.repo, - translate("AddonsInstaller", "Successfully installed") + " " + zipurl, - ) def update_metadata(self): basedir = FreeCAD.getUserAppDataDir() @@ -1539,13 +1571,13 @@ class UpdateMetadataCacheWorker(QtCore.QThread): if have_git and not NOGIT: class GitProgressMonitor(git.RemoteProgress): - """An object that receives git progress updates and transforms them into Qt signals""" - - progress_made = QtCore.Signal(int, int) - info_message = QtCore.Signal(str) + """An object that receives git progress updates and stores them for later display""" def __init__(self): super().__init__() + self.current = 0 + self.total = 100 + self.message = "" def update( self, @@ -1555,9 +1587,10 @@ if have_git and not NOGIT: message: str = "", ) -> None: if max_count: - self.progress_made.emit(int(cur_count), int(max_count)) + self.current = int(cur_count) + self.total = int(max_count) if message: - self.info_message.emit(message) + self.message = message class UpdateAllWorker(QtCore.QThread):