diff --git a/code/Managed Software Center/Managed Software Center/MSUAppDelegate.py b/code/Managed Software Center/Managed Software Center/MSUAppDelegate.py index e5c1524b..4f59c6f1 100644 --- a/code/Managed Software Center/Managed Software Center/MSUAppDelegate.py +++ b/code/Managed Software Center/Managed Software Center/MSUAppDelegate.py @@ -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() diff --git a/code/Managed Software Center/Managed Software Center/MSUMainWindowController.py b/code/Managed Software Center/Managed Software Center/MSUMainWindowController.py index 9e12803a..60e3af73 100644 --- a/code/Managed Software Center/Managed Software Center/MSUMainWindowController.py +++ b/code/Managed Software Center/Managed Software Center/MSUMainWindowController.py @@ -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'] = ('%s' - % 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'] = ( - '' + forced_date_text % date_str - + '

' + 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'] += '
' - 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'] += '
' - - # 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'Select software to install.', 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 = '\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 = '\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 diff --git a/code/Managed Software Center/Managed Software Center/MSUStatusController.py b/code/Managed Software Center/Managed Software Center/MSUStatusController.py index 48c847a0..fad8eafa 100644 --- a/code/Managed Software Center/Managed Software Center/MSUStatusController.py +++ b/code/Managed Software Center/Managed Software Center/MSUStatusController.py @@ -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 diff --git a/code/Managed Software Center/Managed Software Center/msulib.py b/code/Managed Software Center/Managed Software Center/msulib.py index 7acab699..26002d2d 100644 --- a/code/Managed Software Center/Managed Software Center/msulib.py +++ b/code/Managed Software Center/Managed Software Center/msulib.py @@ -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'] = ('%s' + % 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'] = ( + '' + forced_date_text % date_str + + '

' + 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'] += '
' + 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'] += '
' + + # 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'] = '' diff --git a/code/Managed Software Center/Managed Software Center/munki.py b/code/Managed Software Center/Managed Software Center/munki.py index c4b22d41..032deac8 100644 --- a/code/Managed Software Center/Managed Software Center/munki.py +++ b/code/Managed Software Center/Managed Software Center/munki.py @@ -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, diff --git a/code/Managed Software Center/Managed Software Center/templates/myitems_template.html b/code/Managed Software Center/Managed Software Center/templates/myitems_template.html index 7b711aa8..37b906d5 100644 --- a/code/Managed Software Center/Managed Software Center/templates/myitems_template.html +++ b/code/Managed Software Center/Managed Software Center/templates/myitems_template.html @@ -32,7 +32,7 @@
- + ${myitems_rows}
diff --git a/code/client/managedsoftwareupdate b/code/client/managedsoftwareupdate index 791c5529..ac2e2621 100755 --- a/code/client/managedsoftwareupdate +++ b/code/client/managedsoftwareupdate @@ -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