Changes to make updates after a Munki session completes visually smoother, elimnating the reload flash when possible.

This commit is contained in:
Greg Neagle
2014-02-20 15:17:35 -08:00
parent 4b35a7c962
commit f3ec0e0bbd
7 changed files with 420 additions and 304 deletions

View File

@@ -45,8 +45,8 @@ class MSUAppDelegate(NSObject):
NSApp.disableRelaunchOnLogin()
ver = NSBundle.mainBundle().infoDictionary().get('CFBundleShortVersionString')
NSLog("MSU GUI version: %s" % ver)
munki.log("MSU", "launched", "VER=%s" % ver)
NSLog("MSC GUI version: %s" % ver)
munki.log("MSC", "launched", "VER=%s" % ver)
# register for notification messages so we can be told if available
# updates change while we are open
@@ -100,6 +100,7 @@ class MSUAppDelegate(NSObject):
def forcedLogoutWarning(self, notification_obj):
NSLog(u"Managed Software Center got forced logout warning")
# TO-DO: display the logout warning!
def munkiStatusSessionEnded_(self, socketSessionResult):
NSLog(u"MunkiStatus session ended: %s" % socketSessionResult)
@@ -114,7 +115,6 @@ class MSUAppDelegate(NSObject):
# kick off an update check
# attempt to start the update check
result = munki.startUpdateCheck(suppress_apple_update_check)
result = 0
if result == 0:
self.managedsoftwareupdate_task = "manualcheck"
self.statusController.startMunkiStatusSession()

View File

