Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Pepijn Bruienne
2016-03-17 14:07:20 -04:00
80 changed files with 483 additions and 1642 deletions

View File

@@ -2,7 +2,7 @@
File: DockTilePlugIn.h
Copyright 2015 Greg Neagle.
Copyright 2015-2016 Greg Neagle.
Liberally adapted from Apple sample code:
https://developer.apple.com/library/mac/samplecode/DockTile/Listings/DockTilePlugIn_DockTilePlugIn_h.html

View File

@@ -2,7 +2,7 @@
File: DockTilePlugIn.m
Copyright 2015 Greg Neagle.
Copyright 2015-2016 Greg Neagle.
Liberally adapted from Apple sample code:
https://developer.apple.com/library/mac/samplecode/DockTile/Listings/DockTilePlugIn_DockTilePlugIn_h.html

View File

@@ -7,7 +7,7 @@
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>com.googlecode.munki.$(PRODUCT_NAME:rfc1034identifier)</string>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundlePackageType</key>
@@ -19,7 +19,7 @@
<key>CFBundleVersion</key>
<string>1</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2015 The Munki Project. All rights reserved.</string>
<string>Copyright © 2016 The Munki Project. All rights reserved.</string>
<key>NSPrincipalClass</key>
<string>DockTile</string>
</dict>

View File

@@ -34,7 +34,7 @@
C0B9E8B619AF7E9E00DB7247 /* Managed Software Center 10_6.icns in Resources */ = {isa = PBXBuildFile; fileRef = C0B9E8B519AF7E9E00DB7247 /* Managed Software Center 10_6.icns */; };
C0E098BC1857A3C80045DEEB /* msclib.py in Resources */ = {isa = PBXBuildFile; fileRef = C0E098BB1857A3C80045DEEB /* msclib.py */; };
C0EF96BA1ADDB9B2002C02FF /* DockTilePlugIn.m in Sources */ = {isa = PBXBuildFile; fileRef = C0EF96B91ADDB9B2002C02FF /* DockTilePlugIn.m */; };
C0EF96BD1ADDBD88002C02FF /* MSCDockTilePlugin.docktileplugin in Copy Files */ = {isa = PBXBuildFile; fileRef = C0EF96B11ADDB90B002C02FF /* MSCDockTilePlugin.docktileplugin */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
C0EF96BD1ADDBD88002C02FF /* MSCDockTilePlugin.docktileplugin in Copy Files */ = {isa = PBXBuildFile; fileRef = C0EF96B11ADDB90B002C02FF /* MSCDockTilePlugin.docktileplugin */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
C0F1586E187D256200052F9A /* MyStuffTemplate.png in Resources */ = {isa = PBXBuildFile; fileRef = C0F1586D187D256200052F9A /* MyStuffTemplate.png */; };
/* End PBXBuildFile section */
@@ -335,7 +335,7 @@
isa = PBXProject;
attributes = {
CLASSPREFIX = MSC;
LastUpgradeCheck = 0510;
LastUpgradeCheck = 0720;
ORGANIZATIONNAME = "The Munki Project";
TargetAttributes = {
C0EF96B01ADDB90B002C02FF = {
@@ -606,6 +606,7 @@
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_ENABLE_OBJC_EXCEPTIONS = YES;
@@ -649,7 +650,6 @@
C090050F16CDD84E00BE34CE /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ARCHS = "$(ARCHS_STANDARD_32_64_BIT)";
COMBINE_HIDPI_IMAGES = YES;
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = "Managed Software Center/Managed Software Center-Prefix.pch";
@@ -665,6 +665,7 @@
);
MACOSX_DEPLOYMENT_TARGET = 10.6;
ONLY_ACTIVE_ARCH = NO;
PRODUCT_BUNDLE_IDENTIFIER = com.googlecode.munki.ManagedSoftwareCenter;
PRODUCT_NAME = "Managed Software Center";
SDKROOT = macosx;
WRAPPER_EXTENSION = app;
@@ -674,7 +675,6 @@
C090051016CDD84E00BE34CE /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ARCHS = "$(ARCHS_STANDARD_32_64_BIT)";
COMBINE_HIDPI_IMAGES = YES;
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = "Managed Software Center/Managed Software Center-Prefix.pch";
@@ -689,6 +689,7 @@
"$(SYSTEM_LIBRARY_DIR)/Frameworks/Python.framework/Versions/2.6/lib/python2.6/config",
);
MACOSX_DEPLOYMENT_TARGET = 10.6;
PRODUCT_BUNDLE_IDENTIFIER = com.googlecode.munki.ManagedSoftwareCenter;
PRODUCT_NAME = "Managed Software Center";
SDKROOT = macosx;
WRAPPER_EXTENSION = app;
@@ -698,7 +699,6 @@
C0EF96B61ADDB90B002C02FF /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ARCHS = "$(ARCHS_STANDARD_32_64_BIT)";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = NO;
CLANG_WARN_BOOL_CONVERSION = YES;
@@ -731,6 +731,7 @@
"-framework",
AppKit,
);
PRODUCT_BUNDLE_IDENTIFIER = "com.googlecode.munki.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = macosx;
SKIP_INSTALL = NO;
@@ -741,7 +742,6 @@
C0EF96B71ADDB90B002C02FF /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ARCHS = "$(ARCHS_STANDARD_32_64_BIT)";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = NO;
CLANG_WARN_BOOL_CONVERSION = YES;
@@ -770,6 +770,7 @@
"-framework",
AppKit,
);
PRODUCT_BUNDLE_IDENTIFIER = "com.googlecode.munki.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = macosx;
SKIP_INSTALL = NO;

View File

@@ -1,6 +1,6 @@
# encoding: utf-8
#
# Copyright 2009-2014 Greg Neagle.
# Copyright 2009-2016 Greg Neagle.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.

View File

@@ -3,7 +3,7 @@
# MSCAppDelegate.py
# Managed Software Center
#
# Copyright 2013-2014 Greg Neagle.
# Copyright 2013-2016 Greg Neagle.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.

View File

@@ -3,7 +3,7 @@
# MSCBadgedTemplateImage.py
# Managed Software Center
#
# Copyright 2014 Greg Neagle.
# Copyright 2014-2016 Greg Neagle.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -68,8 +68,8 @@ class MSCBadgedTemplateImage(NSImage):
badgeOutlineWidth, badgeOutlineHeight)
# shift the rects around to look better. These are magic numbers.
badgeStringRect = NSOffsetRect(badgeStringRect, -4.75, -1)
badgeOutlineRect = NSOffsetRect(badgeOutlineRect, -1, -4)
badgeStringRect = NSOffsetRect(badgeStringRect, -4.75, -2)
badgeOutlineRect = NSOffsetRect(badgeOutlineRect, -1, -5)
# our erase rect needs to be a little bigger than the badge itself
badgeEraseRect = NSInsetRect(badgeOutlineRect, -1.5, -1.5)

View File

@@ -3,7 +3,7 @@
# MSCMainWindowController.py
# Managed Software Center
#
# Copyright 2013-2014 Greg Neagle.
# Copyright 2013-2016 Greg Neagle.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.

View File

@@ -2,7 +2,7 @@
#
# MSCStatusController.py
#
# Copyright 2009-2014 Greg Neagle.
# Copyright 2009-2016 Greg Neagle.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.

View File

@@ -2,22 +2,16 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSUserNotificationAlertStyle</key>
<string>alert</string>
<key>NSDockTilePlugIn</key>
<string>MSCDockTilePlugin.docktileplugin</string>
<key>CFBundleDisplayName</key>
<string>${PRODUCT_NAME}</string>
<key>LSHasLocalizedDisplayName</key>
<true/>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleDisplayName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleIconFile</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleIdentifier</key>
<string>com.googlecode.munki.ManagedSoftwareCenter</string>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
@@ -25,21 +19,9 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>4.1</string>
<string>4.2</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>GitRevision</key>
<string></string>
<key>LSMinimumSystemVersion</key>
<string>${MACOSX_DEPLOYMENT_TARGET}</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2015 The Munki Project https://github.com/munki/munki</string>
<key>NSMainNibFile</key>
<string>MainMenu</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
@@ -51,5 +33,23 @@
</array>
</dict>
</array>
<key>CFBundleVersion</key>
<string>1</string>
<key>GitRevision</key>
<string></string>
<key>LSHasLocalizedDisplayName</key>
<true/>
<key>LSMinimumSystemVersion</key>
<string>${MACOSX_DEPLOYMENT_TARGET}</string>
<key>NSDockTilePlugIn</key>
<string>MSCDockTilePlugin.docktileplugin</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2016 The Munki Project https://github.com/munki/munki</string>
<key>NSMainNibFile</key>
<string>MainMenu</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
<key>NSUserNotificationAlertStyle</key>
<string>alert</string>
</dict>
</plist>

View File

@@ -5,7 +5,7 @@
#
# Created by Greg Neagle on 2/21/14.
#
# Copyright 2014 Greg Neagle.
# Copyright 2014-2016 Greg Neagle.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.

View File

