def updateDOMforOptionalItem(self, item): '''Update displayed status of an item''' document = self.webView.mainFrameDocument() if not document: return status_line = document.getElementById_('%s_status_text' % item['name']) status_text_span = document.getElementById_('%s_status_text_span' % item['name']) btn = document.getElementById_('%s_action_button_text' % item['name']) if not btn or not status_line: msclog.debug_log('ERROR in updateDOMforOptionalItem: could not find items in DOM') return btn_classes = btn.className().split(' ') # filter out status class btn_classes = [class_name for class_name in btn_classes if class_name in ['msc-button-inner', 'large', 'small', 'install-updates']] btn_classes.append(item['status']) btn.setClassName_(' '.join(btn_classes)) if 'install-updates' in btn_classes: btn.setInnerText_(item['myitem_action_text']) elif 'large' in btn_classes: btn.setInnerText_(item['long_action_text']) else: btn.setInnerText_(item['short_action_text']) if status_text_span: status_text_span.setInnerText_(item['status_text']) status_line.setClassName_(item['status'])
def webView_resource_willSendRequest_redirectResponse_fromDataSource_( self, sender, identifier, request, redirectResponse, dataSource): '''By reacting to this delegate notification, we can build the page the WebView wants to load''' msclog.debug_log('webView_resource_willSendRequest_redirectResponse_fromDataSource_') url = request.URL() msclog.debug_log('Got URL scheme: %s' % url.scheme()) if url.scheme() == NSURLFileScheme: msclog.debug_log(u'Request path is %s' % url.path()) if self.html_dir in url.path(): msclog.debug_log(u'request for %s' % url.path()) filename = unicode(url.lastPathComponent()) if (filename.endswith(u'.html') and (filename.startswith(u'detail-') or filename.startswith(u'category-') or filename.startswith(u'filter-') or filename.startswith(u'developer-') or filename.startswith(u'updatedetail-') or filename == u'myitems.html' or filename == u'updates.html' or filename == u'categories.html')): try: mschtml.build_page(filename) except BaseException, err: msclog.debug_log(u'Could not build page for %s: %s' % (filename, err))
def updateListPage(self): '''Update the optional items list page with current data. Modifies the DOM to avoid ugly browser refresh''' page_url = self.webView.mainFrameURL() filename = NSURL.URLWithString_(page_url).lastPathComponent() name = os.path.splitext(filename)[0] key, p, value = name.partition('-') category = None filter = None developer = None if key == 'category': if value != 'all': category = value elif key == 'filter': filter = value elif key == 'developer': developer = value else: msclog.debug_log('updateListPage unexpected error: _current_page_filename is %s' % filename) return msclog.debug_log( 'updating software list page with category: %s, developer; %s, filter: %s' % (category, developer, filter)) items_html = mschtml.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 load_page(self, url_fragment): '''Tells the WebView to load the appropriate page''' msclog.debug_log('load_page request for %s' % url_fragment) html_file = os.path.join(self.html_dir, url_fragment) request = NSURLRequest.requestWithURL_cachePolicy_timeoutInterval_( NSURL.fileURLWithPath_(html_file), NSURLRequestReloadIgnoringLocalCacheData, 10) self.webView.mainFrame().loadRequest_(request)
def kickOffInstallSession(self): '''start an update install/removal session''' # check for need to logout, restart, firmware warnings # warn about blocking applications, etc... # then start an update session if MunkiItems.updatesRequireRestart() or MunkiItems.updatesRequireLogout(): # switch to updates view self.loadUpdatesPage_(self) # warn about need to logout or restart self.alert_controller.confirmUpdatesAndInstall() else: if self.alert_controller.alertedToBlockingAppsRunning(): self.loadUpdatesPage_(self) return if self.alert_controller.alertedToRunningOnBatteryAndCancelled(): self.loadUpdatesPage_(self) return self.managedsoftwareupdate_task = None msclog.log("user", "install_without_logout") self._update_in_progress = True self.displayUpdateCount() self.setStatusViewTitle_(NSLocalizedString(u"Updating...", u"Updating message")) result = munki.justUpdate() if result: msclog.debug_log("Error starting install session: %s" % result) self.munkiStatusSessionEnded_(2) else: self.managedsoftwareupdate_task = "installwithnologout" NSApp.delegate().statusController.startMunkiStatusSession() self.markPendingItemsAsInstalling()
def confirmUpdatesAndInstall(self): '''Make sure it's OK to proceed with installing if logout or restart is required''' if self.alertedToMultipleUsers(): return elif MunkiItems.updatesRequireRestart(): alert = NSAlert.alertWithMessageText_defaultButton_alternateButton_otherButton_informativeTextWithFormat_( NSLocalizedString(u"Restart Required", u"Restart Required title"), NSLocalizedString(u"Log out and update", u"Log out and Update button text"), NSLocalizedString(u"Cancel", u"Cancel button title/short action text"), nil, u"%@", NSLocalizedString( (u"A restart is required after updating. Please be patient " "as there may be a short delay at the login window. Log " "out and update now?"), u"Restart Required detail")) alert.beginSheetModalForWindow_modalDelegate_didEndSelector_contextInfo_( self.window, self, self.logoutAlertDidEnd_returnCode_contextInfo_, nil) elif MunkiItems.updatesRequireLogout() or munki.installRequiresLogout(): alert = NSAlert.alertWithMessageText_defaultButton_alternateButton_otherButton_informativeTextWithFormat_( NSLocalizedString(u"Logout Required", u"Logout Required title"), NSLocalizedString(u"Log out and update", u"Log out and Update button text"), NSLocalizedString(u"Cancel", u"Cancel button title/short action text"), nil, u"%@", NSLocalizedString( (u"A logout is required before updating. Please be patient " "as there may be a short delay at the login window. Log " "out and update now?"), u"Logout Required detail")) alert.beginSheetModalForWindow_modalDelegate_didEndSelector_contextInfo_( self.window, self, self.logoutAlertDidEnd_returnCode_contextInfo_, nil) else: # we shouldn't have been invoked if neither a restart or logout was required msclog.debug_log( 'confirmUpdatesAndInstall was called but no restart or logout was needed')
def build_updatedetail_page(identifier): '''Build detail page for a non-optional update''' items = MunkiItems.getUpdateList() page_name = u'updatedetail-%s.html' % identifier name, sep, version = identifier.partition('--version-') for item in items: if item['name'] == name and item.get('version_to_install', '') == version: page = MunkiItems.UpdateItem(item) escapeAndQuoteCommonFields(page) addDetailSidebarLabels(page) force_install_after_date = item.get('force_install_after_date') if force_install_after_date: try: local_date = munki.discardTimeZoneFromDate( force_install_after_date) date_str = munki.shortRelativeStringFromDate( local_date) page['dueLabel'] += u' ' page['short_due_date'] = date_str except munki.BadDateError: # some issue with the stored date msclog.debug_log('Problem with force_install_after_date for %s' % identifier) page['dueLabel'] = u'' page['short_due_date'] = u'' else: page['dueLabel'] = u'' page['short_due_date'] = u'' footer = get_template('footer_template.html', raw=True) generate_page(page_name, 'updatedetail_template.html', page, footer=footer) return # if we get here we didn't find any item matching identifier msclog.debug_log('No update detail found for %s' % identifier) build_item_not_found_page(page_name)
def checkProcess_(self, timer): '''Monitors managedsoftwareupdate process for failure to start or unexpected exit, so we're not waiting around forever if managedsoftwareupdate isn't running.''' PYTHON_SCRIPT_NAME = u'managedsoftwareupdate' NEVER_STARTED = -2 UNEXPECTEDLY_QUIT = -1 if self.session_started: if self.got_status_update: # we got a status update since we last checked; no need to # check the process table self.timeout_counter = 6 self.saw_process = True # clear the flag so we have to get another status update self.got_status_update = False elif munki.pythonScriptRunning(PYTHON_SCRIPT_NAME): self.timeout_counter = 6 self.saw_process = True else: msclog.debug_log('managedsoftwareupdate not running...') self.timeout_counter -= 1 if self.timeout_counter == 0: msclog.debug_log('Timed out waiting for managedsoftwareupdate.') if self.saw_process: self.sessionEnded_(UNEXPECTEDLY_QUIT) else: self.sessionEnded_(NEVER_STARTED)
def myItemsActionButtonClicked_(self, item_name): '''this method is called from JavaScript when the user clicks the Install/Remove/Cancel button in the My Items view''' document = self.webView.mainFrameDocument() item = MunkiItems.optionalItemForName_(item_name) status_line = document.getElementById_('%s_status_text' % item_name) btn = document.getElementById_('%s_action_button_text' % item_name) if not item or not btn or not status_line: msclog.debug_log('User clicked MyItems action button for %s' % item_name) msclog.debug_log('Unexpected error finding HTML elements') return prior_status = item['status'] item.update_status() self.displayUpdateCount() if item['status'] == 'not-installed': # we removed item from list of things to install # now remove from display table_row = document.getElementById_('%s_myitems_table_row' % item_name) if table_row: node = table_row.parentNode().removeChild_(table_row) else: btn.setInnerText_(item['myitem_action_text']) status_line.setInnerText_(item['status_text']) status_line.setClassName_('status %s' % item['status']) if item['status'] in ['install-requested', 'removal-requested']: self._alertedUserToOutstandingUpdates = False if not self._update_in_progress: self.updateNow() elif prior_status in ['will-be-installed', 'update-will-be-installed', 'will-be-removed']: # cancelled a pending install or removal; should run an updatecheck self.checkForUpdates(suppress_apple_update_check=True)
def forcedLogoutWarning(self, notification_obj): '''Received a logout warning from the logouthelper for an upcoming forced install''' msclog.debug_log(u"Managed Software Center got forced logout warning") # got a notification of an upcoming forced install # switch to updates view, then display alert self.loadUpdatesPage_(self) self.alert_controller.forcedLogoutWarning(notification_obj)
def build_detail_page(item_name): '''Build page showing detail for a single optional item''' msclog.debug_log('build_detail_page for %s' % item_name) items = MunkiItems.getOptionalInstallItems() page_name = u'detail-%s.html' % item_name for item in items: if item['name'] == item_name: page = MunkiItems.OptionalItem(item) addDetailSidebarLabels(page) # make "More in CategoryFoo" list page['hide_more_in_category'] = u'hidden' more_in_category_html = u'' more_in_category = [] if item.get('category'): category = item['category'] page['category_link'] = u'category-%s.html' % quote(category) more_in_category = [a for a in items if a.get('category') == category and a != item and a.get('status') != 'installed'] if more_in_category: page['hide_more_in_category'] = u'' page['moreInCategoryLabel'] = page['moreInCategoryLabel'] % page['category'] shuffle(more_in_category) more_template = get_template('detail_more_items_template.html') for more_item in more_in_category[:4]: 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 # make "More by DeveloperFoo" list page['hide_more_by_developer'] = u'hidden' more_by_developer_html = u'' more_by_developer = [] if item.get('developer'): developer = item['developer'] page['developer_link'] = u'developer-%s.html' % quote(developer) more_by_developer = [a for a in items if a.get('developer') == developer and a != item and a not in more_in_category and a.get('status') != 'installed'] if more_by_developer: page['hide_more_by_developer'] = u'' page['moreByDeveloperLabel'] = ( page['moreByDeveloperLabel'] % developer) shuffle(more_by_developer) more_template = get_template( 'detail_more_items_template.html') for more_item in more_by_developer[:4]: more_item['second_line'] = more_item.get('category', '') more_by_developer_html += more_template.safe_substitute(more_item) page['more_by_developer'] = more_by_developer_html footer = get_template('footer_template.html', raw=True) generate_page(page_name, 'detail_template.html', page, footer=footer) return msclog.debug_log('No detail found for %s' % item_name) build_item_not_found_page(page_name)
def write_page(page_name, html): '''write html to page_name in our local html directory''' html_file = os.path.join(msclib.html_dir(), page_name) try: f = open(html_file, 'w') f.write(html.encode('utf-8')) f.close() except (OSError, IOError), err: msclog.debug_log('write_page error: %s', str(err)) raise
def openMunkiURL(self, url): '''Display page associated with munki:// url''' parsed_url = urlparse(url) if parsed_url.scheme != 'munki': msclog.debug_log("URL %s has unsupported scheme" % url) return filename = mschtml.unquote(parsed_url.netloc) # add .html if no extension if not os.path.splitext(filename)[1]: filename += u'.html' if filename.endswith(u'.html'): mschtml.build_page(filename) self.mainWindowController.load_page(filename) else: msclog.debug_log("%s doesn't have a valid extension. Prevented from opening" % url)
def openMunkiURL(self, url): '''Display page associated with munki:// url''' parsed_url = urlparse(url) if parsed_url.scheme != 'munki': msclog.debug_log("URL %s has unsupported scheme" % url) return filename = mschtml.unquote(parsed_url.netloc) # add .html if no extension if not os.path.splitext(filename)[1]: filename += u'.html' if filename.endswith(u'.html'): mschtml.build_page(filename) self.mainWindowController.load_page(filename) else: msclog.debug_log( "%s doesn't have a valid extension. Prevented from opening" % url)
def get_custom_resources(): '''copies custom resources into our html dir''' if not _html_dir: return managed_install_dir = munki.pref('ManagedInstallDir') source_path = os.path.join(managed_install_dir, 'client_resources/custom.zip') if os.path.exists(source_path): dest_path = os.path.join(_html_dir, 'custom') if os.path.exists(dest_path): try: shutil.rmtree(dest_path, ignore_errors=True) except (OSError, IOError), err: msclog.debug_log('Error clearing %s: %s' % (dest_path, err)) if not os.path.exists(dest_path): try: os.mkdir(dest_path) except (OSError, IOError), err: msclog.debug_log('Error creating %s: %s' % (dest_path, err))
def openURL_withReplyEvent_(self, event, replyEvent): '''Handle openURL messages''' keyDirectObject = struct.unpack(">i", "----")[0] url = event.paramDescriptorForKeyword_(keyDirectObject).stringValue().decode('utf8') msclog.log("MSU", "Called by external URL: %s", url) parsed_url = urlparse(url) if parsed_url.scheme != 'munki': msclog.debug_log("URL %s has unsupported scheme" % url) return filename = mschtml.unquote(parsed_url.netloc) # add .html if no extension if not os.path.splitext(filename)[1]: filename += u'.html' if filename.endswith(u'.html'): mschtml.build_page(filename) self.mainWindowController.load_page(filename) else: msclog.debug_log("%s doesn't have a valid extension. Prevented from opening" % url)
def confirmUpdatesAndInstall(self): '''Make sure it's OK to proceed with installing if logout or restart is required''' if self.alertedToMultipleUsers(): return elif MunkiItems.updatesRequireRestart(): alert = NSAlert.alertWithMessageText_defaultButton_alternateButton_otherButton_informativeTextWithFormat_( NSLocalizedString(u"Restart Required", u"Restart Required title"), NSLocalizedString(u"Log out and update", u"Log out and Update button text"), NSLocalizedString(u"Cancel", u"Cancel button title/short action text"), nil, u"%@", NSLocalizedString( u"A restart is required after updating. Please be patient " "as there may be a short delay at the login window. Log " "out and update now?", u"Restart Required detail")) alert.beginSheetModalForWindow_modalDelegate_didEndSelector_contextInfo_( self.window, self, self.logoutAlertDidEnd_returnCode_contextInfo_, nil) elif MunkiItems.updatesRequireLogout() or munki.installRequiresLogout( ): alert = NSAlert.alertWithMessageText_defaultButton_alternateButton_otherButton_informativeTextWithFormat_( NSLocalizedString(u"Logout Required", u"Logout Required title"), NSLocalizedString(u"Log out and update", u"Log out and Update button text"), NSLocalizedString(u"Cancel", u"Cancel button title/short action text"), nil, u"%@", NSLocalizedString( u"A logout is required before updating. Please be patient " "as there may be a short delay at the login window. Log " "out and update now?", u"Logout Required detail")) alert.beginSheetModalForWindow_modalDelegate_didEndSelector_contextInfo_( self.window, self, self.logoutAlertDidEnd_returnCode_contextInfo_, nil) else: # we shouldn't have been invoked if neither a restart or logout was # required msclog.debug_log( 'confirmUpdatesAndInstall was called but no restart or logout ' 'was needed')
def openURL_withReplyEvent_(self, event, replyEvent): '''Handle openURL messages''' keyDirectObject = struct.unpack(">i", "----")[0] url = event.paramDescriptorForKeyword_( keyDirectObject).stringValue().decode('utf8') msclog.log("MSU", "Called by external URL: %s", url) parsed_url = urlparse(url) if parsed_url.scheme != 'munki': msclog.debug_log("URL %s has unsupported scheme" % url) return filename = mschtml.unquote(parsed_url.netloc) # add .html if no extension if not os.path.splitext(filename)[1]: filename += u'.html' if filename.endswith(u'.html'): mschtml.build_page(filename) self.mainWindowController.load_page(filename) else: msclog.debug_log( "%s doesn't have a valid extension. Prevented from opening" % url)
def markPendingItemsAsInstalling(self): '''While an install/removal session is happening, mark optional items that are being installed/removed with the appropriate status''' msclog.debug_log('marking pendingItems as installing') install_info = munki.getInstallInfo() items_to_be_installed_names = [item['name'] for item in install_info.get('managed_installs', [])] items_to_be_removed_names = [item['name'] for item in install_info.get('removals', [])] for name in items_to_be_installed_names: # remove names for user selections since we are installing MunkiItems.user_install_selections.discard(name) for name in items_to_be_removed_names: # remove names for user selections since we are removing MunkiItems.user_removal_selections.discard(name) for item in MunkiItems.getOptionalInstallItems(): new_status = None if item['name'] in items_to_be_installed_names: msclog.debug_log('Setting status for %s to "installing"' % item['name']) new_status = u'installing' elif item['name'] in items_to_be_removed_names: msclog.debug_log('Setting status for %s to "removing"' % item['name']) new_status = u'removing' if new_status: item['status'] = new_status self.updateDOMforOptionalItem(item)
def resetAndReload(self): '''Clear cached values, reload from disk. Display any changes. Typically called soon after a Munki session completes''' msclog.debug_log('resetAndReload method called') # need to clear out cached data MunkiItems.reset() # recache SelfService choices self.cached_self_service = MunkiItems.SelfService() # copy any new custom client resources msclib.get_custom_resources() # pending updates may have changed self._alertedUserToOutstandingUpdates = False # enable/disable controls as needed self.enableOrDisableSoftwareViewControls() # what page are we currently viewing? page_url = self.webView.mainFrameURL() filename = NSURL.URLWithString_(page_url).lastPathComponent() name = os.path.splitext(filename)[0] key, p, value = name.partition('-') 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()
def webView_didStartProvisionalLoadForFrame_(self, view, frame): '''Animate progress spinner while we load a page and highlight the proper toolbar button''' self.progressSpinner.startAnimation_(self) main_url = self.webView.mainFrameURL() parts = urlparse(main_url) pagename = os.path.basename(parts.path) msclog.debug_log('Requested pagename is %s' % pagename) if (pagename == 'category-all.html' or pagename.startswith('detail-') or pagename.startswith('filter-') or pagename.startswith('developer-')): self.highlightToolbarButtons_("Software") elif pagename == 'categories.html' or pagename.startswith('category-'): self.highlightToolbarButtons_("Categories") elif pagename == 'myitems.html': self.highlightToolbarButtons_("My Items") elif pagename == 'updates.html' or pagename.startswith('updatedetail-'): self.highlightToolbarButtons_("Updates") else: # no idea what type of item it is self.highlightToolbarButtons_(None)
def build_updatedetail_page(identifier): '''Build detail page for a non-optional update''' items = MunkiItems.getUpdateList() + MunkiItems.getProblemItems() page_name = u'updatedetail-%s.html' % identifier name, sep, version = identifier.partition('--version-') for item in items: if item['name'] == name and item.get('version_to_install', '') == version: page = MunkiItems.UpdateItem(item) escapeAndQuoteCommonFields(page) addDetailSidebarLabels(page) force_install_after_date = item.get('force_install_after_date') if force_install_after_date: try: local_date = munki.discardTimeZoneFromDate( force_install_after_date) date_str = munki.shortRelativeStringFromDate(local_date) page['dueLabel'] += u' ' page['short_due_date'] = date_str except munki.BadDateError: # some issue with the stored date msclog.debug_log( 'Problem with force_install_after_date for %s' % identifier) page['dueLabel'] = u'' page['short_due_date'] = u'' else: page['dueLabel'] = u'' page['short_due_date'] = u'' footer = get_template('footer_template.html', raw=True) generate_page(page_name, 'updatedetail_template.html', page, footer=footer) return # if we get here we didn't find any item matching identifier msclog.debug_log('No update detail found for %s' % identifier) build_item_not_found_page(page_name)
def build_page(filename): '''Dispatch request to build a page to the appropriate function''' msclog.debug_log(u'build_page for %s' % filename) name = os.path.splitext(filename)[0] key, p, value = name.partition('-') if key == 'detail': build_detail_page(value) elif key == 'category': build_list_page(category=value) elif key == 'categories': build_categories_page() elif key == 'filter': build_list_page(filter=value) elif key == 'developer': build_list_page(developer=value) elif key == 'myitems': build_myitems_page() elif key == 'updates': build_updates_page() elif key == 'updatedetail': build_updatedetail_page(value) else: build_item_not_found_page(filename)
def actionButtonClicked_(self, item_name): '''this method is called from JavaScript when the user clicks the Install/Removel/Cancel button in the list or detail view''' item = MunkiItems.optionalItemForName_(item_name) if not item: msclog.debug_log( 'User clicked Install/Removel/Cancel button in the list or detail view') msclog.debug_log('Can\'t find item: %s' % item_name) return prior_status = item['status'] item.update_status() self.displayUpdateCount() self.updateDOMforOptionalItem(item) if item['status'] in ['install-requested', 'removal-requested']: self._alertedUserToOutstandingUpdates = False if not self._update_in_progress: self.updateNow() elif prior_status in ['will-be-installed', 'update-will-be-installed', 'will-be-removed']: # cancelled a pending install or removal; should run an updatecheck self.checkForUpdates(suppress_apple_update_check=True)
def updateNow(self): '''If user has added to/removed from the list of things to be updated, run a check session. If there are no more changes, proceed to an update installation session if items to be installed/removed are exclusively those selected by the user in this session''' if self.stop_requested: # reset the flag self.stop_requested = False self.resetAndReload() return if MunkiItems.updateCheckNeeded(): # any item status changes that require an update check? msclog.debug_log('updateCheck needed') msclog.log("user", "check_then_install_without_logout") # since we are just checking for changed self-service items # we can suppress the Apple update check suppress_apple_update_check = True self._update_in_progress = True self.displayUpdateCount() result = munki.startUpdateCheck(suppress_apple_update_check) if result: msclog.debug_log("Error starting check-then-install session: %s" % result) self.munkiStatusSessionEnded_(2) else: self.managedsoftwareupdate_task = "checktheninstall" NSApp.delegate().statusController.startMunkiStatusSession() self.markRequestedItemsAsProcessing() elif (not self._alertedUserToOutstandingUpdates and MunkiItems.updatesContainNonUserSelectedItems()): # current list of updates contains some not explicitly chosen by the user msclog.debug_log('updateCheck not needed, items require user approval') self._update_in_progress = False self.displayUpdateCount() self.loadUpdatesPage_(self) self.alert_controller.alertToExtraUpdates() else: msclog.debug_log('updateCheck not needed') self._alertedUserToOutstandingUpdates = False self.kickOffInstallSession()
def markRequestedItemsAsProcessing(self): '''When an update check session is happening, mark optional items that have been requested as processing''' msclog.debug_log('marking requested items as processing') for item in MunkiItems.getOptionalInstallItems(): new_status = None if item['status'] == 'install-requested': msclog.debug_log('Setting status for %s to "downloading"' % item['name']) new_status = u'downloading' elif item['status'] == 'removal-requested': msclog.debug_log('Setting status for %s to "preparing-removal"' % item['name']) new_status = u'preparing-removal' if new_status: item['status'] = new_status self.updateDOMforOptionalItem(item)
def updateStatus_(self, notification): '''Got update status notification from managedsoftwareupdate''' msclog.debug_log('Got munkistatus update notification') self.got_status_update = True info = notification.userInfo() msclog.debug_log('%s' % info) # explictly get keys from info object; PyObjC in Mountain Lion # seems to need this info_keys = info.keys() if 'message' in info_keys: self.setMessage_(info['message']) if 'detail' in info_keys: self.setDetail_(info['detail']) if 'percent' in info_keys: self.setPercentageDone_(info['percent']) if 'stop_button_visible' in info_keys: if info['stop_button_visible']: self.showStopButton() else: self.hideStopButton() if 'stop_button_enabled' in info_keys: if info['stop_button_enabled']: self.enableStopButton() else: self.disableStopButton() command = info.get('command') if not self.session_started and command not in [ 'showRestartAlert', 'quit' ]: # we got a status message but we didn't start the session # so switch to the right mode self.startMunkiStatusSession() if command: msclog.debug_log('Received command: %s' % command) if command == 'activate': pass #NSApp.activateIgnoringOtherApps_(YES) #? do we really want to do this? #self.statusWindowController.window().orderFrontRegardless() elif command == 'showRestartAlert': if self.session_started: self.sessionEnded_(0) self.doRestartAlert() elif command == 'quit': self.sessionEnded_(0)
def updateStatus_(self, notification): '''Got update status notification from managedsoftwareupdate''' msclog.debug_log('Got munkistatus update notification') self.got_status_update = True info = notification.userInfo() msclog.debug_log('%s' % info) # explictly get keys from info object; PyObjC in Mountain Lion # seems to need this info_keys = info.keys() if 'message' in info_keys: self.setMessage_(info['message']) if 'detail' in info_keys: self.setDetail_(info['detail']) if 'percent' in info_keys: self.setPercentageDone_(info['percent']) if 'stop_button_visible' in info_keys: if info['stop_button_visible']: self.showStopButton() else: self.hideStopButton() if 'stop_button_enabled' in info_keys: if info['stop_button_enabled']: self.enableStopButton() else: self.disableStopButton() command = info.get('command') if not self.session_started and command not in ['showRestartAlert', 'quit']: # we got a status message but we didn't start the session # so switch to the right mode self.startMunkiStatusSession() if command: msclog.debug_log('Received command: %s' % command) if command == 'activate': pass #NSApp.activateIgnoringOtherApps_(YES) #? do we really want to do this? #self.statusWindowController.window().orderFrontRegardless() elif command == 'showRestartAlert': if self.session_started: self.sessionEnded_(0) self.doRestartAlert() elif command == 'quit': self.sessionEnded_(0)
except (OSError, IOError), err: msclog.debug_log('Error creating %s: %s' % (dest_path, err)) try: archive = ZipFile(source_path) except BadZipfile: # ignore it return archive_files = archive.namelist() # sanity checking in case the archive is not built correctly files_to_extract = [ filename for filename in archive_files if filename.startswith('resources/') or filename.startswith('templates/') ] if not files_to_extract: msclog.debug_log('Invalid client resources archive.') for filename in files_to_extract: try: if filename.endswith('/'): # it's a directory. The extract method in Python 2.6 handles this wrong # do we'll do it ourselves os.makedirs(os.path.join(dest_path, filename)) else: archive.extract(filename, dest_path) except (OSError, IOError), err: msclog.debug_log('Error expanding %s from archive: %s' % (filename, err)) def html_dir(): '''sets up our local html cache directory'''
def webView_didFailProvisionalLoadWithError_forFrame_(self, view, error, frame): '''Stop progress spinner and log''' self.progressSpinner.stopAnimation_(self) msclog.debug_log(u'Provisional load error: %s' % error) files = os.listdir(self.html_dir) msclog.debug_log('Files in html_dir: %s' % files)
try: os.mkdir(dest_path) except (OSError, IOError), err: msclog.debug_log('Error creating %s: %s' % (dest_path, err)) try: archive = ZipFile(source_path) except BadZipfile: # ignore it return archive_files = archive.namelist() # sanity checking in case the archive is not built correctly files_to_extract = [filename for filename in archive_files if filename.startswith('resources/') or filename.startswith('templates/')] if not files_to_extract: msclog.debug_log('Invalid client resources archive.') for filename in files_to_extract: try: if filename.endswith('/'): # it's a directory. The extract method in Python 2.6 handles this wrong # do we'll do it ourselves os.makedirs(os.path.join(dest_path, filename)) else: archive.extract(filename, dest_path) except (OSError, IOError), err: msclog.debug_log('Error expanding %s from archive: %s' % (filename, err)) def html_dir(): '''sets up our local html cache directory''' global _html_dir
def build_list_page_items_html(category=None, developer=None, filter=None): '''Returns HTML for the items on the list page''' items = MunkiItems.getOptionalInstallItems() item_html = u'' if filter: # since the filter term came through the filesystem, # HFS+ does some unicode character decomposition which can cause issue # with comparisons # so before we do our comparison, we normalize the unicode string # using unicodedata.normalize filter = normalize('NFC', filter) msclog.debug_log(u'Filtering on %s' % filter) items = [ item for item in items if filter in item['display_name'].lower() or filter in item['description'].lower() or filter in item['developer'].lower() or filter in item['category'].lower() ] if category: items = [ item for item in items if category.lower() == item.get('category', '').lower() ] if developer: items = [ item for item in items if developer.lower() == item.get('developer', '').lower() ] if category is None and developer is None and filter is None: # this is the default (formerly) "all items" view # look for featured items and display those if we have them featured_items = [item for item in items if item.get('featured')] if featured_items: items = featured_items if items: item_template = get_template('list_item_template.html') for item in sorted(items, key=itemgetter('display_name_lower')): escapeAndQuoteCommonFields(item) item['category_and_developer_escaped'] = escape_html( item['category_and_developer']) item_html += item_template.safe_substitute(item) # pad with extra empty items so we have a multiple of 3 if len(items) % 3: for x in range(3 - (len(items) % 3)): item_html += u'<div class="lockup"></div>\n' else: # no items; build appropriate alert messages status_results_template = get_template('status_results_template.html') alert = {} if filter: alert['primary_status_text'] = NSLocalizedString( u"Your search had no results.", u"No Search Results primary text") alert['secondary_status_text'] = NSLocalizedString( u"Try searching again.", u"No Search Results secondary text") elif category: alert['primary_status_text'] = NSLocalizedString( u"There are no items in this category.", u"No Category Results primary text") alert['secondary_status_text'] = NSLocalizedString( u"Try selecting another category.", u"No Category Results secondary text") elif developer: alert['primary_status_text'] = NSLocalizedString( u"There are no items from this developer.", u"No Developer Results primary text") alert['secondary_status_text'] = NSLocalizedString( u"Try selecting another developer.", u"No Developer Results secondary text") else: alert['primary_status_text'] = NSLocalizedString( u"There are no available software items.", u"No Items primary text") alert['secondary_status_text'] = NSLocalizedString( u"Try again later.", u"No Items secondary text") alert['hide_progress_bar'] = u'hidden' alert['progress_bar_value'] = u'' item_html = status_results_template.safe_substitute(alert) return item_html
def build_detail_page(item_name): '''Build page showing detail for a single optional item''' msclog.debug_log('build_detail_page for %s' % item_name) items = MunkiItems.getOptionalInstallItems() page_name = u'detail-%s.html' % item_name for item in items: if item['name'] == item_name: # make a copy of the item to use to build our page page = MunkiItems.OptionalItem(item) escapeAndQuoteCommonFields(page) addDetailSidebarLabels(page) # make "More in CategoryFoo" list page['hide_more_in_category'] = u'hidden' more_in_category_html = u'' more_in_category = [] if item.get('category'): category = item['category'] page['category_link'] = u'category-%s.html' % quote(category) more_in_category = [ a for a in items if a.get('category') == category and a != item and a.get('status') != 'installed' ] if more_in_category: page['hide_more_in_category'] = u'' page['moreInCategoryLabel'] = page[ 'moreInCategoryLabel'] % page['category'] shuffle(more_in_category) more_template = get_template( 'detail_more_items_template.html') for more_item in more_in_category[:4]: more_item['display_name_escaped'] = escape_html( more_item['display_name']) 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 # make "More by DeveloperFoo" list page['hide_more_by_developer'] = u'hidden' more_by_developer_html = u'' more_by_developer = [] if item.get('developer'): developer = item['developer'] page['developer_link'] = u'developer-%s.html' % quote( developer) more_by_developer = [ a for a in items if a.get('developer') == developer and a != item and a not in more_in_category and a.get('status') != 'installed' ] if more_by_developer: page['hide_more_by_developer'] = u'' page['moreByDeveloperLabel'] = ( page['moreByDeveloperLabel'] % developer) shuffle(more_by_developer) more_template = get_template( 'detail_more_items_template.html') for more_item in more_by_developer[:4]: escapeAndQuoteCommonFields(more_item) more_item['second_line'] = more_item.get( 'category', '') more_by_developer_html += more_template.safe_substitute( more_item) page['more_by_developer'] = more_by_developer_html footer = get_template('footer_template.html', raw=True) generate_page(page_name, 'detail_template.html', page, footer=footer) return msclog.debug_log('No detail found for %s' % item_name) build_item_not_found_page(page_name)
def generate_page(page_name, main_page_template_name, page_dict, **kwargs): '''Assembles HTML and writes the page to page_name in our local html directory''' msclog.debug_log('generate_page for %s' % page_name) html = assemble_page(main_page_template_name, page_dict, **kwargs) write_page(page_name, html)
def updateAvailableUpdates(self): '''If a Munki session is not in progress (that we know of) and we get a updateschanged notification, resetAndReload''' msclog.debug_log(u"Managed Software Center got update notification") if not self._update_in_progress: self.resetAndReload()
def build_list_page_items_html(category=None, developer=None, filter=None): '''Returns HTML for the items on the list page''' items = MunkiItems.getOptionalInstallItems() item_html = u'' if filter: # since the filter term came through the filesystem, # HFS+ does some unicode character decomposition which can cause issues with comparisons # so before we do our comparison, we normalize the unicode string # using unicodedata.normalize filter = normalize('NFC', filter) msclog.debug_log(u'Filtering on %s' % filter) items = [item for item in items if filter in item['display_name'].lower() or filter in item['description'].lower() or filter in item['developer'].lower() or filter in item['category'].lower()] if category: items = [item for item in items if category.lower() in item.get('category', '').lower()] if developer: items = [item for item in items if developer.lower() in item.get('developer', '').lower()] if items: item_template = get_template('list_item_template.html') for item in sorted(items, key=itemgetter('display_name_lower')): escapeAndQuoteCommonFields(item) item['category_and_developer_escaped'] = escape_html(item['category_and_developer']) item_html += item_template.safe_substitute(item) # pad with extra empty items so we have a multiple of 3 if len(items) % 3: for x in range(3 - (len(items) % 3)): item_html += u'<div class="lockup"></div>\n' else: # no items; build appropriate alert messages status_results_template = get_template('status_results_template.html') alert = {} if filter: alert['primary_status_text'] = NSLocalizedString( u"Your search had no results.", u"No Search Results primary text") alert['secondary_status_text'] = NSLocalizedString( u"Try searching again.", u"No Search Results secondary text") elif category: alert['primary_status_text'] = NSLocalizedString( u"There are no items in this category.", u"No Category Results primary text") alert['secondary_status_text'] = NSLocalizedString( u"Try selecting another category.", u"No Category Results secondary text") elif developer: alert['primary_status_text'] = NSLocalizedString( u"There are no items from this developer.", u"No Developer Results primary text") alert['secondary_status_text'] = NSLocalizedString( u"Try selecting another developer.", u"No Developer Results secondary text") else: alert['primary_status_text'] = NSLocalizedString( u"There are no available software items.", u"No Items primary text") alert['secondary_status_text'] = NSLocalizedString( u"Try again later.", u"No Items secondary text") alert['hide_progress_bar'] = u'hidden' alert['progress_bar_value'] = u'' item_html = status_results_template.safe_substitute(alert) return item_html
def webView_didFailLoadWithError_forFrame_(self, view, error, frame): '''Stop progress spinner and log error''' #TO-DO: display an error page? self.progressSpinner.stopAnimation_(self) msclog.debug_log('Committed load error: %s' % error)
def updateOptionalInstallButtonClicked_(self, item_name): '''this method is called from JavaScript when a user clicks the cancel or add button in the updates list''' # TO-DO: better handling of all the possible "unexpected error"s document = self.webView.mainFrameDocument() item = MunkiItems.optionalItemForName_(item_name) if not item: msclog.debug_log('Unexpected error: Can\'t find item for %s' % item_name) return update_table_row = document.getElementById_('%s_update_table_row' % item_name) if not update_table_row: msclog.debug_log('Unexpected error: Can\'t find table row for %s' % item_name) return # remove this row from its current table node = update_table_row.parentNode().removeChild_(update_table_row) previous_status = item['status'] # update item status item.update_status() # do we need to add a new node to the other list? if item.get('needs_update'): # make some new HTML for the updated item managed_update_names = MunkiItems.getInstallInfo().get('managed_updates', []) item_template = mschtml.get_template('update_row_template.html') item_html = item_template.safe_substitute(item) if item['status'] in ['install-requested', 'update-will-be-installed', 'installed']: # add the node to the updates-to-install table table = document.getElementById_('updates-to-install-table') if item['status'] == 'update-available': # add the node to the other-updates table table = document.getElementById_('other-updates-table') if not table: msclog.debug_log('Unexpected error: could not find other-updates-table') return # this isn't the greatest way to add something to the DOM # but it works... table.setInnerHTML_(table.innerHTML() + item_html) # might need to toggle visibility of other updates div other_updates_div = document.getElementById_('other-updates') other_updates_div_classes = other_updates_div.className().split(' ') other_updates_table = document.getElementById_('other-updates-table') if other_updates_table.innerHTML().strip(): if 'hidden' in other_updates_div_classes: other_updates_div_classes.remove('hidden') other_updates_div.setClassName_(' '.join(other_updates_div_classes)) else: if not 'hidden' in other_updates_div_classes: other_updates_div_classes.append('hidden') other_updates_div.setClassName_(' '.join(other_updates_div_classes)) # update the updates-to-install header to reflect the new list of updates to install updateCount = self.getUpdateCount() update_count_message = msclib.updateCountMessage(updateCount) update_count_element = document.getElementById_('update-count-string') if update_count_element: update_count_element.setInnerText_(update_count_message) warning_text = mschtml.get_warning_text() warning_text_element = document.getElementById_('update-warning-text') if warning_text_element: warning_text_element.setInnerHTML_(warning_text) # update text of Install All button install_all_button_element = document.getElementById_('install-all-button-text') if install_all_button_element: install_all_button_element.setInnerText_( msclib.getInstallAllButtonTextForCount(updateCount)) # update count badges self.displayUpdateCount() if MunkiItems.updateCheckNeeded(): # check for updates after a short delay so UI changes visually complete first self.performSelector_withObject_afterDelay_(self.checkForUpdates, True, 1.0)