Files
munki/code/Managed Software Update/MSUAppDelegate.py

877 lines
41 KiB
Python

#
# MSUAppDelegate.py
# Managed Software Update
#
# Created by Greg Neagle on 2/10/10.
# Copyright 2010-2013 Greg Neagle.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from Foundation import *
from AppKit import *
from objc import YES, NO
import os
import munki
import PyObjCTools
DEFAULT_GUI_CACHE_AGE_SECS = 60
munki.setupLogging()
class MSUAppDelegate(NSObject):
munkiStatusController = objc.IBOutlet()
mainWindowController = objc.IBOutlet()
update_view_controller = objc.IBOutlet()
update_array_controller = objc.IBOutlet()
optional_view_controller = objc.IBOutlet()
optional_array_controller = objc.IBOutlet()
_emptyImage = NSImage.imageNamed_("Empty.png")
_restartImage = NSImage.imageNamed_("RestartReq.tif")
_logoutImage = NSImage.imageNamed_("LogOutReq.tif")
_exclamationImage = NSImage.imageNamed_("exclamation.tif")
_listofupdates = []
_optionalInstalls = []
_currentAlert = None
restart_required = False
logout_required = False
runmode = "Normal"
managedsoftwareupdate_task = None
def applicationWillFinishLaunching_(self, sender):
consoleuser = munki.getconsoleuser()
if consoleuser == None or consoleuser == u"loginwindow":
# don't show menu bar
NSMenu.setMenuBarVisible_(NO)
def applicationDidFinishLaunching_(self, sender):
NSLog(u"Managed Software Update finished launching.")
ver = NSBundle.mainBundle().infoDictionary().get(
'CFBundleShortVersionString')
NSLog("MSU GUI version: %s" % ver)
munki.log("MSU", "launched", "VER=%s" % ver)
runmode = (NSUserDefaults.standardUserDefaults().stringForKey_("mode")
or os.environ.get("ManagedSoftwareUpdateMode"))
if runmode:
self.runmode = runmode
NSLog("Runmode: %s" % runmode)
else:
consoleuser = munki.getconsoleuser()
if consoleuser == None or consoleuser == u"loginwindow":
# we're at the loginwindow, so display MunkiStatus
self.runmode = "MunkiStatus"
# Prevent automatic relaunching at login on Lion
if NSApp.respondsToSelector_('disableRelaunchOnLogin'):
NSApp.disableRelaunchOnLogin()
# register for notification messages so we can be told if available
# updates change while we are open
notification_center = NSDistributedNotificationCenter.defaultCenter()
notification_center.addObserver_selector_name_object_suspensionBehavior_(
self,
self.updateAvailableUpdates,
'com.googlecode.munki.ManagedSoftwareUpdate.update',
None,
NSNotificationSuspensionBehaviorDeliverImmediately)
# register for notification messages so we can be told to
# display a logout warning
notification_center = NSDistributedNotificationCenter.defaultCenter()
notification_center.addObserver_selector_name_object_suspensionBehavior_(
self,
self.forcedLogoutWarning,
'com.googlecode.munki.ManagedSoftwareUpdate.logoutwarn',
None,
NSNotificationSuspensionBehaviorDeliverImmediately)
if self.runmode == "MunkiStatus":
self.munkiStatusController.startMunkiStatusSession()
else:
# user may have launched the app manually, or it may have
# been launched by /usr/local/munki/managedsoftwareupdate
# to display available updates
if munki.thereAreUpdatesToBeForcedSoon(hours=2):
# skip the check and just display the updates
# by pretending the lastcheck is now
lastcheck = NSDate.date()
else:
lastcheck = NSDate.dateWithString_(munki.pref('LastCheckDate'))
# if there is no lastcheck timestamp, check for updates.
if not lastcheck:
self.checkForUpdates()
return
# otherwise, only check for updates if the last check is over the
# configured manualcheck cache age max.
max_cache_age = (
munki.pref('CheckResultsCacheSeconds') or
DEFAULT_GUI_CACHE_AGE_SECS)
if lastcheck.timeIntervalSinceNow() * -1 > int(max_cache_age):
self.checkForUpdates()
return
# if needed, get updates from InstallInfo.
if not self._listofupdates:
self.getAvailableUpdates()
# if updates exist, display them.
if self._listofupdates:
self.displayUpdatesWindow()
else:
# only check for updates if cache secs config is not defined.
if munki.pref('CheckResultsCacheSeconds'):
self.mainWindowController.theWindow.makeKeyAndOrderFront_(
self)
self.noUpdatesAlert()
else:
self.checkForUpdates()
def _sortUpdateList(self, l):
# pop any forced install items off the list.
forced_items = []
i = 0
while i < len(l):
if l[i].get('force_install_after_date'):
forced_items.append(l.pop(i))
else:
i += 1
# sort the regular update list, preferring display_name to name.
sort_lambda = lambda i: (i.get('display_name') or i.get('name')).lower()
l.sort(key=sort_lambda)
# if there were any forced items, add them to the top of the list.
if forced_items:
# sort forced items by datetime, reversed so soonest is last.
forced_items.sort(
key=lambda i: i.get('force_install_after_date'), reverse=True)
# insert items at top of the list one by one, so soonest is first.
for i in forced_items:
l.insert(0, i)
def updateAvailableUpdates(self):
NSLog(u"Managed Software Update got update notification")
if self.mainWindowController.theWindow.isVisible():
self.getAvailableUpdates()
self.buildUpdateTableData()
self.getOptionalInstalls()
self.buildOptionalInstallsData()
def displayUpdatesWindow(self):
self.buildUpdateTableData()
if self._optionalInstalls:
self.buildOptionalInstallsData()
self.mainWindowController.theWindow.makeKeyAndOrderFront_(self)
if munki.thereAreUpdatesToBeForcedSoon(hours=2):
NSApp.activateIgnoringOtherApps_(True)
else:
NSApp.requestUserAttention_(NSCriticalRequest)
def munkiStatusSessionEnded_(self, socketSessionResult):
consoleuser = munki.getconsoleuser()
if (self.runmode == "MunkiStatus" or consoleuser == None
or consoleuser == u"loginwindow"):
# Status Window only, so we should just quit
munki.log("MSU", "exit_munkistatus")
# clear launch trigger file so we aren't immediately
# relaunched by launchd
munki.clearLaunchTrigger()
NSApp.terminate_(self)
# The managedsoftwareupdate run will have changed state preferences
# in ManagedInstalls.plist. Load the new values.
munki.reload_prefs()
alertMessageText = NSLocalizedString(u"Update check failed", None)
if self.managedsoftwareupdate_task == "installwithnologout":
alertMessageText = NSLocalizedString(
u"Install session failed", None)
if socketSessionResult == -1:
# connection was dropped unexpectedly
self.mainWindowController.theWindow.makeKeyAndOrderFront_(self)
alert = NSAlert.alertWithMessageText_defaultButton_alternateButton_otherButton_informativeTextWithFormat_(
alertMessageText,
NSLocalizedString(u"Quit", None),
objc.nil,
objc.nil,
NSLocalizedString(
(u"There is a configuration problem with the managed "
"software installer. The process ended unexpectedly. "
"Contact your systems administrator."), None))
alert.beginSheetModalForWindow_modalDelegate_didEndSelector_contextInfo_(
self.mainWindowController.theWindow, self,
self.quitAlertDidEnd_returnCode_contextInfo_, objc.nil)
return
elif socketSessionResult == -2:
# socket timed out before connection
self.mainWindowController.theWindow.makeKeyAndOrderFront_(self)
alert = NSAlert.alertWithMessageText_defaultButton_alternateButton_otherButton_informativeTextWithFormat_(
alertMessageText,
NSLocalizedString(u"Quit", None),
objc.nil,
objc.nil,
NSLocalizedString(
(u"There is a configuration problem with the managed "
"software installer. Could not start the process. "
"Contact your systems administrator."), None))
alert.beginSheetModalForWindow_modalDelegate_didEndSelector_contextInfo_(
self.mainWindowController.theWindow, self,
self.quitAlertDidEnd_returnCode_contextInfo_, objc.nil)
return
if self.managedsoftwareupdate_task == "installwithnologout":
# we're done.
munki.log("MSU", "exit_installwithnologout")
NSApp.terminate_(self)
elif self.managedsoftwareupdate_task == "manualcheck":
self.managedsoftwareupdate_task = None
self._listofupdates = []
self.getAvailableUpdates()
#NSLog(u"Building table of available updates.")
self.buildUpdateTableData()
if self._optionalInstalls:
#NSLog(u"Building table of optional software.")
self.buildOptionalInstallsData()
#NSLog(u"Showing main window.")
self.mainWindowController.theWindow.makeKeyAndOrderFront_(self)
#NSLog(u"Main window was made key and ordered front")
if self._listofupdates:
return
# no list of updates; let's check the LastCheckResult for more info
lastCheckResult = munki.pref("LastCheckResult")
if lastCheckResult == 0:
munki.log("MSU", "no_updates")
self.noUpdatesAlert()
elif lastCheckResult == 1:
NSApp.requestUserAttention_(NSCriticalRequest)
elif lastCheckResult == -1:
munki.log("MSU", "cant_update", "cannot contact server")
alert = NSAlert.alertWithMessageText_defaultButton_alternateButton_otherButton_informativeTextWithFormat_(
NSLocalizedString(u"Cannot check for updates", None),
NSLocalizedString(u"Quit", None),
objc.nil,
objc.nil,
NSLocalizedString(
(u"Managed Software Update cannot contact the update "
"server at this time.\nIf this situation continues, "
"contact your systems administrator."), None))
alert.beginSheetModalForWindow_modalDelegate_didEndSelector_contextInfo_(
self.mainWindowController.theWindow, self,
self.quitAlertDidEnd_returnCode_contextInfo_, objc.nil)
elif lastCheckResult == -2:
munki.log("MSU", "cant_update", "failed preflight")
alert = NSAlert.alertWithMessageText_defaultButton_alternateButton_otherButton_informativeTextWithFormat_(
NSLocalizedString(u"Cannot check for updates", None),
NSLocalizedString(u"Quit", None),
objc.nil,
objc.nil,
NSLocalizedString(
(u"Managed Software Update failed its preflight "
"check.\nTry again later."), None))
alert.beginSheetModalForWindow_modalDelegate_didEndSelector_contextInfo_(
self.mainWindowController.theWindow, self,
self.quitAlertDidEnd_returnCode_contextInfo_, objc.nil)
def noUpdatesAlert(self):
if self._optionalInstalls:
alert = NSAlert.alertWithMessageText_defaultButton_alternateButton_otherButton_informativeTextWithFormat_(
NSLocalizedString(u"Your software is up to date.", None),
NSLocalizedString(u"Quit", None),
NSLocalizedString(u"Optional software...", None),
NSLocalizedString(u"Check again", None),
NSLocalizedString(
u"There is no new software for your computer at this time.",
None))
alert.beginSheetModalForWindow_modalDelegate_didEndSelector_contextInfo_(
self.mainWindowController.theWindow, self, self.quitAlertDidEnd_returnCode_contextInfo_, objc.nil)
else:
alert = NSAlert.alertWithMessageText_defaultButton_alternateButton_otherButton_informativeTextWithFormat_(
NSLocalizedString(u"Your software is up to date.", None),
NSLocalizedString(u"Quit", None),
objc.nil,
NSLocalizedString(u"Check again", None),
NSLocalizedString(
u"There is no new software for your computer at this time.",
None))
alert.beginSheetModalForWindow_modalDelegate_didEndSelector_contextInfo_(
self.mainWindowController.theWindow, self,
self.quitAlertDidEnd_returnCode_contextInfo_, objc.nil)
def checkForUpdates(self, suppress_apple_update_check=False):
# kick off an update check
# close main window
self.mainWindowController.theWindow.orderOut_(self)
# clear data structures
self._listofupdates = []
self._optionalInstalls = []
self.update_view_controller.tableView.deselectAll_(self)
self.update_view_controller.setUpdatelist_([])
self.optional_view_controller.tableView.deselectAll_(self)
self.optional_view_controller.setOptionallist_([])
# attempt to start the update check
result = munki.startUpdateCheck(suppress_apple_update_check)
if result == 0:
self.managedsoftwareupdate_task = "manualcheck"
self.munkiStatusController.window.makeKeyAndOrderFront_(self)
self.munkiStatusController.startMunkiStatusSession()
else:
self.mainWindowController.theWindow.makeKeyAndOrderFront_(self)
alert = NSAlert.alertWithMessageText_defaultButton_alternateButton_otherButton_informativeTextWithFormat_(
NSLocalizedString(u"Update check failed", None),
NSLocalizedString(u"Quit", None),
objc.nil,
objc.nil,
NSLocalizedString(
(u"There is a configuration problem with the managed "
"software installer. Could not start the update check "
"process. Contact your systems administrator."), None))
alert.beginSheetModalForWindow_modalDelegate_didEndSelector_contextInfo_(
self.mainWindowController.theWindow, self,
self.quitAlertDidEnd_returnCode_contextInfo_, objc.nil)
def applicationDidBecomeActive_(self, sender):
pass
def getOptionalInstalls(self):
optionalInstalls = []
installinfo = munki.getInstallInfo()
if installinfo:
optionalInstalls = installinfo.get("optional_installs", [])
if optionalInstalls:
self._sortUpdateList(optionalInstalls)
self._optionalInstalls = optionalInstalls
self.update_view_controller.optionalSoftwareBtn.setHidden_(NO)
else:
self.update_view_controller.optionalSoftwareBtn.setHidden_(YES)
def enableUpdateNowBtn_(self, enable):
self.update_view_controller.updateNowBtn.setEnabled_(enable)
def getAvailableUpdates(self):
updatelist = []
installinfo = munki.getInstallInfo()
munki_updates_contain_apple_items = \
munki.munkiUpdatesContainAppleItems()
if installinfo:
updatelist.extend(installinfo.get('managed_installs', []))
if not munki_updates_contain_apple_items:
appleupdates = munki.getAppleUpdates()
updatelist.extend(appleupdates.get("AppleUpdates", []))
for update in updatelist:
force_install_after_date = update.get(
'force_install_after_date')
if force_install_after_date:
# insert installation deadline into description
local_date = munki.discardTimeZoneFromDate(
force_install_after_date)
date_str = munki.stringFromDate(local_date)
forced_date_text = NSLocalizedString(
u"This item must be installed by ", None)
description = update["description"]
# prepend deadline info to description. This will fail if
# the description is HTML...
update["description"] = (forced_date_text + date_str
+ "\n\n" + description)
if installinfo.get("removals"):
removallist = installinfo.get("removals")
restartNeeded = False
logoutNeeded = False
showRemovalDetail = munki.getRemovalDetailPrefs()
for item in removallist:
restartAction = item.get("RestartAction")
if restartAction in ["RequireRestart", "RecommendRestart"]:
restartNeeded = True
if restartAction in ["RequireLogout", "RecommendLogout"]:
logoutNeeded = True
if showRemovalDetail:
item["display_name"] = (
(item.get("display_name") or item.get("name", ""))
+ NSLocalizedString(u" (will be removed)", None))
item["description"] = NSLocalizedString(
u"This item will be removed.", None)
updatelist.append(item)
if not showRemovalDetail:
row = {}
row["display_name"] = NSLocalizedString(
u"Software removals", None)
row["version"] = ""
row["description"] = NSLocalizedString(
u"Scheduled removal of managed software.", None)
if restartNeeded:
row["RestartAction"] = "RequireRestart"
elif logoutNeeded:
row["RestartAction"] = "RequireLogout"
updatelist.append(row)
if updatelist:
#self._sortUpdateList(updatelist)
self._listofupdates = updatelist
self.enableUpdateNowBtn_(YES)
#self.performSelector_withObject_afterDelay_(
# "enableUpdateNowBtn:", YES, 4)
self.getOptionalInstalls()
else:
self._listofupdates = []
self.update_view_controller.updateNowBtn.setEnabled_(NO)
self.getOptionalInstalls()
def buildOptionalInstallsData(self):
table = []
selfservedata = munki.readSelfServiceManifest()
selfserve_installs = selfservedata.get('managed_installs', [])
selfserve_uninstalls = selfservedata.get('managed_uninstalls', [])
for item in self._optionalInstalls:
row = {}
row['enabled'] = objc.YES
# current install state
row['installed'] = item.get("installed", objc.NO)
# user desired state
row['managed'] = (item['name'] in selfserve_installs)
row['original_managed'] = (item['name'] in selfserve_installs)
row['itemname'] = item['name']
row['name'] = item.get("display_name") or item['name']
row['version'] = munki.trimVersionString(
item.get("version_to_install"))
row['description'] = item.get("description", "")
if item.get("installer_item_size"):
row['size'] = munki.humanReadable(
item.get("installer_item_size"))
elif item.get("installed_size"):
row['size'] = munki.humanReadable(item.get("installed_size"))
else:
row['size'] = ""
if row['installed']:
if item.get("needs_update"):
status = NSLocalizedString(u"Update available", None)
else:
row['size'] = "-"
status = NSLocalizedString(u"Installed", None)
if item.get("will_be_removed"):
status = NSLocalizedString(u"Will be removed", None)
elif not item.get('uninstallable'):
status = NSLocalizedString(u"Not removable", None)
row['enabled'] = objc.NO
else:
status = "Not installed"
if item.get("will_be_installed"):
status = NSLocalizedString(u"Will be installed", None)
elif item.get("note"):
# some reason we can't install
status = item.get("note")
row['enabled'] = objc.NO
row['status'] = status
row['original_status'] = status
row_dict = NSMutableDictionary.dictionaryWithDictionary_(row)
table.append(row_dict)
self.optional_view_controller.setOptionallist_(table)
self.optional_view_controller.tableView.deselectAll_(self)
def addOrRemoveOptionalSoftware(self):
# record any requested changes in installed/removal state
# then kick off an update check
optional_install_choices = {}
optional_install_choices['managed_installs'] = []
optional_install_choices['managed_uninstalls'] = []
for row in self.optional_array_controller.arrangedObjects():
if row['managed']:
# user selected for install
optional_install_choices['managed_installs'].append(
row['itemname'])
elif row['original_managed']:
# was managed, but user deselected it; we should remove it if
# possible
optional_install_choices['managed_uninstalls'].append(
row['itemname'])
munki.writeSelfServiceManifest(optional_install_choices)
self.checkForUpdates(suppress_apple_update_check=True)
def buildUpdateTableData(self):
table = []
self.restart_required = False
self.logout_required = False
for item in self._listofupdates:
row = {}
row['image'] = self._emptyImage
if (item.get("RestartAction") == "RequireRestart"
or item.get("RestartAction") == "RecommendRestart"):
row['image'] = self._restartImage
self.restart_required = True
elif (item.get("RestartAction") == "RequireLogout"
or item.get("RestartAction") == "RecommendLogout"):
row['image'] = self._logoutImage
self.logout_required = True
if item.get("force_install_after_date"):
row['image'] = self._exclamationImage
row['name'] = item.get("display_name") or item.get("name","")
row['version'] = munki.trimVersionString(
item.get("version_to_install"))
if item.get("installer_item_size"):
row['size'] = munki.humanReadable(
item.get("installer_item_size"))
elif item.get("installed_size"):
row['size'] = munki.humanReadable(item.get("installed_size"))
else:
row['size'] = ""
row['description'] = item.get("description","")
row_dict = NSDictionary.dictionaryWithDictionary_(row)
table.append(row_dict)
self.update_view_controller.setUpdatelist_(table)
self.update_view_controller.tableView.deselectAll_(self)
if self.restart_required:
self.update_view_controller.restartInfoFld.setStringValue_(
NSLocalizedString(u"Restart will be required.", None))
self.update_view_controller.restartImageFld.setImage_(
self._restartImage)
elif self.logout_required:
self.update_view_controller.restartInfoFld.setStringValue_(
NSLocalizedString(u"Logout will be required.", None))
self.update_view_controller.restartImageFld.setImage_(
self._logoutImage)
def forcedLogoutWarning(self, notification_obj):
NSApp.activateIgnoringOtherApps_(True)
info = notification_obj.userInfo()
moreText = NSLocalizedString(
(u"\nAll pending updates will be installed. Unsaved work will be "
"lost.\nYou may avoid the forced logout by logging out now."), None)
logout_time = None
if info:
logout_time = info.get('logout_time')
elif munki.thereAreUpdatesToBeForcedSoon():
logout_time = munki.earliestForceInstallDate()
if not logout_time:
return
time_til_logout = int(logout_time.timeIntervalSinceNow() / 60)
if time_til_logout > 55:
deadline_str = munki.stringFromDate(logout_time)
munki.log("user", "forced_logout_warning_initial")
formatString = NSLocalizedString(
u"A logout will be forced at approximately %s.", None)
infoText = formatString % deadline_str + moreText
elif time_til_logout > 0:
munki.log("user", "forced_logout_warning_%s" % time_til_logout)
formatString = NSLocalizedString(
u"A logout will be forced in less than %s minutes.", None)
infoText = formatString % time_til_logout + moreText
else:
munki.log("user", "forced_logout_warning_final")
infoText = NSLocalizedString(
(u"A logout will be forced in less than a minute.\nAll pending "
"updates will be installed. Unsaved work will be lost."), None)
# Set the OK button to default, unless less than 5 minutes to logout
# in which case only the Logout button should be displayed.
self._force_warning_logout_btn = NSLocalizedString(
u"Log out and update now", None)
self._force_warning_ok_btn = NSLocalizedString(u"OK", None)
if time_til_logout > 5:
self._force_warning_btns = {
NSAlertDefaultReturn: self._force_warning_ok_btn,
NSAlertAlternateReturn: self._force_warning_logout_btn,
}
else:
self._force_warning_btns = {
NSAlertDefaultReturn: self._force_warning_logout_btn,
NSAlertAlternateReturn: objc.nil,
}
if self._currentAlert:
NSApp.endSheet_(self._currentAlert.window())
self._currentAlert = None
alert = NSAlert.alertWithMessageText_defaultButton_alternateButton_otherButton_informativeTextWithFormat_(
NSLocalizedString(
u"Forced Logout for Mandatory Install", None),
self._force_warning_btns[NSAlertDefaultReturn],
self._force_warning_btns[NSAlertAlternateReturn],
objc.nil,
infoText)
self._currentAlert = alert
alert.beginSheetModalForWindow_modalDelegate_didEndSelector_contextInfo_(
self.mainWindowController.theWindow, self, self.forceLogoutWarningDidEnd_returnCode_contextInfo_, objc.nil)
def laterBtnClicked(self):
if munki.thereAreUpdatesToBeForcedSoon():
deadline = munki.earliestForceInstallDate()
time_til_logout = deadline.timeIntervalSinceNow()
if time_til_logout > 0:
deadline_str = munki.stringFromDate(deadline)
formatString = NSLocalizedString(
(u"One or more updates must be installed by %s. A logout "
"may be forced if you wait too long to update."), None)
infoText = formatString % deadline_str
else:
infoText = NSLocalizedString(
(u"One or more mandatory updates are overdue for "
"installation. A logout will be forced soon."), None)
alert = NSAlert.alertWithMessageText_defaultButton_alternateButton_otherButton_informativeTextWithFormat_(
NSLocalizedString(u"Manadatory Updates Pending", None),
NSLocalizedString(u"Show updates", None),
NSLocalizedString(u"Update later", None),
objc.nil,
infoText)
self._currentAlert = alert
alert.beginSheetModalForWindow_modalDelegate_didEndSelector_contextInfo_(
self.mainWindowController.theWindow, self,
self.confirmLaterAlertDidEnd_returnCode_contextInfo_, objc.nil)
else:
munki.log("user", "exit_later_clicked")
NSApp.terminate_(self)
def confirmInstallUpdates(self):
if self.mainWindowController.theWindow.isVisible() == objc.NO:
return
if len(munki.currentGUIusers()) > 1:
alert = NSAlert.alertWithMessageText_defaultButton_alternateButton_otherButton_informativeTextWithFormat_(
NSLocalizedString(u"Other users logged in", None),
NSLocalizedString(u"Cancel", None),
objc.nil,
objc.nil,
NSLocalizedString(
(u"There are other users logged into this computer.\n"
"Updating now could cause other users to lose their "
"work.\n\nPlease try again later after the other users "
"have logged out."), None))
self._currentAlert = alert
alert.beginSheetModalForWindow_modalDelegate_didEndSelector_contextInfo_(
self.mainWindowController.theWindow, self, self.multipleUserAlertDidEnd_returnCode_contextInfo_, objc.nil)
elif self.restart_required:
alert = NSAlert.alertWithMessageText_defaultButton_alternateButton_otherButton_informativeTextWithFormat_(
NSLocalizedString(u"Restart Required", None),
NSLocalizedString(u"Log out and update", None),
NSLocalizedString(u"Cancel", None),
objc.nil,
NSLocalizedString(
(u"A restart is required after updating. Please be patient "
"as there may be a short delay at the login window. Logout "
"and update now?"), None))
self._currentAlert = alert
alert.beginSheetModalForWindow_modalDelegate_didEndSelector_contextInfo_(
self.mainWindowController.theWindow, self,
self.logoutAlertDidEnd_returnCode_contextInfo_, objc.nil)
elif self.logout_required or munki.installRequiresLogout():
alert = NSAlert.alertWithMessageText_defaultButton_alternateButton_otherButton_informativeTextWithFormat_(
NSLocalizedString(u"Logout Required", None),
NSLocalizedString(u"Log out and update", None),
NSLocalizedString(u"Cancel", None),
objc.nil,
NSLocalizedString(
(u"A logout is required before updating. Please be patient "
"as there may be a short delay at the login window. Logout "
"and update now?"), None))
self._currentAlert = alert
alert.beginSheetModalForWindow_modalDelegate_didEndSelector_contextInfo_(
self.mainWindowController.theWindow, self,
self.logoutAlertDidEnd_returnCode_contextInfo_, objc.nil)
else:
alert = NSAlert.alertWithMessageText_defaultButton_alternateButton_otherButton_informativeTextWithFormat_(
NSLocalizedString(u"Logout Recommended", None),
NSLocalizedString(u"Log out and update", None),
NSLocalizedString(u"Cancel", None),
NSLocalizedString(u"Update without logging out", None),
NSLocalizedString(
(u"A logout is recommended before updating. Please be "
"patient as there may be a short delay at the login "
"window. Log out and update now?"), None))
self._currentAlert = alert
alert.beginSheetModalForWindow_modalDelegate_didEndSelector_contextInfo_(
self.mainWindowController.theWindow, self,
self.logoutAlertDidEnd_returnCode_contextInfo_, objc.nil)
def alertIfBlockingAppsRunning(self):
apps_to_check = []
for update_item in self._listofupdates:
if 'blocking_applications' in update_item:
apps_to_check.extend(update_item['blocking_applications'])
else:
apps_to_check.extend([os.path.basename(item.get('path'))
for item in update_item.get('installs', [])
if item['type'] == 'application'])
running_apps = munki.getRunningBlockingApps(apps_to_check)
if running_apps:
alert = NSAlert.alertWithMessageText_defaultButton_alternateButton_otherButton_informativeTextWithFormat_(
NSLocalizedString(
u"Conflicting applications running", None),
NSLocalizedString(u"OK", None),
objc.nil,
objc.nil,
NSLocalizedString(
(u"You must quit the following applications before "
"proceeding with installation:\n\n%s"), None)
% '\n'.join(running_apps))
munki.log("MSU", "conflicting_apps", ','.join(running_apps))
self._currentAlert = alert
alert.beginSheetModalForWindow_modalDelegate_didEndSelector_contextInfo_(
self.mainWindowController.theWindow, self,
self.blockingAppsRunningAlertDidEnd_returnCode_contextInfo_,
objc.nil)
return True
else:
return False
def alertIfRunnningOnBattery(self):
power_info = munki.getPowerInfo()
if (power_info.get('PowerSource') == 'Battery Power'
and power_info.get('BatteryCharge', 0) < 50):
alert = NSAlert.alertWithMessageText_defaultButton_alternateButton_otherButton_informativeTextWithFormat_(
NSLocalizedString(
u"Your computer is not connected to a power source.", None),
NSLocalizedString(u"Continue", None),
NSLocalizedString(u"Cancel", None),
objc.nil,
NSLocalizedString(
(u"For best results, you should connect your computer to a "
"power source before updating. Are you sure you want to "
"continue the update?"), None))
munki.log("MSU", "alert_on_battery_power")
# making UI consistent with Apple Software Update...
# set Cancel button to be activated by return key
alert.buttons()[1].setKeyEquivalent_('\r')
# set Continue button to be activated by Escape key
alert.buttons()[0].setKeyEquivalent_(chr(27))
buttonPressed = alert.runModal()
if buttonPressed == NSAlertAlternateReturn:
return True
return False
def installSessionErrorAlert(self):
alert = NSAlert.alertWithMessageText_defaultButton_alternateButton_otherButton_informativeTextWithFormat_(
NSLocalizedString(u"Cannot start installation session", None),
NSLocalizedString(u"Quit", None),
objc.nil,
objc.nil,
NSLocalizedString(
(u"There is a configuration problem with the managed "
"software installer. Could not start the install session. "
"Contact your systems administrator."), None))
munki.log("MSU", "cannot_start")
self._currentAlert = alert
alert.beginSheetModalForWindow_modalDelegate_didEndSelector_contextInfo_(
self.mainWindowController.theWindow, self,
self.quitAlertDidEnd_returnCode_contextInfo_, objc.nil)
@PyObjCTools.AppHelper.endSheetMethod
def logoutAlertDidEnd_returnCode_contextInfo_(
self, alert, returncode, contextinfo):
self._currentAlert = None
if returncode == NSAlertDefaultReturn:
if self.alertIfRunnningOnBattery():
munki.log("user", "alerted_on_battery_power_and_cancelled")
return
NSLog("User chose to logout")
munki.log("user", "install_with_logout")
result = munki.logoutAndUpdate()
if result:
self.installSessionErrorAlert()
elif returncode == NSAlertAlternateReturn:
NSLog("User cancelled")
munki.log("user", "cancelled")
elif returncode == NSAlertOtherReturn:
# dismiss the alert sheet now because we might display
# another alert
alert.window().orderOut_(self)
if self.alertIfBlockingAppsRunning():
return
if self.alertIfRunnningOnBattery():
munki.log("user", "alerted_on_battery_power_and_cancelled")
return
NSLog("User chose to update without logging out")
munki.log("user", "install_without_logout")
result = munki.justUpdate()
if result:
self.installSessionErrorAlert()
else:
self.managedsoftwareupdate_task = "installwithnologout"
self.mainWindowController.theWindow.orderOut_(self)
self.munkiStatusController.window.makeKeyAndOrderFront_(self)
self.munkiStatusController.startMunkiStatusSession()
@PyObjCTools.AppHelper.endSheetMethod
def blockingAppsRunningAlertDidEnd_returnCode_contextInfo_(
self, alert, returncode, contextinfo):
self._currentAlert = None
@PyObjCTools.AppHelper.endSheetMethod
def multipleUserAlertDidEnd_returnCode_contextInfo_(
self, alert, returncode, contextinfo):
self._currentAlert = None
@PyObjCTools.AppHelper.endSheetMethod
def confirmLaterAlertDidEnd_returnCode_contextInfo_(
self, alert, returncode, contextinfo):
self._currentAlert = None
if returncode == NSAlertAlternateReturn:
munki.log("user", "exit_later_clicked")
NSApp.terminate_(self)
@PyObjCTools.AppHelper.endSheetMethod
def forceLogoutWarningDidEnd_returnCode_contextInfo_(
self, alert, returncode, contextinfo):
self._currentAlert = None
btn_pressed = self._force_warning_btns.get(returncode)
if btn_pressed == self._force_warning_logout_btn:
munki.log("user", "install_with_logout")
result = munki.logoutAndUpdate()
elif btn_pressed == self._force_warning_ok_btn:
munki.log("user", "dismissed_forced_logout_warning")
@PyObjCTools.AppHelper.endSheetMethod
def quitAlertDidEnd_returnCode_contextInfo_(
self, alert, returncode, contextinfo):
self._currentAlert = None
if returncode == NSAlertDefaultReturn:
munki.log("user", "quit")
NSApp.terminate_(self)
elif returncode == NSAlertAlternateReturn:
munki.log("user", "view_optional_software")
self.update_view_controller.optionalSoftwareBtn.setHidden_(NO)
self.buildOptionalInstallsData()
self.mainWindowController.theTabView.selectNextTabViewItem_(self)
elif returncode == NSAlertOtherReturn:
munki.log("user", "refresh_clicked")
self.checkForUpdates()
return