New PyObjC-based version of Managed Software Update.app.

git-svn-id: http://munki.googlecode.com/svn/trunk@458 a4e17f2e-e282-11dd-95e1-755cbddbdd66
This commit is contained in:
Greg Neagle
2010-02-17 18:39:22 +00:00
parent 63380a48dd
commit 934f1329bc
21 changed files with 3754 additions and 0 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 146 B

File diff suppressed because it is too large Load Diff
@@ -0,0 +1,106 @@
#!/usr/bin/python
# encoding: utf-8
#
# Copyright 2009 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.
"""FoundationPlist.py -- a tool to generate and parse MacOSX .plist files.
This is intended as a drop-in replacement for Python's included plistlib,
with a few caveats:
- readPlist() and writePlist() operate only on a filepath,
not a file object.
- there is no support for the deprecated functions:
readPlistFromResource()
writePlistToResource()
- there is no support for the deprecated Plist class.
The Property List (.plist) file format is a simple XML pickle supporting
basic object types, like dictionaries, lists, numbers and strings.
Usually the top level object is a dictionary.
To write out a plist file, use the writePlist(rootObject, filepath)
function. 'rootObject' is the top level object, 'filepath' is a
filename.
To parse a plist from a file, use the readPlist(filepath) function,
with a file name. It returns the top level object (again, usually a
dictionary).
To work with plist data in strings, you can use readPlistFromString()
and writePlistToString().
"""
from Foundation import NSData, \
NSPropertyListSerialization, \
NSPropertyListMutableContainers, \
NSPropertyListXMLFormat_v1_0
class NSPropertyListSerializationException(Exception):
pass
def readPlist(filepath):
"""
Read a .plist file from filepath. Return the unpacked root object
(which is usually a dictionary).
"""
plistData = NSData.dataWithContentsOfFile_(filepath)
dataObject, plistFormat, error = \
NSPropertyListSerialization.propertyListFromData_mutabilityOption_format_errorDescription_(
plistData, NSPropertyListMutableContainers, None, None)
if error:
errmsg = "%s in file %s" % (error, filepath)
raise NSPropertyListSerializationException(errmsg)
else:
return dataObject
def readPlistFromString(data):
'''Read a plist data from a string. Return the root object.'''
plistData = buffer(data)
dataObject, plistFormat, error = \
NSPropertyListSerialization.propertyListFromData_mutabilityOption_format_errorDescription_(
plistData, NSPropertyListMutableContainers, None, None)
if error:
raise NSPropertyListSerializationException(error)
else:
return dataObject
def writePlist(dataObject, filepath):
'''
Write 'rootObject' as a plist to filepath.
'''
plistData, error = \
NSPropertyListSerialization.dataFromPropertyList_format_errorDescription_(
dataObject, NSPropertyListXMLFormat_v1_0, None)
if error:
raise NSPropertyListSerializationException(error)
else:
if plistData.writeToFile_atomically_(filepath, True):
return
else:
raise Exception("Failed to write plist data to %s" % filepath)
def writePlistToString(rootObject):
'''Return 'rootObject' as a plist-formatted string.'''
plistData, error = \
NSPropertyListSerialization.dataFromPropertyList_format_errorDescription_(
rootObject, NSPropertyListXMLFormat_v1_0, None)
if error:
raise NSPropertyListSerializationException(error)
else:
return str(plistData)
+30
View File
@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleIconFile</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleIdentifier</key>
<string>com.googlecode.munki.ManagedSoftwareUpdate</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>2.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>2.0</string>
<key>NSMainNibFile</key>
<string>MainMenu</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
</dict>
</plist>
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,34 @@
#
# MSUWebViewPolicyDelegate.py
# ManagedSoftwareUpdate
#
# Created by Greg Neagle on 2/10/10.
# Copyright 2009-2010 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 WebKit import *
class MSUWebViewPolicyDelegate(NSObject):
# needed to intercept clicks on weblinks in descriptions
# and pass them onto the user's default web browser.
# without this the link will load inside the webview in our app.
def webView_decidePolicyForNavigationAction_request_frame_decisionListener_(self, webView, actionInformation, request, frame, listener):
if actionInformation.objectForKey_(u"WebActionNavigationTypeKey").intValue() == WebNavigationTypeLinkClicked:
listener.ignore()
NSWorkspace.sharedWorkspace().openURL_(request.URL())
else:
listener.use()
@@ -0,0 +1,54 @@
#
# MSUWindowController.py
# Managed Software Update
#
# Created by Greg Neagle on 2/11/10.
# Copyright 2009-2010 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 objc import YES, NO, IBAction, IBOutlet
from Foundation import *
from AppKit import *
class MSUWindowController(NSWindowController):
restartInfoFld = IBOutlet()
restartImageFld = IBOutlet()
descriptionView = IBOutlet()
tableView = IBOutlet()
theWindow = IBOutlet()
_updatelist = NSArray.arrayWithArray_([{"image": NSImage.imageNamed_("Empty.png"), "name": "", "version": "", "description": ""}])
def updatelist(self):
return self._updatelist
objc.accessor(updatelist) # PyObjC KVO hack
def setUpdatelist_(self, newlist):
self._updatelist = NSArray.arrayWithArray_(newlist)
objc.accessor(setUpdatelist_) # PyObjC KVO hack
@IBAction
def laterBtnClicked_(self, sender):
NSApp.terminate_(self)
@IBAction
def updateNowBtnClicked_(self, sender):
# alert the user to logout, proceed without logout, or cancel
NSApp.delegate().confirmInstallUpdates()
def windowShouldClose_(self, sender):
# just quit
NSApp.terminate_(self)
@@ -0,0 +1,339 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 44;
objects = {
/* Begin PBXBuildFile section */
77631A270C06C501005415CB /* Python.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 77631A260C06C501005415CB /* Python.framework */; };
77631A3F0C0748CF005415CB /* main.py in Resources */ = {isa = PBXBuildFile; fileRef = 77631A3E0C0748CF005415CB /* main.py */; };
7790198F0C07548A00326F66 /* Managed_Software_UpdateAppDelegate.py in Resources */ = {isa = PBXBuildFile; fileRef = 7790198E0C07548A00326F66 /* Managed_Software_UpdateAppDelegate.py */; };
77C8C1F90C07829500965286 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 77C8C1F70C07829500965286 /* MainMenu.xib */; };
8D11072B0486CEB800E47090 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 089C165CFE840E0CC02AAC07 /* InfoPlist.strings */; };
8D11072D0486CEB800E47090 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 29B97316FDCFA39411CA2CEA /* main.m */; settings = {ATTRIBUTES = (); }; };
8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */; };
C0828C801123DBC5003D5807 /* MSUWebViewPolicyDelegate.py in Resources */ = {isa = PBXBuildFile; fileRef = C0828C7F1123DBC5003D5807 /* MSUWebViewPolicyDelegate.py */; };
C0828C901123DCFF003D5807 /* ShutDownReq.tif in Resources */ = {isa = PBXBuildFile; fileRef = C0828C871123DCFF003D5807 /* ShutDownReq.tif */; };
C0828C911123DCFF003D5807 /* RestartRec.tif in Resources */ = {isa = PBXBuildFile; fileRef = C0828C881123DCFF003D5807 /* RestartRec.tif */; };
C0828C921123DCFF003D5807 /* LogOutReq.tif in Resources */ = {isa = PBXBuildFile; fileRef = C0828C891123DCFF003D5807 /* LogOutReq.tif */; };
C0828C931123DCFF003D5807 /* Managed Software Update.icns in Resources */ = {isa = PBXBuildFile; fileRef = C0828C8A1123DCFF003D5807 /* Managed Software Update.icns */; };
C0828C941123DCFF003D5807 /* Empty.png in Resources */ = {isa = PBXBuildFile; fileRef = C0828C8B1123DCFF003D5807 /* Empty.png */; };
C0828C951123DCFF003D5807 /* RestartReq.tif in Resources */ = {isa = PBXBuildFile; fileRef = C0828C8C1123DCFF003D5807 /* RestartReq.tif */; };
C0828C961123DCFF003D5807 /* Restart.tif in Resources */ = {isa = PBXBuildFile; fileRef = C0828C8D1123DCFF003D5807 /* Restart.tif */; };
C0828C971123DCFF003D5807 /* package.tiff in Resources */ = {isa = PBXBuildFile; fileRef = C0828C8E1123DCFF003D5807 /* package.tiff */; };
C0828C981123DCFF003D5807 /* Installer.tiff in Resources */ = {isa = PBXBuildFile; fileRef = C0828C8F1123DCFF003D5807 /* Installer.tiff */; };
C0828CA2112461C0003D5807 /* MSUWindowController.py in Resources */ = {isa = PBXBuildFile; fileRef = C0828CA1112461C0003D5807 /* MSUWindowController.py */; };
C0828D1011247A37003D5807 /* FoundationPlist.py in Resources */ = {isa = PBXBuildFile; fileRef = C0828D0E11247A37003D5807 /* FoundationPlist.py */; };
C0828D1211247B3A003D5807 /* munki.py in Resources */ = {isa = PBXBuildFile; fileRef = C0828D1111247B3A003D5807 /* munki.py */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
089C165DFE840E0CC02AAC07 /* English */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = English; path = English.lproj/InfoPlist.strings; sourceTree = "<group>"; };
1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = /System/Library/Frameworks/Cocoa.framework; sourceTree = "<absolute>"; };
13E42FB307B3F0F600E4EEF1 /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = /System/Library/Frameworks/CoreData.framework; sourceTree = "<absolute>"; };
29B97316FDCFA39411CA2CEA /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
29B97324FDCFA39411CA2CEA /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = /System/Library/Frameworks/AppKit.framework; sourceTree = "<absolute>"; };
29B97325FDCFA39411CA2CEA /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = "<absolute>"; };
32CA4F630368D1EE00C91783 /* Managed_Software_Update_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Managed_Software_Update_Prefix.pch; sourceTree = "<group>"; };
77631A260C06C501005415CB /* Python.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Python.framework; path = /System/Library/Frameworks/Python.framework; sourceTree = "<absolute>"; };
77631A3E0C0748CF005415CB /* main.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = main.py; sourceTree = "<group>"; };
7790198E0C07548A00326F66 /* Managed_Software_UpdateAppDelegate.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = Managed_Software_UpdateAppDelegate.py; sourceTree = "<group>"; };
77C8C1F80C07829500965286 /* English */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = English; path = English.lproj/MainMenu.xib; sourceTree = "<group>"; };
8D1107310486CEB800E47090 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
8D1107320486CEB800E47090 /* Managed Software Update.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Managed Software Update.app"; sourceTree = BUILT_PRODUCTS_DIR; };
C0828C7F1123DBC5003D5807 /* MSUWebViewPolicyDelegate.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = MSUWebViewPolicyDelegate.py; sourceTree = "<group>"; };
C0828C871123DCFF003D5807 /* ShutDownReq.tif */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = ShutDownReq.tif; sourceTree = "<group>"; };
C0828C881123DCFF003D5807 /* RestartRec.tif */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = RestartRec.tif; sourceTree = "<group>"; };
C0828C891123DCFF003D5807 /* LogOutReq.tif */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = LogOutReq.tif; sourceTree = "<group>"; };
C0828C8A1123DCFF003D5807 /* Managed Software Update.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = "Managed Software Update.icns"; sourceTree = "<group>"; };
C0828C8B1123DCFF003D5807 /* Empty.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Empty.png; sourceTree = "<group>"; };
C0828C8C1123DCFF003D5807 /* RestartReq.tif */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = RestartReq.tif; sourceTree = "<group>"; };
C0828C8D1123DCFF003D5807 /* Restart.tif */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = Restart.tif; sourceTree = "<group>"; };
C0828C8E1123DCFF003D5807 /* package.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = package.tiff; sourceTree = "<group>"; };
C0828C8F1123DCFF003D5807 /* Installer.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; path = Installer.tiff; sourceTree = "<group>"; };
C0828CA1112461C0003D5807 /* MSUWindowController.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = MSUWindowController.py; sourceTree = "<group>"; };
C0828D0E11247A37003D5807 /* FoundationPlist.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = FoundationPlist.py; sourceTree = "<group>"; };
C0828D1111247B3A003D5807 /* munki.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = munki.py; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
8D11072E0486CEB800E47090 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */,
77631A270C06C501005415CB /* Python.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
080E96DDFE201D6D7F000001 /* Classes */ = {
isa = PBXGroup;
children = (
C0828C7F1123DBC5003D5807 /* MSUWebViewPolicyDelegate.py */,
7790198E0C07548A00326F66 /* Managed_Software_UpdateAppDelegate.py */,
C0828CA1112461C0003D5807 /* MSUWindowController.py */,
);
name = Classes;
sourceTree = "<group>";
};
1058C7A0FEA54F0111CA2CBB /* Linked Frameworks */ = {
isa = PBXGroup;
children = (
77631A260C06C501005415CB /* Python.framework */,
1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */,
);
name = "Linked Frameworks";
sourceTree = "<group>";
};
1058C7A2FEA54F0111CA2CBB /* Other Frameworks */ = {
isa = PBXGroup;
children = (
29B97324FDCFA39411CA2CEA /* AppKit.framework */,
13E42FB307B3F0F600E4EEF1 /* CoreData.framework */,
29B97325FDCFA39411CA2CEA /* Foundation.framework */,
);
name = "Other Frameworks";
sourceTree = "<group>";
};
19C28FACFE9D520D11CA2CBB /* Products */ = {
isa = PBXGroup;
children = (
8D1107320486CEB800E47090 /* Managed Software Update.app */,
);
name = Products;
sourceTree = "<group>";
};
29B97314FDCFA39411CA2CEA /* Managed Software Update */ = {
isa = PBXGroup;
children = (
080E96DDFE201D6D7F000001 /* Classes */,
29B97315FDCFA39411CA2CEA /* Other Sources */,
29B97317FDCFA39411CA2CEA /* Resources */,
29B97323FDCFA39411CA2CEA /* Frameworks */,
19C28FACFE9D520D11CA2CBB /* Products */,
);
name = "Managed Software Update";
sourceTree = "<group>";
};
29B97315FDCFA39411CA2CEA /* Other Sources */ = {
isa = PBXGroup;
children = (
C0828D0E11247A37003D5807 /* FoundationPlist.py */,
32CA4F630368D1EE00C91783 /* Managed_Software_Update_Prefix.pch */,
29B97316FDCFA39411CA2CEA /* main.m */,
77631A3E0C0748CF005415CB /* main.py */,
C0828D1111247B3A003D5807 /* munki.py */,
);
name = "Other Sources";
sourceTree = "<group>";
};
29B97317FDCFA39411CA2CEA /* Resources */ = {
isa = PBXGroup;
children = (
C0828C871123DCFF003D5807 /* ShutDownReq.tif */,
C0828C881123DCFF003D5807 /* RestartRec.tif */,
C0828C891123DCFF003D5807 /* LogOutReq.tif */,
C0828C8A1123DCFF003D5807 /* Managed Software Update.icns */,
C0828C8B1123DCFF003D5807 /* Empty.png */,
C0828C8C1123DCFF003D5807 /* RestartReq.tif */,
C0828C8D1123DCFF003D5807 /* Restart.tif */,
C0828C8E1123DCFF003D5807 /* package.tiff */,
C0828C8F1123DCFF003D5807 /* Installer.tiff */,
77C8C1F70C07829500965286 /* MainMenu.xib */,
8D1107310486CEB800E47090 /* Info.plist */,
089C165CFE840E0CC02AAC07 /* InfoPlist.strings */,
);
name = Resources;
sourceTree = "<group>";
};
29B97323FDCFA39411CA2CEA /* Frameworks */ = {
isa = PBXGroup;
children = (
1058C7A0FEA54F0111CA2CBB /* Linked Frameworks */,
1058C7A2FEA54F0111CA2CBB /* Other Frameworks */,
);
name = Frameworks;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
8D1107260486CEB800E47090 /* Managed Software Update */ = {
isa = PBXNativeTarget;
buildConfigurationList = C01FCF4A08A954540054247B /* Build configuration list for PBXNativeTarget "Managed Software Update" */;
buildPhases = (
8D1107290486CEB800E47090 /* Resources */,
8D11072C0486CEB800E47090 /* Sources */,
8D11072E0486CEB800E47090 /* Frameworks */,
);
buildRules = (
);
dependencies = (
);
name = "Managed Software Update";
productInstallPath = "$(HOME)/Applications";
productName = "Managed Software Update";
productReference = 8D1107320486CEB800E47090 /* Managed Software Update.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
29B97313FDCFA39411CA2CEA /* Project object */ = {
isa = PBXProject;
buildConfigurationList = C01FCF4E08A954540054247B /* Build configuration list for PBXProject "Managed Software Update" */;
compatibilityVersion = "Xcode 3.0";
hasScannedForEncodings = 1;
mainGroup = 29B97314FDCFA39411CA2CEA /* Managed Software Update */;
projectDirPath = "";
projectRoot = "";
targets = (
8D1107260486CEB800E47090 /* Managed Software Update */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
8D1107290486CEB800E47090 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
8D11072B0486CEB800E47090 /* InfoPlist.strings in Resources */,
77631A3F0C0748CF005415CB /* main.py in Resources */,
7790198F0C07548A00326F66 /* Managed_Software_UpdateAppDelegate.py in Resources */,
77C8C1F90C07829500965286 /* MainMenu.xib in Resources */,
C0828C801123DBC5003D5807 /* MSUWebViewPolicyDelegate.py in Resources */,
C0828C901123DCFF003D5807 /* ShutDownReq.tif in Resources */,
C0828C911123DCFF003D5807 /* RestartRec.tif in Resources */,
C0828C921123DCFF003D5807 /* LogOutReq.tif in Resources */,
C0828C931123DCFF003D5807 /* Managed Software Update.icns in Resources */,
C0828C941123DCFF003D5807 /* Empty.png in Resources */,
C0828C951123DCFF003D5807 /* RestartReq.tif in Resources */,
C0828C961123DCFF003D5807 /* Restart.tif in Resources */,
C0828C971123DCFF003D5807 /* package.tiff in Resources */,
C0828C981123DCFF003D5807 /* Installer.tiff in Resources */,
C0828CA2112461C0003D5807 /* MSUWindowController.py in Resources */,
C0828D1011247A37003D5807 /* FoundationPlist.py in Resources */,
C0828D1211247B3A003D5807 /* munki.py in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
8D11072C0486CEB800E47090 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
8D11072D0486CEB800E47090 /* main.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXVariantGroup section */
089C165CFE840E0CC02AAC07 /* InfoPlist.strings */ = {
isa = PBXVariantGroup;
children = (
089C165DFE840E0CC02AAC07 /* English */,
);
name = InfoPlist.strings;
sourceTree = "<group>";
};
77C8C1F70C07829500965286 /* MainMenu.xib */ = {
isa = PBXVariantGroup;
children = (
77C8C1F80C07829500965286 /* English */,
);
name = MainMenu.xib;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
C01FCF4B08A954540054247B /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
COPY_PHASE_STRIP = NO;
GCC_DYNAMIC_NO_PIC = NO;
GCC_ENABLE_FIX_AND_CONTINUE = YES;
GCC_MODEL_TUNING = G5;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = Managed_Software_Update_Prefix.pch;
INFOPLIST_FILE = Info.plist;
INSTALL_PATH = "$(HOME)/Applications";
PRODUCT_NAME = "Managed Software Update";
WRAPPER_EXTENSION = app;
ZERO_LINK = YES;
};
name = Debug;
};
C01FCF4C08A954540054247B /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
GCC_MODEL_TUNING = G5;
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = Managed_Software_Update_Prefix.pch;
INFOPLIST_FILE = Info.plist;
INSTALL_PATH = "$(HOME)/Applications";
PRODUCT_NAME = "Managed Software Update";
WRAPPER_EXTENSION = app;
};
name = Release;
};
C01FCF4F08A954540054247B /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
GCC_WARN_ABOUT_RETURN_TYPE = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
PREBINDING = NO;
SDKROOT = "$(DEVELOPER_SDK_DIR)/MacOSX10.5.sdk";
};
name = Debug;
};
C01FCF5008A954540054247B /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ARCHS = (
ppc,
i386,
);
GCC_WARN_ABOUT_RETURN_TYPE = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
PREBINDING = NO;
SDKROOT = "$(DEVELOPER_SDK_DIR)/MacOSX10.5.sdk";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
C01FCF4A08A954540054247B /* Build configuration list for PBXNativeTarget "Managed Software Update" */ = {
isa = XCConfigurationList;
buildConfigurations = (
C01FCF4B08A954540054247B /* Debug */,
C01FCF4C08A954540054247B /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
C01FCF4E08A954540054247B /* Build configuration list for PBXProject "Managed Software Update" */ = {
isa = XCConfigurationList;
buildConfigurations = (
C01FCF4F08A954540054247B /* Debug */,
C01FCF5008A954540054247B /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 29B97313FDCFA39411CA2CEA /* Project object */;
}
@@ -0,0 +1,184 @@
#
# Managed_Software_UpdateAppDelegate.py
# Managed Software Update
#
# Created by Greg Neagle on 2/10/10.
# Copyright 2009-2010 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 *
import munki
import PyObjCTools
class Managed_Software_UpdateAppDelegate(NSObject):
window_controller = objc.IBOutlet()
array_controller = objc.IBOutlet()
_emptyImage = NSImage.imageNamed_("Empty.png")
_restartImage = NSImage.imageNamed_("RestartReq.tif")
_logoutImage = NSImage.imageNamed_("LogOutReq.tif")
_listofupdates = []
restart_required = False
logout_required = False
def applicationDidFinishLaunching_(self, sender):
pass
def applicationDidBecomeActive_(self, sender):
# display updates if available; if no available updates
# trigger an update check
if not self._listofupdates:
self.getAvailableUpdates()
if self._listofupdates:
self.buildTableData()
self.window_controller.theWindow.makeKeyAndOrderFront_(self)
else:
# no updates available. Should we check for some?
result = munki.checkForUpdates()
if result == 0:
self.window_controller.theWindow.makeKeyAndOrderFront_(self)
alert = NSAlert.alertWithMessageText_defaultButton_alternateButton_otherButton_informativeTextWithFormat_(u"Your software is up to date.", u"Quit", objc.nil, objc.nil, "There is no new software for your computer at this time.")
alert.beginSheetModalForWindow_modalDelegate_didEndSelector_contextInfo_(self.window_controller.theWindow, self, self.quitAlertDidEnd_returnCode_contextInfo_, objc.nil)
elif result == -1:
self.window_controller.theWindow.makeKeyAndOrderFront_(self)
alert = NSAlert.alertWithMessageText_defaultButton_alternateButton_otherButton_informativeTextWithFormat_(u"Cannot check for updates", u"Quit", objc.nil, objc.nil, "Managed Software Update cannot contact the update server at this time.\nIf this situtation continues, contact your systems administrator.")
alert.beginSheetModalForWindow_modalDelegate_didEndSelector_contextInfo_(self.window_controller.theWindow, self, self.quitAlertDidEnd_returnCode_contextInfo_, objc.nil)
elif result == -2:
self.window_controller.theWindow.makeKeyAndOrderFront_(self)
alert = NSAlert.alertWithMessageText_defaultButton_alternateButton_otherButton_informativeTextWithFormat_(u"Update check failed", u"Quit", objc.nil, objc.nil, "There is a configuration problem with the managed software installer. Could not start the update check process. Contact your systems administrator.")
alert.beginSheetModalForWindow_modalDelegate_didEndSelector_contextInfo_(self.window_controller.theWindow, self, self.quitAlertDidEnd_returnCode_contextInfo_, objc.nil)
def getAvailableUpdates(self):
updatelist = []
installinfo = munki.getInstallInfo()
if installinfo:
updatelist = installinfo.get("managed_installs", [])
if installinfo.get("removals"):
removallist = installinfo.get("removals")
restartNeeded = False
showRemovalDetail = munki.getRemovalDetailPrefs()
for item in removallist:
if item.get("RestartAction") == "RequireRestart" or item.get("RestartAction") == "RecommendRestart":
restartNeeded = True
if showRemovalDetail:
if display_name in item:
item["display_name"] = item["display_name"] + " (will be removed)"
elif name in item:
item["display_name"] = item["name"] + " (will be removed)"
updatelist.append(item)
if not showRemovalDetail:
row = {}
row["display_name"] = "Software removals"
row["version"] = ""
row["description"] = "Scheduled removal of managed software."
if restartNeeded:
row["RestartAction"] = "RequireRestart"
updatelist.append(row)
if updatelist:
self._listofupdates = updatelist
else:
appleupdates = munki.getAppleUpdates()
if appleupdates:
self._listofupdates = appleupdates.get("AppleUpdates", [])
def buildTableData(self):
table = []
self.restart_required = False
self.logout_required = False
for item in self._listofupdates:
row = {}
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
else:
row['image'] = self._emptyImage
row['name'] = item.get("display_name") or item.get("name","")
row['version'] = munki.trimVersionString(item.get("version_to_install"),3)
row['description'] = item.get("description","")
row_dict = NSDictionary.dictionaryWithDictionary_(row)
table.append(row_dict)
self.window_controller.setUpdatelist_(table)
self.window_controller.tableView.deselectAll_(self)
if self.restart_required:
self.window_controller.restartInfoFld.setStringValue_(u"Restart will be required.")
self.window_controller.restartImageFld.setImage_(self._restartImage)
elif self.logout_required:
self.window_controller.restartInfoFld.setStringValue_(u"Logout will be required.")
self.window_controller.restartImageFld.setImage_(self._logoutImage)
def tableViewSelectionDidChange_(self, sender):
if self.array_controller.selectedObjects():
row = self.array_controller.selectedObjects()[0]
self.window_controller.descriptionView.mainFrame().loadHTMLString_baseURL_(row.get("description",""), None)
else:
self.window_controller.descriptionView.mainFrame().loadHTMLString_baseURL_(u"", None)
def confirmInstallUpdates(self):
if len(munki.currentGUIusers()) > 1:
alert = NSAlert.alertWithMessageText_defaultButton_alternateButton_otherButton_informativeTextWithFormat_(u"Other users logged in", u"Cancel", objc.nil, objc.nil, "There are other users logged into this computer.\nUpdating now could cause other users to lose their work.\n\nPlease try again later after the other users have logged out.")
alert.beginSheetModalForWindow_modalDelegate_didEndSelector_contextInfo_(self.window_controller.theWindow, self, self.multipleUserAlertDidEnd_returnCode_contextInfo_, objc.nil)
elif self.restart_required:
alert = NSAlert.alertWithMessageText_defaultButton_alternateButton_otherButton_informativeTextWithFormat_(u"Restart Required", u"Log out and update", u"Cancel", objc.nil, "A restart is required after updating. Log out and update now?")
alert.beginSheetModalForWindow_modalDelegate_didEndSelector_contextInfo_(self.window_controller.theWindow, self, self.logoutAlertDidEnd_returnCode_contextInfo_, objc.nil)
elif self.logout_required or munki.installRequiresLogout():
alert = NSAlert.alertWithMessageText_defaultButton_alternateButton_otherButton_informativeTextWithFormat_(u"Logout Required", u"Log out and update", u"Cancel", objc.nil, "A logout is required before updating. Log out and update now?")
alert.beginSheetModalForWindow_modalDelegate_didEndSelector_contextInfo_(self.window_controller.theWindow, self, self.logoutAlertDidEnd_returnCode_contextInfo_, objc.nil)
else:
alert = NSAlert.alertWithMessageText_defaultButton_alternateButton_otherButton_informativeTextWithFormat_(u"Logout Recommended", u"Log out and update", u"Cancel", u"Update without logging out", "A logout is recommended before updating. Log out and update now?")
alert.beginSheetModalForWindow_modalDelegate_didEndSelector_contextInfo_(self.window_controller.theWindow, self, self.logoutAlertDidEnd_returnCode_contextInfo_, objc.nil)
def installSessionErrorAlert(self):
alert = NSAlert.alertWithMessageText_defaultButton_alternateButton_otherButton_informativeTextWithFormat_(u"Cannot start installation session", u"Quit", objc.nil, objc.nil, "There is a configuration problem with the managed software installer. Could not start the install session. Contact your systems administrator.")
alert.beginSheetModalForWindow_modalDelegate_didEndSelector_contextInfo_(self.window_controller.theWindow, self, self.quitAlertDidEnd_returnCode_contextInfo_, objc.nil)
@PyObjCTools.AppHelper.endSheetMethod
def logoutAlertDidEnd_returnCode_contextInfo_(self, alert, returncode, contextinfo):
if returncode == 0:
NSLog("User cancelled")
elif returncode == 1:
NSLog("User chose to log out")
result = munki.logoutAndUpdate()
if result:
self.installSessionErrorAlert()
elif returncode == -1:
NSLog("User chose to update without logging out")
result = munki.justUpdate()
if result:
self.installSessionErrorAlert()
else:
NSApp.terminate_(self)
@PyObjCTools.AppHelper.endSheetMethod
def multipleUserAlertDidEnd_returnCode_contextInfo_(self, alert, returncode, contextinfo):
pass
@PyObjCTools.AppHelper.endSheetMethod
def quitAlertDidEnd_returnCode_contextInfo_(self, alert, returncode, contextinfo):
NSApp.terminate_(self)
@@ -0,0 +1,7 @@
//
// Prefix header for all source files of the 'Managed Software Update' target in the 'Managed Software Update' project
//
#ifdef __OBJC__
#import <Cocoa/Cocoa.h>
#endif
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+47
View File
@@ -0,0 +1,47 @@
//
// main.m
// Managed Software Update
//
#import <Python/Python.h>
#import <Cocoa/Cocoa.h>
int main(int argc, char *argv[])
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSBundle *mainBundle = [NSBundle mainBundle];
NSString *resourcePath = [mainBundle resourcePath];
NSArray *pythonPathArray = [NSArray arrayWithObjects: resourcePath, [resourcePath stringByAppendingPathComponent:@"PyObjC"], nil];
setenv("PYTHONPATH", [[pythonPathArray componentsJoinedByString:@":"] UTF8String], 1);
NSArray *possibleMainExtensions = [NSArray arrayWithObjects: @"py", @"pyc", @"pyo", nil];
NSString *mainFilePath = nil;
for (NSString *possibleMainExtension in possibleMainExtensions) {
mainFilePath = [mainBundle pathForResource: @"main" ofType: possibleMainExtension];
if ( mainFilePath != nil ) break;
}
if ( !mainFilePath ) {
[NSException raise: NSInternalInconsistencyException format: @"%s:%d main() Failed to find the Main.{py,pyc,pyo} file in the application wrapper's Resources directory.", __FILE__, __LINE__];
}
Py_SetProgramName("/usr/bin/python");
Py_Initialize();
PySys_SetArgv(argc, (char **)argv);
const char *mainFilePathPtr = [mainFilePath UTF8String];
FILE *mainFile = fopen(mainFilePathPtr, "r");
int result = PyRun_SimpleFile(mainFile, (char *)[[mainFilePath lastPathComponent] UTF8String]);
if ( result != 0 )
[NSException raise: NSInternalInconsistencyException
format: @"%s:%d main() PyRun_SimpleFile failed with file '%@'. See console for errors.", __FILE__, __LINE__, mainFilePath];
[pool drain];
return result;
}
+34
View File
@@ -0,0 +1,34 @@
#
# main.py
# Managed Software Update
#
# Created by Greg Neagle on 2/10/10.
# Copyright 2009-2010 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.
#import modules required by application
import objc
import Foundation
import AppKit
from PyObjCTools import AppHelper
# import modules containing classes required to start application and load MainMenu.nib
import Managed_Software_UpdateAppDelegate
import MSUWindowController
import MSUWebViewPolicyDelegate
# pass control to AppKit
AppHelper.runEventLoop()
+225
View File
@@ -0,0 +1,225 @@
# encoding: utf-8
#
# munki.py
# Managed Software Update
#
# Created by Greg Neagle on 2/11/10.
# Copyright 2009-2010 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.
import os
import subprocess
import FoundationPlist
import time
from Foundation import NSDate
from ScriptingBridge import SBApplication
_updatechecklaunchfile = "/private/tmp/.com.googlecode.munki.updatecheck.launchd"
_MunkiStatusIdentifier = "com.googlecode.munki.MunkiStatus"
def getManagedInstallsPrefs():
# define default values
prefs = {}
prefs['ManagedInstallDir'] = "/Library/Managed Installs"
prefs['InstallAppleSoftwareUpdates'] = False
prefs['ShowRemovalDetail'] = False
prefs['InstallRequiresLogout'] = False
prefsfile = "/Library/Preferences/ManagedInstalls.plist"
pl = {}
if os.path.exists(prefsfile):
try:
pl = FoundationPlist.readPlist(prefsfile)
except FoundationPlist.NSPropertyListSerializationException:
pass
try:
for key in pl.keys():
if type(pl[key]).__name__ == "__NSCFDate":
# convert NSDate/CFDates to strings
prefs[key] = str(pl[key])
else:
prefs[key] = pl[key]
except AttributeError:
pass
return prefs
def getRemovalDetailPrefs():
return getManagedInstallsPrefs().get('ShowRemovalDetail', False)
def installRequiresLogout():
return getManagedInstallsPrefs().get('InstallRequiresLogout', False)
def getInstallInfo():
prefs = getManagedInstallsPrefs()
managedinstallbase = prefs['ManagedInstallDir']
pl = {}
installinfo = os.path.join(managedinstallbase, 'InstallInfo.plist')
if os.path.exists(installinfo):
try:
pl = FoundationPlist.readPlist(installinfo)
except FoundationPlist.NSPropertyListSerializationException:
pass
return pl
def startUpdateCheck():
# does launchd magic to run managedsoftwareupdate as root
cmd = ["/usr/bin/touch", _updatechecklaunchfile]
if subprocess.call(cmd):
return -1
else:
time.sleep(1)
# check to see if we were successful in starting the update
return updateInProgress()
def updateInProgress():
# if MunkiStatus is running, we're doing an update right now
MunkiStatus = SBApplication.applicationWithBundleIdentifier_(_MunkiStatusIdentifier)
if MunkiStatus and MunkiStatus.isRunning():
# bring it back to the front
MunkiStatus.activate()
return 1
elif os.path.exists(_updatechecklaunchfile):
# we tried to trigger the update, but it failed?
try:
# this might fail if we don't own it
os.unlink(_updatechecklaunchfile)
except:
pass
return -1
else:
return 0
def checkForUpdates():
# returns 1 if we've kicked off an update check (or one is in progress),
# returns 0 if we're not going to check (because we just did)
# returns -1 if the munki server is unavailable
# returns -2 if there's an unexpected problem
# are we checking right now (MunkiStatus.app is running)?
update = updateInProgress()
if update == 1:
return 1
elif update == -1:
return -2
# when did we last check?
now = NSDate.new()
prefs = getManagedInstallsPrefs()
lastCheckedDateString = prefs.get("LastCheckDate")
if lastCheckedDateString:
lastCheckedDate = NSDate.dateWithString_(lastCheckedDateString)
if (not lastCheckedDateString) or now.timeIntervalSinceDate_(lastCheckedDate) > 10:
# we haven't checked in more than 10 seconds
result = startUpdateCheck()
if result == 0:
return 1
else:
return -2
else:
# we just finished checking
lastCheckResult = prefs.get("LastCheckResult")
if lastCheckResult == -1:
# check failed
return -1
else:
return 0
def getAppleUpdates():
prefs = getManagedInstallsPrefs()
managedinstallbase = prefs['ManagedInstallDir']
pl = {}
appleUpdatesFile = os.path.join(managedinstallbase, 'AppleUpdates.plist')
if os.path.exists(appleUpdatesFile):
try:
pl = FoundationPlist.readPlist(appleUpdatesFile)
except FoundationPlist.NSPropertyListSerializationException:
pass
return pl
def trimVersionString(versString,tupleCount):
if versString == None or versString == "":
return ""
components = str(versString).split(".")
if len(components) > tupleCount:
components = components[0:tupleCount]
return ".".join(components)
def currentGUIusers():
'''Gets a list of GUI users by parsing the output of /usr/bin/who'''
gui_users = []
p = subprocess.Popen("/usr/bin/who", shell=False, stdin=subprocess.PIPE,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(output, err) = p.communicate()
lines = output.splitlines()
for line in lines:
if "console" in line:
parts = line.split()
gui_users.append(parts[0])
return gui_users
def logoutNow():
# uses oscascript to run an AppleScript
# to tell loginwindow to logout
# ugly, but it works.
script = """
ignoring application responses
tell application "loginwindow"
«event aevtrlgo»
end tell
end ignoring
"""
cmd = ["/usr/bin/osascript"]
for line in script.splitlines():
line = line.rstrip().lstrip()
if line:
cmd.append("-e")
cmd.append(line)
p = subprocess.Popen(cmd, bufsize=1, stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
(output, err) = p.communicate()
def logoutAndUpdate():
# touch a flag so the process that runs after
# logout knows it's OK to install everything
cmd = ["/usr/bin/touch", "/private/tmp/com.googlecode.munki.installatlogout"]
result = subprocess.call(cmd)
if result == 0:
logoutNow()
else:
return result
def justUpdate():
# trigger managedinstaller via launchd KeepAlive path trigger
# we touch a file that launchd is is watching
# launchd, in turn, launches managedsoftwareupdate --installwithnologout as root
cmd = ["/usr/bin/touch", "/private/tmp/.com.googlecode.munki.managedinstall.launchd"]
return subprocess.call(cmd)
Binary file not shown.