diff --git a/Scripts/__init__.py b/Scripts/__init__.py new file mode 100644 index 0000000..962c1d3 --- /dev/null +++ b/Scripts/__init__.py @@ -0,0 +1,4 @@ +from os.path import dirname, basename, isfile +import glob +modules = glob.glob(dirname(__file__)+"/*.py") +__all__ = [ basename(f)[:-3] for f in modules if isfile(f) and not f.endswith('__init__.py')] \ No newline at end of file diff --git a/Scripts/downloader.py b/Scripts/downloader.py new file mode 100644 index 0000000..1b366fe --- /dev/null +++ b/Scripts/downloader.py @@ -0,0 +1,79 @@ +import sys, os, time +# Python-aware urllib stuff +if sys.version_info >= (3, 0): + from urllib.request import urlopen +else: + from urllib2 import urlopen + +class Downloader: + + def __init__(self): + return + + def _progress_hook(self, response, bytes_so_far, total_size): + if total_size > 0: + percent = float(bytes_so_far) / total_size + percent = round(percent*100, 2) + sys.stdout.write("Downloaded {:,} of {:,} bytes ({:.2f}%)\r".format(bytes_so_far, total_size, percent)) + else: + sys.stdout.write("Downloaded {:,} bytes\r".format(bytes_so_far)) + + def get_string(self, url, progress = True): + response = urlopen(url) + CHUNK = 16 * 1024 + bytes_so_far = 0 + try: + total_size = int(response.headers['Content-Length']) + except: + total_size = -1 + chunk_so_far = "".encode("utf-8") + while True: + chunk = response.read(CHUNK) + bytes_so_far += len(chunk) + if progress: + self._progress_hook(response, bytes_so_far, total_size) + if not chunk: + break + chunk_so_far += chunk + return chunk_so_far.decode("utf-8") + + def get_bytes(self, url, progress = True): + response = urlopen(url) + CHUNK = 16 * 1024 + bytes_so_far = 0 + try: + total_size = int(response.headers['Content-Length']) + except: + total_size = -1 + chunk_so_far = "".encode("utf-8") + while True: + chunk = response.read(CHUNK) + bytes_so_far += len(chunk) + if progress: + self._progress_hook(response, bytes_so_far, total_size) + if not chunk: + break + chunk_so_far += chunk + return chunk_so_far + + def stream_to_file(self, url, file, progress = True): + response = urlopen(url) + CHUNK = 16 * 1024 + bytes_so_far = 0 + try: + total_size = int(response.headers['Content-Length']) + except: + total_size = -1 + with open(file, 'wb') as f: + while True: + chunk = response.read(CHUNK) + bytes_so_far += len(chunk) + if progress: + self._progress_hook(response, bytes_so_far, total_size) + if not chunk: + break + f.write(chunk) + if os.path.exists(file): + return file + else: + return None \ No newline at end of file diff --git a/Scripts/plist.py b/Scripts/plist.py new file mode 100644 index 0000000..98b9021 --- /dev/null +++ b/Scripts/plist.py @@ -0,0 +1,182 @@ +### ### +# Imports # +### ### + +import datetime +from io import BytesIO +import os +import plistlib +import struct +import sys + +try: + FMT_XML = plistlib.FMT_XML +except: + FMT_XML = None + +### ### +# Helper Methods # +### ### + +def _check_py3(): + return True if sys.version_info >= (3, 0) else False + +def _is_binary(fp): + header = fp.read(32) + fp.seek(0) + return header[:8] == b'bplist00' + +def _get_inst(): + if _check_py3(): + return (str) + else: + return (str, unicode) + +### ### +# Deprecated Functions - Remapped # +### ### + +def readPlist(pathOrFile): + if not isinstance(pathOrFile, _get_inst()): + return load(pathOrFile) + with open(pathOrFile, "rb") as f: + return load(f) + +def writePlist(value, pathOrFile): + if not isinstance(pathOrFile, _get_inst()): + return dump(value, pathOrFile, fmt=FMT_XML, sort_keys=True, skipkeys=False) + with open(pathOrFile, "wb") as f: + return dump(value, f, fmt=FMT_XML, sort_keys=True, skipkeys=False) + +### ### +# Remapped Functions # +### ### + +def load(fp, fmt=None, use_builtin_types=True, dict_type=dict): + if _check_py3(): + return plistlib.load(fp, fmt=fmt, use_builtin_types=use_builtin_types, dict_type=dict_type) + elif not _is_binary(fp): + return plistlib.readPlist(fp) + else: + return readBinaryPlistFile(fp) + +def loads(value, fmt=None, use_builtin_types=True, dict_type=dict): + if isinstance(value, _get_inst()): + # We were sent a string - let's encode it to some utf-8 bytes for fun! + value = value.encode("utf-8") + fp = BytesIO(value) + if _check_py3(): + return plistlib.load(fp, fmt=fmt, use_builtin_types=use_builtin_types, dict_type=dict_type) + elif not _is_binary(fp): + return plistlib.readPlistFromString(value) + else: + return readBinaryPlistFile(fp) + +def dump(value, fp, fmt=FMT_XML, sort_keys=True, skipkeys=False): + if _check_py3(): + plistlib.dump(value, fp, fmt=fmt, sort_keys=sort_keys, skipkeys=skipkeys) + else: + plistlib.writePlist(value, fp) + +def dumps(value, fmt=FMT_XML, skipkeys=False): + if _check_py3(): + return plistlib.dumps(value, fmt=fmt, skipkeys=skipkeys).encode("utf-8") + else: + return plistlib.writePlistToString(value).encode("utf-8") + + +### ### +# Binary Plist Stuff For Py2 # +### ### + +# timestamp 0 of binary plists corresponds to 1/1/2001 (year of Mac OS X 10.0), instead of 1/1/1970. +MAC_OS_X_TIME_OFFSET = (31 * 365 + 8) * 86400 + +class InvalidFileException(ValueError): + def __str__(self): + return "Invalid file" + def __unicode__(self): + return "Invalid file" + +def readBinaryPlistFile(in_file): + """ + Read a binary plist file, following the description of the binary format: http://opensource.apple.com/source/CF/CF-550/CFBinaryPList.c + Raise InvalidFileException in case of error, otherwise return the root object, as usual + + Original patch diffed here: https://bugs.python.org/issue14455 + """ + in_file.seek(-32, os.SEEK_END) + trailer = in_file.read(32) + if len(trailer) != 32: + return InvalidFileException() + offset_size, ref_size, num_objects, top_object, offset_table_offset = struct.unpack('>6xBB4xL4xL4xL', trailer) + in_file.seek(offset_table_offset) + object_offsets = [] + offset_format = '>' + {1: 'B', 2: 'H', 4: 'L', 8: 'Q', }[offset_size] * num_objects + ref_format = {1: 'B', 2: 'H', 4: 'L', 8: 'Q', }[ref_size] + int_format = {0: (1, '>B'), 1: (2, '>H'), 2: (4, '>L'), 3: (8, '>Q'), } + object_offsets = struct.unpack(offset_format, in_file.read(offset_size * num_objects)) + def getSize(token_l): + """ return the size of the next object.""" + if token_l == 0xF: + m = ord(in_file.read(1)) & 0x3 + s, f = int_format[m] + return struct.unpack(f, in_file.read(s))[0] + return token_l + def readNextObject(offset): + """ read the object at offset. May recursively read sub-objects (content of an array/dict/set) """ + in_file.seek(offset) + token = in_file.read(1) + token_h, token_l = ord(token) & 0xF0, ord(token) & 0x0F #high and low parts + if token == '\x00': + return None + elif token == '\x08': + return False + elif token == '\x09': + return True + elif token == '\x0f': + return '' + elif token_h == 0x10: #int + result = 0 + for k in xrange((2 << token_l) - 1): + result = (result << 8) + ord(in_file.read(1)) + return result + elif token_h == 0x20: #real + if token_l == 2: + return struct.unpack('>f', in_file.read(4))[0] + elif token_l == 3: + return struct.unpack('>d', in_file.read(8))[0] + elif token_h == 0x30: #date + f = struct.unpack('>d', in_file.read(8))[0] + return datetime.datetime.utcfromtimestamp(f + MAC_OS_X_TIME_OFFSET) + elif token_h == 0x80: #data + s = getSize(token_l) + return in_file.read(s) + elif token_h == 0x50: #ascii string + s = getSize(token_l) + return in_file.read(s) + elif token_h == 0x60: #unicode string + s = getSize(token_l) + return in_file.read(s * 2).decode('utf-16be') + elif token_h == 0x80: #uid + return in_file.read(token_l + 1) + elif token_h == 0xA0: #array + s = getSize(token_l) + obj_refs = struct.unpack('>' + ref_format * s, in_file.read(s * ref_size)) + return map(lambda x: readNextObject(object_offsets[x]), obj_refs) + elif token_h == 0xC0: #set + s = getSize(token_l) + obj_refs = struct.unpack('>' + ref_format * s, in_file.read(s * ref_size)) + return set(map(lambda x: readNextObject(object_offsets[x]), obj_refs)) + elif token_h == 0xD0: #dict + result = {} + s = getSize(token_l) + key_refs = struct.unpack('>' + ref_format * s, in_file.read(s * ref_size)) + obj_refs = struct.unpack('>' + ref_format * s, in_file.read(s * ref_size)) + for k, o in zip(key_refs, obj_refs): + key = readNextObject(object_offsets[k]) + obj = readNextObject(object_offsets[o]) + result[key] = obj + return result + raise InvalidFileException() + return readNextObject(object_offsets[top_object]) diff --git a/Scripts/utils.py b/Scripts/utils.py new file mode 100644 index 0000000..a8327dc --- /dev/null +++ b/Scripts/utils.py @@ -0,0 +1,272 @@ +import sys, os, time, re, json, datetime, ctypes, subprocess + +if os.name == "nt": + # Windows + import msvcrt +else: + # Not Windows \o/ + import select + +class Utils: + + def __init__(self, name = "Python Script"): + self.name = name + # Init our colors before we need to print anything + cwd = os.getcwd() + os.chdir(os.path.dirname(os.path.realpath(__file__))) + if os.path.exists("colors.json"): + self.colors_dict = json.load(open("colors.json")) + else: + self.colors_dict = {} + os.chdir(cwd) + + def check_admin(self): + # Returns whether or not we're admin + try: + is_admin = os.getuid() == 0 + except AttributeError: + is_admin = ctypes.windll.shell32.IsUserAnAdmin() != 0 + return is_admin + + def elevate(self, file): + # Runs the passed file as admin + if self.check_admin(): + return + if os.name == "nt": + ctypes.windll.shell32.ShellExecuteW(None, "runas", sys.executable, file, None, 1) + else: + try: + p = subprocess.Popen(["which", "sudo"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + c = p.communicate()[0].decode("utf-8", "ignore").replace("\n", "") + os.execv(c, [ sys.executable, 'python'] + sys.argv) + except: + exit(1) + + def compare_versions(self, vers1, vers2, **kwargs): + # Helper method to compare ##.## strings + # + # vers1 < vers2 = True + # vers1 = vers2 = None + # vers1 > vers2 = False + + # Sanitize the pads + pad = str(kwargs.get("pad", "")) + sep = str(kwargs.get("separator", ".")) + + ignore_case = kwargs.get("ignore_case", True) + + # Cast as strings + vers1 = str(vers1) + vers2 = str(vers2) + + if ignore_case: + vers1 = vers1.lower() + vers2 = vers2.lower() + + # Split and pad lists + v1_parts, v2_parts = self.pad_length(vers1.split(sep), vers2.split(sep)) + + # Iterate and compare + for i in range(len(v1_parts)): + # Remove non-numeric + v1 = ''.join(c.lower() for c in v1_parts[i] if c.isalnum()) + v2 = ''.join(c.lower() for c in v2_parts[i] if c.isalnum()) + # Equalize the lengths + v1, v2 = self.pad_length(v1, v2) + # Compare + if str(v1) < str(v2): + return True + elif str(v1) > str(v2): + return False + # Never differed - return None, must be equal + return None + + def pad_length(self, var1, var2, pad = "0"): + # Pads the vars on the left side to make them equal length + pad = "0" if len(str(pad)) < 1 else str(pad)[0] + if not type(var1) == type(var2): + # Type mismatch! Just return what we got + return (var1, var2) + if len(var1) < len(var2): + if type(var1) is list: + var1.extend([str(pad) for x in range(len(var2) - len(var1))]) + else: + var1 = "{}{}".format((pad*(len(var2)-len(var1))), var1) + elif len(var2) < len(var1): + if type(var2) is list: + var2.extend([str(pad) for x in range(len(var1) - len(var2))]) + else: + var2 = "{}{}".format((pad*(len(var1)-len(var2))), var2) + return (var1, var2) + + def check_path(self, path): + # Loop until we either get a working path - or no changes + count = 0 + while count < 100: + count += 1 + if not len(path): + # We uh.. stripped out everything - bail + return None + if os.path.exists(path): + # Exists! + return os.path.abspath(path) + # Check quotes first + if (path[0] == '"' and path[-1] == '"') or (path[0] == "'" and path[-1] == "'"): + path = path[1:-1] + continue + # Check for tilde + if path[0] == "~": + test_path = os.path.expanduser(path) + if test_path != path: + # We got a change + path = test_path + continue + # If we have no spaces to trim - bail + if not (path[0] == " " or path[0] == " ") and not(path[-1] == " " or path[-1] == " "): + return None + # Here we try stripping spaces/tabs + test_path = path + t_count = 0 + while t_count < 100: + t_count += 1 + t_path = test_path + while len(t_path): + if os.path.exists(t_path): + return os.path.abspath(t_path) + if t_path[-1] == " " or t_path[-1] == " ": + t_path = t_path[:-1] + continue + break + if test_path[0] == " " or test_path[0] == " ": + test_path = test_path[1:] + continue + break + # Escapes! + test_path = "\\".join([x.replace("\\", "") for x in path.split("\\\\")]) + if test_path != path and not (path[0] == " " or path[0] == " "): + path = test_path + continue + if path[0] == " " or path[0] == " ": + path = path[1:] + return None + + def grab(self, prompt, **kwargs): + # Takes a prompt, a default, and a timeout and shows it with that timeout + # returning the result + timeout = kwargs.get("timeout", 0) + default = kwargs.get("default", None) + # If we don't have a timeout - then skip the timed sections + if timeout <= 0: + if sys.version_info >= (3, 0): + return input(prompt) + else: + return str(raw_input(prompt)) + # Write our prompt + sys.stdout.write(prompt) + sys.stdout.flush() + if os.name == "nt": + start_time = time.time() + i = '' + while True: + if msvcrt.kbhit(): + c = msvcrt.getche() + if ord(c) == 13: # enter_key + break + elif ord(c) >= 32: #space_char + i += c + if len(i) == 0 and (time.time() - start_time) > timeout: + break + else: + i, o, e = select.select( [sys.stdin], [], [], timeout ) + if i: + i = sys.stdin.readline().strip() + print('') # needed to move to next line + if len(i) > 0: + return i + else: + return default + + def cls(self): + os.system('cls' if os.name=='nt' else 'clear') + + def cprint(self, message, **kwargs): + strip_colors = kwargs.get("strip_colors", False) + if os.name == "nt": + strip_colors = True + reset = u"\u001b[0m" + # Requires sys import + for c in self.colors: + if strip_colors: + message = message.replace(c["find"], "") + else: + message = message.replace(c["find"], c["replace"]) + if strip_colors: + return message + sys.stdout.write(message) + print(reset) + + # Needs work to resize the string if color chars exist + '''# Header drawing method + def head(self, text = None, width = 55): + if text == None: + text = self.name + self.cls() + print(" {}".format("#"*width)) + len_text = self.cprint(text, strip_colors=True) + mid_len = int(round(width/2-len(len_text)/2)-2) + middle = " #{}{}{}#".format(" "*mid_len, len_text, " "*((width - mid_len - len(len_text))-2)) + if len(middle) > width+1: + # Get the difference + di = len(middle) - width + # Add the padding for the ...# + di += 3 + # Trim the string + middle = middle[:-di] + newlen = len(middle) + middle += "...#" + find_list = [ c["find"] for c in self.colors ] + + # Translate colored string to len + middle = middle.replace(len_text, text + self.rt_color) # always reset just in case + self.cprint(middle) + print("#"*width)''' + + # Header drawing method + def head(self, text = None, width = 55): + if text == None: + text = self.name + self.cls() + print(" {}".format("#"*width)) + mid_len = int(round(width/2-len(text)/2)-2) + middle = " #{}{}{}#".format(" "*mid_len, text, " "*((width - mid_len - len(text))-2)) + if len(middle) > width+1: + # Get the difference + di = len(middle) - width + # Add the padding for the ...# + di += 3 + # Trim the string + middle = middle[:-di] + "...#" + print(middle) + print("#"*width) + + def resize(self, width, height): + print('\033[8;{};{}t'.format(height, width)) + + def custom_quit(self): + self.head() + print("by CorpNewt\n") + print("Thanks for testing it out, for bugs/comments/complaints") + print("send me a message on Reddit, or check out my GitHub:\n") + print("www.reddit.com/u/corpnewt") + print("www.github.com/corpnewt\n") + # Get the time and wish them a good morning, afternoon, evening, and night + hr = datetime.datetime.now().time().hour + if hr > 3 and hr < 12: + print("Have a nice morning!\n\n") + elif hr >= 12 and hr < 17: + print("Have a nice afternoon!\n\n") + elif hr >= 17 and hr < 21: + print("Have a nice evening!\n\n") + else: + print("Have a nice night!\n\n") + exit(0) diff --git a/gibMacOS.bat b/gibMacOS.bat new file mode 100644 index 0000000..42f9a16 --- /dev/null +++ b/gibMacOS.bat @@ -0,0 +1,34 @@ +@echo off +setlocal enableDelayedExpansion + +REM Setup initial vars +set "script_name=gibMacOS.command" +set "thisDir=%~dp0" + +REM Get python location +FOR /F "tokens=* USEBACKQ" %%F IN (`where python 2^> nul`) DO ( + SET "python=%%F" +) + +REM Check for py and give helpful hints! +if /i "!python!"=="" ( + echo Python is not installed or not found in your PATH var. + echo Please install it from https://www.python.org/downloads/windows/ + echo. + echo Make sure you check the box labeled: + echo. + echo "Add Python X.X to PATH" + echo. + echo Where X.X is the py version you're installing. + echo. + echo Press [enter] to quit. + pause > nul + exit /b +) + +REM Python found +if "%*"=="" ( + "!python!" "!thisDir!!script_name!" +) else ( + "!python!" "!thisDir!!script_name!" %* +) \ No newline at end of file diff --git a/gibMacOS.command b/gibMacOS.command new file mode 100644 index 0000000..a5ef8e8 --- /dev/null +++ b/gibMacOS.command @@ -0,0 +1,270 @@ +#!/usr/bin/python +from Scripts import * +import os, datetime, shutil, time, sys, argparse + +class gibMacOS: + def __init__(self): + self.d = downloader.Downloader() + self.u = utils.Utils("gibMacOS") + + self.catalog_suffix = { + "public" : "beta", + "publicrelease" : "", + "customer" : "customerseed", + "developer" : "seed" + } + self.current_macos = 14 + self.min_macos = 5 + self.mac_os_names_url = { + "8" : "mountainlion", + "7" : "lion", + "6" : "snowleopard", + "5" : "leopard" + } + self.current_catalog = "publicrelease" + self.catalog_data = None + self.scripts = "Scripts" + self.plist = "cat.plist" + self.saves = "macOS Downloads" + self.save_local = False + if not self.get_catalog_data(self.save_local): + self.u.head("Catalog Data Error") + print("") + print("The currently selected catalog ({}) was not reachable".format(self.current_catalog)) + if self.save_local: + print("and I was unable to locate a valid {} file in the\n{} directory.".format(self.plist, self.scripts)) + print("Please ensure you have a working internet connection.") + print("") + self.u.grab("Press [enter] to exit...") + self.mac_prods = self.get_dict_for_prods(self.get_installers()) + + def set_catalog(self, catalog): + self.current_catalog = catalog.lower() if catalog.lower() in self.catalog_suffix else "publicrelease" + + def build_url(self, **kwargs): + catalog = kwargs.get("catalog", self.current_catalog).lower() + catalog = catalog if catalog.lower() in self.catalog_suffix else "publicrelease" + version = int(kwargs.get("version", self.current_macos)) + url = "https://swscan.apple.com/content/catalogs/others/index-" + url += "-".join([self.mac_os_names_url[str(x)] if str(x) in self.mac_os_names_url else "10."+str(x) for x in reversed(range(self.min_macos, version+1))]) + url += ".merged-1.sucatalog" + ver_s = self.mac_os_names_url[str(version)] if str(version) in self.mac_os_names_url else "10."+str(version) + url = url.replace(ver_s, ver_s+self.catalog_suffix[catalog]) + return url + + def get_catalog_data(self, local = False): + # Gets the data based on our current_catalog + url = self.build_url(catalog=self.current_catalog, version=self.current_macos) + self.u.head("Downloading Catalog") + print("") + print("Currently downloading {} catalog from\n\n{}\n".format(self.current_catalog, url)) + try: + b = self.d.get_bytes(url) + self.catalog_data = plist.loads(b) + # Assume it's valid data - dump it to a local file + if local: + cwd = os.getcwd() + os.chdir(os.path.dirname(os.path.realpath(__file__))) + with open(os.path.join(os.getcwd(), self.scripts, self.plist), "wb") as f: + plist.dump(self.catalog_data, f) + os.chdir(cwd) + except: + if local: + # Check if we have one locally in our scripts directory + if not os.path.exists(os.path.join(os.path.dirname(os.path.realpath(__file__)), self.scripts, self.plist)): + return False + # It does - try to load it + try: + cwd = os.getcwd() + os.chdir(os.path.dirname(os.path.realpath(__file__))) + with open(os.path.join(os.getcwd(), self.scripts, self.plist), "rb") as f: + self.catalog_data = plist.load(f) + os.chdir(cwd) + except: + return False + return True + + def get_installers(self, plist_dict = None): + if not plist_dict: + plist_dict = self.catalog_data + if not plist_dict: + return [] + mac_prods = [] + for p in plist_dict.get("Products", {}): + if plist_dict.get("Products",{}).get(p,{}).get("ExtendedMetaInfo",{}).get("InstallAssistantPackageIdentifiers",{}).get("OSInstall",{}) == "com.apple.mpkg.OSInstall": + mac_prods.append(p) + return mac_prods + + def get_dict_for_prods(self, prods, plist_dict = None): + if plist_dict==self.catalog_data==None: + plist_dict = {} + else: + plist_dict = self.catalog_data if plist_dict == None else plist_dict + + prod_list = [] + for prod in prods: + # Grab the ServerMetadataURL for the passed product key if it exists + prodd = {"product":prod} + try: + b = self.d.get_bytes(plist_dict.get("Products",{}).get(prod,{}).get("ServerMetadataURL",""), False) + smd = plist.loads(b) + except: + smd = {} + # Populate some info! + prodd["date"] = plist_dict.get("Products",{}).get(prod,{}).get("PostDate","") + prodd["time"] = time.mktime(prodd["date"].timetuple()) + prodd["date"].microsecond / 1E6 + prodd["title"] = smd.get("localization",{}).get("English",{}).get("title","Unknown") + prodd["version"] = smd.get("CFBundleShortVersionString","Unknown") + # Try to get the description too + try: + desc = smd.get("localization",{}).get("English",{}).get("description","").decode("utf-8") + desctext = desc.split('"p1">')[1].split("")[0] + except: + desctext = None + prodd["description"] = desctext + # Iterate the available packages and save their urls and sizes + prodd["packages"] = plist_dict.get("Products",{}).get(prod,{}).get("Packages",{}) + prod_list.append(prodd) + # Sort by newest + prod_list = sorted(prod_list, key=lambda x:x["time"], reverse=True) + return prod_list + + def download_prod(self, prod): + # Takes a dictonary of details and downloads it + cwd = os.getcwd() + os.chdir(os.path.dirname(os.path.realpath(__file__))) + name = "{} - {} {}".format(prod["product"], prod["version"], prod["title"]) + if os.path.exists(os.path.join(os.getcwd(), self.saves, self.current_catalog, name)): + while True: + self.u.head("Already Exists") + print("") + print("It looks like you've already downloaded {}".format(name)) + print("") + menu = self.u.grab("Redownload? (y/n): ") + if not len(menu): + continue + if menu[0].lower() == "n": + return + if menu[0].lower() == "y": + break + # Remove the old copy, then re-download + shutil.rmtree(os.path.join(os.getcwd(), self.saves, self.current_catalog, name)) + # Make it new + os.makedirs(os.path.join(os.getcwd(), self.saves, self.current_catalog, name)) + dl_list = [] + for x in prod["packages"]: + if not x.get("URL",None): + continue + # add it to the list + dl_list.append(x["URL"]) + if not len(dl_list): + self.u.head("Error") + print("") + print("There were no files to download") + print("") + self.u.grab("Press [enter] to return...") + return + c = 0 + failed = [] + succeeded = [] + for x in dl_list: + c += 1 + self.u.head("Downloading File {} of {}".format(c, len(dl_list))) + print("") + print("Downloading {} for {}...".format(os.path.basename(x), name)) + print("") + try: + self.d.stream_to_file(x, os.path.join(os.getcwd(), self.saves, self.current_catalog, name, os.path.basename(x))) + succeeded.append(os.path.basename(x)) + except: + failed.append(os.path.basename(x)) + self.u.head("Downloaded {} of {}".format(len(succeeded), len(dl_list))) + print("") + print("Succeeded:") + if len(succeeded): + for x in succeeded: + print(" {}".format(x)) + else: + print(" None") + print("") + print("Failed:") + if len(failed): + for x in failed: + print(" {}".format(x)) + else: + print(" None") + print("") + self.u.grab("Press [enter] to return...") + + def main(self): + self.u.head() + print("") + print("Available Products:") + print("") + num = 0 + if not len(self.mac_prods): + print("No installers in catalog!") + print("") + exit() + for p in self.mac_prods: + num += 1 + print("{}. {} {}\n - Added {}".format(num, p["version"], p["title"], p["date"])) + print("") + print("Q. Quit") + print("") + menu = self.u.grab("Please select an option: ") + if not len(menu): + return + if menu[0].lower() == "q": + self.u.custom_quit() + + # Assume we picked something + try: + menu = int(menu) + except: + return + if menu < 1 or menu > len(self.mac_prods): + return + self.download_prod(self.mac_prods[menu-1]) + + def get_latest(self): + self.download_prod(self.mac_prods[-1]) + + def get_for_product(self, prod): + for p in self.mac_prods: + if p["product"] == prod: + self.download_prod(prod) + return + print("{} not found".format(prod)) + + def get_for_version(self, vers): + for p in self.mac_prods: + if p["version"] == vers: + self.download_prod(prod) + return + print("{} not found".format(vers)) + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument("-l", "--latest", help="downloads the version avaialble in the current catalog (overrides --version and --product)", action="store_true") + parser.add_argument("-c", "--catalog", help="sets the CATALOG to use - publicrelease, public, customer, developer") + parser.add_argument("-p", "--product", help="sets the product id to search for (overrides --version)") + parser.add_argument("-v", "--version", help="sets the version of macOS to target - eg 10.14") + args = parser.parse_args() + + g = gibMacOS() + if args.catalog: + # Set the catalog + g.set_catalog(args.catalog) + if args.latest: + g.get_latest() + exit() + if args.product: + g.get_for_product(args.product) + exit() + if args.version: + g.get_for_version(args.version) + exit() + + while True: + g.main() \ No newline at end of file