@@ -49,7 +49,7 @@ option {
}
body, button, input {
font-family: "Helvetica", "Arial";
font-family: "Helvetica Neue", "Helvetica", "Arial";
font-smooth: always;
-webkit-font-smoothing: antialiased
}
@@ -223,8 +223,8 @@ a {
html, body {
-webkit-background-size: 36px 635px;
background-repeat: repeat-x;
background-color: #f5f6f7;
font-family: "Lucida Grande", Helvetica, sans-serif;
background-color: white;
font-family: "Helvetica Neue", Helvetica, sans-serif;
font-size: 11px
}
@@ -276,7 +276,7 @@ div.columns {
.sidebar {
width: 200px;
margin-left: 15px
margin-left: 26px;
}
.sidebar h1, .main>h1 {
@@ -284,15 +284,14 @@ div.columns {
-webkit-font-smoothing: subpixel-antialiased;
background-color: #002354;
color: transparent;
font-family: Helvetica;
font-family: "Helvetica Neue", Helvetica;
font-size: 24px;
font-weight: bold;
font-weight: normal;
line-height: 26px;
margin-bottom: 5px;
overflow: visible;
position: relative;
text-shadow: rgba(255, 255, 255, .496094) 0 1px 2px;
top: -5px
top: -5px;
}
footer {
@@ -309,7 +308,7 @@ footer {
}
footer div.bottom-links {
margin: 0 35px 0 0;
margin: 0 0 0 0;
border-top: 1px solid #d3d7db;
position: relative
}
@@ -324,7 +323,6 @@ footer div.bottom-links * {
color: #4f5f6e;
font-size: 10px;
font-weight: normal;
text-shadow: #fff 0 1px 0
}
footer div.bottom-links>span {
@@ -333,7 +331,7 @@ footer div.bottom-links>span {
footer div.bottom-links li a {
color: #576982;
font-weight: bold
font-weight: normal
}
footer div.bottom-links>ul>li:not(:last-child):after {
@@ -396,14 +394,14 @@ div.select {
div.select select {
background: -webkit-gradient(linear, left top, left bottom, from( #fff), to( #eaeaea));
font-size: 11px;
font-weight: bold;
color: #626262;
font-weight: normal;
/*color: #626262;*/
outline: 0;
line-height: 15px;
margin: 0;
padding: 0 25px 1px 5px;
border: 0;
text-shadow: #fff 0 1px 0;
/*text-shadow: #fff 0 1px 0;*/
-webkit-appearance: none;
-webkit-box-shadow: rgba(0, 0, 0, .35) 0 1px 2px, rgba(0, 0, 0, .8) 0 0 1px;
box-shadow: rgba(0, 0, 0, .35) 0 1px 2px, rgba(0, 0, 0, .2) 0 0 1px;
@@ -443,19 +441,14 @@ div.select:after {
}
div.titled-box {
background-color: #fff;
margin-bottom: 15px;
padding-bottom: 10px;
-webkit-border-radius: 5px;
-webkit-box-shadow: rgba(0, 0, 0, .45) 0 1px 3px, rgba(0, 0, 0, .8) 0 0 1px;
box-shadow: rgba(0, 0, 0, .45) 0 1px 4px, rgba(0, 0, 0, .2) 0 0 1px;
-webkit-background-clip: padding
}
div.titled-box h2 {
font: bold 13px 'Lucida Grande';
font: normal 14px "Helvetica Neue", 'Helvetica';
color: #53565e;
padding: 0 10px;
line-height: 24px;
overflow: hidden;
text-overflow: ellipsis;
@@ -463,9 +456,9 @@ div.titled-box h2 {
}
div.titled-box h2 a {
font: bold 12px 'Lucida Grande';
font: normal 12px "Helvetica Neue", 'Helvetica';
color: #53565e;
line-height: 25px
line-height: 24px
}
div.titled-box>div.content {
@@ -477,11 +470,11 @@ div.titled-box.quick-links>div.content {
}
div.titled-box h2, div.titled-box hr {
border-width: 0 0 3px 0;
border: none;
}
div.titled-box hr {
margin: 8px 0
margin: 28px 0
}
div.titled-box li.popup {
@@ -507,7 +500,7 @@ div.titled-box li.link {
padding: 0 11px;
height: 19px;
line-height: 16px;
font-weight: bold;
font-weight: normal;
border-color: transparent;
border-width: 1px 0;
border-style: solid
@@ -523,7 +516,7 @@ div.titled-box li.link.selected {
div.titled-box li.link a {
display: block;
font-size: 11px;
font-weight: bold;
font-weight: normal;
color: #222;
overflow: hidden;
text-overflow: ellipsis;
@@ -539,32 +532,24 @@ body.screen-reader div.chart ol>li div.lockup div.lockup-info>div.multi-button {
}
div.lockup-container, div.container, div.titled-container {
background-color: #fff;
-webkit-border-radius: 5px;
-webkit-box-shadow: rgba(0, 0, 0, .45) 0 1px 3px, rgba(0, 0, 0, .8) 0 0 1px;
box-shadow: rgba(0, 0, 0, .45) 0 1px 4px, rgba(0, 0, 0, .2) 0 0 1px;
-webkit-background-clip: padding
/*-webkit-border-radius: 5px;*/
}
div.lockup-container .title, div.titled-container .title {
padding: 0 10px;
height: 32px;
border-bottom: 1px solid #d7d7d7
}
div.lockup-container .title h2, div.titled-container .title h2 {
display: inline-block;
font: bold 14px 'Lucida Grande';
font: normal 14px "Helvetica Neue", 'Helvetica';
color: #53565e;
padding: 0 10px 0 0;
line-height: 32px
}
div.lockup-container .title, div.titled-container .title, div.titled-box h2 {
text-shadow: 0 1px 0 #fff;
-webkit-border-top-left-radius: 5px;
-webkit-border-top-right-radius: 5px;
background: -webkit-gradient(linear, left bottom, left top, from( #e3e3e3), color-stop(0.49, #ededed), color-stop(0.5, #f4f4f4), to(#fff))
}
div.titled-box h2 {
@@ -572,14 +557,16 @@ div.titled-box h2 {
border-bottom: 1px solid #cacaca;
border-width: 0 0 1px !important;
margin-bottom: 6px;
-webkit-border-image: none
margin-top: 10px;
-webkit-border-image: none;
font-size: 13px;
line-height: 22px
}
div.lockup-container .title h1 {
display: inline-block;
font: bold 14px Lucida Grande;
font: normal 14px "Helvetica Neue", Helvetica;
color: #53565e;
padding: 0 10px 0 0;
line-height: 32px
}
@@ -591,7 +578,7 @@ div.lockup-container .title h1 a {
div.lockup-container .title span {
color: #53565e;
font-size: 10px;
font-weight: bold
font-weight: normal
}
div.lockup-container .content-and-controls, div.titled-container .content-and-controls {
@@ -604,7 +591,6 @@ div.lockup-container .content-and-controls:before, div.titled-container .content
position: absolute;
width: 100%;
top: -1px;
border-width: 0 0 3px 0;
}
div.lockup-container .content-and-controls:after, div.titled-container .content-and-controls:before {
@@ -623,9 +609,6 @@ div.lockup-container div.lockup {
float: left;
width: 33.3%;
height: 98px;
border-right: 1px solid #d9d9d9;
border-bottom: 1px solid #d9d9d9;
border-left: 1px solid #fff
}
div.lockup-container div.lockup div.artwork {
@@ -673,18 +656,6 @@ div.lockup-container[data-columns-current="3"] div.lockup:nth-of-type(3n+2) {
width: 33.4%
}
div.lockup-container[data-columns-current="3"] div.lockup:nth-of-type(6n+1),
div.lockup-container[data-columns-current="3"] div.lockup:nth-of-type(6n+2),
div.lockup-container[data-columns-current="3"] div.lockup:nth-of-type(6n+3) {
background: #fff
}
div.lockup-container[data-columns-current="3"] div.lockup:nth-of-type(6n+4),
div.lockup-container[data-columns-current="3"] div.lockup:nth-of-type(6n+5),
div.lockup-container[data-columns-current="3"] div.lockup:nth-of-type(6n+6) {
background: -webkit-gradient(linear, left top, left bottom, from( #f3f3f3), to( #eaeaea))
}
div.lockup-container[data-columns-current="3"] div.lockup:nth-of-type(3n+1) {
border-left: 0
}
@@ -693,43 +664,16 @@ div.lockup-container:not(.shelf)[data-columns-current="3"] div.lockup:nth-of-typ
display: table-cell;
/*float: none;*/
/*height: auto;*/
border-right: 0;
}
div.lockup-container[data-columns-current="3"] div.lockup:nth-last-of-type(3) {
-webkit-border-bottom-left-radius: 5px
}
div.lockup-container[data-columns-current="4"] div.lockup:nth-of-type(8n+1),
div.lockup-container[data-columns-current="4"] div.lockup:nth-of-type(8n+2),
div.lockup-container[data-columns-current="4"] div.lockup:nth-of-type(8n+3),
div.lockup-container[data-columns-current="4"] div.lockup:nth-of-type(8n+4) {
background: #fff
}
div.lockup-container[data-columns-current="4"] div.lockup:nth-of-type(8n+5),
div.lockup-container[data-columns-current="4"] div.lockup:nth-of-type(8n+6),
div.lockup-container[data-columns-current="4"] div.lockup:nth-of-type(8n+7),
div.lockup-container[data-columns-current="4"] div.lockup:nth-of-type(8n+8) {
background: -webkit-gradient(linear, left top, left bottom, from( #f3f3f3), to( #eaeaea))
}
div.lockup-container[data-columns-current="4"] div.lockup, div.lockup-container[data-columns-current="4"] div.lockup:nth-of-type(3n+2) {
width: 25%
}
div.lockup-container[data-columns-current="4"] div.lockup:nth-of-type(3n+1) {
border-left: 1px solid #fff
}
div.lockup-container[data-columns-current="4"] div.lockup:nth-of-type(3n) {
border-right: 1px solid #d9d9d9
}
div.lockup-container[data-columns-current="4"] div.lockup:nth-of-type(4n+1) {
border-left: 0
}
div.lockup-container:not(.shelf)[data-columns-current="4"] div.lockup:nth-of-type(4n) {
display: table-cell;
/*float: none;
@@ -749,41 +693,10 @@ div.lockup-container[data-columns-current="5"] div.lockup, div.lockup-container[
width: 20%
}
div.lockup-container[data-columns-current="5"] div.lockup:nth-of-type(10n+1),
div.lockup-container[data-columns-current="5"] div.lockup:nth-of-type(10n+2),
div.lockup-container[data-columns-current="5"] div.lockup:nth-of-type(10n+3),
div.lockup-container[data-columns-current="5"] div.lockup:nth-of-type(10n+4),
div.lockup-container[data-columns-current="5"] div.lockup:nth-of-type(10n+5) {
background: #fff
}
div.lockup-container[data-columns-current="5"] div.lockup:nth-of-type(10n+6),
div.lockup-container[data-columns-current="5"] div.lockup:nth-of-type(10n+7),
div.lockup-container[data-columns-current="5"] div.lockup:nth-of-type(10n+8),
div.lockup-container[data-columns-current="5"] div.lockup:nth-of-type(10n+9),
div.lockup-container[data-columns-current="5"] div.lockup:nth-of-type(10n+10) {
background: -webkit-gradient(linear, left top, left bottom, from( #f3f3f3), to( #eaeaea))
}
div.lockup-container[data-columns-current="5"] div.lockup:nth-of-type(3n+1),
div.lockup-container[data-columns-current="5"] div.lockup:nth-of-type(4n+1) {
border-left: 1px solid #fff
}
div.lockup-container[data-columns-current="5"] div.lockup:nth-of-type(3n),
div.lockup-container[data-columns-current="5"] div.lockup:nth-of-type(4n) {
border-right: 1px solid #d9d9d9
}
div.lockup-container[data-columns-current="5"] div.lockup:nth-of-type(5n+1) {
border-left: 0
}
div.lockup-container:not(.shelf)[data-columns-current="5"] div.lockup:nth-of-type(5n) {
display: table-cell;
/*float: none;
height: auto;*/
border-right: 0
}
div.lockup-container[data-columns-current="5"] div.lockup:nth-last-of-type(5) {
@@ -817,7 +730,6 @@ div.lockup ul li {
div.lockup li.name {
color: #000;
font-weight: bold;
max-width: 190px
}
@@ -839,8 +751,7 @@ div.lockup-container div.lockup ul li {
div.lockup-container div.lockup.category ul {
margin-top: 6px;
font-weight: bold;
text-shadow: 0 1px 0 #fff
font-weight: normal;
}
div.lockup-container div.lockup.category li.genre a {
@@ -853,13 +764,13 @@ div.msc-button.small {
div.msc-button-inner {
height: 13px;
-webkit-border-radius: 10px;
background: -webkit-gradient(linear, 0% 0, 0% 100%, from( #A8AAAF), color-stop(0.05, #A6A8AE), color-stop(0.5, #A1A3A9), color-stop(0.96, #9C9EA4), to( #989DA3));
-webkit-border-radius: 3px;
background: -webkit-gradient(linear, 0% 0, 0% 100%, from(rgb(166, 168, 174)), to(rgb(157, 159, 164)));
border: 1px solid rgba(136, 136, 138, .746094);
text-transform: uppercase;
/*text-shadow: rgba(0, 0, 0, .5) 0 -1px 0;*/
color: #fff;
font: normal normal bold 9px/11px 'Lucida Grande';
font: normal normal bold 9px/12px 'Helvetica';
padding-left: 10px;
padding-right: 10px;
}
@@ -878,7 +789,7 @@ div.msc-button-inner.removing {
}
div.msc-button-inner:not(.installed-not-removable):hover {
background: -webkit-gradient(linear, left top, left bottom, from( #85868d), color-stop(0.05, #84858c), color-stop(0.5, #888188), color-stop(0.96, #7b7c83), to( #7a7bb2))
background: -webkit-gradient(linear, left top, left bottom, from( #85868d), to( #7a7bb2))
}
div.msc-button button {
@@ -901,11 +812,10 @@ div.msc-button-inner.large {
font-size: 13px;
font-family: "Helvetica";
text-transform: capitalize;
line-height: 24px;
border: 0;
line-height: 21px;
border: 1px solid rgb(42, 73, 118);
color: #fff;
text-shadow: rgba(0, 0, 0, .5) 0 -1px 0;
background: -webkit-gradient(linear, left top, left bottom, from( #367FD4), color-stop(0.50, #2255A0), color-stop(0.49, #2B68B6), to( #234F92));
background: -webkit-gradient(linear, left top, left bottom, from(rgb(73,124,205)), to(rgb(47,79,143)));
border-color: #1F4A7A;
-webkit-border-radius: 5px;
}
@@ -925,7 +835,7 @@ div.msc-button-inner.large.removing {
}
div.msc-button-inner.large:not(.installed-not-removable):hover {
background: -webkit-gradient(linear, left top, left bottom, from( #0067B8), color-stop(0.49, #00509A), color-stop(0.50, #003E85), to( #003777))
background: -webkit-gradient(linear, left top, left bottom, from( #0067B8), to( #003777))
}
div.msc-button.install-updates {
@@ -1072,9 +982,9 @@ div.msc-button-inner.install-updates:not(.installed-not-removable):hover {
div.progress-spinner {
background: url(progress-spinner.png) 0 0 no-repeat;
-webkit-background-size: 21px 20px;
-webkit-background-size: 20px 20px;
opacity: 1;
width: 21px;
width: 20px;
height: 20px;
-webkit-animation-name: 'generic-loading-spinner-animation';
-webkit-animation-iteration-count: infinite;
@@ -1202,7 +1112,6 @@ div.showcase {
overflow: hidden;
-webkit-transition: opacity .25s;
-webkit-border-bottom-right-radius: 8px;
-webkit-box-shadow: 0 1px 3px #999;
position: relative;
left: 0;
margin-right: 0;
@@ -1223,7 +1132,7 @@ div.stage>img {
position: absolute;
height: 200px;
left: 0;
background-color: #000;
/*background-color: #000;*/
text-align: left;
overflow: hidden;
-webkit-transition: opacity 1s ease-in-out;
@@ -1346,9 +1255,9 @@ div.lockup li.removing span.progress-spinner {
visibility: visible !important;
float: right;
background: url(progress-spinner.png) 0 0 no-repeat;
-webkit-background-size: 21px 20px;
-webkit-background-size: 20px 20px;
opacity: 1;
width: 21px;
width: 20px;
height: 20px;
-webkit-animation-name: 'generic-loading-spinner-animation';
-webkit-animation-iteration-count: infinite;
@@ -1361,8 +1270,8 @@ div.lockup li.preparing-removal span.progress-spinner,
div.lockup li.installing span.progress-spinner,
div.lockup li.removing span.progress-spinner {
position: absolute;
bottom: 2px;
right: 2px;
bottom: 20px;
right: 20px;
}
div.lockup li {

View File

@@ -40,11 +40,11 @@ body.product .product-detail .product-info {
}
body.product .product-detail .product-info .title h1 {
margin-bottom: 12px;
font-family: Helvetica;
font-family: "Helvetica Neue", Helvetica;
font-size: 24px;
line-height: 22px;
font-weight: bold;
color: #6d83a2;
/*font-weight: bold;*/
/*color: #6d83a2;*/
text-shadow: rgba(255,255,255,1) 0 1px 0
}
body.product .product-detail .product-info .title a {
@@ -52,7 +52,7 @@ color: currentcolor
}
body.product .product-detail .product-info .product-review h4 {
margin-bottom: 4px;
font-family: Helvetica;
font-family: "Helvetica Neue", Helvetica;
font-size: 16px;
font-weight: bold;
color: #6d83a2;
@@ -63,7 +63,6 @@ font-size: 12px;
line-height: 16px
}
body.product .sidebar .app-info .content {
padding: 0 10px;
font-size: 11px;
color: #000
}
@@ -72,7 +71,7 @@ margin-bottom: 5px
}
body.product .sidebar .app-info li .label,
body.product .sidebar .app-info p span.label {
font-weight: bold;
font-weight: normal;
color: #787878
}
body.product .sidebar .more-by {
@@ -85,7 +84,6 @@ body.product .sidebar .more-by div.lockup {
display: -webkit-box;
padding: 7px 10px;
border-width: 0 0 1px 0;
background-image: -webkit-gradient(linear,left top,left bottom,from(#fff),to( #f9f9f9));
height: 55px
}
body.product .sidebar .more-by div.lockup:last-child {
@@ -115,7 +113,7 @@ overflow: hidden
body.product .sidebar .more-by div.lockup li.name {
margin-bottom: 1px;
font-size: 11px;
font-weight: bold
/*font-weight: bold*/
}
body.product .sidebar .more-by div.lockup li.genre {
color: #6c6c6c;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@@ -22,6 +22,9 @@
.main>div:not(:last-of-type) {
margin-bottom: 20px
}
div.status-results {
border-bottom: 1px solid #d9d9d9
}
div.installations {
margin: 14px 0;
position: relative
@@ -41,19 +44,15 @@ div.installations table:not(.no-header):before {
}
div.installations thead {
border-radius: 5px 5px 0 0;
padding: 0 0 0 10px;
height: 32px;
border-bottom: 1px solid #d7d7d7;
text-shadow: 0 1px 0 #fff;
background: -webkit-gradient(linear,left bottom,left top,from( #e3e3e3),color-stop(0.49, #ededed),color-stop(0.5, #f4f4f4),to(#fff))
}
div.installations th {
color: #53565e;
font-size: 14px;
font-weight: bold;
font-weight: normal;
height: 32px;
line-height: 14px;
padding: 0 10px;
text-align: left
}
#updates div.installations th {
@@ -63,7 +62,13 @@ div.installations th {
}
#update-count-string {
margin-left: -10px
/*margin-left: -10px*/
}
#updates-progress-spinner {
margin-right: 4px;
position: relative;
left: 0px;
}
div.installations th:first-child {
@@ -72,9 +77,6 @@ div.installations th:first-child {
div.installations th:last-child {
-webkit-border-top-right-radius: 5px
}
div.installations tbody tr:nth-of-type(even) {
background: -webkit-gradient(linear,left top,left bottom,from( #f3f3f3),to( #eaeaea))
}
.installation td {
border-top: 1px solid #fff;
border-bottom: 1px solid #d9d9d9;
@@ -83,6 +85,9 @@ div.installations tbody tr:nth-of-type(even) {
.installation td,.installation td a {
color: #494949
}
.installation td.install-info-cell,.installation td.install-info-cell a, li.install-info-cell {
color: rgb(121,121,121)
}
.installation:first-child td {
border-top: transparent
}
@@ -100,7 +105,7 @@ div.installations tbody tr:nth-of-type(even) {
.installation h2 {
color: #565656;
font-size: 13px;
font-weight: bold;
font-weight: normal;
overflow: hidden;
text-overflow: ellipsis;
max-width: 300px
@@ -133,7 +138,7 @@ div.installations tbody tr:nth-of-type(even) {
-webkit-box-shadow: none
}
#other-updates #os-updates,#os-and-app-updates #os-updates {
border-bottom: 1px solid #d9d9d9
/*border-bottom: 1px solid #d9d9d9*/
}
#other-updates #os-updatesth:last-child,#os-and-app-updates #os-updates th:last-child {
width: 100%;
@@ -148,29 +153,21 @@ div.installations tbody tr:nth-of-type(even) {
top: 6px;
left: 0
}
#os-and-app-updates #os-updates:not(.hidden)+#app-updates tbody tr:nth-of-type(even) {
background: 0
}
#os-and-app-updates #os-updates:not(.hidden)+#app-updates tbody tr:nth-of-type(odd) {
background: -webkit-gradient(linear,left top,left bottom,from( #f3f3f3),to( #eaeaea))
}
@-webkit-keyframes fade-out-and-remove {
0% {opacity: 1}
100% {opacity: 0}
}
#purchases {
text-shadow:#fff 0 1px 0
}
div.purchases .installation td {
height: 41px
}
div.purchases .installation:last-child td:first-child {
/*div.purchases .installation:last-child td:first-child {
-webkit-border-bottom-left-radius: 5px
}
div.purchases .installation:last-child td:last-child {
-webkit-border-bottom-right-radius: 5px
}
}*/
div.purchases .installation td:first-child {
width: 225px
}
@@ -238,9 +235,8 @@ div.purchases .installation td.status span.preparing-removal
}
#updates #header h1 {
font-size: 15px;
font-weight: bold;
font-weight: normal;
color: #929292;
text-shadow: 0 1px 0 #fff;
text-align: center
}
div.updates table {
@@ -385,13 +381,12 @@ div.os-updates.expanded tbody tr:nth-child(n+3):before {
top: 0;
right: 100px;
left: 240px;
background: -webkit-linear-gradient(left,rgba(0,0,0,.04) 0,rgba(0,0,0,.16) 20%,rgba(0,0,0,.16) 80%,rgba(0,0,0,.04) 100%)
}
div.os-updates.expanded tbody tr.sosumi:before {
display: none
}
.activity-indicator {
font: bold 20px Helvetica
font: normal 20px "Helvetica Neue", Helvetica
}
#updates #header .scan-progress {
padding: 0 0 1px 13px

View File

@@ -2,4 +2,4 @@
"CFBundleName" = "Managed Software Center";
"CFBundleDisplayName" = "Managed Software Center";
NSHumanReadableCopyright = "Copyright © 2010-2014 The Munki Project\nhttps://github.com/munki/munki";
NSHumanReadableCopyright = "Copyright © 2010-2016 The Munki Project\nhttps://github.com/munki/munki";

View File

@@ -2,4 +2,4 @@
"CFBundleName" = "Geführte Softwareaktualisierung";
"CFBundleDisplayName" = "Geführte Softwareaktualisierung";
NSHumanReadableCopyright = "Copyright © 2010-2014 The Munki Project\nhttps://github.com/munki/munki";
NSHumanReadableCopyright = "Copyright © 2010-2016 The Munki Project\nhttps://github.com/munki/munki";

View File

@@ -2,4 +2,4 @@
"CFBundleName" = "Managed Software Center";
"CFBundleDisplayName" = "Managed Software Center";
NSHumanReadableCopyright = "Copyright © 2010-2014 The Munki Project\nhttps://github.com/munki/munki";
NSHumanReadableCopyright = "Copyright © 2010-2016 The Munki Project\nhttps://github.com/munki/munki";

View File

@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="7531" systemVersion="14D136" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="7706" systemVersion="15D21" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none">
<dependencies>
<deployment version="1060" identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="7531"/>
<plugIn identifier="com.apple.WebKitIBPlugin" version="7531"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="7706"/>
<plugIn identifier="com.apple.WebKitIBPlugin" version="7706"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="NSApplication"/>
@@ -211,11 +211,13 @@
<webView id="S5Y-lq-2nB">
<rect key="frame" x="0.0" y="0.0" width="1143" height="730"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<animations/>
<webPreferences key="preferences" defaultFontSize="12" defaultFixedFontSize="12" plugInsEnabled="NO" javaEnabled="NO">
<nil key="identifier"/>
</webPreferences>
</webView>
</subviews>
<animations/>
</view>
<toolbar key="toolbar" implicitIdentifier="1393D938-3469-4EFC-8191-CC1B9C26850B" autosavesConfiguration="NO" allowsUserCustomization="NO" displayMode="iconAndLabel" sizeMode="regular" id="c5f-Iv-Tk6">
<allowedToolbarItems>
@@ -228,6 +230,7 @@
<button key="view" id="CPR-oF-gUx" customClass="MSCToolbarButton">
<rect key="frame" x="3" y="14" width="48" height="30"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<animations/>
<buttonCell key="cell" type="squareTextured" bezelStyle="texturedSquare" image="AllItemsTemplate" imagePosition="overlaps" alignment="center" inset="2" id="6UP-LY-MAs" customClass="MSCToolbarButtonCell">
<behavior key="behavior" pushIn="YES" changeContents="YES" lightByContents="YES"/>
<font key="font" metaFont="smallSystem"/>
@@ -244,6 +247,7 @@
<button key="view" id="YGi-pE-3SV" customClass="MSCToolbarButton">
<rect key="frame" x="8" y="14" width="48" height="30"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<animations/>
<buttonCell key="cell" type="squareTextured" bezelStyle="texturedSquare" image="toolbarCategoriesTemplate" imagePosition="only" alignment="center" inset="2" id="Vtx-wt-JLV" customClass="MSCToolbarButtonCell">
<behavior key="behavior" pushIn="YES" changeContents="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
@@ -260,6 +264,7 @@
<button key="view" id="eek-hQ-v8t" customClass="MSCToolbarButton">
<rect key="frame" x="4" y="14" width="48" height="30"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<animations/>
<buttonCell key="cell" type="squareTextured" bezelStyle="texturedSquare" image="MyStuffTemplate" imagePosition="only" alignment="center" inset="2" id="Z0f-h1-4qy" customClass="MSCToolbarButtonCell">
<behavior key="behavior" pushIn="YES" changeContents="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
@@ -276,6 +281,7 @@
<button key="view" id="caG-fL-UJh" customClass="MSCToolbarButton">
<rect key="frame" x="1" y="14" width="48" height="30"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<animations/>
<buttonCell key="cell" type="squareTextured" bezelStyle="texturedSquare" image="updatesTemplate" imagePosition="overlaps" alignment="center" inset="2" id="ZVD-Hl-dlF" customClass="MSCToolbarButtonCell">
<behavior key="behavior" pushIn="YES" changeContents="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
@@ -292,6 +298,7 @@
<customView key="view" id="Mui-Sc-36b">
<rect key="frame" x="38" y="14" width="4" height="30"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<animations/>
</customView>
</toolbarItem>
<toolbarItem implicitItemIdentifier="DF488649-5B94-417F-B23E-C646362F6FCA" label="" paletteLabel="Navigation" tag="-1" id="CEb-sx-e3H">
@@ -305,13 +312,15 @@
<rect key="frame" x="0.0" y="0.0" width="150" height="30"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<progressIndicator horizontalHuggingPriority="750" verticalHuggingPriority="750" maxValue="100" displayedWhenStopped="NO" bezeled="NO" indeterminate="YES" controlSize="small" style="spinning" id="mXP-yD-2El">
<rect key="frame" x="62" y="2" width="16" height="16"/>
<progressIndicator wantsLayer="YES" horizontalHuggingPriority="750" verticalHuggingPriority="750" maxValue="100" displayedWhenStopped="NO" bezeled="NO" indeterminate="YES" controlSize="small" style="spinning" id="mXP-yD-2El">
<rect key="frame" x="62" y="3" width="16" height="16"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<animations/>
</progressIndicator>
<segmentedControl verticalHuggingPriority="750" id="jcc-R8-zGb">
<rect key="frame" x="1" y="-2" width="53" height="25"/>
<rect key="frame" x="1" y="-1" width="53" height="25"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<animations/>
<segmentedCell key="cell" borderStyle="border" alignment="left" style="texturedRounded" trackingMode="momentary" id="ZsM-fh-8z5">
<font key="font" metaFont="system"/>
<segments>
@@ -324,9 +333,11 @@
</connections>
</segmentedControl>
</subviews>
<animations/>
</view>
<animations/>
<color key="borderColor" white="0.0" alpha="0.41999999999999998" colorSpace="calibratedWhite"/>
<color key="fillColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<color key="fillColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</box>
</toolbarItem>
<toolbarItem implicitItemIdentifier="E1A9A599-F94D-4780-9330-F2B0F75FA3BA" label="" paletteLabel="Search" tag="-1" id="fbJ-cF-weR">
@@ -341,8 +352,9 @@
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<searchField wantsLayer="YES" verticalHuggingPriority="750" id="gLI-O8-siB">
<rect key="frame" x="0.0" y="0.0" width="150" height="22"/>
<rect key="frame" x="0.0" y="1" width="150" height="22"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
<animations/>
<searchFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" editable="YES" borderStyle="bezel" usesSingleLineMode="YES" bezelStyle="round" sendsWholeSearchString="YES" id="Rxm-nO-bmO">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
@@ -353,9 +365,11 @@
</connections>
</searchField>
</subviews>
<animations/>
</view>
<animations/>
<color key="borderColor" white="0.0" alpha="0.41999999999999998" colorSpace="calibratedWhite"/>
<color key="fillColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
<color key="fillColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</box>
</toolbarItem>
</allowedToolbarItems>
@@ -417,6 +431,6 @@
<image name="NSGoLeftTemplate" width="9" height="12"/>
<image name="NSGoRightTemplate" width="9" height="12"/>
<image name="toolbarCategoriesTemplate" width="25" height="24"/>
<image name="updatesTemplate" width="28" height="27.5"/>
<image name="updatesTemplate" width="22.079999923706055" height="22.079999923706055"/>
</resources>
</document>

View File

@@ -2,4 +2,4 @@
"CFBundleName" = "Managed Software Centre";
"CFBundleDisplayName" = "Managed Software Centre";
NSHumanReadableCopyright = "Copyright © 2010-2014 The Munki Project\nhttps://github.com/munki/munki";
NSHumanReadableCopyright = "Copyright © 2010-2016 The Munki Project\nhttps://github.com/munki/munki";

View File

@@ -2,4 +2,4 @@
"CFBundleName" = "Managed Software Centre";
"CFBundleDisplayName" = "Managed Software Centre";
NSHumanReadableCopyright = "Copyright © 2010-2014 The Munki Project\nhttps://github.com/munki/munki";
NSHumanReadableCopyright = "Copyright © 2010-2016 The Munki Project\nhttps://github.com/munki/munki";

View File

@@ -2,4 +2,4 @@
"CFBundleName" = "Managed Software Centre";
"CFBundleDisplayName" = "Managed Software Centre";
NSHumanReadableCopyright = "Copyright © 2010-2014 The Munki Project\nhttps://github.com/munki/munki";
NSHumanReadableCopyright = "Copyright © 2010-2016 The Munki Project\nhttps://github.com/munki/munki";

View File

@@ -2,4 +2,4 @@
"CFBundleName" = "Centro de aplicaciones";
"CFBundleDisplayName" = "Centro de aplicaciones";
NSHumanReadableCopyright = "Copyright © 2010-2014 The Munki Project\nhttps://github.com/munki/munki";
NSHumanReadableCopyright = "Copyright © 2010-2016 The Munki Project\nhttps://github.com/munki/munki";

View File

@@ -2,4 +2,4 @@
"CFBundleName" = "Managed Software Center";
"CFBundleDisplayName" = "Managed Software Center";
NSHumanReadableCopyright = "Copyright © 2010-2014 The Munki Project\nhttps://github.com/munki/munki";
NSHumanReadableCopyright = "Copyright © 2010-2016 The Munki Project\nhttps://github.com/munki/munki";

View File

@@ -2,4 +2,4 @@
"CFBundleName" = "Centre de gestion des logiciels";
"CFBundleDisplayName" = "Centre de gestion des logiciels";
NSHumanReadableCopyright = "Copyright © 2010-2014 The Munki Project\nhttps://github.com/munki/munki";
NSHumanReadableCopyright = "Copyright © 2010-2016 The Munki Project\nhttps://github.com/munki/munki";

View File

@@ -2,4 +2,4 @@
"CFBundleName" = "Centro Gestione Applicazioni";
"CFBundleDisplayName" = "Centro Gestione Applicazioni";
NSHumanReadableCopyright = "Copyright © 2010-2014 The Munki Project\nhttps://github.com/munki/munki";
NSHumanReadableCopyright = "Copyright © 2010-2016 The Munki Project\nhttps://github.com/munki/munki";

View File

@@ -2,7 +2,7 @@
// main.m
// Managed Software Center
//
// Copyright 2013-2014 Greg Neagle.
// Copyright 2013-2016 Greg Neagle.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.

View File

@@ -3,7 +3,7 @@
# msclib.py
#
# Created by Greg Neagle on 12/10/13.
# Copyright 2010-2014 Greg Neagle.
# Copyright 2010-2016 Greg Neagle.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.

View File

@@ -6,7 +6,7 @@
# Created by Greg Neagle on 2/23/14.
# Original by John Randolph <jrand@google.com>
#
# Copyright 2009-2014 Greg Neagle.
# Copyright 2009-2016 Greg Neagle.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.

View File

@@ -4,7 +4,7 @@
# Managed Software Center
#
# Created by Greg Neagle on 2/11/10.
# Copyright 2010-2014 Greg Neagle.
# Copyright 2010-2016 Greg Neagle.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.

View File

@@ -2,4 +2,4 @@
"CFBundleName" = "Managed Software Center";
"CFBundleDisplayName" = "Managed Software Center";
NSHumanReadableCopyright = "Copyright © 2010-2014 The Munki Project\nhttps://github.com/munki/munki";
NSHumanReadableCopyright = "Copyright © 2010-2016 The Munki Project\nhttps://github.com/munki/munki";

View File

@@ -2,4 +2,4 @@
"CFBundleName" = "Managed Software Center";
"CFBundleDisplayName" = "Managed Software Center";
NSHumanReadableCopyright = "Copyright © 2010-2014 The Munki Project\nhttps://github.com/munki/munki";
NSHumanReadableCopyright = "Copyright © 2010-2016 The Munki Project\nhttps://github.com/munki/munki";

View File

@@ -2,4 +2,4 @@
"CFBundleName" = "Центр Управления ПО";
"CFBundleDisplayName" = "Центр Управления ПО";
NSHumanReadableCopyright = "Copyright © 2010-2014 The Munki Project\nhttps://github.com/munki/munki";
NSHumanReadableCopyright = "Copyright © 2010-2016 The Munki Project\nhttps://github.com/munki/munki";

View File

@@ -2,4 +2,4 @@
"CFBundleName" = "Managed Software Center";
"CFBundleDisplayName" = "Managed Software Center";
NSHumanReadableCopyright = "Copyright © 2010-2014 The Munki Project\nhttps://github.com/munki/munki";
NSHumanReadableCopyright = "Copyright © 2010-2016 The Munki Project\nhttps://github.com/munki/munki";

View File

@@ -7,12 +7,12 @@
</a>
<ul class="list info">
<li><h2><a title="${display_name_escaped}" href="${detail_link}">${display_name_escaped}</a></h2></li>
<li>${developer_escaped}</li>
<li class="install-info-cell">${developer_escaped}</li>
</ul>
</td>
<td>${version_to_install}</td>
<td>${size}</td>
<td class="status">
<td class="install-info-cell">${version_to_install}</td>
<td class="install-info-cell">${size}</td>
<td class="status install-info-cell">
<span class="${status}" id="${name_escaped}_status_text">
${status_text}
<a class="follow" href="updates.html">

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@@ -2,7 +2,7 @@
// BorderlessWindow.h
//
// Created by Greg Neagle on 5/16/09.
// Copyright 2009-2014 Greg Neagle.
// Copyright 2009-2016 Greg Neagle.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.

View File

@@ -2,7 +2,7 @@
// BorderlessWindow.m
//
// Created by Greg Neagle on 5/16/09.
// Copyright 2009-2014 Greg Neagle.
// Copyright 2009-2016 Greg Neagle.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.

View File

@@ -1,7 +1,7 @@
#!/usr/bin/python
# encoding: utf-8
#
# Copyright 2009-2014 Greg Neagle.
# Copyright 2009-2016 Greg Neagle.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.

View File

@@ -3,7 +3,7 @@
# MSUAppDelegate.py
# MunkiStatus
#
# Copyright 2013-2014 Greg Neagle.
# Copyright 2013-2016 Greg Neagle.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.

View File

@@ -3,7 +3,7 @@
# MSUStatusWindowController.py
# MunkiStatus
#
# Copyright 2009-2014 Greg Neagle.
# Copyright 2009-2016 Greg Neagle.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.

View File

@@ -29,7 +29,7 @@
<key>LSMinimumSystemVersion</key>
<string>${MACOSX_DEPLOYMENT_TARGET}</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2014 The Munki Project https://github.com/munki/munki</string>
<string>Copyright © 2016 The Munki Project https://github.com/munki/munki</string>
<key>NSMainNibFile</key>
<string>MainMenu</string>
<key>NSPrincipalClass</key>

View File

@@ -3,7 +3,7 @@
//
// Created by Greg Neagle on 5/27/09.
//
// Copyright 2009-2014 Greg Neagle.
// Copyright 2009-2016 Greg Neagle.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.

View File

@@ -3,7 +3,7 @@
//
// Created by Greg Neagle on 5/27/09.
//
// Copyright 2009-2014 Greg Neagle.
// Copyright 2009-2016 Greg Neagle.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.

View File

@@ -2,4 +2,4 @@
"CFBundleName" = "MunkiStatus";
"CFBundleDisplayName" = "MunkiStatus";
NSHumanReadableCopyright = "Copyright © 2010-2014 The Munki Project\nhttps://github.com/munki/munki";
NSHumanReadableCopyright = "Copyright © 2010-2016 The Munki Project\nhttps://github.com/munki/munki";

View File

@@ -2,4 +2,4 @@
"CFBundleName" = "Centre de gestion des logiciels";
"CFBundleDisplayName" = "Centre de gestion des logiciels";
NSHumanReadableCopyright = "Copyright © 2010-2014 The Munki Project\nhttps://github.com/munki/munki";
NSHumanReadableCopyright = "Copyright © 2010-2016 The Munki Project\nhttps://github.com/munki/munki";

View File

@@ -2,4 +2,4 @@
"CFBundleName" = "MunkiStatus";
"CFBundleDisplayName" = "MunkiStatus";
NSHumanReadableCopyright = "Copyright © 2010-2014 The Munki Project\nhttps://github.com/munki/munki";
NSHumanReadableCopyright = "Copyright © 2010-2016 The Munki Project\nhttps://github.com/munki/munki";

View File

@@ -2,7 +2,7 @@
// main.m
// MunkiStatus
//
// Copyright 2013-2014 Greg Neagle.
// Copyright 2013-2016 Greg Neagle.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.

View File

@@ -2,7 +2,7 @@
# main.py
# MunkiStatus
#
# Copyright 2013-2014 Greg Neagle.
# Copyright 2013-2016 Greg Neagle.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.

View File

@@ -4,7 +4,7 @@
# MunkiStatus
#
# Created by Greg Neagle on 2/11/10.
# Copyright 2010-2014 Greg Neagle.
# Copyright 2010-2016 Greg Neagle.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.

View File

@@ -1,7 +1,7 @@
#!/usr/bin/python
# encoding: utf-8
#
# Copyright 2010-2014 Greg Neagle.
# Copyright 2010-2016 Greg Neagle.
#
# Licensed under the Apache License, Version 2.0 (the 'License');
# you may not use this file except in compliance with the License.

View File

@@ -1,7 +1,7 @@
#!/usr/bin/python
# encoding: utf-8
#
# Copyright 2010-2014 Greg Neagle.
# Copyright 2010-2016 Greg Neagle.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.

View File

@@ -1,7 +1,7 @@
#!/usr/bin/python
# encoding: utf-8
#
# Copyright 2011-2014 Greg Neagle.
# Copyright 2011-2016 Greg Neagle.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env python
# encoding: utf-8
#
# Copyright 2009-2014 Greg Neagle.
# Copyright 2009-2016 Greg Neagle.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.

View File

@@ -1,7 +1,7 @@
#!/usr/bin/python
# encoding: utf-8
#
# Copyright 2008-2014 Greg Neagle.
# Copyright 2008-2016 Greg Neagle.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.

View File

@@ -1,7 +1,7 @@
#!/usr/bin/python
# encoding: utf-8
#
# Copyright 2009-2014 Greg Neagle.
# Copyright 2009-2016 Greg Neagle.
#
# Licensed under the Apache License, Version 2.0 (the 'License');
# you may not use this file except in compliance with the License.
@@ -634,7 +634,7 @@ def main():
munkicommon.log("### Starting managedsoftwareupdate run: %s ###" % runtype)
if options.verbose:
print 'Managed Software Update Tool'
print 'Copyright 2010-2014 The Munki Project'
print 'Copyright 2010-2016 The Munki Project'
print 'https://github.com/munki/munki\n'
munkicommon.display_status_major('Starting...')
@@ -710,6 +710,7 @@ def main():
if options.manualcheck:
# record our result
recordUpdateCheckResult(-1)
if options.munkistatusoutput:
# connect to socket and quit
munkistatus.activate()
munkistatus.quit()

View File

@@ -1,7 +1,7 @@
#!/usr/bin/python
# encoding: utf-8
#
# Copyright 2011-2014 Greg Neagle.
# Copyright 2011-2016 Greg Neagle.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.

View File

@@ -1,7 +1,7 @@
#!/usr/bin/python
# encoding: utf-8
#
# Copyright 2010-2014 Greg Neagle.
# Copyright 2010-2016 Greg Neagle.
#
# Licensed under the Apache License, Version 2.0 (the 'License');
# you may not use this file except in compliance with the License.

View File

@@ -1,7 +1,7 @@
#!/usr/bin/python
# encoding: utf-8
#
# Copyright 2009-2014 Greg Neagle.
# Copyright 2009-2016 Greg Neagle.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.

View File

@@ -7,7 +7,7 @@ Utilities to enable munki to install/uninstall Adobe CS3/CS4/CS5 products
using the CS3/CS4/CS5 Deployment Toolkits.
"""
# Copyright 2009-2014 Greg Neagle.
# Copyright 2009-2016 Greg Neagle.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.

View File

@@ -6,7 +6,7 @@ appleupdates.py
Utilities for dealing with Apple Software Update.
"""
# Copyright 2009-2015 Greg Neagle.
# Copyright 2009-2016 Greg Neagle.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -1610,7 +1610,7 @@ class AppleUpdates(object):
if key in metadata_to_copy and metadata[key]:
if key == 'RestartAction':
# Ensure that a heavier weighted 'RestartAction' is not
# overriden by one supplied in metadata
# overridden by one supplied in metadata
if metadata[key] not in RestartActions.get(
item.get(key, 'None')):
munkicommon.display_debug2(

View File

@@ -1,7 +1,7 @@
#!/usr/bin/python
# encoding: utf-8
#
# Copyright 2011-2014 Greg Neagle.
# Copyright 2011-2016 Greg Neagle.
#
# Licensed under the Apache License, Version 2.0 (the 'License');
# you may not use this file except in compliance with the License.

View File

@@ -1,7 +1,7 @@
#!/usr/bin/python
# encoding: utf-8
#
# Copyright 2009-2014 Greg Neagle.
# Copyright 2009-2016 Greg Neagle.
#
# Licensed under the Apache License, Version 2.0 (the 'License');
# you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@
gurl.py
Created by Greg Neagle on 2013-11-21.
Modified in Feb 2016 to add support for NSURLSession.
curl replacement using NSURLConnection and friends
"""
@@ -32,22 +33,63 @@ from objc import super
# PyLint cannot properly find names inside Cocoa libraries, so issues bogus
# No name 'Foo' in module 'Bar' warnings. Disable them.
# pylint: disable=E0611
from Foundation import NSBundle
from Foundation import NSRunLoop, NSDate
from Foundation import NSObject, NSURL, NSURLConnection
from Foundation import NSMutableURLRequest
from Foundation import NSURLRequestReloadIgnoringLocalCacheData
from Foundation import NSURLResponseUnknownLength
from Foundation import NSLog
from Foundation import NSURLCredential, NSURLCredentialPersistenceNone
from Foundation import NSPropertyListSerialization
from Foundation import NSPropertyListMutableContainersAndLeaves
from Foundation import NSPropertyListXMLFormat_v1_0
# pylint: enable=E0611
from Foundation import NSBundle, \
NSRunLoop, NSDate, \
NSObject, NSURL, NSURLConnection, \
NSMutableURLRequest, \
NSURLRequestReloadIgnoringLocalCacheData, \
NSURLResponseUnknownLength, \
NSLog, \
NSURLCredential, NSURLCredentialPersistenceNone, \
NSPropertyListSerialization, \
NSPropertyListMutableContainersAndLeaves, \
NSPropertyListXMLFormat_v1_0
try:
from Foundation import NSURLSession, NSURLSessionConfiguration
NSURLSESSION_AVAILABLE = True
except ImportError:
NSURLSESSION_AVAILABLE = False
# Disable PyLint complaining about 'invalid' names
# pylint: disable=C0103
if NSURLSESSION_AVAILABLE:
# NSURLSessionAuthChallengeDisposition enum constants
NSURLSessionAuthChallengeUseCredential = 0
NSURLSessionAuthChallengePerformDefaultHandling = 1
NSURLSessionAuthChallengeCancelAuthenticationChallenge = 2
NSURLSessionAuthChallengeRejectProtectionSpace = 3
# NSURLSessionResponseDisposition enum constants
NSURLSessionResponseCancel = 0
NSURLSessionResponseAllow = 1
NSURLSessionResponseBecomeDownload = 2
# TLS/SSLProtocol enum constants
kSSLProtocolUnknown = 0
kSSLProtocol3 = 2
kTLSProtocol1 = 4
kTLSProtocol11 = 7
kTLSProtocol12 = 8
kDTLSProtocol1 = 9
# define a helper function for block callbacks
import ctypes
import objc
_objc_so = ctypes.cdll.LoadLibrary(
os.path.join(objc.__path__[0], '_objc.so'))
PyObjCMethodSignature_WithMetaData = (
_objc_so.PyObjCMethodSignature_WithMetaData)
PyObjCMethodSignature_WithMetaData.restype = ctypes.py_object
def objc_method_signature(signature_str):
'''Return a PyObjCMethodSignature given a call signature in string
format'''
return PyObjCMethodSignature_WithMetaData(
ctypes.create_string_buffer(signature_str), None, False)
# pylint: enable=E0611
# disturbing hack warning!
# this works around an issue with App Transport Security on 10.11
@@ -56,6 +98,11 @@ info = bundle.localizedInfoDictionary() or bundle.infoDictionary()
info['NSAppTransportSecurity'] = {'NSAllowsArbitraryLoads': True}
def NSLogWrapper(message):
'''A wrapper function for NSLog to prevent format string errors'''
NSLog('%@', message)
ssl_error_codes = {
-9800: u'SSL protocol error',
-9801: u'Cipher Suite negotiation failure',
@@ -111,7 +158,7 @@ ssl_error_codes = {
class Gurl(NSObject):
'''A class for getting content from a URL
using NSURLConnection and friends'''
using NSURLConnection/NSURLSession and friends'''
# since we inherit from NSObject, PyLint issues a few bogus warnings
# pylint: disable=W0232,E1002
@@ -139,8 +186,11 @@ class Gurl(NSObject):
'download_only_if_changed', False)
self.cache_data = options.get('cache_data')
self.connection_timeout = options.get('connection_timeout', 60)
if NSURLSESSION_AVAILABLE:
self.minimum_tls_protocol = options.get(
'minimum_tls_protocol', kTLSProtocol1)
self.log = options.get('logging_function', NSLog)
self.log = options.get('logging_function', NSLogWrapper)
self.resume = False
self.response = None
@@ -155,6 +205,8 @@ class Gurl(NSObject):
self.expectedLength = -1
self.percentComplete = 0
self.connection = None
self.session = None
self.task = None
return self
def start(self):
@@ -189,18 +241,34 @@ class Gurl(NSObject):
if 'etag' in stored_data:
request.setValue_forHTTPHeaderField_(
stored_data['etag'], 'if-none-match')
self.connection = NSURLConnection.alloc().initWithRequest_delegate_(
request, self)
if NSURLSESSION_AVAILABLE:
configuration = \
NSURLSessionConfiguration.defaultSessionConfiguration()
# set minumum supported TLS protocol (defaults to TLS1)
configuration.setTLSMinimumSupportedProtocol_(
self.minimum_tls_protocol)
self.session = \
NSURLSession.sessionWithConfiguration_delegate_delegateQueue_(
configuration, self, None)
self.task = self.session.dataTaskWithRequest_(request)
self.task.resume()
else:
self.connection = NSURLConnection.alloc().initWithRequest_delegate_(
request, self)
def cancel(self):
'''Cancel the connection'''
if self.connection:
self.connection.cancel()
if NSURLSESSION_AVAILABLE:
self.session.invalidateAndCancel()
else:
self.connection.cancel()
self.done = True
def isDone(self):
'''Check if the connection request is complete. As a side effect,
allow the delegates to work my letting the run loop run for a bit'''
allow the delegates to work by letting the run loop run for a bit'''
if self.done:
return self.done
# let the delegates do their thing
@@ -255,13 +323,8 @@ class Gurl(NSObject):
new_dict[key.lower()] = value
return new_dict
def connection_didFailWithError_(self, connection, error):
'''NSURLConnection delegate method
Sent when a connection fails to load its request successfully.'''
# we don't actually use the connection argument, so
# pylint: disable=W0613
def recordError_(self, error):
'''Record any error info from completed connection/session'''
self.error = error
# If this was an SSL error, try to extract the SSL error code.
if 'NSUnderlyingError' in error.userInfo():
@@ -270,13 +333,40 @@ class Gurl(NSObject):
if ssl_code:
self.SSLerror = (ssl_code, ssl_error_codes.get(
ssl_code, 'Unknown SSL error'))
def removeExpectedSizeFromStoredHeaders(self):
'''If a successful transfer, clear the expected size so we
don\'t attempt to resume the download next time'''
if str(self.status).startswith('2'):
# remove the expected-size from the stored headers
headers = self.get_stored_headers()
if 'expected-length' in headers:
del headers['expected-length']
self.store_headers(headers)
def URLSession_task_didCompleteWithError_(self, session, task, error):
'''NSURLSessionTaskDelegate method.'''
# we don't actually use the session or task arguments, so
# pylint: disable=W0613
if self.destination and self.destination_path:
self.destination.close()
self.removeExpectedSizeFromStoredHeaders()
if error:
self.recordError_(error)
self.done = True
def connection_didFailWithError_(self, connection, error):
'''NSURLConnectionDelegate method
Sent when a connection fails to load its request successfully.'''
# we don't actually use the connection argument, so
# pylint: disable=W0613
self.recordError_(error)
self.done = True
if self.destination and self.destination_path:
self.destination.close()
# delete it? Might not want to...
def connectionDidFinishLoading_(self, connection):
'''NSURLConnectionDataDelegat delegate method
'''NSURLConnectionDataDelegate method
Sent when a connection has finished loading successfully.'''
# we don't actually use the connection argument, so
@@ -285,18 +375,11 @@ class Gurl(NSObject):
self.done = True
if self.destination and self.destination_path:
self.destination.close()
if str(self.status).startswith('2'):
# remove the expected-size from the stored headers
headers = self.get_stored_headers()
if 'expected-length' in headers:
del headers['expected-length']
self.store_headers(headers)
def connection_didReceiveResponse_(self, connection, response):
'''NSURLConnectionDataDelegate delegate method
Sent when the connection has received sufficient data to construct the
URL response for its request.'''
self.removeExpectedSizeFromStoredHeaders()
def handleResponse_withCompletionHandler_(
self, response, completionHandler):
'''Handle the response to the connection'''
self.response = response
self.bytesReceived = 0
self.percentComplete = -1
@@ -330,7 +413,12 @@ class Gurl(NSObject):
# we have a partial for
self.log(
'Can\'t resume download; file on server has changed.')
connection.cancel()
if completionHandler:
# tell the session task to cancel
completionHandler(NSURLSessionResponseCancel)
else:
# cancel the connection
self.connection.cancel()
self.log('Removing %s' % self.destination_path)
os.unlink(self.destination_path)
# restart and attempt to download the entire file
@@ -355,85 +443,107 @@ class Gurl(NSObject):
# the downloadand for future checking if the file on the server
# has changed
self.store_headers(download_data)
if completionHandler:
# tell the session task to continue
completionHandler(NSURLSessionResponseAllow)
def connection_willSendRequest_redirectResponse_(
self, connection, request, response):
def URLSession_dataTask_didReceiveResponse_completionHandler_(
self, session, task, response, completionHandler):
'''NSURLSessionDataDelegate method'''
# we don't actually use the session or task arguments, so
# pylint: disable=W0613
completionHandler.__block_signature__ = objc_method_signature('v@i')
self.handleResponse_withCompletionHandler_(response, completionHandler)
def connection_didReceiveResponse_(self, connection, response):
'''NSURLConnectionDataDelegate delegate method
Sent when the connection determines that it must change URLs in order to
continue loading a request.'''
Sent when the connection has received sufficient data to construct the
URL response for its request.'''
# we don't actually use the connection argument, so
# pylint: disable=W0613
self.handleResponse_withCompletionHandler_(response, None)
def handleRedirect_newRequest_withCompletionHandler_(
self, response, request, completionHandler):
'''Handle the redirect request'''
if response == None:
# This isn't a real redirect, this is without talking to a server.
# Pass it back as-is
return request
# But if we're here, it appears to be a real redirect attempt
# the request has changed the NSURLRequest in order to standardize
# its format, for example, changing a request for
# http://www.apple.com to http://www.apple.com/. This occurs because
# the standardized, or canonical, version of the request is used for
# cache management. Pass the request back as-is
# (it appears that at some point Apple also defined a redirect like
# http://developer.apple.com to https://developer.apple.com to be
# 'merely' a change in the canonical URL.)
# Further -- it appears that this delegate method isn't called at
# all in this scenario, unlike NSConnectionDelegate method
# connection:willSendRequest:redirectResponse:
# we'll leave this here anyway in case we're wrong about that
if completionHandler:
completionHandler(request)
return
else:
return request
# If we get here, it appears to be a real redirect attempt
# Annoyingly, we apparently can't get access to the headers from the
# site that told us to redirect. All we know is that we were told
# to redirect and where the new location is.
newURL = request.URL().absoluteString()
self.redirection.append([newURL, dict(response.allHeaderFields())])
newParsedURL = urlparse(newURL)
# This code was largely based on the work of Andreas Fuchs
# This code was largely based on the work of Andreas Fuchs
# (https://github.com/munki/munki/pull/465)
if self.follow_redirects == True or self.follow_redirects == 'all':
# Allow the redirect
self.log('Allowing redirect to: %s' % newURL)
return request
elif self.follow_redirects == 'https' and newParsedURL.scheme == 'https':
if completionHandler:
completionHandler(request)
return
else:
return request
elif (self.follow_redirects == 'https'
and newParsedURL.scheme == 'https'):
# Once again, allow the redirect
self.log('Allowing redirect to: %s' % newURL)
return request
if completionHandler:
completionHandler(request)
return
else:
return request
else:
# If we're down here either the preference was set to 'none',
# the url we're forwarding on to isn't https or follow_redirects
# was explicitly set to False
self.log('Denying redirect to: %s' % newURL)
return None
if completionHandler:
completionHandler(None)
return
else:
return None
def connection_willSendRequestForAuthenticationChallenge_(
self, connection, challenge):
'''NSURLConnection delegate method
Tells the delegate that the connection will send a request for an
authentication challenge.
New in 10.7.'''
def URLSession_task_willPerformHTTPRedirection_newRequest_completionHandler_(
self, session, task, response, request, completionHandler):
'''NSURLSessionTaskDelegate method'''
# we don't actually use the session or task arguments, so
# pylint: disable=W0613
self.log(
'URLSession_task_willPerformHTTPRedirection_newRequest_'
'completionHandler_')
completionHandler.__block_signature__ = objc_method_signature('v@@')
self.handleRedirect_newRequest_withCompletionHandler_(
response, request, completionHandler)
def connection_willSendRequest_redirectResponse_(
self, connection, request, response):
'''NSURLConnectionDataDelegate method
Sent when the connection determines that it must change URLs in order to
continue loading a request.'''
# we don't actually use the connection argument, so
# pylint: disable=W0613
self.log('connection_willSendRequestForAuthenticationChallenge_')
protectionSpace = challenge.protectionSpace()
host = protectionSpace.host()
realm = protectionSpace.realm()
authenticationMethod = protectionSpace.authenticationMethod()
self.log(
'Authentication challenge for Host: %s Realm: %s AuthMethod: %s'
% (host, realm, authenticationMethod))
if challenge.previousFailureCount() > 0:
# we have the wrong credentials. just fail
self.log('Previous authentication attempt failed.')
challenge.sender().cancelAuthenticationChallenge_(challenge)
if self.username and self.password and authenticationMethod in [
'NSURLAuthenticationMethodDefault',
'NSURLAuthenticationMethodHTTPBasic',
'NSURLAuthenticationMethodHTTPDigest']:
self.log('Will attempt to authenticate.')
self.log('Username: %s Password: %s'
% (self.username, ('*' * len(self.password or ''))))
credential = (
NSURLCredential.credentialWithUser_password_persistence_(
self.username, self.password,
NSURLCredentialPersistenceNone))
challenge.sender().useCredential_forAuthenticationChallenge_(
credential, challenge)
else:
# fall back to system-provided default behavior
self.log('Allowing OS to handle authentication request')
challenge.sender(
).performDefaultHandlingForAuthenticationChallenge_(
challenge)
self.log('connection_willSendRequest_redirectResponse_')
return self.handleRedirect_newRequest_withCompletionHandler_(
response, request, None)
def connection_canAuthenticateAgainstProtectionSpace_(
self, connection, protectionSpace):
@@ -464,17 +574,9 @@ class Gurl(NSObject):
self.log('Allowing OS to handle authentication request')
return False
def connection_didReceiveAuthenticationChallenge_(
self, connection, challenge):
'''NSURLConnection delegate method
Sent when a connection must authenticate a challenge in order to
download its request.
Deprecated in 10.10'''
# we don't actually use the connection argument, so
# pylint: disable=W0613
self.log('connection_didReceiveAuthenticationChallenge_')
def handleChallenge_withCompletionHandler_(
self, challenge, completionHandler):
'''Handle an authentication challenge'''
protectionSpace = challenge.protectionSpace()
host = protectionSpace.host()
realm = protectionSpace.realm()
@@ -485,7 +587,12 @@ class Gurl(NSObject):
if challenge.previousFailureCount() > 0:
# we have the wrong credentials. just fail
self.log('Previous authentication attempt failed.')
challenge.sender().cancelAuthenticationChallenge_(challenge)
if completionHandler:
completionHandler(
NSURLSessionAuthChallengeCancelAuthenticationChallenge,
None)
else:
challenge.sender().cancelAuthenticationChallenge_(challenge)
if self.username and self.password and authenticationMethod in [
'NSURLAuthenticationMethodDefault',
'NSURLAuthenticationMethodHTTPBasic',
@@ -497,22 +604,65 @@ class Gurl(NSObject):
NSURLCredential.credentialWithUser_password_persistence_(
self.username, self.password,
NSURLCredentialPersistenceNone))
challenge.sender().useCredential_forAuthenticationChallenge_(
credential, challenge)
if completionHandler:
completionHandler(
NSURLSessionAuthChallengeUseCredential, credential)
else:
challenge.sender().useCredential_forAuthenticationChallenge_(
credential, challenge)
else:
# fall back to system-provided default behavior
self.log('Continuing without credential.')
challenge.sender(
).continueWithoutCredentialForAuthenticationChallenge_(
challenge)
def connection_didReceiveData_(self, connection, data):
'''NSURLConnectionDataDelegate method
Sent as a connection loads data incrementally'''
self.log('Allowing OS to handle authentication request')
if completionHandler:
completionHandler(
NSURLSessionAuthChallengePerformDefaultHandling, None)
else:
if (challenge.sender().respondsToSelector_(
'performDefaultHandlingForAuthenticationChallenge:')):
self.log('Allowing OS to handle authentication request')
challenge.sender(
).performDefaultHandlingForAuthenticationChallenge_(
challenge)
else:
# Mac OS X 10.6 doesn't support
# performDefaultHandlingForAuthenticationChallenge:
self.log('Continuing without credential.')
challenge.sender(
).continueWithoutCredentialForAuthenticationChallenge_(
challenge)
def connection_willSendRequestForAuthenticationChallenge_(
self, connection, challenge):
'''NSURLConnection delegate method
Tells the delegate that the connection will send a request for an
authentication challenge. New in 10.7.'''
# we don't actually use the connection argument, so
# pylint: disable=W0613
self.log('connection_willSendRequestForAuthenticationChallenge_')
self.handleChallenge_withCompletionHandler_(challenge, None)
def URLSession_task_didReceiveChallenge_completionHandler_(
self, session, task, challenge, completionHandler):
'''NSURLSessionTaskDelegate method'''
# we don't actually use the session or task arguments, so
# pylint: disable=W0613
completionHandler.__block_signature__ = objc_method_signature('v@i@')
self.log('URLSession_task_didReceiveChallenge_completionHandler_')
self.handleChallenge_withCompletionHandler_(
challenge, completionHandler)
def connection_didReceiveAuthenticationChallenge_(
self, connection, challenge):
'''NSURLConnection delegate method
Sent when a connection must authenticate a challenge in order to
download its request. Deprecated in 10.10'''
# we don't actually use the connection argument, so
# pylint: disable=W0613
self.log('connection_didReceiveAuthenticationChallenge_')
self.handleChallenge_withCompletionHandler_(challenge, None)
def handleReceivedData_(self, data):
'''Handle received data'''
if self.destination:
self.destination.write(str(data))
else:
@@ -521,3 +671,16 @@ class Gurl(NSObject):
if self.expectedLength != NSURLResponseUnknownLength:
self.percentComplete = int(
float(self.bytesReceived)/float(self.expectedLength) * 100.0)
def URLSession_dataTask_didReceiveData_(self, session, task, data):
'''NSURLSessionDataDelegate method'''
# we don't actually use the session or task arguments, so
# pylint: disable=W0613
self.handleReceivedData_(data)
def connection_didReceiveData_(self, connection, data):
'''NSURLConnectionDataDelegate method
Sent as a connection loads data incrementally'''
# we don't actually use the connection argument, so
# pylint: disable=W0613
self.handleReceivedData_(data)

View File

@@ -1,7 +1,7 @@
#!/usr/bin/python
# encoding: utf-8
#
# Copyright 2010-2014 Greg Neagle.
# Copyright 2010-2016 Greg Neagle.
#
# Licensed under the Apache License, Version 2.0 (the 'License');
# you may not use this file except in compliance with the License.

View File

@@ -1,7 +1,7 @@
#!/usr/bin/python
# encoding: utf-8
#
# Copyright 2009-2014 Greg Neagle.
# Copyright 2009-2016 Greg Neagle.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -467,7 +467,7 @@ def copyItemsFromMountpoint(mountpoint, itemlist):
# all tests passed, OK to copy
munkicommon.display_status_minor(
"Copying %s to %s" % (source_itemname, full_destpath))
retcode = subprocess.call(["/bin/cp", "-pR",
retcode = subprocess.call(["/usr/bin/ditto", "--noqtn",
source_itempath, full_destpath])
if retcode:
munkicommon.display_error(
@@ -506,20 +506,6 @@ def copyItemsFromMountpoint(mountpoint, itemlist):
"Error setting mode for %s" % (full_destpath))
return retcode
# remove com.apple.quarantine attribute from copied item
cmd = ["/usr/bin/xattr", full_destpath]
proc = subprocess.Popen(cmd, shell=False, bufsize=-1,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
(out, dummy_err) = proc.communicate()
if out:
xattrs = str(out).splitlines()
if "com.apple.quarantine" in xattrs:
dummy_result = subprocess.call(
["/usr/bin/xattr", "-d", "com.apple.quarantine",
full_destpath])
# all items copied successfully!
return 0

View File

@@ -1,7 +1,7 @@
#!/usr/bin/python
# encoding: utf-8
#
# Copyright 2014 Greg Neagle.
# Copyright 2014-2016 Greg Neagle.
#
# Licensed under the Apache License, Version 2.0 (the 'License');
# you may not use this file except in compliance with the License.

View File

@@ -1,7 +1,7 @@
#!/usr/bin/python
# encoding: utf-8
#
# Copyright 2011-2014 Greg Neagle.
# Copyright 2011-2016 Greg Neagle.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.

View File

@@ -1,7 +1,7 @@
#!/usr/bin/python
# encoding: utf-8
#
# Copyright 2009-2014 Greg Neagle.
# Copyright 2009-2016 Greg Neagle.
#
# Licensed under the Apache License, Version 2.0 (the 'License');
# you may not use this file except in compliance with the License.

View File

@@ -1,7 +1,7 @@
#!/usr/bin/python
# encoding: utf-8
#
# Copyright 2009-2014 Greg Neagle.
# Copyright 2009-2016 Greg Neagle.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.

View File

@@ -1,7 +1,7 @@
#!/usr/bin/python
# encoding: utf-8
#
# Copyright 2014 Greg Neagle.
# Copyright 2016 Greg Neagle.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.

View File

@@ -1,7 +1,7 @@
#!/usr/bin/python
# encoding: utf-8
#
# Copyright 2009-2014 Greg Neagle.
# Copyright 2009-2016 Greg Neagle.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -335,7 +335,7 @@ def ImportBom(bompath, curs):
package database into our internal package database.
"""
# If we completely trusted the accuracy of Apple's database, we wouldn't
# need the bom files, but in my enviroment at least, the bom files are
# need the bom files, but in my environment at least, the bom files are
# a better indicator of what flat packages have actually been installed
# on the current machine.
# We still need to consult Apple's package database

View File

@@ -1,7 +1,7 @@
#!/usr/bin/python
# encoding: utf-8
#
# Copyright 2009-2014 Greg Neagle.
# Copyright 2009-2016 Greg Neagle.
#
# Licensed under the Apache License, Version 2.0 (the 'License');
# you may not use this file except in compliance with the License.
@@ -1467,7 +1467,7 @@ def lookForUpdates(itemname, cataloglist):
update_list = []
for catalogname in cataloglist:
if not catalogname in CATALOG.keys():
# in case the list refers to a non-existant catalog
# in case the list refers to a non-existent catalog
continue
updaters = CATALOG[catalogname]['updaters']
@@ -1925,7 +1925,7 @@ def processInstall(manifestitem, cataloglist, installinfo):
iteminfo[key] = item_pl[key]
if not 'apple_item' in iteminfo:
# admin did not explictly mark this item; let's determine if
# admin did not explicitly mark this item; let's determine if
# it's from Apple
if isAppleItem(item_pl):
munkicommon.log("Marking %s as apple_item - this may affect \
@@ -2370,7 +2370,7 @@ def processRemoval(manifestitem, cataloglist, installinfo):
iteminfo[key] = uninstall_item[key]
if not 'apple_item' in iteminfo:
# admin did not explictly mark this item; let's determine if
# admin did not explicitly mark this item; let's determine if
# it's from Apple
if isAppleItem(item_pl):
iteminfo['apple_item'] = True
@@ -2892,7 +2892,7 @@ def download_icons(item_list):
def download_client_resources():
"""Download client customization resources (if any)."""
# Munki's preferences can specify an explict name
# Munki's preferences can specify an explicit name
# under ClientResourcesFilename
# if that doesn't exist, use the primary manifest name as the
# filename. If that fails, try site_default.zip

View File

@@ -1,7 +1,7 @@
#!/usr/bin/python
# encoding: utf-8
#
# Copyright 2010-2013 Google Inc. All Rights Reserved.
# Copyright 2010-2016 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the 'License');
# you may not use this file except in compliance with the License.
@@ -21,7 +21,7 @@ Created by Justin McWilliams on 2010-10-26.
Common utility functions used throughout Munki.
Note: this module should be 100% free of ObjC-dependant Python imports.
Note: this module should be 100% free of ObjC-dependent Python imports.
"""

View File

@@ -3,6 +3,6 @@
<plist version="1.0">
<dict>
<key>CFBundleShortVersionString</key>
<string>2.5.1</string>
<string>2.6.0</string>
</dict>
</plist>

View File

@@ -1,7 +1,7 @@
#!/usr/bin/python
# encoding: utf-8
#
# Copyright 2011-2013 Google Inc. All Rights Reserved.
# Copyright 2011-2016 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the 'License');
# you may not use this file except in compliance with the License.

View File

@@ -1,228 +0,0 @@
#!/usr/bin/env python
# encoding: utf-8
#
# Copyright 2009-2014 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
#
# https://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.
"""
app2pkg.py
Created by Greg Neagle on 2009-09-28.
"""
import sys
import os
import stat
import plistlib
import subprocess
import optparse
import tempfile
# change these to suit yourself
packagemaker = "/Developer/usr/bin/packagemaker"
pkgidprefix = "com.myorg.pkg."
pkgoutputdir = "/Users/Shared/pkgs"
def makeDMG(pkgpath):
print "Making disk image..."
cmd = ["/usr/bin/hdiutil", "create", "-srcfolder", pkgpath,
pkgpath + ".dmg"]
p = subprocess.Popen(cmd, shell=False, bufsize=1, stdin=subprocess.PIPE,
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
while True:
output = p.stdout.readline()
if not output and (p.poll() != None):
break
print output.rstrip("\n")
sys.stdout.flush()
retcode = p.poll()
if retcode:
print >>sys.stderr, "Disk image creation failed."
return(-1)
def main():
# command-line options
p = optparse.OptionParser()
p.add_option('--makedmg', '-d', action='store_true',
help='Makes a disk image containing the package.')
p.add_option('--name', '-n',
help='Specify a name for the package.')
p.add_option('--version', '-v',
help='Specify a version number for the package.')
p.add_option('--displayname',
help='Specify a display name for the package.')
p.add_option('--description',
help='Specify a description for the package.')
p.add_option('--id',
help='Specify a package id for the package.')
# Get our options and our package names
options, app_paths = p.parse_args()
if not os.path.exists(packagemaker):
print >>sys.stderr, "packagemaker tool not found at %s." % \
packagemaker
exit(-1)
if len(app_paths) == 0:
print >>sys.stderr, "You must specify a path to an application!"
exit(-1)
if len(app_paths) > 1:
print >>sys.stderr, "You may package only one app at a time."
exit(-1)
app_path = app_paths[0]
if not os.path.exists(app_path):
print "Nothing exists at %s" % app_path
exit(-1)
mytmpdir = tempfile.mkdtemp()
if options.name:
pkgname = options.name
else:
pkgname = os.path.splitext(os.path.basename(app_path))[0]
enclosingpath = os.path.dirname(app_path).lstrip("/") or "Applications"
# make packageroot directory
packageroot = os.path.join(mytmpdir, pkgname)
os.mkdir(packageroot)
application_dir = os.path.join(packageroot, enclosingpath)
os.makedirs(application_dir)
copytodir = os.path.join(application_dir, os.path.basename(app_path))
cmd = ['/usr/bin/ditto', '--noqtn', app_path, copytodir ]
p = subprocess.Popen(cmd, shell=False, bufsize=1, stdin=subprocess.PIPE,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(output, err) = p.communicate()
if p.returncode != 0:
print >>sys.stderr, err
exit(-1)
if err:
print >>sys.stderr, err
exit(-1)
# fix uid/gid/perms on directories
for dirpath, dirnames, filenames in os.walk(packageroot):
srcdir = dirpath[len(packageroot):]
if srcdir == "": srcdir = "/"
os.chown(dirpath, os.stat(srcdir).st_uid, os.stat(srcdir).st_gid)
os.chmod(dirpath, stat.S_IMODE(os.stat(srcdir).st_mode))
pkgid = pkgvers = ""
if options.id:
pkgid = options.id
if options.version:
pkgvers = options.version
appinfo = os.path.join(app_path, "Contents/Info.plist")
if os.path.exists(appinfo):
pl = plistlib.readPlist(appinfo)
if "CFBundleIdentifier" in pl and pkgid == "":
pkgid = pl["CFBundleIdentifier"] + ".pkg"
if "CFBundleShortVersionString" in pl and pkgvers == "":
pkgvers = pl["CFBundleShortVersionString"]
if pkgid == "":
pkgid = pkgidprefix + pkgname.lower().replace(" ","_")
if pkgvers == "":
pkgvers = "1.0.0"
print "Package name: %s" % pkgname
newdisplayname = raw_input("Display name [%s]: "
% options.displayname)
options.displayname = newdisplayname or options.displayname
newdescription = raw_input("Description [%s]: " % options.description)
options.description = newdescription or options.description
newid = raw_input("PackageID [%s]: " % pkgid)
pkgid = newid or pkgid
newversion = raw_input("Version [%s]: " % pkgvers)
pkgvers = newversion or pkgvers
print
print
print "Package name: %s" % pkgname
print "Display name: %s" % options.displayname
print "Description: %s" % options.description
print "PackageID: %s" % pkgid
print "Version: %s" % pkgvers
print
answer = raw_input("Build the package? [y/n] ")
if not answer.lower().startswith("y"):
exit(0)
# build package
outputname = os.path.join(pkgoutputdir, pkgname + ".pkg")
cmd = [packagemaker, '--root', packageroot, '--id', pkgid,
'--version', pkgvers,
'--no-recommend', '--out', outputname, '--verbose',
'--filter', '.DS_Store$']
print cmd
p = subprocess.Popen(cmd, shell=False, bufsize=1, stdin=subprocess.PIPE,
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
while True:
output = p.stdout.readline()
if not output and (p.poll() != None):
break
print output.rstrip("\n")
sys.stdout.flush()
retcode = p.poll()
if retcode:
print >>sys.stderr, "Package creation failed."
exit(-1)
else:
# remove relocatable stuff
tokendefinitions = os.path.join(outputname,
"Contents/Resources/TokenDefinitions.plist")
if os.path.exists(tokendefinitions):
os.remove(tokendefinitions)
infoplist = os.path.join(outputname, "Contents/Info.plist")
pl = plistlib.readPlist(infoplist)
if 'IFPkgPathMappings' in pl:
del pl['IFPkgPathMappings']
plistlib.writePlist(pl, infoplist)
if options.displayname or options.description:
languages = ['en.lproj', 'English.lproj']
for item in languages:
lprojpath = os.path.join(outputname,
'Contents/Resources', item)
if os.path.exists(lprojpath):
descriptionplist = os.path.join(lprojpath,
"Description.plist")
pl = {}
pl['IFPkgDescriptionTitle'] = (options.displayname or
pkgname)
pl['IFPkgDescriptionDescription'] = (options.description
or "")
plistlib.writePlist(pl, descriptionplist)
break
print "Completed package is at %s" % outputname
if options.makedmg:
makeDMG(outputname)
#cleanup temp dir
retcode = subprocess.call(["/bin/rm", "-rf", mytmpdir])
if __name__ == '__main__':
main()

View File

@@ -1,354 +0,0 @@
#!/usr/bin/env python
# encoding: utf-8
#
# Copyright 2009-2014 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
#
# https://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.
"""
filelist2pkg.py
Created by Greg Neagle on 2009-11-24.
"""
import sys
import os
import stat
import plistlib
import subprocess
import optparse
import tempfile
# change these to suit yourself
packagemaker = '/Developer/usr/bin/packagemaker'
pkgidprefix = "com.myorg.pkg."
pkgoutputdir = "/Users/Shared/pkgs"
def nameAndVersion(s):
"""
Splits a string into the name and version numbers:
'TextWrangler2.3b1' becomes ('TextWrangler', '2.3b1')
'AdobePhotoshopCS3-11.2.1' becomes ('AdobePhotoshopCS3', '11.2.1')
'MicrosoftOffice2008v12.2.1' becomes ('MicrosoftOffice2008', '12.2.1')
"""
index = 0
for char in s:
if char in "0123456789":
possibleVersion = s[index:]
if not (" " in possibleVersion or "_" in possibleVersion
or "-" in possibleVersion or "v" in possibleVersion):
return (s[0:index].rstrip(" .-_v"), possibleVersion)
index += 1
# no version number found, just return original string and empty string
return (s, '')
def makeDMG(pkgpath):
print "Making disk image..."
cmd = ["/usr/bin/hdiutil", "create", "-srcfolder",
pkgpath, pkgpath + ".dmg"]
p = subprocess.Popen(cmd, shell=False, bufsize=1, stdin=subprocess.PIPE,
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
while True:
output = p.stdout.readline()
if not output and (p.poll() != None):
break
print output.rstrip("\n")
sys.stdout.flush()
retcode = p.poll()
if retcode:
print >>sys.stderr, "Disk image creation failed."
return(-1)
def copyItem(sourceitem, packageroot):
if not os.path.lexists(sourceitem):
print >>sys.stderr, "%s does not exist!" % sourceitem
return
paths = []
pathitem = sourceitem
while pathitem != "/":
pathitem = os.path.dirname(pathitem)
paths.append(pathitem)
paths.reverse()
for sourcepath in paths:
targetpath = os.path.join(packageroot, sourcepath.lstrip('/'))
if not os.path.exists(targetpath):
os.mkdir(targetpath)
os.chown(targetpath, os.stat(sourcepath).st_uid,
os.stat(sourcepath).st_gid)
os.chmod(targetpath, stat.S_IMODE(os.stat(sourcepath).st_mode))
targetitem = os.path.join(packageroot, sourceitem.lstrip('/'))
if os.path.isdir(sourceitem) and not os.path.islink(sourceitem):
os.mkdir(targetitem)
os.chown(targetitem, os.stat(sourceitem).st_uid,
os.stat(sourceitem).st_gid)
os.chmod(targetitem, stat.S_IMODE(os.stat(sourceitem).st_mode))
elif os.path.islink(sourceitem):
# make sure we don't follow the symlink
err = subprocess.call(['/bin/cp', "-a", sourceitem, targetitem])
else:
err = subprocess.call(['/bin/cp', "-p", sourceitem, targetitem])
def copyItemsFromList(filelist, packageroot):
f = open(filelist, 'rb')
while 1:
item = f.readline()
if not item:
break
if not (item.startswith('.') or item.startswith("/")):
continue
item = item.lstrip('./').rstrip('\n')
sourceitem = os.path.join("/", item)
copyItem(sourceitem, packageroot)
f.close()
def main():
# command-line options
p = optparse.OptionParser()
p.add_option('--nomakedmg', action='store_true',
help='Don\'t make a disk image containing the package.')
p.add_option('--name', '-n',
help='Specify a name for the package.')
p.add_option('--version', '-v',
help='Specify a version number for the package.')
p.add_option('--id',
help='Specify a package id for the package.')
p.add_option('--displayname',
help='Specify a display name for the package.')
p.add_option('--description',
help='Specify a description for the package.')
p.add_option('--restart', action='store_true',
help='Restart is required on install.')
p.add_option('--logout', action='store_true',
help='Logout is required to install.')
# Get our options and our package names
options, filelists = p.parse_args()
if not os.path.exists(packagemaker):
print >>sys.stderr, \
"packagemaker tool not found at %s." % packagemaker
exit(-1)
if len(filelists) == 0:
print >>sys.stderr, "You must specify a file list!"
exit(-1)
if len(filelists) > 1:
print >>sys.stderr, "You may convert only one file list at a time."
exit(-1)
filelist = filelists[0]
if not os.path.exists(filelist):
print "No file list at %s" % filelist
exit(-1)
mytmpdir = tempfile.mkdtemp()
if options.name:
pkgname = options.name
else:
pkgname = os.path.splitext(os.path.basename(filelist))[0]
packageroot = os.path.join(mytmpdir, pkgname)
os.mkdir(packageroot)
copyItemsFromList(filelist, packageroot)
# some default values
(name, versionInName) = nameAndVersion(pkgname)
if options.id:
pkgid = options.id
else:
pkgid = pkgidprefix + name.lower()
if options.version:
pkgvers = options.version
elif versionInName:
pkgvers = versionInName
else:
pkgvers = "1.0.0"
infopath = ""
# look through packageroot dir for Receipts
pkgrootreceipts = os.path.join(packageroot, "Library/Receipts")
if os.path.exists(pkgrootreceipts):
receiptlist = os.listdir(pkgrootreceipts)
if len(receiptlist) == 1:
receipt = os.path.join(pkgrootreceipts, receiptlist[0])
infopath = os.path.join(receipt,"Contents/Info.plist")
if os.path.exists(infopath):
print "Using package info from %s" % infopath
else:
infopath = ""
else:
print >>sys.stderr, \
("Found multiple receipts, "
"so cannot determine pkgid and version.")
if not infopath:
# look for a single application bundle and get info from that
appinfo = ""
for dirpath, dirnames, filenames in os.walk(packageroot):
if dirpath.endswith('.app'):
if not appinfo:
appinfo = os.path.join(dirpath, "Contents/Info.plist")
if not os.path.exists(appinfo):
appinfo = ""
else:
# crap, found more than one.
appinfo = ""
print >>sys.stderr, \
("Found multiple application bundles, "
"so cannot determine pkgid and version.")
break
if appinfo:
pl = plistlib.readPlist(appinfo)
if "CFBundleIdentifier" in pl and not options.id:
pkgid = pl["CFBundleIdentifier"] + ".pkg"
if "CFBundleShortVersionString" in pl and not options.version:
pkgvers = pl["CFBundleShortVersionString"]
print "Using pkgid: %s, version: %s from %s" % (pkgid, pkgvers,
appinfo)
else:
# let's look for any Contents/Info.plist
infoplist = ""
for dirpath, dirnames, filenames in os.walk(packageroot):
if dirpath.endswith("/Contents") and \
"Info.plist" in filenames:
if not infoplist:
infoplist = os.path.join(dirpath, "Info.plist")
if not os.path.exists(infoplist):
infoplist = ""
else:
# found more than one Info.plist
infoplist = ""
break
if infoplist:
pl = plistlib.readPlist(infoplist)
if "CFBundleIdentifier" in pl and not options.id:
pkgid = pl["CFBundleIdentifier"] + ".pkg"
if "CFBundleShortVersionString" in pl and not options.version:
pkgvers = pl["CFBundleShortVersionString"]
print "Using pkgid: %s, version: %s from %s" % (pkgid,
pkgvers,
infoplist)
print "Package name: %s" % pkgname
newdisplayname = raw_input("Display name [%s]: "
% options.displayname)
options.displayname = newdisplayname or options.displayname
newdescription = raw_input("Description [%s]: " % options.description)
options.description = newdescription or options.description
newid = raw_input("PackageID [%s]: " % pkgid)
pkgid = newid or pkgid
newversion = raw_input("Version [%s]: " % pkgvers)
pkgvers = newversion or pkgvers
print
print
print "Package name: %s" % pkgname
print "Display name: %s" % options.displayname
print "Description: %s" % options.description
print "PackageID: %s" % pkgid
print "Version: %s" % pkgvers
print
answer = raw_input("Build the package? [y/n] ")
if not answer.lower().startswith("y"):
exit(0)
# build package
outputname = os.path.join(pkgoutputdir, pkgname + ".pkg")
if os.path.exists(outputname):
retcode = subprocess.call(["/bin/rm", "-rf", outputname])
if infopath:
cmd = [packagemaker, '--root', packageroot, '--info', infopath,
'--no-recommend', '--out', outputname, '--verbose',
'--filter', 'Library/Receipts',
'--filter', '.DS_Store$']
else:
cmd = [packagemaker, '--root', packageroot, '--id', pkgid,
'--version', pkgvers, '--out', outputname,
'--verbose', '--no-recommend',
'--filter', 'Library/Receipts',
'--filter', '.DS_Store$']
print cmd
p = subprocess.Popen(cmd, shell=False, bufsize=1, stdin=subprocess.PIPE,
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
while True:
output = p.stdout.readline()
if not output and (p.poll() != None):
break
print output.rstrip("\n")
sys.stdout.flush()
retcode = p.poll()
if retcode:
print >>sys.stderr, "Package creation failed."
exit(-1)
else:
# remove relocatable stuff
tokendefinitions = os.path.join(outputname,
"Contents/Resources/TokenDefinitions.plist")
if os.path.exists(tokendefinitions):
os.remove(tokendefinitions)
infoplist = os.path.join(outputname, "Contents/Info.plist")
pl = plistlib.readPlist(infoplist)
if 'IFPkgPathMappings' in pl:
del pl['IFPkgPathMappings']
if options.restart:
# add restart info to plist
pl['IFPkgFlagRestartAction'] = "RequiredRestart"
elif options.logout:
# add logout info to plist
pl['IFPkgFlagRestartAction'] = "RequiredLogout"
plistlib.writePlist(pl, infoplist)
if options.displayname or options.description:
languages = ['en.lproj', 'English.lproj']
for item in languages:
lprojpath = os.path.join(outputname,
'Contents/Resources', item)
if os.path.exists(lprojpath):
descriptionplist = os.path.join(lprojpath,
"Description.plist")
pl = {}
pl['IFPkgDescriptionTitle'] = (options.displayname or
pkgname)
pl['IFPkgDescriptionDescription'] = (options.description
or "")
plistlib.writePlist(pl, descriptionplist)
break
print "Completed package is at %s" % outputname
if not options.nomakedmg:
makeDMG(outputname)
#cleanup temp dir
retcode = subprocess.call(["/bin/rm", "-rf", mytmpdir])
if __name__ == '__main__':
main()

View File

@@ -1,313 +0,0 @@
#!/usr/bin/env python
# encoding: utf-8
#
# Copyright 2011-2014 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
#
# https://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.
"""
tar2pkg.py
Created by Greg Neagle on 2011-06-16.
"""
import sys
import os
import stat
import plistlib
import subprocess
import optparse
import tempfile
# change these to suit yourself
packagemaker = "/Developer/usr/bin/packagemaker"
pkgidprefix = "com.myorg.pkg."
pkgoutputdir = "/Users/Shared/pkgs"
def nameAndVersion(s):
"""
Splits a string into the name and version numbers:
'TextWrangler2.3b1' becomes ('TextWrangler', '2.3b1')
'AdobePhotoshopCS3-11.2.1' becomes ('AdobePhotoshopCS3', '11.2.1')
'MicrosoftOffice2008v12.2.1' becomes ('MicrosoftOffice2008', '12.2.1')
"""
index = 0
for char in s:
if char in "0123456789":
possibleVersion = s[index:]
if not (" " in possibleVersion or "_" in possibleVersion
or "-" in possibleVersion or "v" in possibleVersion):
return (s[0:index].rstrip(" .-_v"), possibleVersion)
index += 1
# no version number found, just return original string and empty string
return (s, '')
def makeDMG(pkgpath):
print "Making disk image..."
cmd = ["/usr/bin/hdiutil", "create", "-srcfolder",
pkgpath, pkgpath + ".dmg"]
p = subprocess.Popen(cmd, shell=False, bufsize=1, stdin=subprocess.PIPE,
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
while True:
output = p.stdout.readline()
if not output and (p.poll() != None):
break
print output.rstrip("\n")
sys.stdout.flush()
retcode = p.poll()
if retcode:
print >>sys.stderr, "Disk image creation failed."
return(-1)
def untarItem(sourceitem, packageroot):
'''untars item into packageroot'''
os.chdir(packageroot)
return subprocess.call(['/usr/bin/tar', '-xf', sourceitem])
def main():
# command-line options
p = optparse.OptionParser()
p.add_option('--nomakedmg', action='store_true',
help='Don\'t make a disk image containing the package.')
p.add_option('--name', '-n',
help='Specify a name for the package.')
p.add_option('--version', '-v',
help='Specify a version number for the package.')
p.add_option('--id',
help='Specify a package id for the package.')
p.add_option('--displayname',
help='Specify a display name for the package.')
p.add_option('--description',
help='Specify a description for the package.')
p.add_option('--restart', action='store_true',
help='Restart is required on install.')
p.add_option('--logout', action='store_true',
help='Logout is required to install.')
# Get our options and our package names
options, tarballs = p.parse_args()
if not os.path.exists(packagemaker):
print >>sys.stderr, \
"packagemaker tool not found at %s." % packagemaker
exit(-1)
if len(tarballs) == 0:
print >>sys.stderr, "You must specify a tar archive!"
exit(-1)
if len(tarballs) > 1:
print >>sys.stderr, "You may convert only one tar archive at a time."
exit(-1)
tarball = tarballs[0]
if not os.path.exists(tarball):
print "No tar archive at %s" % tarball
exit(-1)
mytmpdir = tempfile.mkdtemp()
if options.name:
pkgname = options.name
else:
pkgname = os.path.splitext(os.path.basename(tarball))[0]
packageroot = os.path.join(mytmpdir, pkgname)
os.mkdir(packageroot)
untarItem(tarball, packageroot)
# some default values
(name, versionInName) = nameAndVersion(pkgname)
if options.id:
pkgid = options.id
else:
pkgid = pkgidprefix + name.lower()
if options.version:
pkgvers = options.version
elif versionInName:
pkgvers = versionInName
else:
pkgvers = "1.0.0"
infopath = ""
# look through packageroot dir for Receipts
pkgrootreceipts = os.path.join(packageroot, "Library/Receipts")
if os.path.exists(pkgrootreceipts):
receiptlist = os.listdir(pkgrootreceipts)
if len(receiptlist) == 1:
receipt = os.path.join(pkgrootreceipts, receiptlist[0])
infopath = os.path.join(receipt,"Contents/Info.plist")
if os.path.exists(infopath):
print "Using package info from %s" % infopath
else:
infopath = ""
else:
print >>sys.stderr, \
("Found multiple receipts, "
"so cannot determine pkgid and version.")
if not infopath:
# look for a single application bundle and get info from that
appinfo = ""
for dirpath, dirnames, filenames in os.walk(packageroot):
if dirpath.endswith('.app'):
if not appinfo:
appinfo = os.path.join(dirpath, "Contents/Info.plist")
if not os.path.exists(appinfo):
appinfo = ""
else:
# crap, found more than one.
appinfo = ""
print >>sys.stderr, \
("Found multiple application bundles, "
"so cannot determine pkgid and version.")
break
if appinfo:
pl = plistlib.readPlist(appinfo)
if "CFBundleIdentifier" in pl and not options.id:
pkgid = pl["CFBundleIdentifier"] + ".pkg"
if "CFBundleShortVersionString" in pl and not options.version:
pkgvers = pl["CFBundleShortVersionString"]
print "Using pkgid: %s, version: %s from %s" % (pkgid, pkgvers,
appinfo)
else:
# let's look for any Contents/Info.plist
infoplist = ""
for dirpath, dirnames, filenames in os.walk(packageroot):
if dirpath.endswith("/Contents") and \
"Info.plist" in filenames:
if not infoplist:
infoplist = os.path.join(dirpath, "Info.plist")
if not os.path.exists(infoplist):
infoplist = ""
else:
# found more than one Info.plist
infoplist = ""
break
if infoplist:
pl = plistlib.readPlist(infoplist)
if "CFBundleIdentifier" in pl and not options.id:
pkgid = pl["CFBundleIdentifier"] + ".pkg"
if "CFBundleShortVersionString" in pl and not options.version:
pkgvers = pl["CFBundleShortVersionString"]
print "Using pkgid: %s, version: %s from %s" % (pkgid,
pkgvers,
infoplist)
print "Package name: %s" % pkgname
newdisplayname = raw_input("Display name [%s]: "
% options.displayname)
options.displayname = newdisplayname or options.displayname
newdescription = raw_input("Description [%s]: " % options.description)
options.description = newdescription or options.description
newid = raw_input("PackageID [%s]: " % pkgid)
pkgid = newid or pkgid
newversion = raw_input("Version [%s]: " % pkgvers)
pkgvers = newversion or pkgvers
print
print
print "Package name: %s" % pkgname
print "Display name: %s" % options.displayname
print "Description: %s" % options.description
print "PackageID: %s" % pkgid
print "Version: %s" % pkgvers
print
answer = raw_input("Build the package? [y/n] ")
if not answer.lower().startswith("y"):
exit(0)
# build package
outputname = os.path.join(pkgoutputdir, pkgname + ".pkg")
if os.path.exists(outputname):
retcode = subprocess.call(["/bin/rm", "-rf", outputname])
if infopath:
cmd = [packagemaker, '--root', packageroot, '--info', infopath,
'--no-recommend', '--out', outputname, '--verbose',
'--filter', 'Library/Receipts',
'--filter', '.DS_Store$']
else:
cmd = [packagemaker, '--root', packageroot, '--id', pkgid,
'--version', pkgvers, '--out', outputname,
'--verbose', '--no-recommend',
'--filter', 'Library/Receipts',
'--filter', '.DS_Store$']
print cmd
p = subprocess.Popen(cmd, shell=False, bufsize=1, stdin=subprocess.PIPE,
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
while True:
output = p.stdout.readline()
if not output and (p.poll() != None):
break
print output.rstrip("\n")
sys.stdout.flush()
retcode = p.poll()
if retcode:
print >>sys.stderr, "Package creation failed."
exit(-1)
else:
# remove relocatable stuff
tokendefinitions = os.path.join(outputname,
"Contents/Resources/TokenDefinitions.plist")
if os.path.exists(tokendefinitions):
os.remove(tokendefinitions)
infoplist = os.path.join(outputname, "Contents/Info.plist")
pl = plistlib.readPlist(infoplist)
if 'IFPkgPathMappings' in pl:
del pl['IFPkgPathMappings']
if options.restart:
# add restart info to plist
pl['IFPkgFlagRestartAction'] = "RequiredRestart"
elif options.logout:
# add logout info to plist
pl['IFPkgFlagRestartAction'] = "RequiredLogout"
plistlib.writePlist(pl, infoplist)
if options.displayname or options.description:
languages = ['en.lproj', 'English.lproj']
for item in languages:
lprojpath = os.path.join(outputname,
'Contents/Resources', item)
if os.path.exists(lprojpath):
descriptionplist = os.path.join(lprojpath,
"Description.plist")
pl = {}
pl['IFPkgDescriptionTitle'] = (options.displayname or
pkgname)
pl['IFPkgDescriptionDescription'] = (options.description
or "")
plistlib.writePlist(pl, descriptionplist)
break
print "Completed package is at %s" % outputname
if not options.nomakedmg:
makeDMG(outputname)
#cleanup temp dir
retcode = subprocess.call(["/bin/rm", "-rf", mytmpdir])
if __name__ == '__main__':
main()

View File

@@ -1,331 +0,0 @@
#!/usr/bin/env python
# encoding: utf-8
#
# Copyright 2009-2014 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
#
# https://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.
"""
transcript2pkg.py
Created by Greg Neagle on 2009-06-04.
"""
import sys
import os
import stat
import plistlib
import subprocess
import optparse
import tempfile
# change these to suit yourself
packagemaker = "/Developer/usr/bin/packagemaker"
t2pkg = "/Users/Shared/bin/t2pkg"
pkgidprefix = "com.myorg.pkg."
pkgoutputdir = "/Users/Shared/pkgs"
def nameAndVersion(s):
"""
Splits a string into the name and version numbers:
'TextWrangler2.3b1' becomes ('TextWrangler', '2.3b1')
'AdobePhotoshopCS3-11.2.1' becomes ('AdobePhotoshopCS3', '11.2.1')
'MicrosoftOffice2008v12.2.1' becomes ('MicrosoftOffice2008', '12.2.1')
"""
index = 0
for char in s:
if char in "0123456789":
possibleVersion = s[index:]
if not (" " in possibleVersion or "_" in possibleVersion
or "-" in possibleVersion or "v" in possibleVersion):
return (s[0:index].rstrip(" .-_v"), possibleVersion)
index += 1
# no version number found, just return original string and empty string
return (s, '')
def makeDMG(pkgpath):
print "Making disk image..."
cmd = ["/usr/bin/hdiutil", "create", "-srcfolder",
pkgpath, pkgpath + ".dmg"]
p = subprocess.Popen(cmd, shell=False, bufsize=1, stdin=subprocess.PIPE,
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
while True:
output = p.stdout.readline()
if not output and (p.poll() != None):
break
print output.rstrip("\n")
sys.stdout.flush()
retcode = p.poll()
if retcode:
print >>sys.stderr, "Disk image creation failed."
return(-1)
def main():
# command-line options
p = optparse.OptionParser()
p.add_option('--makedmg', '-d', action='store_true',
help='Makes a disk image containing the package.')
p.add_option('--name', '-n',
help='Specify a name for the package.')
p.add_option('--version', '-v',
help='Specify a version number for the package.')
p.add_option('--id',
help='Specify a package id for the package.')
p.add_option('--displayname',
help='Specify a display name for the package.')
p.add_option('--description',
help='Specify a description for the package.')
p.add_option('--restart', action='store_true',
help='Restart is required on install.')
p.add_option('--logout', action='store_true',
help='Logout is required to install.')
# Get our options and our package names
options, transcripts = p.parse_args()
if not os.path.exists(packagemaker):
print >>sys.stderr, "packagemaker tool not found at %s." % \
packagemaker
exit(-1)
if not os.path.exists(t2pkg):
print >>sys.stderr, "t2pkg tool not found at %s." % t2pkg
exit(-1)
if len(transcripts) == 0:
print >>sys.stderr, "You must specify a transcript!"
exit(-1)
if len(transcripts) > 1:
print >>sys.stderr, "You may convert only one transcript at a time."
exit(-1)
transcript = transcripts[0]
if transcript.find("/") == -1:
# transcript name only; we'll prepend the standard
# client path
transcript = os.path.join("/var/radmind/client", transcript)
if not os.path.exists(transcript):
print "No transcript file at %s" % transcript
exit(-1)
mytmpdir = tempfile.mkdtemp()
if options.name:
pkgname = options.name
else:
pkgname = os.path.splitext(os.path.basename(transcript))[0]
# make packageroot directory using t2pkg
packageroot = os.path.join(mytmpdir, pkgname)
cmd = [t2pkg, '-d', packageroot, '-r', '/', transcript]
p = subprocess.Popen(cmd, shell=False, bufsize=1, stdin=subprocess.PIPE,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(output, err) = p.communicate()
if p.returncode != 0:
print >>sys.stderr, err
exit(-1)
if err:
print >>sys.stderr, err
exit(-1)
# fix uid/gid/perms on directories
for dirpath, dirnames, filenames in os.walk(packageroot):
srcdir = dirpath[len(packageroot):]
if srcdir == "": srcdir = "/"
os.chown(dirpath, os.stat(srcdir).st_uid, os.stat(srcdir).st_gid)
os.chmod(dirpath, stat.S_IMODE(os.stat(srcdir).st_mode))
# some default values
(name, versionInName) = nameAndVersion(pkgname)
if options.id:
pkgid = options.id
else:
pkgid = pkgidprefix + name.lower()
if options.version:
pkgvers = options.version
elif versionInName:
pkgvers = versionInName
else:
pkgvers = "1.0.0"
infopath = ""
# look through packageroot dir for Receipts
pkgrootreceipts = os.path.join(packageroot, "Library/Receipts")
if os.path.exists(pkgrootreceipts):
receiptlist = os.listdir(pkgrootreceipts)
if len(receiptlist) == 1:
receipt = os.path.join(pkgrootreceipts, receiptlist[0])
infopath = os.path.join(receipt,"Contents/Info.plist")
if os.path.exists(infopath):
print "Using package info from %s" % infopath
else:
infopath = ""
else:
print >>sys.stderr, \
"Found multiple receipts, so cannot determine pkgid and version."
if not infopath:
# look for a single application bundle and get info from that
appinfo = ""
for dirpath, dirnames, filenames in os.walk(packageroot):
if dirpath.endswith('.app'):
if not appinfo:
appinfo = os.path.join(dirpath, "Contents/Info.plist")
if not os.path.exists(appinfo):
appinfo = ""
else:
# crap, found more than one.
appinfo = ""
print >>sys.stderr, \
"Found multiple application bundles, so cannot determine pkgid and version."
break
if appinfo:
pl = plistlib.readPlist(appinfo)
if "CFBundleIdentifier" in pl and not options.id:
pkgid = pl["CFBundleIdentifier"] + ".pkg"
if "CFBundleShortVersionString" in pl and not options.version:
pkgvers = pl["CFBundleShortVersionString"]
print "Using pkgid: %s, version: %s from %s" % (pkgid, pkgvers,
appinfo)
else:
# let's look for any Contents/Info.plist
infoplist = ""
for dirpath, dirnames, filenames in os.walk(packageroot):
if dirpath.endswith("/Contents") and \
"Info.plist" in filenames:
if not infoplist:
infoplist = os.path.join(dirpath, "Info.plist")
if not os.path.exists(infoplist):
infoplist = ""
else:
# found more than one Info.plist
infoplist = ""
break
if infoplist:
pl = plistlib.readPlist(infoplist)
if "CFBundleIdentifier" in pl and not options.id:
pkgid = pl["CFBundleIdentifier"] + ".pkg"
if "CFBundleShortVersionString" in pl and not options.version:
pkgvers = pl["CFBundleShortVersionString"]
print "Using pkgid: %s, version: %s from %s" % (pkgid,
pkgvers, infoplist)
print "Package name: %s" % pkgname
newdisplayname = raw_input("Display name [%s]: "
% options.displayname)
options.displayname = newdisplayname or options.displayname
newdescription = raw_input("Description [%s]: " % options.description)
options.description = newdescription or options.description
newid = raw_input("PackageID [%s]: " % pkgid)
pkgid = newid or pkgid
newversion = raw_input("Version [%s]: " % pkgvers)
pkgvers = newversion or pkgvers
print
print
print "Package name: %s" % pkgname
print "Display name: %s" % options.displayname
print "Description: %s" % options.description
print "PackageID: %s" % pkgid
print "Version: %s" % pkgvers
print
answer = raw_input("Build the package? [y/n] ")
if not answer.lower().startswith("y"):
exit(0)
# build package
outputname = os.path.join(pkgoutputdir, pkgname + ".pkg")
if os.path.exists(outputname):
retcode = subprocess.call(["/bin/rm", "-rf", outputname])
if infopath:
cmd = [packagemaker, '--root', packageroot, '--info', infopath,
'--no-recommend', '--out', outputname, '--verbose',
'--filter', 'Library/Receipts',
'--filter', '.DS_Store$',
'--filter', 'Library/FA/Applications']
else:
cmd = [packagemaker, '--root', packageroot, '--id', pkgid,
'--version', pkgvers, '--out', outputname,
'--verbose', '--no-recommend',
'--filter', 'Library/Receipts',
'--filter', '.DS_Store$',
'--filter', 'Library/FA/Applications']
print cmd
p = subprocess.Popen(cmd, shell=False, bufsize=1, stdin=subprocess.PIPE,
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
while True:
output = p.stdout.readline()
if not output and (p.poll() != None):
break
print output.rstrip("\n")
sys.stdout.flush()
retcode = p.poll()
if retcode:
print >>sys.stderr, "Package creation failed."
exit(-1)
else:
# remove relocatable stuff
tokendefinitions = os.path.join(outputname,
"Contents/Resources/TokenDefinitions.plist")
if os.path.exists(tokendefinitions):
os.remove(tokendefinitions)
infoplist = os.path.join(outputname, "Contents/Info.plist")
pl = plistlib.readPlist(infoplist)
if 'IFPkgPathMappings' in pl:
del pl['IFPkgPathMappings']
if options.restart:
# add restart info to plist
pl['IFPkgFlagRestartAction'] = "RequiredRestart"
elif options.logout:
# add logout info to plist
pl['IFPkgFlagRestartAction'] = "RequiredLogout"
plistlib.writePlist(pl, infoplist)
if options.displayname or options.description:
languages = ['en.lproj', 'English.lproj']
for item in languages:
lprojpath = os.path.join(outputname,
'Contents/Resources', item)
if os.path.exists(lprojpath):
descriptionplist = os.path.join(lprojpath,
"Description.plist")
pl = {}
pl['IFPkgDescriptionTitle'] = (options.displayname or
pkgname)
pl['IFPkgDescriptionDescription'] = (options.description
or "")
plistlib.writePlist(pl, descriptionplist)
break
print "Completed package is at %s" % outputname
if options.makedmg:
makeDMG(outputname)
#cleanup temp dir
retcode = subprocess.call(["/bin/rm", "-rf", mytmpdir])
if __name__ == '__main__':
main()

View File

@@ -6,7 +6,7 @@ munkicommon_display_unicode_test.py
Unit tests for munkicommon's display_* functions.
"""
# Copyright 2014 Greg Neagle.
# Copyright 2014-2016 Greg Neagle.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.