macOS_Updater/default.py

342 lines
16 KiB
Python
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# The Mac OS Updater
## Install macOS updates from the TUI.
import os; import time as clock
## app information
class app:
title = "The Mac OS Updater"
description = "Install macOS updates from the TUI."
version = 22.0616 ### YY.MMDD
## localization
class texts:
current_language = "en"
class screens:
class titles:
s_2_0 = 'Download macOS'
s_2_1 = 'Advanced Options'
s_2_2 = 'Beta Update Branch Removal'
s_2_2_1 = 'Disabling Beta Branch Search…'
class messages:
s_2_1 = 'Select the beta update branch to be included.'
s_2_2 = 'Do you want to remove the beta update branch when looking for updates? '
s_2_3 = 'Please read and agree to the lisence before to include the public beta update branch.'
s_2_4 = 'Please read and agree to the lisence before to include the developer beta update branch.'
class menus:
main = ['quit', 'refresh', 'install updates', 'download macOS', 'help']
s_2_0 = ['⬅︎' , 'refresh', 'advanced', 'download: Type an OS version number to download it.']
s_2_1 = ['⬅︎' , '🚫', 'beta release', 'developer release']
s_2_2 = ['⬅︎' , 'revert']
s_2_3 = ['⬅︎' , 'agree']
s_2_4 = ['⬅︎' , 'agree']
class status:
class checkCompatibility:
checking = 'Checking compatibility…'
done = ''
class errors:
class compatibility:
incompatibility = "There is no use running this outside macOS."
noSoftwareUpdater = "There is no CLI software updater. \nAre you using an old version of macOS?"
class quit:
terminationPrompt = "The program refuses to proceed, so it will be closed. \nTo continue, click enter or quit the program yourself."
class default:
syntaxerr = "There is a spelling mistake!"
typeerr = "Too bad, something couldn't be converted."
attributeerr = "Something was not linked in properly. "
class screens:
s_2_2_1: 'Sorry. You are still stuck with the beta branch search enabled. \nPress ^c to go back, and come back to this page to try again.'
try:
## on Terminal outputs
### text formatting
class formatting:
clear = '\033[0m'
class color:
purple = '\033[95m'
cyan = '\033[96m'
cyan_dark = '\033[36m'
blue = '\033[94m'
green = '\033[92m'
yellow = '\033[93m'
red = '\033[91m'
class font:
bold = '\033[1m'
underline = '\033[4m'
### screen functions
def cls(times = 1): #### to clear screen
times = int(abs(times));
for _ in range(times):
try:
ipy_str = str(type(get_ipython()))
if 'zmqshell' in ipy_str: from IPython.display import clear_output as clear; clear()
elif 'terminal' in ipy_str: clear
except NameError: ##### The user is running directly from the terminal, which is expected.
import os; return(os.system('clear'))
class info: ### screen messages
def status(message): print(formatting.font.bold + '\t' + formatting.clear + message)
def err(message): print(formatting.font.bold + formatting.color.red + '\t' + formatting.clear + formatting.clear + message)
def warning(message): print(formatting.font.bold + formatting.color.yellow + '⚠️\t' + formatting.clear + formatting.clear + message)
def success(message): print(formatting.font.bold + formatting.color.green + '\t' + formatting.clear + formatting.clear + message)
### terminate with fail function
def fail(message = '', displayAppInformation = True, critical = True):
import time;
if critical:
try:
if displayAppInformation: operations.display_title_screen(); print('\n')
info.err(message); input('\n\n' + texts.errors.quit.terminationPrompt); raise KeyboardInterrupt
except KeyboardInterrupt: cls(); exit()
else: print(message)
time.sleep(1);
class user: ## user data
class navigation: ### TLI data
screen = 0
response = ''
class window:
try:
width = int((os.get_terminal_size()).columns)
lines = int((os.get_terminal_size()).lines)
except OSError:
width = False
lines = False
class compatibility: ### for operations.check_compatibility()
isRunningMacOS = (os.name =='posix' and not(os.system('cd /Applications')) and not(os.system('cd /Library')) and not(os.system('cd /System/Library')))
hasCLIUpdaterInstalled = not(os.system('softwareupdate --help >/dev/null 2>&1')) #### Check if softwareupdate command is there.
class shell:
try: #### determine Python type, if running in terminal, ipython, or jupyter
ipy_str = str(type(get_ipython()))
if 'zmqshell' in ipy_str: pythonType = 'jupyter'
elif 'terminal' in ipy_str: pythonType = 'ipython'
except:
pythonType = 'terminal'
class operations: ## all operations
def display_title_screen(message = ''): ### display title screen and optional message
screenManager.menu.titleBar(app.title); print (app.description + '\nV' + str(app.version)) #### display app information
message = str(message).strip()
if message != '': print(message) #### display message
def check_compatibility(): ### compatibility check
cls()
operations.display_title_screen(texts.status.checkCompatibility.checking)
import os
if user.compatibility.isRunningMacOS:
if user.compatibility.hasCLIUpdaterInstalled:
return True
else:
fail(texts.errors.compatibility.noSoftwareUpdater); return False
else:
fail(texts.errors.compatibility.noSoftwareUpdater); return False
class software:
def check_updates():
import os
return(os.system('softwareupdate -l'))
def check_OS_installers():
import os
return(os.system('softwareupdate --list-full-installers'))
class installer_branch:
def none():
import os; return(os.system('sudo /System/Library/PrivateFrameworks/Seeding.framework/Versions/A/Resources/seedutil unenroll'))
def publicBeta():
import os; return(os.system('sudo /System/Library/PrivateFrameworks/Seeding.framework/Versions/A/Resources/seedutil enroll PublicSeed'))
def privateBeta():
import os; return(os.system('sudo /System/Library/PrivateFrameworks/Seeding.framework/Versions/A/Resources/seedutil enroll DeveloperSeed'))
class screenManager: ## TLI
class graphical:
def horizontalLine(times = 1):
times = int(abs(times))
if times == 0:
times = 1
for _ in range(times):
screenManager.redefineDimensions()
if user.navigation.window.width != False:
totalText = ''
for _ in range(user.navigation.window.width): totalText = totalText + ''
print(totalText)
else: print('')
class menu: ### for menus
def titleBar(title):
title = title.strip()
titleLength = len(title)
totalText = ''
if user.navigation.window.width != False:
for _ in range(int(((user.navigation.window.width - titleLength)/2)-1)): totalText = totalText + ''
totalText = (totalText + ' ')
totalText = (totalText + formatting.font.bold + title + formatting.clear)
if user.navigation.window.width != False:
totalText = (totalText + ' ')
for _ in range(int(((user.navigation.window.width - titleLength)/2)-1)): totalText = totalText + ''
print(totalText); return(totalText)
def topMenu(menuShortcut, menuList, displayHorizontalLineBelow = True): #### bottom menu
entryNumber = 0; displayText = '|'
for menuItem in menuList:
entryNumber = menuList.index(menuItem)
if menuShortcut[entryNumber].strip() == '':
displayText = (displayText + menuItem + '|')
else:
displayText = (displayText + formatting.font.bold + menuShortcut[entryNumber] + formatting.clear + ' ' + menuItem + '|')
print(displayText);
if displayHorizontalLineBelow: screenManager.graphical.horizontalLine()
def inputMenu(menuShortcut, menuList, backSupported = True, passNumbers = False, passAnything = False): #### input
while True:
screenManager.graphical.horizontalLine();
try:
response = input()
response = response.strip()
except KeyboardInterrupt: ##### User cancelled.
if backSupported: return -1;
else: print("", end="\r") ##### Loop again.
else:
if response in menuShortcut or response in menuList or passAnything:
return(response)
elif passNumbers:
try: response = float(response)
except: print("", end="\r")
else: return(response)
else: print("", end="\r")
def promptMenu(promptTitle, promptDescription, promptOptions, promptShortcuts, canCancel = True, extraInputAllowed = False, numbersAllowed = False):
screenManager.menu.titleBar(promptTitle.strip())
print(promptDescription)
if user.navigation.window.lines != False:
for _ in range((user.navigation.window.lines)/4): print('')
else: print('\n\n')
screenManager.menu.topMenu(promptShortcuts, promptOptions)
user.navigation.response = screenManager.menu.inputMenu(promptShortcuts, promptOptions, canCancel, numbersAllowed, extraInputAllowed)
return(user.navigation.response)
def redefineDimensions():
try: user.navigation.window.width = int((os.get_terminal_size()).columns)
except OSError: user.navigation.window.width = False
try: user.navigation.window.lines = int((os.get_terminal_size()).lines)
except OSError: user.navigation.window.lines = False
def do():
while user.navigation.screen >= 0:
cls(); screenManager.redefineDimensions()
if user.navigation.screen < 1: ### when opened
if user.navigation.screen == 0:
operations.display_title_screen()
clock.sleep(1)
user.navigation.screen = 0.1
elif user.navigation.screen == 0.1:
if operations.check_compatibility(): user.navigation.screen = 1
elif user.navigation.screen < 2:
if user.navigation.screen == 1:
menuShortcut = ['^c', 'r', 'i', 'd', '?']
screenManager.menu.titleBar(app.title); screenManager.menu.topMenu(menuShortcut, texts.menus.main)
operations.software.check_updates()
user.navigation.response = screenManager.menu.inputMenu(menuShortcut, texts.menus.main)
if user.navigation.response == -1 or user.navigation.response == '^c':
user.navigation.screen = -1
elif user.navigation.response == 'd':
user.navigation.screen = 2
elif user.navigation.screen < 3: ### full installer downloader, necessary and recommended to upgrade the OS
if user.navigation.screen == 2:
menuShortcut = ['^c', 'r', 'A', '']
screenManager.menu.titleBar(texts.screens.titles.s_2_0); screenManager.menu.topMenu(menuShortcut, texts.menus.s_2_0)
operations.software.check_OS_installers()
user.navigation.response = screenManager.menu.inputMenu(menuShortcut, texts.menus.s_2_0, True, True)
if user.navigation.response == -1 or user.navigation.response == '^c':
user.navigation.screen = 1
elif user.navigation.response == 'A':
user.navigation.screen = 2.1
elif user.navigation.screen == 2.1:
menuShortcut = ['^c', 'n', 'b', 'D']
screenManager.menu.promptMenu(texts.screens.titles.s_2_1, texts.screens.messages.s_2_1, texts.menus.s_2_1, menuShortcut)
if user.navigation.response == -1 or user.navigation.response == '^c':
user.navigation.screen = 2
elif user.navigation.response == 'n' or user.navigation.response == '🚫':
user.navigation.screen = 2.2
elif user.navigation.screen == 2.2:
menuShortcut = ['^c', '']
screenManager.menu.promptMenu(texts.screens.titles.s_2_2, texts.screens.messages.s_2_2, texts.menus.s_2_2, menuShortcut)
if user.navigation.response == -1 or user.navigation.response == '^c':
user.navigation.screen = 2.1
elif user.navigation.response == '' or user.navigation.response == 'revert':
user.navigation.screen == 2.21
elif user.navigation.screen == 2.21:
screenManager.menu.titleBar(texts.screens.titles.s_2_2_1);
if (operations.software.installer_branch.none()):
try:
info.err(texts.errors.screens.s_2_2_1)
except KeyboardInterrupt:
user.navigation.screen = 2.1
else:
user.navigation.screen = 2.1
if user.navigation.screen == -1:
cls()
def main():
screenManager.do()
main()
except SyntaxError as e: fail(texts.errors.default.syntaxerr + '\n' + str(e))
except NameError as e: fail(texts.errors.default.syntaxerr + '\n' + str(e))
except IndentationError as e: fail(texts.errors.default.syntaxerr + '\n' + str(e))
except TypeError as e: fail(texts.errors.default.typeerr + '\n' + str(e))
except AttributeError as e: fail(texts.errors.default.attributeerr + '\n' + str(e))