Example #1
0
 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'])
Example #2
0
 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))
Example #3
0
 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)
Example #4
0
 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)
Example #5
0
 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()
Example #6
0
 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')
Example #7
0
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)
Example #8
0
 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)
Example #9
0
    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)
Example #10
0
 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)
Example #11
0
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)
Example #12
0
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
Example #13
0
 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)
Example #14
0
 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)
Example #15
0
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))
Example #16
0
 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)
Example #17
0
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))
Example #18
0
 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')
Example #19
0
 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)
Example #20
0
 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)
Example #21
0
 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()
Example #22
0
 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)
Example #23
0
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)
Example #24
0
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)
Example #25
0
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)
Example #26
0
 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)
Example #27
0
 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()
Example #28
0
 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)
Example #29
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)
Example #30
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)
Example #31
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'''
Example #32
0
 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)
Example #33
0
            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
Example #34
0
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
Example #35
0
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)
Example #36
0
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)
Example #37
0
 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()
Example #38
0
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
Example #39
0
 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)
Example #40
0
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)
Example #41
0
    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)