From 49e17745d5b3152495afb43fe23ddd60a9ce4771 Mon Sep 17 00:00:00 2001 From: Jim Northrup Date: Sun, 31 Aug 2025 06:48:42 -0500 Subject: [PATCH] Add aria2c downloader support for faster downloads MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Added optional aria2c integration for multi-threaded downloads - Auto-detects aria2c availability on the system - Falls back to default downloader if aria2c is not available - Adds toggle option 'A' in main menu when aria2c is detected - Preserves all existing functionality with seamless fallback 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- Scripts/downloader.py | 64 ++++++++++++++++++++++++++++++++++++++++++- gibMacOS.py | 11 +++++++- 2 files changed, 73 insertions(+), 2 deletions(-) diff --git a/Scripts/downloader.py b/Scripts/downloader.py index c5b3b40..486c7b3 100755 --- a/Scripts/downloader.py +++ b/Scripts/downloader.py @@ -1,4 +1,4 @@ -import sys, os, time, ssl, gzip, multiprocessing +import sys, os, time, ssl, gzip, multiprocessing, subprocess, shutil from io import BytesIO # Python-aware urllib stuff try: @@ -145,6 +145,7 @@ class Downloader: def __init__(self,**kwargs): self.ua = kwargs.get("useragent",{"User-Agent":"Mozilla"}) self.chunk = 1048576 # 1024 x 1024 i.e. 1MiB + self.use_aria2c = kwargs.get("use_aria2c", self.check_aria2c()) if os.name=="nt": os.system("color") # Initialize cmd for ANSI escapes # Provide reasonable default logic to workaround macOS CA file handling cafile = ssl.get_default_verify_paths().openssl_cafile @@ -158,6 +159,15 @@ def __init__(self,**kwargs): # None of the above worked, disable certificate verification for now self.ssl_context = ssl._create_unverified_context() return + + def check_aria2c(self): + # Check if aria2c is available in the system PATH + return shutil.which('aria2c') is not None + + def set_use_aria2c(self, use_aria2c): + # Allow toggling between aria2c and default downloader + self.use_aria2c = use_aria2c if use_aria2c and self.check_aria2c() else False + return self.use_aria2c def _decode(self, value, encoding="utf-8", errors="ignore"): # Helper method to only decode if bytes type @@ -264,7 +274,59 @@ def get_bytes(self, url, progress = True, headers = None, expand_gzip = True): process.join() return chunk_so_far + def stream_to_file_aria2c(self, url, file_path, progress = True, headers = None, ensure_size_if_present = True, allow_resume = False): + # Download using aria2c if available + try: + # Build aria2c command + cmd = ['aria2c', url, '-o', os.path.basename(file_path), '-d', os.path.dirname(file_path)] + + # Add user agent header if provided + if headers: + for key, value in headers.items(): + if key.lower() == 'user-agent': + cmd.extend(['--user-agent', value]) + else: + cmd.extend(['--header', '{}:{}'.format(key, value)]) + elif self.ua: + ua_value = self.ua.get('User-Agent', 'Mozilla') + cmd.extend(['--user-agent', ua_value]) + + # Add resume support + if allow_resume: + cmd.append('-c') + + # Add connection options for better performance + cmd.extend(['-x', '16', '-s', '16', '-k', '1M']) + + # Disable certificate verification if using unverified context + if hasattr(self, 'ssl_context') and isinstance(self.ssl_context, ssl.SSLContext): + if not self.ssl_context.check_hostname: + cmd.append('--check-certificate=false') + + # Show progress + if not progress: + cmd.append('-q') + + # Run aria2c + result = subprocess.run(cmd, capture_output=False) + + if result.returncode == 0 and os.path.exists(file_path): + return file_path + else: + return None + except Exception as e: + # Fall back to default downloader + return self.stream_to_file_default(url, file_path, progress, headers, ensure_size_if_present, allow_resume) + def stream_to_file(self, url, file_path, progress = True, headers = None, ensure_size_if_present = True, allow_resume = False): + if self.use_aria2c: + result = self.stream_to_file_aria2c(url, file_path, progress, headers, ensure_size_if_present, allow_resume) + if result: + return result + # Fall back to default implementation + return self.stream_to_file_default(url, file_path, progress, headers, ensure_size_if_present, allow_resume) + + def stream_to_file_default(self, url, file_path, progress = True, headers = None, ensure_size_if_present = True, allow_resume = False): response = self.open_url(url, headers) if response is None: return None bytes_so_far = 0 diff --git a/gibMacOS.py b/gibMacOS.py index 1fefbf3..2f94f35 100755 --- a/gibMacOS.py +++ b/gibMacOS.py @@ -93,13 +93,16 @@ def __init__(self, interactive = True, download_dir = None): "RecoveryHDUpdate.pkg", "RecoveryHDMetaDmg.pkg" ) + self.use_aria2c = self.settings.get("use_aria2c", self.d.check_aria2c()) + self.d.set_use_aria2c(self.use_aria2c) self.settings_to_save = ( "current_macos", "current_catalog", "print_urls", "find_recovery", "hide_pid", - "caffeinate_downloads" + "caffeinate_downloads", + "use_aria2c" ) self.mac_prods = [] @@ -659,6 +662,8 @@ def main(self, dmg = False): lines.append("L. Clear SoftwareUpdate Catalog") lines.append("F. Caffeinate Downloads to Prevent Sleep (Currently {})".format("On" if self.caffeinate_downloads else "Off")) lines.append("R. Toggle Recovery-Only (Currently {})".format("On" if self.find_recovery else "Off")) + if self.d.check_aria2c(): + lines.append("A. Toggle aria2c Downloader (Currently {})".format("On" if self.use_aria2c else "Off")) lines.append("U. Show Catalog URL") lines.append("Q. Quit") lines.append(" ") @@ -707,6 +712,10 @@ def main(self, dmg = False): elif menu[0].lower() == "r": self.find_recovery ^= True self.save_settings() + elif menu[0].lower() == "a" and self.d.check_aria2c(): + self.use_aria2c ^= True + self.d.set_use_aria2c(self.use_aria2c) + self.save_settings() if menu[0].lower() in ["m","c","r"]: self.resize() self.u.head("Parsing Data")