More changes to support softwareupdate in Big Sur

This commit is contained in:
Greg Neagle
2020-09-04 16:28:53 -07:00
parent a840bed778
commit 696e40ca8a
2 changed files with 123 additions and 26 deletions

View File

@@ -195,13 +195,21 @@ class AppleUpdates(object):
return []
su_results = su_tool.run(['-l', '--no-scan'])
filtered_updates = []
# add update to filtered_updates only if it is also listed
# in `softwareupdate -l` output
for item in su_results.get('updates', []):
for update in recommended_updates:
if (item.get('identifier') == update.get('Identifier') and
item.get('version') == update.get('Display Version')):
# add update to filtered_updates only if it is also listed
# in `softwareupdate -l` output
filtered_updates.append(update)
item.update(update)
filtered_updates.append(item)
elif item.get('Title'):
# new-style info first seen in Catalina
if (item.get('Title') == update.get('Display Name') and
item.get('Version') == update.get('Display Version')
):
item.update(update)
filtered_updates.append(item)
return filtered_updates
def available_update_product_ids(self):
@@ -379,11 +387,16 @@ class AppleUpdates(object):
product_keys = []
english_su_info = {}
apple_updates = []
updates_dict = {}
# first, try to get the list from com.apple.SoftwareUpdate preferences
# and softwareupdate -l
recommended_updates = self.get_filtered_recommendedupdates()
for item in recommended_updates:
try:
updates_dict[item['Product Key']] = item
except (TypeError, AttributeError, KeyError):
pass
try:
update_display_names[item['Product Key']] = (
item['Display Name'])
@@ -401,7 +414,31 @@ class AppleUpdates(object):
pass
for product_key in product_keys:
if not self.update_downloaded(product_key):
if updates_dict.get(product_key, {}).get('MobileSoftwareUpdate'):
# New in Big Sur. These updates are not downloaded to
# /Library/Updates and we don't (yet) have access to any
# additional metadata
su_info = {}
su_info['productKey'] = product_key
su_info['name'] = updates_dict[
product_key].get('Display Name', '')
su_info['apple_product_name'] = su_info['name']
su_info['display_name'] = su_info['name']
su_info['version_to_install'] = updates_dict[
product_key].get('Display Version', '')
su_info['description'] = ''
try:
size = int(updates_dict[
product_key].get('Size', '0K')[:-1])
su_info['installer_item_size'] = size
su_info['installed_size'] = size
except (ValueError, TypeError, IndexError):
su_info['installed_size'] = 0
if updates_dict[product_key].get('Action') == 'restart':
su_info['RestartAction'] = 'RequireRestart'
apple_updates.append(su_info)
continue
elif not self.update_downloaded(product_key):
display.display_warning(
'Product %s does not appear to be downloaded',
product_key)
@@ -502,7 +539,7 @@ class AppleUpdates(object):
for item in apple_updates:
display.display_info(
' + %s-%s' % (
item.get('display_name', ''),
item.get('display_name', item.get('name', '')),
item.get('version_to_install', '')))
if item.get('RestartAction') in self.RESTART_ACTIONS:
display.display_info(' *Restart required')

View File

@@ -67,6 +67,78 @@ def find_ptty_tool():
return cmd
def parse_su_update_line_new_style(line):
'''Parses a new-style software update line'''
info = {}
line = line.strip().rstrip(',')
for subitem in line.split(', '):
key, _, value = subitem.partition(": ")
if key:
info[key] = value
return info
def parse_su_update_line_old_style(line):
'''Parses an old-style (pre-10.15) softwareupdate -l output line
into a dict'''
info = {}
# strip leading and trailing whitespace
line = line.strip()
title, seperator, line = line.partition("(")
if not seperator == "(":
# no idea of the format, just return an empty dict
return {}
info['Title'] = title.rstrip()
version, seperator, line = line.partition(")")
if not seperator == ")":
# no idea of the format, just return an empty dict
return {}
info['Version'] = version
line = line.lstrip(', ')
size, seperator, line = line.partition('K')
if seperator == 'K':
info['Size'] = '%sK' % size
# now start from the end
if line.endswith(" [restart]"):
line = line[0:-len(" [restart]")]
info['Action'] = 'restart'
if line.endswith(" [recommended]"):
line = line[0:-len(" [recommended]")]
info['Recommended'] = 'YES'
else:
info['Recommended'] = 'NO'
return info
def parse_su_identifier(line):
'''parses first line of softwareupdate -l item output'''
if line.startswith(' * '):
update_entry = line[5:]
elif line.startswith('* Label: '):
update_entry = line[9:]
else:
return {}
update_parts = update_entry.split('-')
# version is the bit after the last hyphen
# (let's hope there are no hyphens in the versions!)
vers = update_parts[-1]
# identifier is everything before the last hyphen
identifier = '-'.join(update_parts[0:-1])
return {'full_identifier': update_entry,
'identifier': identifier,
'version': vers}
def parse_su_update_lines(line1, line2):
'''Parses two lines from softwareupdate -l output and returns a dict'''
info = parse_su_identifier(line1)
if line1.startswith(' * '):
info.update(parse_su_update_line_old_style(line2))
elif line1.startswith('* Label: '):
info.update(parse_su_update_line_new_style(line2))
return info
def run(options_list, catalog_url=None, stop_allowed=False):
"""Runs /usr/sbin/softwareupdate with options.
@@ -168,28 +240,16 @@ def run(options_list, catalog_url=None, stop_allowed=False):
# --list-specific output
if mode == 'list':
if output.strip().startswith('*'):
if output.startswith((' * ', '* Label: ')):
# collect list of items available for install
if output.startswith(' * '):
update_entry = output[5:]
elif output.startswith('* Label: '):
# seen in 10.15 betas
update_entry = output[9:]
else:
# unknown format
continue
update_parts = update_entry.split('-')
# version is the bit after the last hyphen
# (let's hope there are no hyphens in the versions!)
vers = update_parts[-1]
# identifier is everything before the last hyphen
identifier = '-'.join(update_parts[0:-1])
results['updates'].append(
{'identifier': identifier, 'version': vers})
continue
else:
# we don't want any output from calling `softwareupdate -l`
continue
first_line = output
second_line = job.stdout.readline()
if second_line:
second_line = second_line.decode('UTF-8').rstrip('\n\r')
item = parse_su_update_lines(first_line, second_line)
results['updates'].append(item)
# we don't want any output from calling `softwareupdate -l`
continue
output = output.strip()
# --download-specific output