@@ -56,6 +56,8 @@ class MSUMainWindowController(NSWindowController):
_status_detail = ''
_status_percent = -1
_current_page_filename = None
html_dir = None
tabControl = IBOutlet()
@@ -120,7 +122,10 @@ class MSUMainWindowController(NSWindowController):
def loadInitialView(self):
self.html_dir = msulib.setupHtmlDir()
self.load_page('category-all.html')
if self.getEffectiveUpdateList():
self.loadUpdatesPage_(self)
else:
self.loadAllSoftwarePage_(self)
self.displayUpdateCount()
@AppHelper.endSheetMethod
@@ -134,97 +139,64 @@ class MSUMainWindowController(NSWindowController):
# The managedsoftwareupdate run will have changed state preferences
# in ManagedInstalls.plist. Load the new values.
munki.reload_prefs()
alertMessageText = NSLocalizedString(u"Update check failed", u'UpdateCheckFailedTitle')
if tasktype == "installwithnologout":
alertMessageText = NSLocalizedString(
u"Install session failed", u'InstallSessionFailedTitle')
if sessionResult == -1:
# connection was dropped unexpectedly
self.window().makeKeyAndOrderFront_(self)
alert = NSAlert.alertWithMessageText_defaultButton_alternateButton_otherButton_informativeTextWithFormat_(
alertMessageText,
NSLocalizedString(u"OK", u'OKButtonTitle'),
objc.nil,
objc.nil,
NSLocalizedString(
(u"There is a configuration problem with the managed "
"software installer. The process ended unexpectedly. "
"Contact your systems administrator."), None))
alert.beginSheetModalForWindow_modalDelegate_didEndSelector_contextInfo_(
self.window(), self,
self.munkiSessionErrorAlertDidEnd_returnCode_contextInfo_, nil)
return
elif sessionResult == -2:
# socket timed out before connection
self.window().makeKeyAndOrderFront_(self)
alert = NSAlert.alertWithMessageText_defaultButton_alternateButton_otherButton_informativeTextWithFormat_(
alertMessageText,
NSLocalizedString(u"OK", u'OKButtonTitle'),
objc.nil,
objc.nil,
NSLocalizedString(
(u"There is a configuration problem with the managed "
"software installer. Could not start the process. "
"Contact your systems administrator."), None))
alert.beginSheetModalForWindow_modalDelegate_didEndSelector_contextInfo_(
self.window(), self,
self.munkiSessionErrorAlertDidEnd_returnCode_contextInfo_, nil)
return
lastCheckResult = munki.pref("LastCheckResult")
if sessionResult != 0 or lastCheckResult < 0:
OKButtonTitle = NSLocalizedString(u"OK", u'OKButtonTitle')
alertMessageText = NSLocalizedString(u"Update check failed", u'UpdateCheckFailedTitle')
if tasktype == "installwithnologout":
alertMessageText = NSLocalizedString(
u"Install session failed", u'InstallSessionFailedTitle')
if sessionResult == -1:
# connection was dropped unexpectedly
munki.log("MSC", "cant_update", "unexpected process end")
detailText = NSLocalizedString(
(u"There is a configuration problem with the managed software installer. "
"The process ended unexpectedly. Contact your systems administrator."),
u'UnexpectedSessionEndMessage')
elif sessionResult == -2:
# session never started
munki.log("MSC", "cant_update", "process did not start")
detailText = NSLocalizedString(
(u"There is a configuration problem with the managed software installer. "
"Could not start the process. Contact your systems administrator."),
u'CouldNotStartSessionMessage')
elif lastCheckResult == -1:
# server not reachable
munki.log("MSC", "cant_update", "cannot contact server")
detailText = NSLocalizedString(
(u"Managed Software Center cannot contact the update server at this time.\n"
"Try again later. If this situation continues, "
"contact your systems administrator."), u'CannotContactServerDetail')
elif lastCheckResult == -2:
# preflight failed
munki.log("MSU", "cant_update", "failed preflight")
detailText = NSLocalizedString(
(u"Managed Software Center cannot check for updates now.\n"
"Try again later. If this situation continues, "
"contact your systems administrator."), u'FailedPreflightCheckDetail')
# show the alert sheet
self.window().makeKeyAndOrderFront_(self)
alert = NSAlert.alertWithMessageText_defaultButton_alternateButton_otherButton_informativeTextWithFormat_(
alertMessageText, OKButtonTitle, nil, nil, detailText)
alert.beginSheetModalForWindow_modalDelegate_didEndSelector_contextInfo_(
self.window(), self,
self.munkiSessionErrorAlertDidEnd_returnCode_contextInfo_, nil)
return
if lastCheckResult == 1:
# there are some updates to install; are we expected to install them now?
if tasktype == 'checktheninstall':
# done checking; now try to install...
self.kickOffUpdateSession()
else:
self.resetAndReload()
if self._update_queue:
# more stuff pending? Let's do it
self._update_queue.clear()
self.updateNow()
return
elif lastCheckResult == -1:
munki.log("MSU", "cant_update", "cannot contact server")
alert = NSAlert.alertWithMessageText_defaultButton_alternateButton_otherButton_informativeTextWithFormat_(
NSLocalizedString(u"Cannot contact update server", u'CannotContactServerTitle'),
NSLocalizedString(u"OK", u'OKButtonTitle'),
nil,
nil,
NSLocalizedString(
(u"Managed Software Center cannot contact the update "
"server at this time.\n"
"Try again later. If this situation continues, "
"contact your systems administrator."), u'CannotContactServerDetail'))
alert.beginSheetModalForWindow_modalDelegate_didEndSelector_contextInfo_(
self.window(), self,
self.munkiSessionErrorAlertDidEnd_returnCode_contextInfo_, nil)
elif lastCheckResult == -2:
munki.log("MSU", "cant_update", "failed preflight")
alert = NSAlert.alertWithMessageText_defaultButton_alternateButton_otherButton_informativeTextWithFormat_(
NSLocalizedString(u"Cannot process updates", u'FailedPreflightCheckTitle'),
NSLocalizedString(u"OK", u'OKButtonTitle'),
nil,
nil,
NSLocalizedString(
(u"Managed Software Center cannot check for updates now.\n"
"Try again later. If this situation continues, "
"contact your systems administrator."), u'FailedPreflightCheckDetail'))
alert.beginSheetModalForWindow_modalDelegate_didEndSelector_contextInfo_(
self.window(), self,
self.munkiSessionErrorAlertDidEnd_returnCode_contextInfo_, nil)
else:
# all done checking and/or installing: display results
self.resetAndReload()
if self._update_queue:
# more stuff pending? Let's do it
self._update_queue.clear()
self.updateNow()
# all done checking and/or installing: display results
self.resetAndReload()
if self._update_queue:
# more stuff pending? Let's do it
self._update_queue.clear()
self.updateNow()
def resetAndReload(self):
NSLog('resetAndReload method called')
@@ -235,8 +207,30 @@ class MSUMainWindowController(NSWindowController):
self._optionalItemList = None
self._self_service_info = None
self._alertedUserToOutstandingUpdates = False
# then reload the current view
self.webView.reload_(self)
# what page are we currently viewing?
filename = self._current_page_filename
name = os.path.splitext(filename)[0]
key, p, quoted_value = name.partition('-')
value = unquote_plus(quoted_value)
if key == 'detail':
# optional item detail page
self.webView.reload_(self)
if key in ['category', 'filter', 'developer']:
# optional item list page
self.updateListPage()
if key == 'categories':
# categories page
self.updateCategoriesPage()
if key == 'myitems':
# my items page
self.updateMyItemsPage()
if key == 'updates':
# updates page
self.webView.reload_(self)
self._alertedUserToOutstandingUpdates = True
if key == 'updatedetail':
# update detail page
self.webView.reload_(self)
# update count might have changed
self.displayUpdateCount()
@@ -275,9 +269,9 @@ class MSUMainWindowController(NSWindowController):
NSLog("Error starting install session: %s" % result)
NSApp.delegate().munkiStatusSessionEnded_(2)
else:
self.markPendingItemsAsInstalling()
NSApp.delegate().managedsoftwareupdate_task = "installwithnologout"
NSApp.delegate().statusController.startMunkiStatusSession()
self.markPendingItemsAsInstalling()
def markPendingItemsAsInstalling(self):
install_info = munki.getInstallInfo()
@@ -441,96 +435,6 @@ class MSUMainWindowController(NSWindowController):
return item
return None
def processItems_asRemovals_areOptional_(
self, items, are_removals, are_optional):
for item in items:
# convert all unicode values to utf-8 strings
for key, value in item.items():
if isinstance(value, unicode):
item[key] = value.encode('utf-8')
if not 'developer' in item:
item['developer'] = msulib.guessDeveloper(item)
item['developer_sort'] = 1
if not are_removals and item['developer'] == 'Apple':
item['developer_sort'] = 0
item['icon'] = msulib.getIcon(item, self.html_dir)
if not item.get('detail_link'):
item['detail_link'] = ('updatedetail-%s.html'
% quote_plus(item['name']))
if are_removals:
item['will_be_removed'] = True
removal_text = NSLocalizedString(
u'Will be removed',
u'WillBeRemovedDisplayText').encode('utf-8')
item['version_label'] = ('<span class="warning">%s</span>'
% removal_text)
item['version_to_install'] = ''
else:
item['version_label'] = NSLocalizedString(
u'Version',
u'VersionLabel').encode('utf-8')
if 'description' in item:
item['description'] = msulib.filtered_html(item['description'])
else:
item['description'] = ''
item['due_date_sort'] = NSDate.distantFuture()
if not are_removals:
force_install_after_date = item.get('force_install_after_date')
if force_install_after_date:
item['category'] = NSLocalizedString(
u'Critical Update', u'CriticalUpdateType')
item['due_date_sort'] = force_install_after_date
# insert installation deadline into description
local_date = munki.discardTimeZoneFromDate(
force_install_after_date)
date_str = munki.stringFromDate(local_date).encode('utf-8')
forced_date_text = NSLocalizedString(
u'This item must be installed by %s',
u'ForcedDateWarning').encode('utf-8')
description = item['description']
# prepend deadline info to description.
item['description'] = (
'<span class="warning">' + forced_date_text % date_str
+ '</span><br><br>' + description)
if not 'category' in item and not are_optional:
item['category'] = NSLocalizedString(u'Managed Update',
u'ManagedUpdateType').encode('utf-8')
if are_optional:
item['hide_cancel_button'] = ''
item['cancel_or_add'] = 'cancel'
if item['status'] not in [
'will-be-removed', 'will-be-installed', 'update-will-be-installed']:
item['cancel_or_add'] = 'add'
else:
item['hide_cancel_button'] = 'hidden'
item['cancel_or_add'] = ''
# sort items that need restart highest, then logout, then other
if item.get('RestartAction') in [None, 'None']:
item['restart_action_text'] = ''
item['restart_sort'] = 2
elif item['RestartAction'] in ['RequireRestart', 'RecommendRestart']:
item['restart_sort'] = 0
item['restart_action_text'] = NSLocalizedString(
u'Restart Required', u'RequireRestartMessage').encode('utf-8')
item['restart_action_text'] += '<div class="restart-needed-icon"></div>'
elif item['RestartAction'] in ['RequireLogout', 'RecommendLogout']:
item['restart_sort'] = 1
item['restart_action_text'] = NSLocalizedString(
u'Logout Required', u'RequireLogoutMessage').encode('utf-8')
item['restart_action_text'] += '<div class="logout-needed-icon"></div>'
# sort bigger installs to the top
if item.get('installed_size'):
item['size_sort'] = -int(item['installed_size'])
item['size'] = munki.humanReadable(item['installed_size'])
elif item.get('installer_item_size'):
item['size_sort'] = -int(item['installer_item_size'])
item['size'] = munki.humanReadable(item['installer_item_size'])
else:
item['size_sort'] = 0
item['size'] = ''
def getUpdatesList(self):
if self._updateList is not None:
return self._updateList
@@ -545,11 +449,11 @@ class MSUMainWindowController(NSWindowController):
install_info = munki.getInstallInfo()
update_items.extend(install_info.get('managed_installs', []))
self.processItems_asRemovals_areOptional_(update_items, False, False)
msulib.processItems(update_items, self.html_dir)
removal_items = install_info.get('removals', [])
# TO-DO: handle the case where removal detail is suppressed
self.processItems_asRemovals_areOptional_(removal_items, True, False)
msulib.processItems(removal_items, self.html_dir, are_removals=True)
update_items.extend(removal_items)
self._updateList = sorted(update_items, key=itemgetter(
@@ -617,14 +521,14 @@ class MSUMainWindowController(NSWindowController):
optional_items = self.getOptionalInstallItems()
items = [dict(item) for item in optional_items
if item['status'] in ['will-be-installed', 'update-will-be-installed']]
self.processItems_asRemovals_areOptional_(items, False, True)
msulib.processItems(items, self.html_dir, are_optional=True)
return items
def getOptionalWillBeRemovedItems(self):
optional_items = self.getOptionalInstallItems()
items = [dict(item) for item in optional_items
if item['status'] == 'will-be-removed']
self.processItems_asRemovals_areOptional_(items, True, True)
msulib.processItems(items, self.html_dir,are_removals=True, are_optional=True)
return items
def getEffectiveUpdateList(self):
@@ -643,13 +547,6 @@ class MSUMainWindowController(NSWindowController):
optional_removals = self.getOptionalWillBeRemovedItems()
return filtered_updates + optional_installs + optional_removals
def getFooter(self, vars=None):
'''Return html footer'''
if not vars:
vars = {}
footer_template = msulib.get_template('footer_template.html')
return footer_template.safe_substitute(vars)
def get_warning_text(self):
'''Return localized text warning about forced installs and/or
logouts and/or restarts'''
@@ -670,29 +567,26 @@ class MSUMainWindowController(NSWindowController):
warning_text = restart_text
return warning_text
def build_myitems_page(self):
page_name = 'myitems.html'
page_template = msulib.get_template('myitems_template.html')
item_template = msulib.get_template('myitems_row_template.html')
def getMyItemsList(self):
'''Returns a list of optional_installs items the user has chosen
to install or to remove'''
subscribed_item_names = self.subscribedItemNames()
item_list = [item for item in self.getOptionalInstallItems()
if item['name'] in subscribed_item_names]
unsubscribed_item_names = self.unsubscribedItemNames()
items_to_remove = [item for item in self.getOptionalInstallItems()
if item['name'] in unsubscribed_item_names
and item.get('installed')]
item_list.extend(items_to_remove)
page = {}
page['my_items_header_label'] = NSLocalizedString(
u'My Items', u'MyItemsHeaderLabel').encode('utf-8')
return item_list
def build_myitems_rows(self):
item_list = self.getMyItemsList()
if item_list:
page['myitems_rows'] = ''
item_template = msulib.get_template('myitems_row_template.html')
myitems_rows = ''
for item in sorted(item_list, key=itemgetter('display_name_lower')):
page['myitems_rows'] += item_template.safe_substitute(item)
myitems_rows += item_template.safe_substitute(item)
else:
status_results_template = msulib.get_template('status_results_template.html')
alert = {}
@@ -703,9 +597,27 @@ class MSUMainWindowController(NSWindowController):
u'<a href="category-all.html">Select software to install.</a>',
u'NoInstalledSoftwareSecondaryText').encode('utf-8')
alert['hide_progress_bar'] = 'hidden'
page['myitems_rows'] = status_results_template.safe_substitute(alert)
myitems_rows = status_results_template.safe_substitute(alert)
return myitems_rows
def updateMyItemsPage(self):
'''Modifies the DOM to avoid ugly browser refresh'''
myitems_rows = self.build_myitems_rows()
document = self.webView.mainFrameDocument()
table_body_element = document.getElementById_('my_items_rows')
table_body_element.setInnerHTML_(myitems_rows)
page['footer'] = self.getFooter()
def build_myitems_page(self):
page_name = 'myitems.html'
page_template = msulib.get_template('myitems_template.html')
item_list = self.getMyItemsList()
page = {}
page['my_items_header_label'] = NSLocalizedString(
u'My Items', u'MyItemsHeaderLabel').encode('utf-8')
page['myitems_rows'] = self.build_myitems_rows()
page['footer'] = msulib.getFooter()
html = page_template.safe_substitute(page)
@@ -755,7 +667,7 @@ class MSUMainWindowController(NSWindowController):
page['install_btn_label'] = NSLocalizedString(
u'Cancel', u'CancelButtonText').encode('utf-8')
page['warning_text'] = ''
page['footer'] = self.getFooter()
page['footer'] = msulib.getFooter()
page_template = msulib.get_template('updates_template.html')
html = page_template.safe_substitute(page)
@@ -765,6 +677,41 @@ class MSUMainWindowController(NSWindowController):
f.close()
return html_file
def build_update_items_html(self):
'''probably don't need this, because we'll never call it'''
html = {}
html['update_rows'] = ''
item_list = self.getEffectiveUpdateList()
other_updates = [item for item in self.getOptionalInstallItems()
if item.get('needs_update')
and item['status'] not in
['installed', 'update-will-be-installed', 'will-be-removed']]
more_link_text = NSLocalizedString(u'More', u'MoreLinkText').encode('utf-8')
item_template = msulib.get_template('update_row_template.html')
if item_list:
for item in item_list:
html['update_rows'] += item_template.safe_substitute(
item, more_link_text=more_link_text)
elif not other_updates:
status_results_template = msulib.get_template('status_results_template.html')
alert = {}
alert['primary_status_text'] = NSLocalizedString(
u'Your software is up to date.', u'NoPendingUpdatesPrimaryText').encode('utf-8')
alert['secondary_status_text'] = NSLocalizedString(
u'There is no new software for your computer at this time.',
u'NoPendingUpdatesSecondaryText').encode('utf-8')
alert['hide_progress_bar'] = 'hidden'
alert['progress_bar_value'] = ''
html['update_rows'] = status_results_template.safe_substitute(alert)
html['other_update_rows'] = ''
if other_updates:
msulib.processItems(other_updates, self.html_dir, are_optional=True)
for item in other_updates:
html['other_update_rows'] += item_template.safe_substitute(
item, more_link_text=more_link_text)
return html
def build_updates_page(self):
'''available/pending updates'''
page_name = 'updates.html'
@@ -818,13 +765,12 @@ class MSUMainWindowController(NSWindowController):
if other_updates:
page['hide_other_updates'] = ''
self.processItems_asRemovals_areOptional_(
other_updates, False, True)
msulib.processItems(other_updates, self.html_dir, are_optional=True)
for item in other_updates:
page['other_update_rows'] += item_template.safe_substitute(
item, more_link_text=more_link_text)
page['footer'] = self.getFooter()
page['footer'] = msulib.getFooter()
page_template = msulib.get_template('updates_template.html')
html = page_template.safe_substitute(page)
@@ -840,7 +786,7 @@ class MSUMainWindowController(NSWindowController):
for item in items:
if item['name'] == item_name:
page = dict(item)
page['footer'] = self.getFooter()
page['footer'] = msulib.getFooter()
msulib.addSidebarLabels(page)
force_install_after_date = item.get('force_install_after_date')
if force_install_after_date:
@@ -911,7 +857,7 @@ class MSUMainWindowController(NSWindowController):
more_item['second_line'] = more_item.get('developer', '')
more_in_category_html += more_template.safe_substitute(more_item)
page['more_in_category'] = more_in_category_html
page['footer'] = self.getFooter()
page['footer'] = msulib.getFooter()
template = msulib.get_template('detail_template.html')
html = template.safe_substitute(page)
@@ -923,17 +869,22 @@ class MSUMainWindowController(NSWindowController):
NSLog('No detail found for %s' % item_name)
return None # TO-DO: need an error html file!
def build_categories_page(self):
all_items = self.getOptionalInstallItems()
header = 'Categories'
page_name = 'categories.html'
category_list = []
for item in all_items:
if 'category' in item and item['category'] not in category_list:
category_list.append(item['category'])
item_template = msulib.get_template('category_item_template.html')
def updateCategoriesPage(self):
'''Modifies DOM on currently displayed page to avoid nasty page refresh'''
items_html = self.build_category_items_html()
document = self.webView.mainFrameDocument()
items_div_element = document.getElementById_('optional_installs_items')
items_div_element.setInnerHTML_(items_html)
def build_category_items_html(self):
all_items = self.getOptionalInstallItems()
if all_items:
category_list = []
for item in all_items:
if 'category' in item and item['category'] not in category_list:
category_list.append(item['category'])
item_template = msulib.get_template('category_item_template.html')
item_html = ''
for category in sorted(category_list):
category_data = {}
@@ -977,6 +928,18 @@ class MSUMainWindowController(NSWindowController):
alert['hide_progress_bar'] = 'hidden'
alert['progress_bar_value'] = ''
item_html = status_results_template.safe_substitute(alert)
return item_html
def build_categories_page(self):
all_items = self.getOptionalInstallItems()
header = 'Categories'
page_name = 'categories.html'
category_list = []
for item in all_items:
if 'category' in item and item['category'] not in category_list:
category_list.append(item['category'])
item_html = self.build_category_items_html()
categories_html = '<option selected>All Categories</option>\n'
for item in sorted(category_list):
@@ -986,7 +949,7 @@ class MSUMainWindowController(NSWindowController):
page['list_items'] = item_html
page['category_items'] = categories_html
page['header_text'] = header
page['footer'] = self.getFooter()
page['footer'] = msulib.getFooter()
page['hide_showcase'] = 'hidden'
html_template = msulib.get_template('list_template.html')
html = html_template.safe_substitute(page)
@@ -997,28 +960,39 @@ class MSUMainWindowController(NSWindowController):
f.close()
return html_file
def build_list_page(self, category=None, developer=None, filter=None):
def updateListPage(self):
'''Modifies DOM on currently displayed page to avoid nasty page refresh'''
filename = self._current_page_filename
if not filename:
NSLog('updateListPage unexpected error: no _current_page_filename')
return
name = os.path.splitext(filename)[0]
key, p, quoted_value = name.partition('-')
category = None
filter = None
developer = None
value = unquote_plus(quoted_value)
if key == 'category':
if value != 'all':
category = value
elif key == 'filter':
filter = value
elif key == 'developer':
developer = value
else:
NSLog('updateListPage unexpected error: _current_page_filename is %s' %
filename)
return
NSLog('updating with category: %s, developer; %s, filter: %s' %
(category, developer, filter))
items_html = self.build_list_page_items_html(
category=category, developer=developer, filter=filter)
document = self.webView.mainFrameDocument()
items_div_element = document.getElementById_('optional_installs_items')
items_div_element.setInnerHTML_(items_html)
def build_list_page_items_html(self, category=None, developer=None, filter=None):
items = self.getOptionalInstallItems()
header = 'All items'
page_name = 'category-all.html'
if category == 'all':
category = None
if category:
header = category
page_name = 'category-%s.html' % quote_plus(category)
if developer:
header = developer
page_name = 'developer-%s.html' % quote_plus(developer)
if filter:
header = 'Search results for %s' % filter
page_name = 'filter-%s.html' % quote_plus(filter)
category_list = []
for item in items:
if 'category' in item and item['category'] not in category_list:
category_list.append(item['category'])
item_html = ''
if filter:
filterStr = filter.encode('utf-8')
@@ -1077,6 +1051,32 @@ class MSUMainWindowController(NSWindowController):
alert['hide_progress_bar'] = 'hidden'
alert['progress_bar_value'] = ''
item_html = status_results_template.safe_substitute(alert)
return item_html
def build_list_page(self, category=None, developer=None, filter=None):
items = self.getOptionalInstallItems()
header = 'All items'
page_name = 'category-all.html'
if category == 'all':
category = None
if category:
header = category
page_name = 'category-%s.html' % quote_plus(category)
if developer:
header = developer
page_name = 'developer-%s.html' % quote_plus(developer)
if filter:
header = 'Search results for %s' % filter
page_name = 'filter-%s.html' % quote_plus(filter)
category_list = []
for item in items:
if 'category' in item and item['category'] not in category_list:
category_list.append(item['category'])
item_html = self.build_list_page_items_html(
category=category, developer=developer, filter=filter)
if category:
categories_html = '<option>All Categories</option>\n'
@@ -1093,7 +1093,7 @@ class MSUMainWindowController(NSWindowController):
page['list_items'] = item_html
page['category_items'] = categories_html
page['header_text'] = header
page['footer'] = self.getFooter()
page['footer'] = msulib.getFooter()
if category or filter or developer:
page['hide_showcase'] = 'hidden'
else:
@@ -1114,7 +1114,8 @@ class MSUMainWindowController(NSWindowController):
self.webView.mainFrame().loadRequest_(request)
def build_page(self, filename):
NSLog('build_page %s' % filename)
#NSLog('build_page %s' % filename)
self._current_page_filename = filename
name = os.path.splitext(filename)[0]
key, p, quoted_value = name.partition('-')
value = unquote_plus(quoted_value)
@@ -1140,11 +1141,13 @@ class MSUMainWindowController(NSWindowController):
# we generate each page dynamically; we want things
# that are changed in one page view to be reflected
# immediately in all page views
identifier = 'com.googlecode.munki.ManagedSoftwareUpdate'
identifier = 'com.googlecode.munki.ManagedSoftwareCenter'
prefs = WebPreferences.alloc().initWithIdentifier_(identifier)
prefs.setUsesPageCache_(False)
self.webView.setPreferencesIdentifier_(identifier)
##### WebView delegate methods #####
def webView_decidePolicyForNewWindowAction_request_newFrameName_decisionListener_(
self, webView, actionInformation, request, frameName, listener):
# open link in default browser
@@ -1214,6 +1217,8 @@ class MSUMainWindowController(NSWindowController):
return NO # this selector is NOT _excluded_ from scripting, so it can be called.
return YES # disallow everything else
#### handling DOM UI elements ####
def installButtonClicked(self):
# this method is called from JavaScript when the user
# clicks the Install All button in the Updates view
@@ -1340,10 +1345,10 @@ class MSUMainWindowController(NSWindowController):
# make some new HTML for the updated item
managed_update_names = munki.getInstallInfo().get('managed_updates', [])
if item['name'] in managed_update_names:
self.processItems_asRemovals_areOptional_([item], False, False)
msulib.processItems([item], self.html_dir)
else:
# possible to change the status
self.processItems_asRemovals_areOptional_([item], False, True)
msulib.processItems([item], self.html_dir, are_optional=True)
item_template = msulib.get_template('update_row_template.html')
more_link_text = NSLocalizedString(u'More', u'MoreLinkText').encode('utf-8')
@@ -1420,7 +1425,8 @@ class MSUMainWindowController(NSWindowController):
status_line.setInnerText_(item['status_text'])
status_line.setClassName_('status %s' % item['status'])
if not self._update_in_progress:
if item['status'] in ['will-be-installed', 'update-will-be-installed', 'will-be-removed']:
if item['status'] in ['will-be-installed', 'update-will-be-installed',
'will-be-removed']:
self.updateNow()
def updateDOMforOptionalItem(self, item):
@@ -1518,6 +1524,16 @@ class MSUMainWindowController(NSWindowController):
'''return True if current tab selected is updates'''
selectedCell = self.tabControl.selectedCell()
return (selectedCell is not None and selectedCell.tag() == 4)
def currentPageIsMyItemsPage(self):
'''return True if current tab selected is updates'''
selectedCell = self.tabControl.selectedCell()
return (selectedCell is not None and selectedCell.tag() == 3)
def currentPageIsCategoriesPage(self):
'''return True if current tab selected is updates'''
selectedCell = self.tabControl.selectedCell()
return (selectedCell is not None and selectedCell.tag() == 2)
##### required status methods #####
@@ -1561,6 +1577,7 @@ class MSUMainWindowController(NSWindowController):
def restartAlertDidEnd_returnCode_contextInfo_(
self, alert, returncode, contextinfo):
self._status_restartAlertDismissed = 1
# TO-DO: initiate actual restart
def doRestartAlert(self):
self._status_restartAlertDismissed = 0
@@ -1603,56 +1620,60 @@ class MSUMainWindowController(NSWindowController):
return self._status_stopBtnState
def hideStopButton(self):
if not self._status_stopBtnState:
self._status_stopBtnHidden = True
document = self.webView.mainFrameDocument()
spinner = document.getElementById_('updates-progress-spinner')
if spinner: # we are displaying the updates status page
install_btn = document.getElementById_('install-all-button-text')
if install_btn:
btn_classes = install_btn.className().split(' ')
if not 'hidden' in btn_classes:
btn_classes.append('hidden')
install_btn.setClassName_(' '.join(btn_classes))
if self._status_stopBtnState:
return
self._status_stopBtnHidden = True
document = self.webView.mainFrameDocument()
spinner = document.getElementById_('updates-progress-spinner')
if spinner: # we are displaying the updates status page
install_btn = document.getElementById_('install-all-button-text')
if install_btn:
btn_classes = install_btn.className().split(' ')
if not 'hidden' in btn_classes:
btn_classes.append('hidden')
install_btn.setClassName_(' '.join(btn_classes))
def showStopButton(self):
if not self._status_stopBtnState:
self._status_stopBtnHidden = False
document = self.webView.mainFrameDocument()
spinner = document.getElementById_('updates-progress-spinner')
if spinner: # we are displaying the updates status page
install_btn = document.getElementById_('install-all-button-text')
if install_btn:
btn_classes = install_btn.className().split(' ')
if 'hidden' in btn_classes:
btn_classes.remove('hidden')
install_btn.setClassName_(' '.join(btn_classes))
if self._status_stopBtnState:
return
self._status_stopBtnHidden = False
document = self.webView.mainFrameDocument()
spinner = document.getElementById_('updates-progress-spinner')
if spinner: # we are displaying the updates status page
install_btn = document.getElementById_('install-all-button-text')
if install_btn:
btn_classes = install_btn.className().split(' ')
if 'hidden' in btn_classes:
btn_classes.remove('hidden')
install_btn.setClassName_(' '.join(btn_classes))
def enableStopButton(self):
if not self._status_stopBtnState:
self._status_stopBtnDisabled = False
document = self.webView.mainFrameDocument()
spinner = document.getElementById_('updates-progress-spinner')
if spinner: # we are displaying the updates status page
install_btn = document.getElementById_('install-all-button-text')
if install_btn:
btn_classes = install_btn.className().split(' ')
if 'installed-not-removable' in btn_classes:
btn_classes.remove('installed-not-removable')
install_btn.setClassName_(' '.join(btn_classes))
if self._status_stopBtnState:
return
self._status_stopBtnDisabled = False
document = self.webView.mainFrameDocument()
spinner = document.getElementById_('updates-progress-spinner')
if spinner: # we are displaying the updates status page
install_btn = document.getElementById_('install-all-button-text')
if install_btn:
btn_classes = install_btn.className().split(' ')
if 'installed-not-removable' in btn_classes:
btn_classes.remove('installed-not-removable')
install_btn.setClassName_(' '.join(btn_classes))
def disableStopButton(self):
if not self._status_stopBtnState:
self._status_stopBtnDisabled = True
document = self.webView.mainFrameDocument()
spinner = document.getElementById_('updates-progress-spinner')
if spinner: # we are displaying the updates status page
install_btn = document.getElementById_('install-all-button-text')
if install_btn:
btn_classes = install_btn.className().split(' ')
if not 'installed-not-removable' in btn_classes:
btn_classes.append('installed-not-removable')
install_btn.setClassName_(' '.join(btn_classes))
if self._status_stopBtnState:
return
self._status_stopBtnDisabled = True
document = self.webView.mainFrameDocument()
spinner = document.getElementById_('updates-progress-spinner')
if spinner: # we are displaying the updates status page
install_btn = document.getElementById_('install-all-button-text')
if install_btn:
btn_classes = install_btn.className().split(' ')
if not 'installed-not-removable' in btn_classes:
btn_classes.append('installed-not-removable')
install_btn.setClassName_(' '.join(btn_classes))
def getRestartAlertDismissed(self):
return self._status_restartAlertDismissed

View File

@@ -49,7 +49,6 @@ class MSUStatusController(NSObject):
NSNotificationSuspensionBehaviorDeliverImmediately)
self.receiving_notifications = True
def unregisterForNotifications(self):
'''Tell the DistributedNotificationCenter to stop sending us notifications'''
NSDistributedNotificationCenter.defaultCenter().removeObserver_(self)
@@ -104,11 +103,11 @@ class MSUStatusController(NSObject):
sessionResult = UNEXPECTEDLY_QUIT
else:
sessionResult = NEVER_STARTED
self.performSelectorOnMainThread_withObject_waitUntilDone_(
self.sessionEnded_, sessionResult, NO)
break
time.sleep(5)
if self.session_started:
self.performSelectorOnMainThread_withObject_waitUntilDone_(
self.sessionEnded_, sessionResult, NO)
# Clean up autorelease pool
del pool

View File

@@ -23,6 +23,7 @@ import sys
import shutil
from string import Template
from urllib import quote_plus
from HTMLParser import HTMLParser
@@ -30,6 +31,7 @@ from Foundation import *
from AppKit import *
import FoundationPlist
import munki
class MSUHTMLFilter(HTMLParser):
'''Filters HTML and HTML fragments for use inside description paragraphs'''
@@ -422,3 +424,100 @@ def get_template(template_name):
except (IOError, OSError):
return None
def getFooter(vars=None):
'''Return html footer'''
if not vars:
vars = {}
footer_template = get_template('footer_template.html')
return footer_template.safe_substitute(vars)
def processItems(items, html_dir, are_removals=False, are_optional=False):
for item in items:
# convert all unicode values to utf-8 strings
for key, value in item.items():
if isinstance(value, unicode):
item[key] = value.encode('utf-8')
if not 'developer' in item:
item['developer'] = guessDeveloper(item)
item['developer_sort'] = 1
if not are_removals and item['developer'] == 'Apple':
item['developer_sort'] = 0
item['icon'] = getIcon(item, html_dir)
if not item.get('detail_link'):
item['detail_link'] = ('updatedetail-%s.html'
% quote_plus(item['name']))
if are_removals:
item['will_be_removed'] = True
removal_text = NSLocalizedString(
u'Will be removed',
u'WillBeRemovedDisplayText').encode('utf-8')
item['version_label'] = ('<span class="warning">%s</span>'
% removal_text)
item['version_to_install'] = ''
else:
item['version_label'] = NSLocalizedString(
u'Version',
u'VersionLabel').encode('utf-8')
if 'description' in item:
item['description'] = filtered_html(item['description'])
else:
item['description'] = ''
item['due_date_sort'] = NSDate.distantFuture()
if not are_removals:
force_install_after_date = item.get('force_install_after_date')
if force_install_after_date:
item['category'] = NSLocalizedString(
u'Critical Update', u'CriticalUpdateType')
item['due_date_sort'] = force_install_after_date
# insert installation deadline into description
local_date = munki.discardTimeZoneFromDate(
force_install_after_date)
date_str = munki.stringFromDate(local_date).encode('utf-8')
forced_date_text = NSLocalizedString(
u'This item must be installed by %s',
u'ForcedDateWarning').encode('utf-8')
description = item['description']
# prepend deadline info to description.
item['description'] = (
'<span class="warning">' + forced_date_text % date_str
+ '</span><br><br>' + description)
if not 'category' in item and not are_optional:
item['category'] = NSLocalizedString(u'Managed Update',
u'ManagedUpdateType').encode('utf-8')
if are_optional:
item['hide_cancel_button'] = ''
item['cancel_or_add'] = 'cancel'
if item['status'] not in [
'will-be-removed', 'will-be-installed', 'update-will-be-installed']:
item['cancel_or_add'] = 'add'
else:
item['hide_cancel_button'] = 'hidden'
item['cancel_or_add'] = ''
# sort items that need restart highest, then logout, then other
if item.get('RestartAction') in [None, 'None']:
item['restart_action_text'] = ''
item['restart_sort'] = 2
elif item['RestartAction'] in ['RequireRestart', 'RecommendRestart']:
item['restart_sort'] = 0
item['restart_action_text'] = NSLocalizedString(
u'Restart Required', u'RequireRestartMessage').encode('utf-8')
item['restart_action_text'] += '<div class="restart-needed-icon"></div>'
elif item['RestartAction'] in ['RequireLogout', 'RecommendLogout']:
item['restart_sort'] = 1
item['restart_action_text'] = NSLocalizedString(
u'Logout Required', u'RequireLogoutMessage').encode('utf-8')
item['restart_action_text'] += '<div class="logout-needed-icon"></div>'
# sort bigger installs to the top
if item.get('installed_size'):
item['size_sort'] = -int(item['installed_size'])
item['size'] = munki.humanReadable(item['installed_size'])
elif item.get('installer_item_size'):
item['size_sort'] = -int(item['installer_item_size'])
item['size'] = munki.humanReadable(item['installer_item_size'])
else:
item['size_sort'] = 0
item['size'] = ''

View File

@@ -113,7 +113,7 @@ def reload_prefs():
this needs to be run after returning from MunkiStatus"""
CFPreferencesAppSynchronize(BUNDLE_ID)
DEFAULT_GUI_CACHE_AGE_SECS = 60
DEFAULT_GUI_CACHE_AGE_SECS = 600
def pref(pref_name):
"""Return a preference. Since this uses CFPreferencesCopyAppValue,

View File

@@ -32,7 +32,7 @@
</table>
<div class='lockup-container installations purchases subscribed-items'>
<table class="no-header">
<tbody>
<tbody id='my_items_rows'>
${myitems_rows}
</tbody>
</table>

View File

@@ -895,10 +895,7 @@ def main():
if options.verbose:
print 'Done.'
if options.manualcheck:
# just quit munkistatus; Managed Software Update will notify
munkistatus.quit()
elif notify_user:
if notify_user:
# it may have been more than a minute since we ran our
# original updatecheck so tickle the updatecheck time
# so MSU.app knows to display results immediately