Updated Sparkle framework

CQTexperiment
Chris Moeller 2015-06-02 00:34:50 -07:00
parent d4c971f9d2
commit ab7e5e6966
114 changed files with 4256 additions and 1001 deletions

View File

@ -1,3 +1,42 @@
# 1.10.0
* Massive improvements to the BinaryDelta tool (Zorg)
- Ability to track file permissions (Zorg)
- Nicely formatted log output (Zorg)
- Numerous bug fixes in handling of symlinks, empty directories, case-insensitive names, etc. (Zorg)
- Refactored and modernized code (Zorg)
- libxar is no longer weak-linked (C.W. Betts)
* Double-check the code signature of the the app after installation (Isaac Wankerl)
* Added headless guided package installation (Graham Miln)
* Added ability to inject custom HTTP headers in appcast request (Mattias Gunneras)
* Changes to make unarching more reliable (Zorg, Kornel Lesiński)
* Have Sparkle build a framework module (C.W. Betts)
* Stdout used for non error outputs (JDuquennoy)
* French locale update (Kent Sutherland)
# 1.9.0
* Added SUUpdater delegate method for failures. (Benjamin Gordon)
* Make the error definitions public (C.W. Betts)
* Add support for lzma compressed tarballs (Kyle Fuller)
* Back to SKIP_INSTALL=YES by default (Tony Arnold)
* Properly set install names and rpaths for targets (Jake Petroules)
* Use Library/Caches rather than app support directory (Kornel Lesiński)
* Check for a modal window being onscreen before trying to put up the Sparkle prompt (Alf Watt)
* Fixed crashes on 10.7 (Chris Campbell, Ger Teunis)
* Fixed Sparkle tags parsing (Tamás Lustyik)
* SULog code cleanups (Kevin Wojniak)
* Make sure CFBundleVersion is a semantic version number. (Jake Petroules)
* Replace typedef enums with typedef NS_ENUM to make Swift happier (C.W. Betts)
* Fix warnings under Xcode 6.1 relating the SUUpdateAlert XIB (Tony Arnold)
* Prefer string constants to strings (Jake Petroules)
* Use Info.plist keys instead of macros (Jake Petroules)
* Only export public symbols. (Jake Petroules)
* BinaryDelta: avoid crash with bad paths (Jake Petroules)
* Fixing Swedish translations (Erik Vikström)
* Turkish localization fixes (Emir)
* Proofing of Ukrainian localization (Vera Tkachenko)
# 1.8.0
* New SUDSAVerifier based on up-to-date OS X APIs (Zachary Waldowski)

View File

@ -15,7 +15,7 @@ SPARKLE_AUTOMATED_DOWNGRADES = 0
SPARKLE_NORMALIZE_INSTALLED_APPLICATION_NAME = 0
SPARKLE_VERSION_MAJOR = 1
SPARKLE_VERSION_MINOR = 8
SPARKLE_VERSION_MINOR = 10
SPARKLE_VERSION_PATCH = 0
SPARKLE_VERSION = $(SPARKLE_VERSION_MAJOR).$(SPARKLE_VERSION_MINOR).$(SPARKLE_VERSION_PATCH)
@ -35,6 +35,7 @@ CLANG_ENABLE_OBJC_ARC = YES
CLANG_ANALYZER_SECURITY_FLOATLOOPCOUNTER = YES
CLANG_ANALYZER_SECURITY_INSECUREAPI_RAND = YES
CLANG_ANALYZER_SECURITY_INSECUREAPI_STRCPY = YES
CLANG_ENABLE_MODULES = YES
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES
CLANG_WARN_DOCUMENTATION_COMMENTS = YES
@ -73,4 +74,4 @@ GCC_WARN_UNUSED_VARIABLE = YES
WARNING_CFLAGS_EXTRA = -Wno-custom-atomic-properties -Wno-implicit-atomic-properties
// Turn on all warnings, then disable a few which are almost impossible to avoid
WARNING_CFLAGS = -Wall -Weverything -Wno-empty-translation-unit -Wno-unused-macros -Wno-gnu-statement-expression -Wno-receiver-is-weak -Wno-arc-repeated-use-of-weak $(WARNING_CFLAGS_EXTRA)
WARNING_CFLAGS = -Wall -Weverything -Wno-empty-translation-unit -Wno-unused-macros -Wno-gnu-statement-expression -Wno-receiver-is-weak -Wno-arc-repeated-use-of-weak -Wno-auto-import -Wno-cstring-format-directive $(WARNING_CFLAGS_EXTRA)

View File

@ -9,3 +9,5 @@ FRAMEWORK_VERSION = A
INFOPLIST_FILE = Sparkle/Sparkle-Info.plist
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) BUILDING_SPARKLE=1
OTHER_LDFLAGS = -Wl,-U,_NSURLQuarantinePropertiesKey
SKIP_INSTALL = YES
DEFINES_MODULE = YES

View File

@ -1,2 +1,4 @@
#include "ConfigFramework.xcconfig"
// Unit tests need access to non-public classes
GCC_SYMBOLS_PRIVATE_EXTERN = NO

View File

@ -4,4 +4,5 @@ INFOPLIST_FILE = Sparkle/Autoupdate/Autoupdate-Info.plist
PRODUCT_NAME = $(SPARKLE_RELAUNCH_TOOL_NAME)
SKIP_INSTALL = YES
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon
CLANG_ENABLE_MODULES = NO
OTHER_LDFLAGS = -Wl,-U,_NSURLQuarantinePropertiesKey

View File

@ -30,6 +30,9 @@ EXTERNAL LICENSES
bspatch.c and bsdiff.c, from bsdiff 4.3 <http://www.daemonology.net/bsdiff/>:
Copyright (c) 2003-2005 Colin Percival.
sais.c and sais.c, from sais-lite (2010/08/07) <https://sites.google.com/site/yuta256/sais>:
Copyright (c) 2008-2010 Yuta Mori.
SUDSAVerifier.m:
Copyright (c) 2011 Mark Hamlin.

View File

@ -1,7 +1,8 @@
# Sparkle <img src="Resources/Sparkle.png" width=48 height=48 alt=""/>
is an easy-to-use software update framework for Cocoa developers.
# Sparkle [![Build Status](https://travis-ci.org/sparkle-project/Sparkle.svg?branch=master)](https://travis-ci.org/sparkle-project/Sparkle)
[![Build Status](https://travis-ci.org/sparkle-project/Sparkle.svg?branch=master)](https://travis-ci.org/sparkle-project/Sparkle)
An easy-to-use software update framework for Cocoa developers.
<img src="Resources/Screenshot.png" width="715" alt="Sparkle shows familiar update window with release notes">
## Changes since 1.5b

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 754 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = "Sparkle"
s.version = "1.8.0"
s.version = "1.9.0"
s.summary = "A software update framework for OS X"
s.description = "Sparkle is an easy-to-use software update framework for Cocoa developers."
s.homepage = "http://sparkle-project.org"

View File

@ -34,9 +34,7 @@
14652F7C19A9725300959E44 /* SUBinaryDeltaCommon.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D06E8E20FD68CC7005AE3F6 /* SUBinaryDeltaCommon.m */; };
14652F7D19A9726700959E44 /* SUBinaryDeltaApply.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D06E8E00FD68CC7005AE3F6 /* SUBinaryDeltaApply.m */; };
14652F7E19A9728A00959E44 /* bspatch.c in Sources */ = {isa = PBXBuildFile; fileRef = 5D06E8DC0FD68CB9005AE3F6 /* bspatch.c */; settings = {COMPILER_FLAGS = "-w"; }; };
14652F7F19A973F900959E44 /* SUDSAVerifier.m in Sources */ = {isa = PBXBuildFile; fileRef = 61299A2E09CA2DAB00B7442F /* SUDSAVerifier.m */; };
14652F8019A9740F00959E44 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 61B5F8F609C4CEB300B25A18 /* Security.framework */; };
14652F8119A9744200959E44 /* SUBinaryDeltaCommon.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D06E8E20FD68CC7005AE3F6 /* SUBinaryDeltaCommon.m */; };
14652F8219A9746000959E44 /* SULog.m in Sources */ = {isa = PBXBuildFile; fileRef = 55C14F05136EF6DB00649790 /* SULog.m */; };
14652F8419A978C200959E44 /* SUExport.h in Headers */ = {isa = PBXBuildFile; fileRef = 14652F8319A9759F00959E44 /* SUExport.h */; settings = {ATTRIBUTES = (Public, ); }; };
14732BC01960F2C200593899 /* test_app_only_dsa_pub.pem in Resources */ = {isa = PBXBuildFile; fileRef = 14732BBF1960F0AC00593899 /* test_app_only_dsa_pub.pem */; };
@ -49,7 +47,6 @@
14950071195FCE3D00BC5B5B /* AppKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0867D6A5FE840307C02AAC07 /* AppKit.framework */; };
14950072195FCE4B00BC5B5B /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0867D69BFE84028FC02AAC07 /* Foundation.framework */; };
14950073195FCE4E00BC5B5B /* AppKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0867D6A5FE840307C02AAC07 /* AppKit.framework */; };
14950075195FDF5900BC5B5B /* SUUpdaterTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 14950074195FDF5900BC5B5B /* SUUpdaterTest.m */; };
14958C6E19AEBC950061B14F /* signed-test-file.txt in Resources */ = {isa = PBXBuildFile; fileRef = 14958C6B19AEBC530061B14F /* signed-test-file.txt */; };
14958C6F19AEBC980061B14F /* test-pubkey.pem in Resources */ = {isa = PBXBuildFile; fileRef = 14958C6C19AEBC610061B14F /* test-pubkey.pem */; };
3772FEA913DE0B6B00F79537 /* SUVersionDisplayProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 3772FEA813DE0B6B00F79537 /* SUVersionDisplayProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; };
@ -75,6 +72,11 @@
55C14F7E136F005000649790 /* SUPlainInstallerInternals.m in Sources */ = {isa = PBXBuildFile; fileRef = 61B5F8E509C4CE3C00B25A18 /* SUPlainInstallerInternals.m */; };
55C14F9A136F045400649790 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 61B5F8F609C4CEB300B25A18 /* Security.framework */; };
55C14FC7136F05E100649790 /* Sparkle.strings in Resources */ = {isa = PBXBuildFile; fileRef = 61AAE8220A321A7F00D8810D /* Sparkle.strings */; };
55E6F33319EC9F6C00005E76 /* SUErrors.h in Headers */ = {isa = PBXBuildFile; fileRef = 55E6F33219EC9F6C00005E76 /* SUErrors.h */; settings = {ATTRIBUTES = (Public, ); }; };
5AF6C1E51AE86A410014A3AB /* SUPipedUnarchiverTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 5AF6C1EA1AE870A90014A3AB /* SUPipedUnarchiverTest.m */; };
5AF6C1E91AE86E270014A3AB /* test archive.zip in Resources */ = {isa = PBXBuildFile; fileRef = 5AF6C1E81AE86E270014A3AB /* test archive.zip */; };
5AF6C74F1AEA46D10014A3AB /* test.sparkle_guided.pkg in Resources */ = {isa = PBXBuildFile; fileRef = 5AF6C74E1AEA46D10014A3AB /* test.sparkle_guided.pkg */; };
5AF6C7541AEA49840014A3AB /* SUInstallerTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 5AF6C74C1AEA40760014A3AB /* SUInstallerTest.m */; };
5AF9DC3C1981DBEE001EA135 /* SUDSAVerifierTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 5AF9DC3B1981DBEE001EA135 /* SUDSAVerifierTest.m */; };
5D06E8E90FD68CDB005AE3F6 /* bsdiff.c in Sources */ = {isa = PBXBuildFile; fileRef = 5D06E8DB0FD68CB9005AE3F6 /* bsdiff.c */; settings = {COMPILER_FLAGS = "-w"; }; };
5D06E8EA0FD68CDB005AE3F6 /* SUBinaryDeltaTool.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D06E8E30FD68CC7005AE3F6 /* SUBinaryDeltaTool.m */; };
@ -106,13 +108,12 @@
61180BCB0D64138900B4E0D1 /* SUWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 61180BC90D64138900B4E0D1 /* SUWindowController.m */; };
6120721209CC5C4B007FE0F6 /* SUAutomaticUpdateAlert.h in Headers */ = {isa = PBXBuildFile; fileRef = 6120721009CC5C4B007FE0F6 /* SUAutomaticUpdateAlert.h */; settings = {ATTRIBUTES = (); }; };
6120721309CC5C4B007FE0F6 /* SUAutomaticUpdateAlert.m in Sources */ = {isa = PBXBuildFile; fileRef = 6120721109CC5C4B007FE0F6 /* SUAutomaticUpdateAlert.m */; };
61227A160DB548B800AB99EA /* SUVersionComparisonTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 61227A150DB548B800AB99EA /* SUVersionComparisonTest.m */; };
61299A2F09CA2DAB00B7442F /* SUDSAVerifier.h in Headers */ = {isa = PBXBuildFile; fileRef = 61299A2D09CA2DAB00B7442F /* SUDSAVerifier.h */; settings = {ATTRIBUTES = (); }; };
61299A3009CA2DAB00B7442F /* SUDSAVerifier.m in Sources */ = {isa = PBXBuildFile; fileRef = 61299A2E09CA2DAB00B7442F /* SUDSAVerifier.m */; settings = {COMPILER_FLAGS = "-Wno-deprecated-declarations"; }; };
61299A4A09CA2DD000B7442F /* SUPlainInstallerInternals.h in Headers */ = {isa = PBXBuildFile; fileRef = 6129984309C9E2DA00B7442F /* SUPlainInstallerInternals.h */; settings = {ATTRIBUTES = (); }; };
61299A5C09CA6D4500B7442F /* SUConstants.h in Headers */ = {isa = PBXBuildFile; fileRef = 61299A5B09CA6D4500B7442F /* SUConstants.h */; settings = {ATTRIBUTES = (); }; };
61299A6009CA6EB100B7442F /* SUConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = 61299A5F09CA6EB100B7442F /* SUConstants.m */; };
61299A8D09CA790200B7442F /* SUUnarchiver.h in Headers */ = {isa = PBXBuildFile; fileRef = 61299A8B09CA790200B7442F /* SUUnarchiver.h */; settings = {ATTRIBUTES = (); }; };
61299A8D09CA790200B7442F /* SUUnarchiver.h in Headers */ = {isa = PBXBuildFile; fileRef = 61299A8B09CA790200B7442F /* SUUnarchiver.h */; settings = {ATTRIBUTES = (Private, ); }; };
61299A8E09CA790200B7442F /* SUUnarchiver.m in Sources */ = {isa = PBXBuildFile; fileRef = 61299A8C09CA790200B7442F /* SUUnarchiver.m */; };
61299B3609CB04E000B7442F /* Sparkle.h in Headers */ = {isa = PBXBuildFile; fileRef = 61299B3509CB04E000B7442F /* Sparkle.h */; settings = {ATTRIBUTES = (Public, ); }; };
612DCBAF0D488BC60015DBEA /* SUUpdatePermissionPrompt.h in Headers */ = {isa = PBXBuildFile; fileRef = 612DCBAD0D488BC60015DBEA /* SUUpdatePermissionPrompt.h */; settings = {ATTRIBUTES = (); }; };
@ -166,6 +167,20 @@
61F83F720DBFE140006FDD30 /* SUBasicUpdateDriver.m in Sources */ = {isa = PBXBuildFile; fileRef = 61F83F700DBFE137006FDD30 /* SUBasicUpdateDriver.m */; };
61F83F740DBFE141006FDD30 /* SUBasicUpdateDriver.h in Headers */ = {isa = PBXBuildFile; fileRef = 61F83F6F0DBFE137006FDD30 /* SUBasicUpdateDriver.h */; settings = {ATTRIBUTES = (); }; };
61FA52880E2D9EA400EF58AD /* Sparkle.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8DC2EF5B0486A6940098B216 /* Sparkle.framework */; settings = {ATTRIBUTES = (Required, ); }; };
721CF1A71AD7643600D9AC09 /* bsdiff.c in Sources */ = {isa = PBXBuildFile; fileRef = 5D06E8DB0FD68CB9005AE3F6 /* bsdiff.c */; };
721CF1A81AD7644100D9AC09 /* bspatch.c in Sources */ = {isa = PBXBuildFile; fileRef = 5D06E8DC0FD68CB9005AE3F6 /* bspatch.c */; };
721CF1A91AD7644C00D9AC09 /* sais.c in Sources */ = {isa = PBXBuildFile; fileRef = 7223E7611AD1AEFF008E3161 /* sais.c */; };
721CF1AA1AD7647000D9AC09 /* libxar.1.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 5D1AF5890FD7678C0065DB48 /* libxar.1.dylib */; };
721CF1AB1AD764EB00D9AC09 /* libbz2.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 5D06E8FB0FD68D61005AE3F6 /* libbz2.dylib */; };
7223E7631AD1AEFF008E3161 /* sais.c in Sources */ = {isa = PBXBuildFile; fileRef = 7223E7611AD1AEFF008E3161 /* sais.c */; };
7268AC631AD634C200C3E0C1 /* SUBinaryDeltaCreate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7268AC621AD634C200C3E0C1 /* SUBinaryDeltaCreate.m */; };
72D4DAA11AD7632900B211E2 /* SUBinaryDeltaCreate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7268AC621AD634C200C3E0C1 /* SUBinaryDeltaCreate.m */; };
767B61AC1972D488004E0C3C /* SUGuidedPackageInstaller.h in Headers */ = {isa = PBXBuildFile; fileRef = 767B61AA1972D488004E0C3C /* SUGuidedPackageInstaller.h */; };
767B61AD1972D488004E0C3C /* SUGuidedPackageInstaller.m in Sources */ = {isa = PBXBuildFile; fileRef = 767B61AB1972D488004E0C3C /* SUGuidedPackageInstaller.m */; };
767B61AE1972D488004E0C3C /* SUGuidedPackageInstaller.m in Sources */ = {isa = PBXBuildFile; fileRef = 767B61AB1972D488004E0C3C /* SUGuidedPackageInstaller.m */; };
F8761EB11ADC5068000C9034 /* SUCodeSigningVerifierTest.m in Sources */ = {isa = PBXBuildFile; fileRef = F8761EB01ADC5068000C9034 /* SUCodeSigningVerifierTest.m */; };
F8761EB31ADC50EB000C9034 /* SparkleTestCodeSignApp.zip in Resources */ = {isa = PBXBuildFile; fileRef = F8761EB21ADC50EB000C9034 /* SparkleTestCodeSignApp.zip */; };
F8761EB61ADC5E7A000C9034 /* SUCodeSigningVerifier.m in Sources */ = {isa = PBXBuildFile; fileRef = 61B078CD15A5FB6100600039 /* SUCodeSigningVerifier.m */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@ -398,7 +413,12 @@
55C14F04136EF6DB00649790 /* SULog.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SULog.h; sourceTree = "<group>"; };
55C14F05136EF6DB00649790 /* SULog.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SULog.m; sourceTree = "<group>"; };
55C14F31136EFC2400649790 /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; };
55E6F33219EC9F6C00005E76 /* SUErrors.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SUErrors.h; sourceTree = "<group>"; };
5AEF45D9189D1CC90030D7DC /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/Sparkle.strings; sourceTree = "<group>"; };
5AF6C1E81AE86E270014A3AB /* test archive.zip */ = {isa = PBXFileReference; lastKnownFileType = archive.zip; path = "test archive.zip"; sourceTree = "<group>"; };
5AF6C1EA1AE870A90014A3AB /* SUPipedUnarchiverTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SUPipedUnarchiverTest.m; sourceTree = "<group>"; };
5AF6C74C1AEA40760014A3AB /* SUInstallerTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SUInstallerTest.m; sourceTree = "<group>"; };
5AF6C74E1AEA46D10014A3AB /* test.sparkle_guided.pkg */ = {isa = PBXFileReference; lastKnownFileType = file; path = test.sparkle_guided.pkg; sourceTree = "<group>"; };
5AF9DC3B1981DBEE001EA135 /* SUDSAVerifierTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SUDSAVerifierTest.m; sourceTree = "<group>"; };
5D06E8D00FD68C7C005AE3F6 /* BinaryDelta */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = BinaryDelta; sourceTree = BUILT_PRODUCTS_DIR; };
5D06E8DB0FD68CB9005AE3F6 /* bsdiff.c */ = {isa = PBXFileReference; comments = "-Wno-shorten-64-to-32"; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = bsdiff.c; sourceTree = "<group>"; };
@ -529,8 +549,16 @@
61F614540E24A12D009F47E7 /* it */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Sparkle.strings; sourceTree = "<group>"; };
61F83F6F0DBFE137006FDD30 /* SUBasicUpdateDriver.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SUBasicUpdateDriver.h; sourceTree = "<group>"; };
61F83F700DBFE137006FDD30 /* SUBasicUpdateDriver.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SUBasicUpdateDriver.m; sourceTree = "<group>"; };
7223E7611AD1AEFF008E3161 /* sais.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = sais.c; sourceTree = "<group>"; };
7223E7621AD1AEFF008E3161 /* sais.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = sais.h; sourceTree = "<group>"; };
7268AC621AD634C200C3E0C1 /* SUBinaryDeltaCreate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SUBinaryDeltaCreate.m; sourceTree = "<group>"; };
7268AC641AD634E400C3E0C1 /* SUBinaryDeltaCreate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SUBinaryDeltaCreate.h; sourceTree = "<group>"; };
767B61AA1972D488004E0C3C /* SUGuidedPackageInstaller.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SUGuidedPackageInstaller.h; sourceTree = "<group>"; };
767B61AB1972D488004E0C3C /* SUGuidedPackageInstaller.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SUGuidedPackageInstaller.m; sourceTree = "<group>"; };
8DC2EF5A0486A6940098B216 /* Sparkle-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "Sparkle-Info.plist"; sourceTree = "<group>"; };
8DC2EF5B0486A6940098B216 /* Sparkle.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Sparkle.framework; sourceTree = BUILT_PRODUCTS_DIR; };
F8761EB01ADC5068000C9034 /* SUCodeSigningVerifierTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SUCodeSigningVerifierTest.m; sourceTree = "<group>"; };
F8761EB21ADC50EB000C9034 /* SparkleTestCodeSignApp.zip */ = {isa = PBXFileReference; lastKnownFileType = archive.zip; path = SparkleTestCodeSignApp.zip; sourceTree = "<group>"; };
FA1941CA0D94A70100DD942E /* ConfigFrameworkDebug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = ConfigFrameworkDebug.xcconfig; sourceTree = "<group>"; };
FA1941CB0D94A70100DD942E /* ConfigTestAppDebug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = ConfigTestAppDebug.xcconfig; sourceTree = "<group>"; };
FA1941CC0D94A70100DD942E /* ConfigCommonRelease.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = ConfigCommonRelease.xcconfig; sourceTree = "<group>"; };
@ -579,8 +607,10 @@
files = (
14732BD119610A1200593899 /* AppKit.framework in Frameworks */,
14732BD019610A0D00593899 /* Foundation.framework in Frameworks */,
61FA52880E2D9EA400EF58AD /* Sparkle.framework in Frameworks */,
721CF1AB1AD764EB00D9AC09 /* libbz2.dylib in Frameworks */,
721CF1AA1AD7647000D9AC09 /* libxar.1.dylib in Frameworks */,
14652F8019A9740F00959E44 /* Security.framework in Frameworks */,
61FA52880E2D9EA400EF58AD /* Sparkle.framework in Frameworks */,
14732BD319610A1800593899 /* XCTest.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -700,6 +730,8 @@
5D06E8DB0FD68CB9005AE3F6 /* bsdiff.c */,
5D06E8DC0FD68CB9005AE3F6 /* bspatch.c */,
611142E810FB1BE5009810AA /* bspatch.h */,
7223E7611AD1AEFF008E3161 /* sais.c */,
7223E7621AD1AEFF008E3161 /* sais.h */,
);
path = bsdiff;
sourceTree = "<group>";
@ -756,7 +788,10 @@
isa = PBXGroup;
children = (
14958C6B19AEBC530061B14F /* signed-test-file.txt */,
F8761EB21ADC50EB000C9034 /* SparkleTestCodeSignApp.zip */,
5AF6C1E81AE86E270014A3AB /* test archive.zip */,
14958C6C19AEBC610061B14F /* test-pubkey.pem */,
5AF6C74E1AEA46D10014A3AB /* test.sparkle_guided.pkg */,
);
path = Resources;
sourceTree = "<group>";
@ -777,6 +812,8 @@
5D06E8E00FD68CC7005AE3F6 /* SUBinaryDeltaApply.m */,
5D06E8E10FD68CC7005AE3F6 /* SUBinaryDeltaCommon.h */,
5D06E8E20FD68CC7005AE3F6 /* SUBinaryDeltaCommon.m */,
7268AC641AD634E400C3E0C1 /* SUBinaryDeltaCreate.h */,
7268AC621AD634C200C3E0C1 /* SUBinaryDeltaCreate.m */,
5D06E8E30FD68CC7005AE3F6 /* SUBinaryDeltaTool.m */,
5D06E9370FD69271005AE3F6 /* SUBinaryDeltaUnarchiver.h */,
5D06E9380FD69271005AE3F6 /* SUBinaryDeltaUnarchiver.m */,
@ -802,12 +839,15 @@
61227A100DB5484000AB99EA /* Tests */ = {
isa = PBXGroup;
children = (
14958C7019AEBE350061B14F /* Resources */,
612279DA0DB5470200AB99EA /* SparkleTests-Info.plist */,
142E0E0819A83AAC00E4312B /* SUBinaryDeltaTest.m */,
F8761EB01ADC5068000C9034 /* SUCodeSigningVerifierTest.m */,
5AF9DC3B1981DBEE001EA135 /* SUDSAVerifierTest.m */,
5AF6C74C1AEA40760014A3AB /* SUInstallerTest.m */,
5AF6C1EA1AE870A90014A3AB /* SUPipedUnarchiverTest.m */,
14950074195FDF5900BC5B5B /* SUUpdaterTest.m */,
61227A150DB548B800AB99EA /* SUVersionComparisonTest.m */,
14958C7019AEBE350061B14F /* Resources */,
);
path = Tests;
sourceTree = "<group>";
@ -849,6 +889,8 @@
618FA6DB0DB485440026945C /* Installation */ = {
isa = PBXGroup;
children = (
767B61AA1972D488004E0C3C /* SUGuidedPackageInstaller.h */,
767B61AB1972D488004E0C3C /* SUGuidedPackageInstaller.m */,
618FA4FF0DAE88B40026945C /* SUInstaller.h */,
618FA5000DAE88B40026945C /* SUInstaller.m */,
618FA5200DAE8E8A0026945C /* SUPackageInstaller.h */,
@ -868,6 +910,7 @@
61CFB3280E385186007A1735 /* Sparkle.pch */,
61299A5B09CA6D4500B7442F /* SUConstants.h */,
61299A5F09CA6EB100B7442F /* SUConstants.m */,
55E6F33219EC9F6C00005E76 /* SUErrors.h */,
14652F8319A9759F00959E44 /* SUExport.h */,
61EF67580E25C5B400F754E0 /* SUHost.h */,
61EF67550E25B58D00F754E0 /* SUHost.m */,
@ -881,10 +924,10 @@
61B5F91D09C4CF7F00B25A18 /* Test Application */ = {
isa = PBXGroup;
children = (
14732BBF1960F0AC00593899 /* test_app_only_dsa_pub.pem */,
61B5F92A09C4CFD800B25A18 /* InfoPlist.strings */,
61B5F92409C4CFC900B25A18 /* main.m */,
61B5F92C09C4CFD800B25A18 /* MainMenu.xib */,
14732BBF1960F0AC00593899 /* test_app_only_dsa_pub.pem */,
61B5F90409C4CEE200B25A18 /* TestApplication-Info.plist */,
);
name = "Test Application";
@ -983,9 +1026,11 @@
5D06E9390FD69271005AE3F6 /* SUBinaryDeltaUnarchiver.h in Headers */,
61B078CE15A5FB6100600039 /* SUCodeSigningVerifier.h in Headers */,
61299A5C09CA6D4500B7442F /* SUConstants.h in Headers */,
14652F8419A978C200959E44 /* SUExport.h in Headers */,
6102FE4A0E07803800F85D09 /* SUDiskImageUnarchiver.h in Headers */,
61299A2F09CA2DAB00B7442F /* SUDSAVerifier.h in Headers */,
55E6F33319EC9F6C00005E76 /* SUErrors.h in Headers */,
14652F8419A978C200959E44 /* SUExport.h in Headers */,
767B61AC1972D488004E0C3C /* SUGuidedPackageInstaller.h in Headers */,
61EF67590E25C5B400F754E0 /* SUHost.h in Headers */,
618FA5010DAE88B40026945C /* SUInstaller.h in Headers */,
55C14F06136EF6DB00649790 /* SULog.h in Headers */,
@ -1156,7 +1201,7 @@
isa = PBXProject;
attributes = {
CLASSPREFIX = SU;
LastUpgradeCheck = 0600;
LastUpgradeCheck = 0630;
ORGANIZATIONNAME = "Sparkle Project";
TargetAttributes = {
612279D80DB5470200AB99EA = {
@ -1233,8 +1278,11 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
14958C6F19AEBC980061B14F /* test-pubkey.pem in Resources */,
14958C6E19AEBC950061B14F /* signed-test-file.txt in Resources */,
F8761EB31ADC50EB000C9034 /* SparkleTestCodeSignApp.zip in Resources */,
5AF6C1E91AE86E270014A3AB /* test archive.zip in Resources */,
14958C6F19AEBC980061B14F /* test-pubkey.pem in Resources */,
5AF6C74F1AEA46D10014A3AB /* test.sparkle_guided.pkg in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -1252,10 +1300,10 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
14732BC01960F2C200593899 /* test_app_only_dsa_pub.pem in Resources */,
1420DF50196247F900203BB0 /* Images.xcassets in Resources */,
61B5F92E09C4CFD800B25A18 /* InfoPlist.strings in Resources */,
61B5F92F09C4CFD800B25A18 /* MainMenu.xib in Resources */,
14732BC01960F2C200593899 /* test_app_only_dsa_pub.pem in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -1361,7 +1409,9 @@
buildActionMask = 2147483647;
files = (
55C14BD4136EEFCE00649790 /* Autoupdate.m in Sources */,
F8761EB61ADC5E7A000C9034 /* SUCodeSigningVerifier.m in Sources */,
55C14F00136EF6B700649790 /* SUConstants.m in Sources */,
767B61AE1972D488004E0C3C /* SUGuidedPackageInstaller.m in Sources */,
55C14F0C136EF6EA00649790 /* SUHost.m in Sources */,
55C14F0D136EF6F200649790 /* SUInstaller.m in Sources */,
55C14F08136EF6DB00649790 /* SULog.m in Sources */,
@ -1381,8 +1431,10 @@
files = (
5D06E8E90FD68CDB005AE3F6 /* bsdiff.c in Sources */,
14652F7E19A9728A00959E44 /* bspatch.c in Sources */,
7223E7631AD1AEFF008E3161 /* sais.c in Sources */,
14652F7D19A9726700959E44 /* SUBinaryDeltaApply.m in Sources */,
14652F7C19A9725300959E44 /* SUBinaryDeltaCommon.m in Sources */,
7268AC631AD634C200C3E0C1 /* SUBinaryDeltaCreate.m in Sources */,
5D06E8EA0FD68CDB005AE3F6 /* SUBinaryDeltaTool.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -1391,13 +1443,16 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
14652F8119A9744200959E44 /* SUBinaryDeltaCommon.m in Sources */,
14652F7F19A973F900959E44 /* SUDSAVerifier.m in Sources */,
14652F8219A9746000959E44 /* SULog.m in Sources */,
5AF9DC3C1981DBEE001EA135 /* SUDSAVerifierTest.m in Sources */,
721CF1A71AD7643600D9AC09 /* bsdiff.c in Sources */,
721CF1A81AD7644100D9AC09 /* bspatch.c in Sources */,
721CF1A91AD7644C00D9AC09 /* sais.c in Sources */,
72D4DAA11AD7632900B211E2 /* SUBinaryDeltaCreate.m in Sources */,
142E0E0919A83AAC00E4312B /* SUBinaryDeltaTest.m in Sources */,
14950075195FDF5900BC5B5B /* SUUpdaterTest.m in Sources */,
61227A160DB548B800AB99EA /* SUVersionComparisonTest.m in Sources */,
F8761EB11ADC5068000C9034 /* SUCodeSigningVerifierTest.m in Sources */,
5AF9DC3C1981DBEE001EA135 /* SUDSAVerifierTest.m in Sources */,
5AF6C7541AEA49840014A3AB /* SUInstallerTest.m in Sources */,
14652F8219A9746000959E44 /* SULog.m in Sources */,
5AF6C1E51AE86A410014A3AB /* SUPipedUnarchiverTest.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -1427,6 +1482,7 @@
61299A6009CA6EB100B7442F /* SUConstants.m in Sources */,
6102FE4B0E07803800F85D09 /* SUDiskImageUnarchiver.m in Sources */,
61299A3009CA2DAB00B7442F /* SUDSAVerifier.m in Sources */,
767B61AD1972D488004E0C3C /* SUGuidedPackageInstaller.m in Sources */,
61EF67560E25B58D00F754E0 /* SUHost.m in Sources */,
618FA5020DAE88B40026945C /* SUInstaller.m in Sources */,
55C14F07136EF6DB00649790 /* SULog.m in Sources */,

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0610"
LastUpgradeVersion = "0630"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0610"
LastUpgradeVersion = "0630"
version = "2.0">
<BuildAction
parallelizeBuildables = "YES"

View File

@ -18,7 +18,7 @@ static const NSTimeInterval SUInstallationTimeLimit = 5;
*/
static const NSTimeInterval SUParentQuitCheckInterval = .25;
@interface TerminationListener : NSObject <SUInstallerDelegate>
@interface TerminationListener : NSObject
@property (copy) NSString *hostpath;
@property (copy) NSString *executablepath;
@ -32,6 +32,7 @@ static const NSTimeInterval SUParentQuitCheckInterval = .25;
@property (strong) SUHost *host;
@property (assign) BOOL shouldRelaunch;
@property (assign) BOOL shouldShowUI;
@property (strong) SUStatusController *statusController;
- (void)parentHasQuit;
@ -57,6 +58,7 @@ static const NSTimeInterval SUParentQuitCheckInterval = .25;
@synthesize host;
@synthesize shouldRelaunch;
@synthesize shouldShowUI;
@synthesize statusController;
- (instancetype)initWithHostPath:(NSString *)inhostpath executablePath:(NSString *)execpath parentProcessId:(pid_t)ppid folderPath:(NSString *)infolderpath shouldRelaunch:(BOOL)relaunch shouldShowUI:(BOOL)showUI selfPath:(NSString *)inSelfPath
{
@ -86,6 +88,7 @@ static const NSTimeInterval SUParentQuitCheckInterval = .25;
- (void)dealloc
{
[self.longInstallationTimer invalidate];
[self.statusController close];
}
@ -152,40 +155,40 @@ static const NSTimeInterval SUParentQuitCheckInterval = .25;
[statusCtl beginActionWithTitle:SULocalizedString(@"Installing update...", @"")
maxProgressValue: 0 statusText: @""];
[statusCtl showWindow:self];
[self.statusController close]; // If there's an existing status controller, close it before we release our strong reference to it.
self.statusController = statusCtl; // Keep a strong reference to the status controller, or else it might get prematurely deallocated.
}
[SUInstaller installFromUpdateFolder:self.folderpath
overHost:self.host
installationPath:self.installationPath
delegate:self
versionComparator:[SUStandardVersionComparator defaultComparator]];
}
- (void)installerFinishedForHost:(SUHost *)__unused aHost
{
[self relaunch];
}
- (void)installerForHost:(SUHost *)__unused host failedWithError:(NSError *)error __attribute__((noreturn))
{
if (self.shouldShowUI)
versionComparator:[SUStandardVersionComparator defaultComparator]
completionHandler:^(NSError *error) {
if (error) {
if (self.shouldShowUI) {
NSRunAlertPanel(@"", @"%@", @"OK", @"", @"", [error localizedDescription]);
}
exit(EXIT_FAILURE);
} else {
[self relaunch];
}
}];
}
@end
int main(int __unused argc, const char __unused *argv[])
{
@autoreleasepool {
@autoreleasepool
{
NSArray *args = [[NSProcessInfo processInfo] arguments];
if (args.count < 5 || args.count > 7) {
return EXIT_FAILURE;
}
BOOL shouldShowUI = (args.count > 6) ? [args[6] boolValue] : YES;
if (shouldShowUI)
{
if (shouldShowUI) {
[[NSApplication sharedApplication] activateIgnoringOtherApps:YES];
}
@ -198,9 +201,9 @@ int main(int __unused argc, const char __unused *argv[])
shouldShowUI:shouldShowUI
selfPath:[[NSBundle mainBundle] bundlePath]];
[termListen class];
[[NSApplication sharedApplication] run];
// Ensure termListen is not deallocated by ARC before caling -[NSApplication run]
[termListen class];
}
return EXIT_SUCCESS;

View File

@ -9,24 +9,18 @@
#ifndef SUAPPCAST_H
#define SUAPPCAST_H
#import <Foundation/Foundation.h>
#import "SUExport.h"
@protocol SUAppcastDelegate;
@class SUAppcastItem;
SU_EXPORT @interface SUAppcast : NSObject <NSURLDownloadDelegate>
SU_EXPORT @interface SUAppcast : NSObject<NSURLDownloadDelegate>
@property (weak) id<SUAppcastDelegate> delegate;
@property (copy) NSString *userAgentString;
@property (copy) NSDictionary *httpHeaders;
- (void)fetchAppcastFromURL:(NSURL *)url;
- (void)fetchAppcastFromURL:(NSURL *)url completionBlock:(void (^)(NSError *))err;
@property (readonly, copy) NSArray *items;
@end
@protocol SUAppcastDelegate <NSObject>
- (void)appcastDidFinishLoading:(SUAppcast *)appcast;
- (void)appcast:(SUAppcast *)appcast failedToLoadWithError:(NSError *)error;
@end
#endif

View File

@ -33,6 +33,7 @@
@end
@interface SUAppcast () <NSURLDownloadDelegate>
@property (strong) void (^completionBlock)(NSError *);
@property (copy) NSString *downloadFilename;
@property (strong) NSURLDownload *download;
@property (copy) NSArray *items;
@ -43,18 +44,28 @@
@implementation SUAppcast
@synthesize downloadFilename;
@synthesize delegate;
@synthesize completionBlock;
@synthesize userAgentString;
@synthesize httpHeaders;
@synthesize download;
@synthesize items;
- (void)fetchAppcastFromURL:(NSURL *)url
- (void)fetchAppcastFromURL:(NSURL *)url completionBlock:(void (^)(NSError *))block
{
self.completionBlock = block;
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:30.0];
if (self.userAgentString) {
[request setValue:self.userAgentString forHTTPHeaderField:@"User-Agent"];
}
if (self.httpHeaders) {
for (NSString *key in self.httpHeaders) {
id value = [self.httpHeaders objectForKey:key];
[request setValue:value forHTTPHeaderField:key];
}
}
[request setValue:@"application/rss+xml,*/*;q=0.1" forHTTPHeaderField:@"Accept"];
self.download = [[NSURLDownload alloc] initWithRequest:request delegate:self];
@ -202,31 +213,23 @@
}
}
if ([appcastItems count])
{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wselector"
// @selector(date) is from SUAppcastItem
NSSortDescriptor *sort = [[NSSortDescriptor alloc] initWithKey:NSStringFromSelector(@selector(date)) ascending:NO];
#pragma clang diagnostic pop
if ([appcastItems count]) {
NSSortDescriptor *sort = [[NSSortDescriptor alloc] initWithKey:@"date" ascending:NO];
[appcastItems sortUsingDescriptors:@[sort]];
self.items = appcastItems;
}
if (failed)
{
if (failed) {
[self reportError:[NSError errorWithDomain:SUSparkleErrorDomain code:SUAppcastParseError userInfo:@{ NSLocalizedDescriptionKey: SULocalizedString(@"An error occurred while parsing the update feed.", nil) }]];
}
else if ([self.delegate respondsToSelector:@selector(appcastDidFinishLoading:)])
{
[self.delegate appcastDidFinishLoading:self];
} else {
self.completionBlock(nil);
self.completionBlock = nil;
}
}
- (void)download:(NSURLDownload *)__unused aDownload didFailWithError:(NSError *)error
{
if (self.downloadFilename)
{
if (self.downloadFilename) {
[[NSFileManager defaultManager] removeItemAtPath:self.downloadFilename error:nil];
}
self.downloadFilename = nil;
@ -241,10 +244,15 @@
- (void)reportError:(NSError *)error
{
if ([self.delegate respondsToSelector:@selector(appcast:failedToLoadWithError:)])
{
[self.delegate appcast:self failedToLoadWithError:[NSError errorWithDomain:SUSparkleErrorDomain code:SUAppcastError userInfo:@{NSLocalizedDescriptionKey: SULocalizedString(@"An error occurred in retrieving update information. Please try again later.", nil), NSLocalizedFailureReasonErrorKey: [error localizedDescription]}]];
}
NSURL *failingUrl = error.userInfo[NSURLErrorFailingURLErrorKey];
self.completionBlock([NSError errorWithDomain:SUSparkleErrorDomain code:SUAppcastError userInfo:@{
NSLocalizedDescriptionKey: SULocalizedString(@"An error occurred in retrieving update information. Please try again later.", nil),
NSLocalizedFailureReasonErrorKey: [error localizedDescription],
NSUnderlyingErrorKey: error,
NSURLErrorFailingURLErrorKey: failingUrl ? failingUrl : [NSNull null],
}]);
self.completionBlock = nil;
}
- (NSXMLNode *)bestNodeInNodes:(NSArray *)nodes

View File

@ -11,8 +11,6 @@
#import "SUWindowController.h"
@protocol SUAutomaticUpdateAlertDelegate;
typedef NS_ENUM(NSInteger, SUAutomaticInstallationChoice) {
SUInstallNowChoice,
SUInstallLaterChoice,
@ -22,15 +20,11 @@ typedef NS_ENUM(NSInteger, SUAutomaticInstallationChoice) {
@class SUAppcastItem, SUHost;
@interface SUAutomaticUpdateAlert : SUWindowController
- (instancetype)initWithAppcastItem:(SUAppcastItem *)item host:(SUHost *)hostBundle delegate:(id<SUAutomaticUpdateAlertDelegate>)delegate;
- (instancetype)initWithAppcastItem:(SUAppcastItem *)item host:(SUHost *)hostBundle completionBlock:(void (^)(SUAutomaticInstallationChoice))c;
- (IBAction)installNow:sender;
- (IBAction)installLater:sender;
- (IBAction)doNotInstall:sender;
@end
@protocol SUAutomaticUpdateAlertDelegate <NSObject>
- (void)automaticUpdateAlert:(SUAutomaticUpdateAlert *)aua finishedWithChoice:(SUAutomaticInstallationChoice)choice;
@end
#endif

View File

@ -11,23 +11,22 @@
#import "SUHost.h"
@interface SUAutomaticUpdateAlert ()
@property (strong) void(^completionBlock)(SUAutomaticInstallationChoice);
@property (strong) SUAppcastItem *updateItem;
@property (weak) id<SUAutomaticUpdateAlertDelegate> delegate;
@property (strong) SUHost *host;
@end
@implementation SUAutomaticUpdateAlert
@synthesize delegate;
@synthesize host;
@synthesize updateItem;
@synthesize completionBlock;
- (instancetype)initWithAppcastItem:(SUAppcastItem *)item host:(SUHost *)aHost delegate:(id<SUAutomaticUpdateAlertDelegate>)del
- (instancetype)initWithAppcastItem:(SUAppcastItem *)item host:(SUHost *)aHost completionBlock:(void (^)(SUAutomaticInstallationChoice))block
{
self = [super initWithHost:aHost windowNibName:@"SUAutomaticUpdateAlert"];
if (self)
{
self = [super initWithWindowNibName:@"SUAutomaticUpdateAlert"];
if (self) {
self.updateItem = item;
self.delegate = del;
self.completionBlock = block;
self.host = aHost;
[self setShouldCascadeWindows:NO];
[[self window] center];
@ -40,19 +39,22 @@
- (IBAction)installNow:(id)__unused sender
{
[self close];
[self.delegate automaticUpdateAlert:self finishedWithChoice:SUInstallNowChoice];
self.completionBlock(SUInstallNowChoice);
self.completionBlock = nil;
}
- (IBAction)installLater:(id)__unused sender
{
[self close];
[self.delegate automaticUpdateAlert:self finishedWithChoice:SUInstallLaterChoice];
self.completionBlock(SUInstallLaterChoice);
self.completionBlock = nil;
}
- (IBAction)doNotInstall:(id)__unused sender
{
[self close];
[self.delegate automaticUpdateAlert:self finishedWithChoice:SUDoNotInstallChoice];
self.completionBlock(SUDoNotInstallChoice);
self.completionBlock = nil;
}
- (NSImage *)applicationIcon

View File

@ -13,7 +13,7 @@
#import "SUBasicUpdateDriver.h"
#import "SUAutomaticUpdateAlert.h"
@interface SUAutomaticUpdateDriver : SUBasicUpdateDriver <SUUnarchiverDelegate, SUAutomaticUpdateAlertDelegate>
@interface SUAutomaticUpdateDriver : SUBasicUpdateDriver <SUUnarchiverDelegate>
@end

View File

@ -42,13 +42,14 @@ static const NSTimeInterval SUAutomaticUpdatePromptImpatienceTimer = 60 * 60 * 2
- (void)showUpdateAlert
{
self.interruptible = NO;
self.alert = [[SUAutomaticUpdateAlert alloc] initWithAppcastItem:self.updateItem host:self.host delegate:self];
self.alert = [[SUAutomaticUpdateAlert alloc] initWithAppcastItem:self.updateItem host:self.host completionBlock:^(SUAutomaticInstallationChoice choice) {
[self automaticUpdateAlertFinishedWithChoice:choice];
}];
// If the app is a menubar app or the like, we need to focus it first and alter the
// update prompt to behave like a normal window. Otherwise if the window were hidden
// there may be no way for the application to be activated to make it visible again.
if ([self.host isBackgroundApplication])
{
if ([self.host isBackgroundApplication]) {
[[self.alert window] setHidesOnDeactivate:NO];
[NSApp activateIgnoringOtherApps:YES];
}
@ -138,7 +139,7 @@ static const NSTimeInterval SUAutomaticUpdatePromptImpatienceTimer = 60 * 60 * 2
[[NSNotificationCenter defaultCenter] removeObserver:self name:NSApplicationDidBecomeActiveNotification object:NSApp];
}
- (void)automaticUpdateAlert:(SUAutomaticUpdateAlert *)__unused aua finishedWithChoice:(SUAutomaticInstallationChoice)choice
- (void)automaticUpdateAlertFinishedWithChoice:(SUAutomaticInstallationChoice)choice
{
switch (choice)
{
@ -161,8 +162,6 @@ static const NSTimeInterval SUAutomaticUpdatePromptImpatienceTimer = 60 * 60 * 2
}
}
- (BOOL)shouldInstallSynchronously { return self.postponingInstallation; }
- (void)installWithToolAndRelaunch:(BOOL)relaunch displayingUserInterface:(BOOL)showUI
{
if (relaunch) {
@ -180,10 +179,18 @@ static const NSTimeInterval SUAutomaticUpdatePromptImpatienceTimer = 60 * 60 * 2
- (void)abortUpdateWithError:(NSError *)error
{
if (self.showErrors)
if (self.showErrors) {
[super abortUpdateWithError:error];
else
} else {
// Call delegate separately here because otherwise it won't know we stopped.
// Normally this gets called by the superclass
id<SUUpdaterDelegate> updaterDelegate = [self.updater delegate];
if ([updaterDelegate respondsToSelector:@selector(updater:didAbortWithError:)]) {
[updaterDelegate updater:self.updater didAbortWithError:error];
}
[self abortUpdate];
}
}
@end

View File

@ -15,7 +15,7 @@
#import "SUAppcast.h"
@class SUAppcastItem, SUHost;
@interface SUBasicUpdateDriver : SUUpdateDriver <NSURLDownloadDelegate, SUUnarchiverDelegate, SUAppcastDelegate>
@interface SUBasicUpdateDriver : SUUpdateDriver <NSURLDownloadDelegate, SUUnarchiverDelegate>
@property (strong, readonly) SUAppcastItem *updateItem;
@property (strong, readonly) NSURLDownload *download;
@ -27,6 +27,7 @@
- (BOOL)hostSupportsItem:(SUAppcastItem *)ui;
- (BOOL)itemContainsSkippedVersion:(SUAppcastItem *)ui;
- (BOOL)itemContainsValidUpdate:(SUAppcastItem *)ui;
- (void)appcastDidFinishLoading:(SUAppcast *)ac;
- (void)didFindValidUpdate;
- (void)didNotFindUpdate;

View File

@ -54,9 +54,15 @@
SUAppcast *appcast = [[SUAppcast alloc] init];
[appcast setDelegate:self];
[appcast setUserAgentString:[self.updater userAgentString]];
[appcast fetchAppcastFromURL:URL];
[appcast setHttpHeaders:[self.updater httpHeaders]];
[appcast fetchAppcastFromURL:URL completionBlock:^(NSError *error) {
if (error) {
[self abortUpdateWithError:error];
} else {
[self appcastDidFinishLoading:appcast];
}
}];
}
- (id<SUVersionComparison>)versionComparator
@ -136,14 +142,12 @@
item = [updateEnumerator nextObject];
} while (item && ![self hostSupportsItem:item]);
if (binaryDeltaSupported()) {
SUAppcastItem *deltaUpdateItem = [item deltaUpdates][[self.host version]];
if (deltaUpdateItem && [self hostSupportsItem:deltaUpdateItem]) {
self.nonDeltaUpdateItem = item;
item = deltaUpdateItem;
}
}
}
if ([self itemContainsValidUpdate:item]) {
self.updateItem = item;
@ -154,11 +158,6 @@
}
}
- (void)appcast:(SUAppcast *)__unused ac failedToLoadWithError:(NSError *)error
{
[self abortUpdateWithError:error];
}
- (void)didFindValidUpdate
{
assert(self.updateItem);
@ -200,11 +199,11 @@
NSString *downloadFileName = [NSString stringWithFormat:@"%@ %@", [self.host name], [self.updateItem versionString]];
self.tempDir = [[self.host appSupportPath] stringByAppendingPathComponent:downloadFileName];
self.tempDir = [self.host.appCachePath stringByAppendingPathComponent:downloadFileName];
int cnt = 1;
while ([[NSFileManager defaultManager] fileExistsAtPath:self.tempDir] && cnt <= 999)
{
self.tempDir = [[self.host appSupportPath] stringByAppendingPathComponent:[NSString stringWithFormat:@"%@ %d", downloadFileName, cnt++]];
self.tempDir = [self.host.appCachePath stringByAppendingPathComponent:[NSString stringWithFormat:@"%@ %d", downloadFileName, cnt++]];
}
// Create the temporary directory if necessary.
@ -227,7 +226,7 @@
if (newBundlePath) {
if ([SUCodeSigningVerifier hostApplicationIsCodeSigned]) {
NSError *error = nil;
if ([SUCodeSigningVerifier codeSignatureIsValidAtPath:newBundlePath error:&error]) {
if ([SUCodeSigningVerifier codeSignatureMatchesHostAndIsValidAtPath:newBundlePath error:&error]) {
return YES;
} else {
SULog(@"Code signature check on update failed: %@. Sparkle will use DSA signature instead.", error);
@ -254,7 +253,16 @@
- (void)download:(NSURLDownload *)__unused download didFailWithError:(NSError *)error
{
[self abortUpdateWithError:[NSError errorWithDomain:SUSparkleErrorDomain code:SURelaunchError userInfo:@{NSLocalizedDescriptionKey: SULocalizedString(@"An error occurred while downloading the update. Please try again later.", nil), NSLocalizedFailureReasonErrorKey: [error localizedDescription]}]];
NSURL *failingUrl = error.userInfo[NSURLErrorFailingURLErrorKey];
if (!failingUrl) {
failingUrl = [self.updateItem fileURL];
}
[self abortUpdateWithError:[NSError errorWithDomain:SUSparkleErrorDomain code:SURelaunchError userInfo:@{
NSLocalizedDescriptionKey: SULocalizedString(@"An error occurred while downloading the update. Please try again later.", nil),
NSUnderlyingErrorKey: error,
NSURLErrorFailingURLErrorKey: failingUrl ? failingUrl : [NSNull null],
}]];
}
- (BOOL)download:(NSURLDownload *)__unused download shouldDecodeSourceDataOfMIMEType:(NSString *)encodingType
@ -266,14 +274,13 @@
- (void)extractUpdate
{
SUUnarchiver *unarchiver = [SUUnarchiver unarchiverForPath:self.downloadPath updatingHost:self.host];
if (!unarchiver)
{
SUUnarchiver *unarchiver = [SUUnarchiver unarchiverForPath:self.downloadPath updatingHostBundlePath:[[self.host bundle] bundlePath]];
if (!unarchiver) {
SULog(@"Error: No valid unarchiver for %@!", self.downloadPath);
[self unarchiverDidFail:nil];
return;
}
[unarchiver setDelegate:self];
unarchiver.delegate = self;
[unarchiver start];
}
@ -303,8 +310,6 @@
[self abortUpdateWithError:[NSError errorWithDomain:SUSparkleErrorDomain code:SUUnarchivingError userInfo:@{ NSLocalizedDescriptionKey: SULocalizedString(@"An error occurred while extracting the archive. Please try again later.", nil) }]];
}
- (BOOL)shouldInstallSynchronously { return NO; }
- (void)installWithToolAndRelaunch:(BOOL)relaunch
{
// Perhaps a poor assumption but: if we're not relaunching, we assume we shouldn't be showing any UI either. Because non-relaunching installations are kicked off without any user interaction, we shouldn't be interrupting them.
@ -356,27 +361,32 @@
// Copy the relauncher into a temporary directory so we can get to it after the new version's installed.
// Only the paranoid survive: if there's already a stray copy of relaunch there, we would have problems.
NSString *const relaunchPathToCopy = [sparkleBundle pathForResource:[[sparkleBundle infoDictionary] objectForKey:SURelaunchToolNameKey] ofType:@"app"];
if (relaunchPathToCopy != nil)
{
NSString *targetPath = [[self.host appSupportPath] stringByAppendingPathComponent:[relaunchPathToCopy lastPathComponent]];
if (relaunchPathToCopy != nil) {
NSString *targetPath = [self.host.appCachePath stringByAppendingPathComponent:[relaunchPathToCopy lastPathComponent]];
// Only the paranoid survive: if there's already a stray copy of relaunch there, we would have problems.
NSError *error = nil;
[[NSFileManager defaultManager] createDirectoryAtPath:[targetPath stringByDeletingLastPathComponent] withIntermediateDirectories:YES attributes:@{} error:&error];
if ([SUPlainInstaller copyPathWithAuthentication:relaunchPathToCopy overPath:targetPath temporaryName:nil error:&error])
if ([SUPlainInstaller copyPathWithAuthentication:relaunchPathToCopy overPath:targetPath temporaryName:nil error:&error]) {
self.relaunchPath = targetPath;
else
[self abortUpdateWithError:[NSError errorWithDomain:SUSparkleErrorDomain code:SURelaunchError userInfo:@{NSLocalizedDescriptionKey: SULocalizedString(@"An error occurred while extracting the archive. Please try again later.", nil), NSLocalizedFailureReasonErrorKey: [NSString stringWithFormat:@"Couldn't copy relauncher (%@) to temporary path (%@)! %@", relaunchPathToCopy, targetPath, (error ? [error localizedDescription] : @"")]}]];
} else {
[self abortUpdateWithError:[NSError errorWithDomain:SUSparkleErrorDomain code:SURelaunchError userInfo:@{
NSLocalizedDescriptionKey: SULocalizedString(@"An error occurred while extracting the archive. Please try again later.", nil),
NSLocalizedFailureReasonErrorKey: [NSString stringWithFormat:@"Couldn't copy relauncher (%@) to temporary path (%@)! %@", relaunchPathToCopy, targetPath, (error ? [error localizedDescription] : @"")]
}]];
}
}
[[NSNotificationCenter defaultCenter] postNotificationName:SUUpdaterWillRestartNotification object:self];
if ([updaterDelegate respondsToSelector:@selector(updaterWillRelaunchApplication:)])
[updaterDelegate updaterWillRelaunchApplication:self.updater];
if(!self.relaunchPath || ![[NSFileManager defaultManager] fileExistsAtPath:self.relaunchPath])
{
if (!self.relaunchPath || ![[NSFileManager defaultManager] fileExistsAtPath:self.relaunchPath]) {
// Note that we explicitly use the host app's name here, since updating plugin for Mail relaunches Mail, not just the plugin.
[self abortUpdateWithError:[NSError errorWithDomain:SUSparkleErrorDomain code:SURelaunchError userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat:SULocalizedString(@"An error occurred while relaunching %1$@, but the new version will be available next time you run %1$@.", nil), [self.host name]], NSLocalizedFailureReasonErrorKey: [NSString stringWithFormat:@"Couldn't find the relauncher (expected to find it at %@)", self.relaunchPath]}]];
[self abortUpdateWithError:[NSError errorWithDomain:SUSparkleErrorDomain code:SURelaunchError userInfo:@{
NSLocalizedDescriptionKey: [NSString stringWithFormat:SULocalizedString(@"An error occurred while relaunching %1$@, but the new version will be available next time you run %1$@.", nil), [self.host name]],
NSLocalizedFailureReasonErrorKey: [NSString stringWithFormat:@"Couldn't find the relauncher (expected to find it at %@)", self.relaunchPath]
}]];
// We intentionally don't abandon the update here so that the host won't initiate another.
return;
}
@ -414,10 +424,16 @@
- (void)installerForHost:(SUHost *)aHost failedWithError:(NSError *)error
{
if (aHost != self.host) { return; }
if (aHost != self.host) {
return;
}
NSError *dontThrow = nil;
[[NSFileManager defaultManager] removeItemAtPath:self.relaunchPath error:&dontThrow]; // Clean up the copied relauncher
[self abortUpdateWithError:[NSError errorWithDomain:SUSparkleErrorDomain code:SUInstallationError userInfo:@{NSLocalizedDescriptionKey: SULocalizedString(@"An error occurred while installing the update. Please try again later.", nil), NSLocalizedFailureReasonErrorKey: [error localizedDescription]}]];
[self abortUpdateWithError:[NSError errorWithDomain:SUSparkleErrorDomain code:SUInstallationError userInfo:@{
NSLocalizedDescriptionKey: SULocalizedString(@"An error occurred while installing the update. Please try again later.", nil),
NSLocalizedFailureReasonErrorKey: [error localizedDescription],
NSUnderlyingErrorKey: error,
}]];
}
- (void)abortUpdate
@ -431,11 +447,18 @@
- (void)abortUpdateWithError:(NSError *)error
{
if ([error code] != SUNoUpdateError) { // Let's not bother logging this.
SULog(@"Error: %@ %@", [error localizedDescription], [error localizedFailureReason]);
SULog(@"Error: %@ %@ (URL %@)", error.localizedDescription, error.localizedFailureReason, error.userInfo[NSURLErrorFailingURLErrorKey]);
}
if (self.download) {
[self.download cancel];
}
// Notify host app that update has aborted
id<SUUpdaterDelegate> updaterDelegate = [self.updater delegate];
if ([updaterDelegate respondsToSelector:@selector(updater:didAbortWithError:)]) {
[updaterDelegate updater:self.updater didAbortWithError:error];
}
[self abortUpdate];
}

View File

@ -10,6 +10,6 @@
#define SUBINARYDELTAAPPLY_H
@class NSString;
int applyBinaryDelta(NSString *source, NSString *destination, NSString *patchFile);
int applyBinaryDelta(NSString *source, NSString *destination, NSString *patchFile, BOOL verbose);
#endif

View File

@ -15,57 +15,125 @@
#include <stdlib.h>
#include <xar/xar.h>
static void applyBinaryDeltaToFile(xar_t x, xar_file_t file, NSString *sourceFilePath, NSString *destinationFilePath)
static BOOL applyBinaryDeltaToFile(xar_t x, xar_file_t file, NSString *sourceFilePath, NSString *destinationFilePath)
{
NSString *patchFile = temporaryFilename(@"apply-binary-delta");
xar_extract_tofile(x, file, [patchFile fileSystemRepresentation]);
const char *argv[] = {"/usr/bin/bspatch", [sourceFilePath fileSystemRepresentation], [destinationFilePath fileSystemRepresentation], [patchFile fileSystemRepresentation]};
bspatch(4, (char **)argv);
BOOL success = (bspatch(4, (char **)argv) == 0);
unlink([patchFile fileSystemRepresentation]);
return success;
}
int applyBinaryDelta(NSString *source, NSString *destination, NSString *patchFile)
int applyBinaryDelta(NSString *source, NSString *destination, NSString *patchFile, BOOL verbose)
{
xar_t x = xar_open([patchFile UTF8String], READ);
xar_t x = xar_open([patchFile fileSystemRepresentation], READ);
if (!x) {
fprintf(stderr, "Unable to open %s. Giving up.\n", [patchFile UTF8String]);
fprintf(stderr, "Unable to open %s. Giving up.\n", [patchFile fileSystemRepresentation]);
return 1;
}
NSString *expectedBeforeHash = nil;
NSString *expectedAfterHash = nil;
SUBinaryDeltaMajorVersion majorDiffVersion = FIRST_DELTA_DIFF_MAJOR_VERSION;
SUBinaryDeltaMinorVersion minorDiffVersion = FIRST_DELTA_DIFF_MINOR_VERSION;
NSString *expectedBeforeHashv1 = nil;
NSString *expectedAfterHashv1 = nil;
NSString *expectedNewBeforeHash = nil;
NSString *expectedNewAfterHash = nil;
xar_subdoc_t subdoc;
for (subdoc = xar_subdoc_first(x); subdoc; subdoc = xar_subdoc_next(subdoc)) {
if (!strcmp(xar_subdoc_name(subdoc), "binary-delta-attributes")) {
if (!strcmp(xar_subdoc_name(subdoc), BINARY_DELTA_ATTRIBUTES_KEY)) {
const char *value = 0;
xar_subdoc_prop_get(subdoc, "before-sha1", &value);
if (value)
expectedBeforeHash = @(value);
xar_subdoc_prop_get(subdoc, "after-sha1", &value);
// available in version 2.0 or later
xar_subdoc_prop_get(subdoc, MAJOR_DIFF_VERSION_KEY, &value);
if (value)
expectedAfterHash = @(value);
majorDiffVersion = (SUBinaryDeltaMajorVersion)[@(value) intValue];
// available in version 2.0 or later
xar_subdoc_prop_get(subdoc, MINOR_DIFF_VERSION_KEY, &value);
if (value)
minorDiffVersion = (SUBinaryDeltaMinorVersion)[@(value) intValue];
// available in version 2.0 or later
xar_subdoc_prop_get(subdoc, BEFORE_TREE_SHA1_KEY, &value);
if (value)
expectedNewBeforeHash = @(value);
// available in version 2.0 or later
xar_subdoc_prop_get(subdoc, AFTER_TREE_SHA1_KEY, &value);
if (value)
expectedNewAfterHash = @(value);
// only available in version 1.0
xar_subdoc_prop_get(subdoc, BEFORE_TREE_SHA1_OLD_KEY, &value);
if (value)
expectedBeforeHashv1 = @(value);
// only available in version 1.0
xar_subdoc_prop_get(subdoc, AFTER_TREE_SHA1_OLD_KEY, &value);
if (value)
expectedAfterHashv1 = @(value);
}
}
if (majorDiffVersion < FIRST_DELTA_DIFF_MAJOR_VERSION) {
fprintf(stderr, "Unable to identify diff-version %u in delta. Giving up.\n", majorDiffVersion);
return 1;
}
if (majorDiffVersion > LATEST_DELTA_DIFF_MAJOR_VERSION) {
fprintf(stderr, "A later version is needed to apply this patch (on major version %u, but patch requests version %u).\n", LATEST_DELTA_DIFF_MAJOR_VERSION, majorDiffVersion);
return 1;
}
BOOL usesNewTreeHash = MAJOR_VERSION_IS_AT_LEAST(majorDiffVersion, SUBeigeMajorVersion);
NSString *expectedBeforeHash = usesNewTreeHash ? expectedNewBeforeHash : expectedBeforeHashv1;
NSString *expectedAfterHash = usesNewTreeHash ? expectedNewAfterHash : expectedAfterHashv1;
if (!expectedBeforeHash || !expectedAfterHash) {
fprintf(stderr, "Unable to find before-sha1 or after-sha1 metadata in delta. Giving up.\n");
return 1;
}
fprintf(stderr, "Verifying source... ");
NSString *beforeHash = hashOfTree(source);
if (verbose) {
fprintf(stderr, "Applying version %u.%u patch...\n", majorDiffVersion, minorDiffVersion);
fprintf(stderr, "Verifying source...");
}
if (![beforeHash isEqualToString:expectedBeforeHash]) {
fprintf(stderr, "Source doesn't have expected hash (%s != %s). Giving up.\n", [expectedBeforeHash UTF8String], [beforeHash UTF8String]);
NSString *beforeHash = hashOfTreeWithVersion(source, majorDiffVersion);
if (!beforeHash) {
fprintf(stderr, "\nUnable to calculate hash of tree %s\n", [source fileSystemRepresentation]);
return 1;
}
fprintf(stderr, "\nCopying files... ");
removeTree(destination);
copyTree(source, destination);
if (![beforeHash isEqualToString:expectedBeforeHash]) {
fprintf(stderr, "\nSource doesn't have expected hash (%s != %s). Giving up.\n", [expectedBeforeHash UTF8String], [beforeHash UTF8String]);
return 1;
}
fprintf(stderr, "\nPatching... ");
if (verbose) {
fprintf(stderr, "\nCopying files...");
}
if (!removeTree(destination)) {
fprintf(stderr, "\nFailed to remove %s\n", [destination fileSystemRepresentation]);
return 1;
}
if (!copyTree(source, destination)) {
fprintf(stderr, "\nFailed to copy %s to %s\n", [source fileSystemRepresentation], [destination fileSystemRepresentation]);
return 1;
}
BOOL hasExtractKeyAvailable = MAJOR_VERSION_IS_AT_LEAST(majorDiffVersion, SUBeigeMajorVersion);
if (verbose) {
fprintf(stderr, "\nPatching...");
}
NSFileManager *fileManager = [[NSFileManager alloc] init];
xar_file_t file;
xar_iter_t iter = xar_iter_new();
for (file = xar_file_first(x, iter); file; file = xar_file_next(iter)) {
@ -73,29 +141,86 @@ int applyBinaryDelta(NSString *source, NSString *destination, NSString *patchFil
NSString *sourceFilePath = [source stringByAppendingPathComponent:path];
NSString *destinationFilePath = [destination stringByAppendingPathComponent:path];
// Don't use -[NSFileManager fileExistsAtPath:] because it will follow symbolic links
BOOL fileExisted = verbose && [fileManager attributesOfItemAtPath:destinationFilePath error:nil];
BOOL removedFile = NO;
const char *value;
if (!xar_prop_get(file, "delete", &value) || !xar_prop_get(file, "delete-then-extract", &value)) {
removeTree(destinationFilePath);
if (!xar_prop_get(file, "delete", &value))
if (!xar_prop_get(file, DELETE_KEY, &value) ||
(!hasExtractKeyAvailable && !xar_prop_get(file, DELETE_THEN_EXTRACT_OLD_KEY, &value))) {
if (!removeTree(destinationFilePath)) {
fprintf(stderr, "\n%s or %s: failed to remove %s\n", DELETE_KEY, DELETE_THEN_EXTRACT_OLD_KEY, [destination fileSystemRepresentation]);
return 1;
}
if (!hasExtractKeyAvailable && !xar_prop_get(file, DELETE_KEY, &value)) {
if (verbose) {
fprintf(stderr, "\n%s %s", VERBOSE_DELETED, [path fileSystemRepresentation]);
}
continue;
}
if (!xar_prop_get(file, "binary-delta", &value))
applyBinaryDeltaToFile(x, file, sourceFilePath, destinationFilePath);
else
xar_extract_tofile(x, file, [destinationFilePath fileSystemRepresentation]);
removedFile = YES;
}
if (!xar_prop_get(file, BINARY_DELTA_KEY, &value)) {
if (!applyBinaryDeltaToFile(x, file, sourceFilePath, destinationFilePath)) {
fprintf(stderr, "\nUnable to patch %s to destination %s\n", [sourceFilePath fileSystemRepresentation], [destinationFilePath fileSystemRepresentation]);
return 1;
}
if (verbose) {
fprintf(stderr, "\n🔨 %s %s", VERBOSE_PATCHED, [path fileSystemRepresentation]);
}
} else if ((hasExtractKeyAvailable && !xar_prop_get(file, EXTRACT_KEY, &value)) ||
(!hasExtractKeyAvailable && xar_prop_get(file, MODIFY_PERMISSIONS_KEY, &value))) { // extract and permission modifications don't coexist
if (xar_extract_tofile(x, file, [destinationFilePath fileSystemRepresentation]) != 0) {
fprintf(stderr, "\nUnable to extract file to %s\n", [destinationFilePath fileSystemRepresentation]);
return 1;
}
if (verbose) {
if (fileExisted) {
fprintf(stderr, "\n✏️ %s %s", VERBOSE_UPDATED, [path fileSystemRepresentation]);
} else {
fprintf(stderr, "\n%s %s", VERBOSE_ADDED, [path fileSystemRepresentation]);
}
}
} else if (verbose && removedFile) {
fprintf(stderr, "\n%s %s", VERBOSE_DELETED, [path fileSystemRepresentation]);
}
if (!xar_prop_get(file, MODIFY_PERMISSIONS_KEY, &value)) {
mode_t mode = (mode_t)[[NSString stringWithUTF8String:value] intValue];
if (!modifyPermissions(destinationFilePath, mode)) {
fprintf(stderr, "\nUnable to modify permissions (%s) on file %s\n", value, [destinationFilePath fileSystemRepresentation]);
return 1;
}
if (verbose) {
fprintf(stderr, "\n👮 %s %s (0%o)", VERBOSE_MODIFIED, [path fileSystemRepresentation], mode);
}
}
}
xar_close(x);
fprintf(stderr, "\nVerifying destination... ");
NSString *afterHash = hashOfTree(destination);
if (verbose) {
fprintf(stderr, "\nVerifying destination...");
}
NSString *afterHash = hashOfTreeWithVersion(destination, majorDiffVersion);
if (!afterHash) {
fprintf(stderr, "\nUnable to calculate hash of tree %s\n", [destination fileSystemRepresentation]);
return 1;
}
if (![afterHash isEqualToString:expectedAfterHash]) {
fprintf(stderr, "Destination doesn't have expected hash (%s != %s). Giving up.\n", [expectedAfterHash UTF8String], [afterHash UTF8String]);
fprintf(stderr, "\nDestination doesn't have expected hash (%s != %s). Giving up.\n", [expectedAfterHash UTF8String], [afterHash UTF8String]);
removeTree(destination);
return 1;
}
if (verbose) {
fprintf(stderr, "\nDone!\n");
}
return 0;
}

View File

@ -11,16 +11,71 @@
#include <fts.h>
#define PERMISSION_FLAGS (S_IRWXU | S_IRWXG | S_IRWXO | S_ISUID | S_ISGID | S_ISVTX)
#define IS_VALID_PERMISSIONS(mode) \
(((mode & PERMISSION_FLAGS) == 0755) || ((mode & PERMISSION_FLAGS) == 0644))
#define BINARY_DELTA_ATTRIBUTES_KEY "binary-delta-attributes"
#define MAJOR_DIFF_VERSION_KEY "major-version"
#define MINOR_DIFF_VERSION_KEY "minor-version"
#define BEFORE_TREE_SHA1_KEY "before-tree-sha1"
#define AFTER_TREE_SHA1_KEY "after-tree-sha1"
#define DELETE_KEY "delete"
#define EXTRACT_KEY "extract"
#define BINARY_DELTA_KEY "binary-delta"
#define MODIFY_PERMISSIONS_KEY "mod-permissions"
// Properties no longer used in new patches
#define DELETE_THEN_EXTRACT_OLD_KEY "delete-then-extract"
#define BEFORE_TREE_SHA1_OLD_KEY "before-sha1"
#define AFTER_TREE_SHA1_OLD_KEY "after-sha1"
#define VERBOSE_DELETED "Deleted" // file is deleted from the file system when applying a patch
#define VERBOSE_REMOVED "Removed" // file is set to be removed when creating a patch
#define VERBOSE_ADDED "Added" // file is added to the patch or file system
#define VERBOSE_DIFFED "Diffed" // file is diffed when creating a patch
#define VERBOSE_PATCHED "Patched" // file is patched when applying a patch
#define VERBOSE_UPDATED "Updated" // file's contents are updated
#define VERBOSE_MODIFIED "Modified" // file's metadata is modified
#define MAJOR_VERSION_IS_AT_LEAST(actualMajor, expectedMajor) (actualMajor >= expectedMajor)
// Each major version will be assigned a name of a color
// Changes that break backwards compatibility will have different major versions
// Changes that affect creating but not applying patches will have different minor versions
typedef NS_ENUM(uint16_t, SUBinaryDeltaMajorVersion)
{
SUAzureMajorVersion = 1,
SUBeigeMajorVersion = 2
};
// Only keep track of the latest minor version for each major version
typedef NS_ENUM(uint16_t, SUBinaryDeltaMinorVersion)
{
SUAzureMinorVersion = 1,
SUBeigeMinorVersion = 0,
};
#define FIRST_DELTA_DIFF_MAJOR_VERSION SUAzureMajorVersion
#define FIRST_DELTA_DIFF_MINOR_VERSION 0
#define LATEST_DELTA_DIFF_MAJOR_VERSION SUBeigeMajorVersion
@class NSString;
@class NSData;
extern int binaryDeltaSupported(void);
extern int compareFiles(const FTSENT **a, const FTSENT **b);
extern NSData *hashOfFile(FTSENT *ent);
extern NSData *hashOfFileContents(FTSENT *ent);
extern NSString *hashOfTreeWithVersion(NSString *path, uint16_t majorVersion);
extern NSString *hashOfTree(NSString *path);
extern void removeTree(NSString *path);
extern void copyTree(NSString *source, NSString *dest);
extern BOOL removeTree(NSString *path);
extern BOOL copyTree(NSString *source, NSString *dest);
extern BOOL modifyPermissions(NSString *path, mode_t desiredPermissions);
extern NSString *pathRelativeToDirectory(NSString *directory, NSString *path);
NSString *temporaryFilename(NSString *base);
NSString *temporaryDirectory(NSString *base);
NSString *stringWithFileSystemRepresentation(const char*);
SUBinaryDeltaMinorVersion latestMinorVersionForMajorVersion(SUBinaryDeltaMajorVersion majorVersion);
#endif

View File

@ -17,13 +17,6 @@
#include <sys/stat.h>
#include <xar/xar.h>
int binaryDeltaSupported(void)
{
// OS X 10.4 didn't include libxar, so we link against it weakly.
// This checks whether libxar is available at runtime.
return xar_close != 0;
}
int compareFiles(const FTSENT **a, const FTSENT **b)
{
return strcoll((*a)->fts_name, (*b)->fts_name);
@ -42,6 +35,17 @@ NSString *stringWithFileSystemRepresentation(const char *input) {
return [[NSFileManager defaultManager] stringWithFileSystemRepresentation:input length:strlen(input)];
}
SUBinaryDeltaMinorVersion latestMinorVersionForMajorVersion(SUBinaryDeltaMajorVersion majorVersion)
{
switch (majorVersion) {
case SUAzureMajorVersion:
return SUAzureMinorVersion;
case SUBeigeMajorVersion:
return SUBeigeMinorVersion;
}
return 0;
}
NSString *temporaryFilename(NSString *base)
{
NSString *template = [NSTemporaryDirectory() stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.XXXXXXXXXX", base]];
@ -63,6 +67,22 @@ NSString *temporaryFilename(NSString *base)
return stringWithFileSystemRepresentation(buffer);
}
NSString *temporaryDirectory(NSString *base)
{
NSString *template = [NSTemporaryDirectory() stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.XXXXXXXXXX", base]];
NSMutableData *data = [NSMutableData data];
[data appendBytes:template.fileSystemRepresentation length:strlen(template.fileSystemRepresentation) + 1];
char *buffer = data.mutableBytes;
char *templateResult = mkdtemp(buffer);
if (templateResult == NULL) {
perror("mkdtemp");
return nil;
}
return stringWithFileSystemRepresentation(templateResult);
}
static void _hashOfBuffer(unsigned char *hash, const char* buffer, ssize_t bufferLength)
{
assert(bufferLength >= 0 && bufferLength <= UINT32_MAX);
@ -72,59 +92,57 @@ static void _hashOfBuffer(unsigned char *hash, const char* buffer, ssize_t buffe
CC_SHA1_Final(hash, &hashContext);
}
static void _hashOfFile(unsigned char* hash, FTSENT *ent)
static BOOL _hashOfFileContents(unsigned char* hash, FTSENT *ent)
{
if (ent->fts_info == FTS_SL) {
char linkDestination[MAXPATHLEN + 1];
ssize_t linkDestinationLength = readlink(ent->fts_path, linkDestination, MAXPATHLEN);
if (linkDestinationLength < 0) {
perror("readlink");
return;
return NO;
}
_hashOfBuffer(hash, linkDestination, linkDestinationLength);
return;
}
if (ent->fts_info == FTS_F) {
} else if (ent->fts_info == FTS_F) {
int fileDescriptor = open(ent->fts_path, O_RDONLY);
if (fileDescriptor == -1) {
perror("open");
return;
return NO;
}
ssize_t fileSize = ent->fts_statp->st_size;
if (fileSize == 0) {
_hashOfBuffer(hash, NULL, 0);
close(fileDescriptor);
return;
}
} else {
void *buffer = mmap(0, (size_t)fileSize, PROT_READ, MAP_FILE | MAP_PRIVATE, fileDescriptor, 0);
if (buffer == (void*)-1) {
close(fileDescriptor);
perror("mmap");
return;
return NO;
}
_hashOfBuffer(hash, buffer, fileSize);
munmap(buffer, (size_t)fileSize);
close(fileDescriptor);
return;
}
if (ent->fts_info == FTS_D)
close(fileDescriptor);
} else if (ent->fts_info == FTS_D) {
memset(hash, 0xdd, CC_SHA1_DIGEST_LENGTH);
} else {
return NO;
}
return YES;
}
NSData *hashOfFile(FTSENT *ent)
NSData *hashOfFileContents(FTSENT *ent)
{
unsigned char fileHash[CC_SHA1_DIGEST_LENGTH];
_hashOfFile(fileHash, ent);
if (!_hashOfFileContents(fileHash, ent)) {
return nil;
}
return [NSData dataWithBytes:fileHash length:CC_SHA1_DIGEST_LENGTH];
}
NSString *hashOfTree(NSString *path)
NSString *hashOfTreeWithVersion(NSString *path, uint16_t majorVersion)
{
const char *sourcePaths[] = {[path fileSystemRepresentation], 0};
FTS *fts = fts_open((char* const*)sourcePaths, FTS_PHYSICAL | FTS_NOCHDIR, compareFiles);
@ -138,16 +156,34 @@ NSString *hashOfTree(NSString *path)
FTSENT *ent = 0;
while ((ent = fts_read(fts))) {
if (ent->fts_info != FTS_F && ent->fts_info != FTS_SL)
if (ent->fts_info != FTS_F && ent->fts_info != FTS_SL && ent->fts_info != FTS_D)
continue;
if (ent->fts_info == FTS_D && !MAJOR_VERSION_IS_AT_LEAST(majorVersion, SUBeigeMajorVersion)) {
continue;
}
NSString *relativePath = pathRelativeToDirectory(path, stringWithFileSystemRepresentation(ent->fts_path));
if (relativePath.length == 0)
continue;
unsigned char fileHash[CC_SHA1_DIGEST_LENGTH];
_hashOfFile(fileHash, ent);
if (!_hashOfFileContents(fileHash, ent)) {
return nil;
}
CC_SHA1_Update(&hashContext, fileHash, sizeof(fileHash));
NSString *relativePath = pathRelativeToDirectory(path, stringWithFileSystemRepresentation(ent->fts_path));
const char *relativePathBytes = [relativePath fileSystemRepresentation];
CC_SHA1_Update(&hashContext, relativePathBytes, (CC_LONG)strlen(relativePathBytes));
if (MAJOR_VERSION_IS_AT_LEAST(majorVersion, SUBeigeMajorVersion)) {
uint16_t mode = ent->fts_statp->st_mode;
uint16_t type = ent->fts_info;
uint16_t permissions = mode & PERMISSION_FLAGS;
CC_SHA1_Update(&hashContext, &type, sizeof(type));
CC_SHA1_Update(&hashContext, &permissions, sizeof(permissions));
}
}
fts_close(fts);
@ -162,12 +198,41 @@ NSString *hashOfTree(NSString *path)
return @(hexHash);
}
void removeTree(NSString *path)
extern NSString *hashOfTree(NSString *path)
{
[[NSFileManager defaultManager] removeItemAtPath:path error:nil];
return hashOfTreeWithVersion(path, LATEST_DELTA_DIFF_MAJOR_VERSION);
}
void copyTree(NSString *source, NSString *dest)
BOOL removeTree(NSString *path)
{
[[NSFileManager defaultManager] copyItemAtPath:source toPath:dest error:nil];
NSFileManager *fileManager = [NSFileManager defaultManager];
// Don't use fileExistsForPath: because it will try to follow symbolic links
if (![fileManager attributesOfItemAtPath:path error:nil]) {
return YES;
}
return [fileManager removeItemAtPath:path error:nil];
}
BOOL copyTree(NSString *source, NSString *dest)
{
return [[NSFileManager defaultManager] copyItemAtPath:source toPath:dest error:nil];
}
BOOL modifyPermissions(NSString *path, mode_t desiredPermissions)
{
NSFileManager *fileManager = [NSFileManager defaultManager];
NSDictionary *attributes = [fileManager attributesOfItemAtPath:path error:nil];
if (!attributes) {
return NO;
}
NSNumber *permissions = attributes[NSFilePosixPermissions];
if (!permissions) {
return NO;
}
mode_t newMode = ([permissions unsignedShortValue] & ~PERMISSION_FLAGS) | desiredPermissions;
int (*changeModeFunc)(const char *, mode_t) = [attributes[NSFileType] isEqualToString:NSFileTypeSymbolicLink] ? lchmod : chmod;
if (changeModeFunc([path fileSystemRepresentation], newMode) != 0) {
return NO;
}
return YES;
}

View File

@ -0,0 +1,17 @@
//
// SUBinaryDeltaCreate.m
// Sparkle
//
// Created by Mayur Pawashe on 4/9/15.
// Copyright (c) 2015 Sparkle Project. All rights reserved.
//
#ifndef SUBINARYDELTACREATE_H
#define SUBINARYDELTACREATE_H
#import "SUBinaryDeltaCommon.h"
@class NSString;
int createBinaryDelta(NSString *source, NSString *destination, NSString *patchFile, SUBinaryDeltaMajorVersion majorVersion, BOOL verbose);
#endif

View File

@ -0,0 +1,462 @@
//
// SUBinaryDeltaCreate.m
// Sparkle
//
// Created by Mayur Pawashe on 4/9/15.
// Copyright (c) 2015 Sparkle Project. All rights reserved.
//
#import "SUBinaryDeltaCreate.h"
#import <Foundation/Foundation.h>
#include "SUBinaryDeltaCommon.h"
#import <CommonCrypto/CommonDigest.h>
#include <fcntl.h>
#include <fts.h>
#include <libgen.h>
#include <stdio.h>
#include <sys/mman.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <unistd.h>
#include <xar/xar.h>
extern int bsdiff(int argc, const char **argv);
@interface CreateBinaryDeltaOperation : NSOperation
@property (copy) NSString *relativePath;
@property (strong) NSString *resultPath;
@property (strong) NSNumber *oldPermissions;
@property (strong) NSNumber *permissions;
@property (strong) NSString *_fromPath;
@property (strong) NSString *_toPath;
- (id)initWithRelativePath:(NSString *)relativePath oldTree:(NSString *)oldTree newTree:(NSString *)newTree oldPermissions:(NSNumber *)oldPermissions newPermissions:(NSNumber *)permissions;
@end
@implementation CreateBinaryDeltaOperation
@synthesize relativePath = _relativePath;
@synthesize resultPath = _resultPath;
@synthesize oldPermissions = _oldPermissions;
@synthesize permissions = _permissions;
@synthesize _fromPath = _fromPath;
@synthesize _toPath = _toPath;
- (id)initWithRelativePath:(NSString *)relativePath oldTree:(NSString *)oldTree newTree:(NSString *)newTree oldPermissions:(NSNumber *)oldPermissions newPermissions:(NSNumber *)permissions
{
if ((self = [super init])) {
self.relativePath = relativePath;
self.oldPermissions = oldPermissions;
self.permissions = permissions;
self._fromPath = [oldTree stringByAppendingPathComponent:relativePath];
self._toPath = [newTree stringByAppendingPathComponent:relativePath];
}
return self;
}
- (void)main
{
NSString *temporaryFile = temporaryFilename(@"BinaryDelta");
const char *argv[] = {"/usr/bin/bsdiff", [self._fromPath fileSystemRepresentation], [self._toPath fileSystemRepresentation], [temporaryFile fileSystemRepresentation]};
int result = bsdiff(4, argv);
if (!result)
self.resultPath = temporaryFile;
}
@end
#define INFO_HASH_KEY @"hash"
#define INFO_TYPE_KEY @"type"
#define INFO_PERMISSIONS_KEY @"permissions"
#define INFO_SIZE_KEY @"size"
static NSDictionary *infoForFile(FTSENT *ent)
{
NSData *hash = hashOfFileContents(ent);
if (!hash) {
return nil;
}
off_t size = (ent->fts_info != FTS_D) ? ent->fts_statp->st_size : 0;
assert(ent->fts_statp != NULL);
mode_t permissions = ent->fts_statp->st_mode & PERMISSION_FLAGS;
return @{INFO_HASH_KEY: hash, INFO_TYPE_KEY: @(ent->fts_info), INFO_PERMISSIONS_KEY : @(permissions), INFO_SIZE_KEY: @(size)};
}
static bool aclExists(const FTSENT *ent)
{
// OS X does not currently support ACLs for symlinks
if (ent->fts_info == FTS_SL) {
return NO;
}
acl_t acl = acl_get_link_np(ent->fts_path, ACL_TYPE_EXTENDED);
if (acl != NULL) {
acl_entry_t entry;
int result = acl_get_entry(acl, ACL_FIRST_ENTRY, &entry);
assert(acl_free((void *)acl) == 0);
return (result == 0);
}
return false;
}
static NSString *absolutePath(NSString *path)
{
NSURL *url = [[NSURL alloc] initFileURLWithPath:path];
return [[url absoluteURL] path];
}
static NSString *temporaryPatchFile(NSString *patchFile)
{
NSString *path = absolutePath(patchFile);
NSString *directory = [path stringByDeletingLastPathComponent];
NSString *file = [path lastPathComponent];
return [NSString stringWithFormat:@"%@/.%@.tmp", directory, file];
}
#define MIN_FILE_SIZE_FOR_CREATING_DELTA 4096
static BOOL shouldSkipDeltaCompression(NSDictionary* originalInfo, NSDictionary *newInfo)
{
unsigned long long fileSize = [newInfo[INFO_SIZE_KEY] unsignedLongLongValue];
if (fileSize < MIN_FILE_SIZE_FOR_CREATING_DELTA) {
return YES;
}
if (!originalInfo) {
return YES;
}
if ([originalInfo[INFO_TYPE_KEY] unsignedShortValue] != [newInfo[INFO_TYPE_KEY] unsignedShortValue]) {
return YES;
}
if ([originalInfo[INFO_HASH_KEY] isEqual:newInfo[INFO_HASH_KEY]]) {
// this is possible if just the permissions have changed
return YES;
}
return NO;
}
static BOOL shouldDeleteThenExtract(NSDictionary* originalInfo, NSDictionary *newInfo)
{
if (!originalInfo) {
return NO;
}
if ([originalInfo[INFO_TYPE_KEY] unsignedShortValue] != [newInfo[INFO_TYPE_KEY] unsignedShortValue]) {
return YES;
}
return NO;
}
static BOOL shouldSkipExtracting(NSDictionary *originalInfo, NSDictionary *newInfo)
{
if (!originalInfo) {
return NO;
}
if ([originalInfo[INFO_TYPE_KEY] unsignedShortValue] != [newInfo[INFO_TYPE_KEY] unsignedShortValue]) {
return NO;
}
if (![originalInfo[INFO_HASH_KEY] isEqual:newInfo[INFO_HASH_KEY]]) {
return NO;
}
return YES;
}
static BOOL shouldChangePermissions(NSDictionary *originalInfo, NSDictionary *newInfo)
{
if (!originalInfo) {
return NO;
}
if ([originalInfo[INFO_TYPE_KEY] unsignedShortValue] != [newInfo[INFO_TYPE_KEY] unsignedShortValue]) {
return NO;
}
if ([originalInfo[INFO_PERMISSIONS_KEY] unsignedShortValue] == [newInfo[INFO_PERMISSIONS_KEY] unsignedShortValue]) {
return NO;
}
return YES;
}
int createBinaryDelta(NSString *source, NSString *destination, NSString *patchFile, SUBinaryDeltaMajorVersion majorVersion, BOOL verbose)
{
if (majorVersion < FIRST_DELTA_DIFF_MAJOR_VERSION) {
fprintf(stderr, "Version provided (%u) is not valid\n", majorVersion);
return 1;
}
if (majorVersion > LATEST_DELTA_DIFF_MAJOR_VERSION) {
fprintf(stderr, "This program is too old to create a version %u patch, or the version number provided is invalid\n", majorVersion);
return 1;
}
SUBinaryDeltaMinorVersion minorVersion = latestMinorVersionForMajorVersion(majorVersion);
NSMutableDictionary *originalTreeState = [NSMutableDictionary dictionary];
const char *sourcePaths[] = {[source fileSystemRepresentation], 0};
FTS *fts = fts_open((char* const*)sourcePaths, FTS_PHYSICAL | FTS_NOCHDIR, compareFiles);
if (!fts) {
perror("fts_open");
return 1;
}
if (verbose) {
fprintf(stderr, "Creating version %u.%u patch...\n", majorVersion, minorVersion);
fprintf(stderr, "Processing %s...", [source fileSystemRepresentation]);
}
FTSENT *ent = 0;
while ((ent = fts_read(fts))) {
if (ent->fts_info != FTS_F && ent->fts_info != FTS_SL && ent->fts_info != FTS_D) {
continue;
}
NSString *key = pathRelativeToDirectory(source, stringWithFileSystemRepresentation(ent->fts_path));
if (![key length]) {
continue;
}
NSDictionary *info = infoForFile(ent);
if (!info) {
fprintf(stderr, "\nFailed to retrieve info for file %s\n", ent->fts_path);
return 1;
}
originalTreeState[key] = info;
if (aclExists(ent)) {
fprintf(stderr, "\nDiffing ACLs are not supported. Detected ACL in before-tree on file %s\n", ent->fts_path);
return 1;
}
}
fts_close(fts);
NSString *beforeHash = hashOfTreeWithVersion(source, majorVersion);
if (!beforeHash) {
fprintf(stderr, "\nFailed to generate hash for tree %s\n", [source fileSystemRepresentation]);
return 1;
}
NSMutableDictionary *newTreeState = [NSMutableDictionary dictionary];
for (NSString *key in originalTreeState)
{
newTreeState[key] = [NSNull null];
}
if (verbose) {
fprintf(stderr, "\nProcessing %s...", [destination fileSystemRepresentation]);
}
sourcePaths[0] = [destination fileSystemRepresentation];
fts = fts_open((char* const*)sourcePaths, FTS_PHYSICAL | FTS_NOCHDIR, compareFiles);
if (!fts) {
perror("fts_open");
return 1;
}
while ((ent = fts_read(fts))) {
if (ent->fts_info != FTS_F && ent->fts_info != FTS_SL && ent->fts_info != FTS_D) {
continue;
}
NSString *key = pathRelativeToDirectory(destination, stringWithFileSystemRepresentation(ent->fts_path));
if (![key length]) {
continue;
}
NSDictionary *info = infoForFile(ent);
if (!info) {
fprintf(stderr, "\nFailed to retrieve info from file %s\n", ent->fts_path);
return 1;
}
// We should validate permissions and ACLs even if we don't store the info in the diff in the case of ACLs,
// or in the case of permissions if the patch version is 1
mode_t permissions = [info[INFO_PERMISSIONS_KEY] unsignedShortValue];
if (!IS_VALID_PERMISSIONS(permissions)) {
fprintf(stderr, "\nInvalid file permissions after-tree on file %s\nOnly permissions with modes 0755 and 0644 are supported\n", ent->fts_path);
return 1;
}
if (aclExists(ent)) {
fprintf(stderr, "\nDiffing ACLs are not supported. Detected ACL in after-tree on file %s\n", ent->fts_path);
return 1;
}
NSDictionary *oldInfo = originalTreeState[key];
if ([info isEqual:oldInfo]) {
[newTreeState removeObjectForKey:key];
} else {
newTreeState[key] = info;
if (oldInfo && [oldInfo[INFO_TYPE_KEY] unsignedShortValue] == FTS_D && [info[INFO_TYPE_KEY] unsignedShortValue] != FTS_D) {
NSArray *parentPathComponents = key.pathComponents;
for (NSString *childPath in originalTreeState) {
NSArray *childPathComponents = childPath.pathComponents;
if (childPathComponents.count > parentPathComponents.count &&
[parentPathComponents isEqualToArray:[childPathComponents subarrayWithRange:NSMakeRange(0, parentPathComponents.count)]]) {
[newTreeState removeObjectForKey:childPath];
}
}
}
}
}
fts_close(fts);
NSString *afterHash = hashOfTreeWithVersion(destination, majorVersion);
if (!afterHash) {
fprintf(stderr, "\nFailed to generate hash for tree %s\n", [destination fileSystemRepresentation]);
return 1;
}
if (verbose) {
fprintf(stderr, "\nGenerating delta...");
}
NSString *temporaryFile = temporaryPatchFile(patchFile);
xar_t x = xar_open([temporaryFile fileSystemRepresentation], WRITE);
xar_opt_set(x, XAR_OPT_COMPRESSION, "bzip2");
xar_subdoc_t attributes = xar_subdoc_new(x, BINARY_DELTA_ATTRIBUTES_KEY);
xar_subdoc_prop_set(attributes, MAJOR_DIFF_VERSION_KEY, [[NSString stringWithFormat:@"%u", majorVersion] UTF8String]);
xar_subdoc_prop_set(attributes, MINOR_DIFF_VERSION_KEY, [[NSString stringWithFormat:@"%u", minorVersion] UTF8String]);
// Version 1 patches don't have a major or minor version field, so we need to differentiate between the hash keys
const char *beforeHashKey =
MAJOR_VERSION_IS_AT_LEAST(majorVersion, SUBeigeMajorVersion) ? BEFORE_TREE_SHA1_KEY : BEFORE_TREE_SHA1_OLD_KEY;
const char *afterHashKey =
MAJOR_VERSION_IS_AT_LEAST(majorVersion, SUBeigeMajorVersion) ? AFTER_TREE_SHA1_KEY : AFTER_TREE_SHA1_OLD_KEY;
xar_subdoc_prop_set(attributes, beforeHashKey, [beforeHash UTF8String]);
xar_subdoc_prop_set(attributes, afterHashKey, [afterHash UTF8String]);
NSOperationQueue *deltaQueue = [[NSOperationQueue alloc] init];
NSMutableArray *deltaOperations = [NSMutableArray array];
// Sort the keys by preferring the ones from the original tree to appear first
// We want to enforce deleting before extracting in the case paths differ only by case
NSArray *keys = [[newTreeState allKeys] sortedArrayUsingComparator:^NSComparisonResult(NSString *key1, NSString *key2) {
NSComparisonResult insensitiveCompareResult = [key1 caseInsensitiveCompare:key2];
if (insensitiveCompareResult != NSOrderedSame) {
return insensitiveCompareResult;
}
return originalTreeState[key1] ? NSOrderedAscending : NSOrderedDescending;
}];
for (NSString* key in keys) {
id value = [newTreeState valueForKey:key];
if ([value isEqual:[NSNull null]]) {
xar_file_t newFile = xar_add_frombuffer(x, 0, [key fileSystemRepresentation], (char *)"", 1);
assert(newFile);
xar_prop_set(newFile, DELETE_KEY, "true");
if (verbose) {
fprintf(stderr, "\n%s %s", VERBOSE_REMOVED, [key fileSystemRepresentation]);
}
continue;
}
NSDictionary *originalInfo = originalTreeState[key];
NSDictionary *newInfo = newTreeState[key];
if (shouldSkipDeltaCompression(originalInfo, newInfo)) {
if (MAJOR_VERSION_IS_AT_LEAST(majorVersion, SUBeigeMajorVersion) && shouldSkipExtracting(originalInfo, newInfo)) {
if (shouldChangePermissions(originalInfo, newInfo)) {
xar_file_t newFile = xar_add_frombuffer(x, 0, [key fileSystemRepresentation], (char *)"", 1);
assert(newFile);
xar_prop_set(newFile, MODIFY_PERMISSIONS_KEY, [[NSString stringWithFormat:@"%u", [newInfo[INFO_PERMISSIONS_KEY] unsignedShortValue]] UTF8String]);
if (verbose) {
fprintf(stderr, "\n👮 %s %s (0%o -> 0%o)", VERBOSE_MODIFIED, [key fileSystemRepresentation], [originalInfo[INFO_PERMISSIONS_KEY] unsignedShortValue], [newInfo[INFO_PERMISSIONS_KEY] unsignedShortValue]);
}
}
} else {
NSString *path = [destination stringByAppendingPathComponent:key];
xar_file_t newFile = xar_add_frompath(x, 0, [key fileSystemRepresentation], [path fileSystemRepresentation]);
assert(newFile);
if (shouldDeleteThenExtract(originalInfo, newInfo)) {
if (MAJOR_VERSION_IS_AT_LEAST(majorVersion, SUBeigeMajorVersion)) {
xar_prop_set(newFile, DELETE_KEY, "true");
} else {
xar_prop_set(newFile, DELETE_THEN_EXTRACT_OLD_KEY, "true");
}
}
if (MAJOR_VERSION_IS_AT_LEAST(majorVersion, SUBeigeMajorVersion)) {
xar_prop_set(newFile, EXTRACT_KEY, "true");
}
if (verbose) {
if (originalInfo) {
fprintf(stderr, "\n✏️ %s %s", VERBOSE_UPDATED, [key fileSystemRepresentation]);
} else {
fprintf(stderr, "\n%s %s", VERBOSE_ADDED, [key fileSystemRepresentation]);
}
}
}
} else {
NSNumber *permissions =
(MAJOR_VERSION_IS_AT_LEAST(majorVersion, SUBeigeMajorVersion) && shouldChangePermissions(originalInfo, newInfo)) ?
newInfo[INFO_PERMISSIONS_KEY] :
nil;
CreateBinaryDeltaOperation *operation = [[CreateBinaryDeltaOperation alloc] initWithRelativePath:key oldTree:source newTree:destination oldPermissions:originalInfo[INFO_PERMISSIONS_KEY] newPermissions:permissions];
[deltaQueue addOperation:operation];
[deltaOperations addObject:operation];
}
}
[deltaQueue waitUntilAllOperationsAreFinished];
for (CreateBinaryDeltaOperation *operation in deltaOperations) {
NSString *resultPath = [operation resultPath];
if (!resultPath) {
fprintf(stderr, "\nFailed to create patch from source %s and destination %s\n", [[operation relativePath] fileSystemRepresentation], [resultPath fileSystemRepresentation]);
return 1;
}
if (verbose) {
fprintf(stderr, "\n🔨 %s %s", VERBOSE_DIFFED, [[operation relativePath] fileSystemRepresentation]);
}
xar_file_t newFile = xar_add_frompath(x, 0, [[operation relativePath] fileSystemRepresentation], [resultPath fileSystemRepresentation]);
assert(newFile);
xar_prop_set(newFile, BINARY_DELTA_KEY, "true");
unlink([resultPath fileSystemRepresentation]);
if (operation.permissions) {
xar_prop_set(newFile, MODIFY_PERMISSIONS_KEY, [[NSString stringWithFormat:@"%u", [operation.permissions unsignedShortValue]] UTF8String]);
if (verbose) {
fprintf(stderr, "\n👮 %s %s (0%o -> 0%o)", VERBOSE_MODIFIED, [[operation relativePath] fileSystemRepresentation], operation.oldPermissions.unsignedShortValue, operation.permissions.unsignedShortValue);
}
}
}
xar_close(x);
unlink([patchFile fileSystemRepresentation]);
link([temporaryFile fileSystemRepresentation], [patchFile fileSystemRepresentation]);
unlink([temporaryFile fileSystemRepresentation]);
if (verbose) {
fprintf(stderr, "\nDone!\n");
}
return 0;
}

View File

@ -6,275 +6,213 @@
// Copyright 2009 Mark Rowe. All rights reserved.
//
#define _DARWIN_NO_64_BIT_INODE 1
#include "SUBinaryDeltaCommon.h"
#include "SUBinaryDeltaApply.h"
#include <CommonCrypto/CommonDigest.h>
#include "SUBinaryDeltaCreate.h"
#import "SUBinaryDeltaCommon.h"
#include <Foundation/Foundation.h>
#include <fcntl.h>
#include <fts.h>
#include <libgen.h>
#include <stdio.h>
#include <sys/mman.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <unistd.h>
#include <xar/xar.h>
extern int bsdiff(int argc, const char **argv);
#define VERBOSE_FLAG @"--verbose"
#define VERSION_FLAG @"--version"
@interface CreateBinaryDeltaOperation : NSOperation
@property (copy) NSString *relativePath;
@property (strong) NSString *resultPath;
@property (strong) NSString *_fromPath;
@property (strong) NSString *_toPath;
- (id)initWithRelativePath:(NSString *)relativePath oldTree:(NSString *)oldTree newTree:(NSString *)newTree;
@end
#define CREATE_COMMAND @"create"
#define APPLY_COMMAND @"apply"
#define VERSION_COMMAND @"version"
#define VERSION_ALTERNATE_COMMAND @"--version"
@implementation CreateBinaryDeltaOperation
@synthesize relativePath = _relativePath;
@synthesize resultPath = _resultPath;
@synthesize _fromPath = _fromPath;
@synthesize _toPath = _toPath;
- (id)initWithRelativePath:(NSString *)relativePath oldTree:(NSString *)oldTree newTree:(NSString *)newTree
static void printUsage(NSString *programName)
{
if ((self = [super init])) {
self.relativePath = relativePath;
self._fromPath = [oldTree stringByAppendingPathComponent:relativePath];
self._toPath = [newTree stringByAppendingPathComponent:relativePath];
}
return self;
fprintf(stderr, "Usage:\n");
fprintf(stderr, "%s create [--verbose] [--version=<version>] <before-tree> <after-tree> <patch-file>\n", [programName UTF8String]);
fprintf(stderr, "%s apply [--verbose] <before-tree> <after-tree> <patch-file>\n", [programName UTF8String]);
fprintf(stderr, "%s version [<patch-file>]\n", [programName UTF8String]);
}
- (void)main
static int runCreateCommand(NSString *programName, NSArray *args)
{
NSString *temporaryFile = temporaryFilename(@"BinaryDelta");
const char *argv[] = {"/usr/bin/bsdiff", [self._fromPath fileSystemRepresentation], [self._toPath fileSystemRepresentation], [temporaryFile fileSystemRepresentation]};
int result = bsdiff(4, argv);
if (!result)
self.resultPath = temporaryFile;
if (args.count < 3 || args.count > 5) {
printUsage(programName);
return 1;
}
NSUInteger numberOflagsFound = 0;
NSUInteger verboseIndex = [args indexOfObject:VERBOSE_FLAG];
NSUInteger versionIndex = NSNotFound;
for (NSUInteger argumentIndex = 0; argumentIndex < args.count; ++argumentIndex) {
if ([args[argumentIndex] hasPrefix:VERSION_FLAG]) {
versionIndex = argumentIndex;
break;
}
}
if (verboseIndex != NSNotFound) {
++numberOflagsFound;
}
if (versionIndex != NSNotFound) {
++numberOflagsFound;
}
if (args.count - numberOflagsFound < 3) {
printUsage(programName);
return 1;
}
BOOL verbose = (verboseIndex != NSNotFound);
NSString *versionField = (versionIndex != NSNotFound) ? args[versionIndex] : nil;
NSArray *versionComponents = nil;
if (versionField) {
versionComponents = [versionField componentsSeparatedByString:@"="];
if (versionComponents.count != 2) {
printUsage(programName);
return 1;
}
}
SUBinaryDeltaMajorVersion patchVersion =
!versionComponents ?
LATEST_DELTA_DIFF_MAJOR_VERSION :
(SUBinaryDeltaMajorVersion)[[versionComponents[1] componentsSeparatedByString:@"."][0] intValue]; // ignore minor version if provided
NSMutableArray *fileArgs = [NSMutableArray array];
for (NSString *argument in args) {
if (![argument hasPrefix:VERSION_FLAG] && ![argument isEqualToString:VERBOSE_FLAG]) {
[fileArgs addObject:argument];
}
}
if (fileArgs.count != 3) {
printUsage(programName);
return 1;
}
BOOL isDirectory;
if (![[NSFileManager defaultManager] fileExistsAtPath:fileArgs[0] isDirectory:&isDirectory] || !isDirectory) {
printUsage(programName);
fprintf(stderr, "Error: before-tree must be a directory\n");
return 1;
}
if (![[NSFileManager defaultManager] fileExistsAtPath:fileArgs[1] isDirectory:&isDirectory] || !isDirectory) {
printUsage(programName);
fprintf(stderr, "Error: after-tree must be a directory\n");
return 1;
}
return createBinaryDelta(fileArgs[0], fileArgs[1], fileArgs[2], patchVersion, verbose);
}
@end
static NSDictionary *infoForFile(FTSENT *ent)
static int runApplyCommand(NSString *programName, NSArray *args)
{
NSData *hash = hashOfFile(ent);
NSNumber *size = @0;
if (ent->fts_info != FTS_D) {
size = @(ent->fts_statp->st_size);
if (args.count < 3 || args.count > 4) {
printUsage(programName);
return 1;
}
return @{@"hash": hash, @"type": @(ent->fts_info), @"size": size};
BOOL verbose = [args containsObject:VERBOSE_FLAG];
if (args.count == 4 && !verbose) {
printUsage(programName);
return 1;
}
NSMutableArray *fileArgs = [NSMutableArray array];
for (NSString *argument in args) {
if (![argument isEqualToString:VERBOSE_FLAG]) {
[fileArgs addObject:argument];
}
}
if (fileArgs.count != 3) {
printUsage(programName);
return 1;
}
BOOL isDirectory;
if (![[NSFileManager defaultManager] fileExistsAtPath:fileArgs[0] isDirectory:&isDirectory] || !isDirectory) {
printUsage(programName);
fprintf(stderr, "Error: before-tree must be a directory\n");
return 1;
}
if (![[NSFileManager defaultManager] fileExistsAtPath:fileArgs[2] isDirectory:&isDirectory] || isDirectory) {
printUsage(programName);
fprintf(stderr, "Error: patch-file must be a file %d\n", isDirectory);
return 1;
}
return applyBinaryDelta(fileArgs[0], fileArgs[1], fileArgs[2], verbose);
}
static NSString *absolutePath(NSString *path)
static int runVersionCommand(NSString *programName, NSArray *args)
{
NSURL *url = [[NSURL alloc] initFileURLWithPath:path];
return [[url absoluteURL] path];
}
static NSString *temporaryPatchFile(NSString *patchFile)
{
NSString *path = absolutePath(patchFile);
NSString *directory = [path stringByDeletingLastPathComponent];
NSString *file = [path lastPathComponent];
return [NSString stringWithFormat:@"%@/.%@.tmp", directory, file];
}
static BOOL shouldSkipDeltaCompression(NSString * __unused key, NSDictionary* originalInfo, NSDictionary *newInfo)
{
unsigned long long fileSize = [newInfo[@"size"] unsignedLongLongValue];
if (fileSize < 4096) {
return YES;
if (args.count > 1) {
printUsage(programName);
return 1;
}
if (!originalInfo) {
return YES;
if (args.count == 0) {
fprintf(stdout, "%u.%u\n", LATEST_DELTA_DIFF_MAJOR_VERSION, latestMinorVersionForMajorVersion(LATEST_DELTA_DIFF_MAJOR_VERSION));
} else {
NSString *patchFile = args[0];
xar_t x = xar_open([patchFile fileSystemRepresentation], READ);
if (!x) {
fprintf(stderr, "Unable to open patch %s\n", [patchFile fileSystemRepresentation]);
return 1;
}
if ([originalInfo[@"type"] unsignedShortValue] != [newInfo[@"type"] unsignedShortValue]) {
return YES;
SUBinaryDeltaMajorVersion majorDiffVersion = FIRST_DELTA_DIFF_MAJOR_VERSION;
SUBinaryDeltaMinorVersion minorDiffVersion = FIRST_DELTA_DIFF_MINOR_VERSION;
xar_subdoc_t subdoc;
for (subdoc = xar_subdoc_first(x); subdoc; subdoc = xar_subdoc_next(subdoc)) {
if (!strcmp(xar_subdoc_name(subdoc), BINARY_DELTA_ATTRIBUTES_KEY)) {
const char *value = 0;
// available in version 2.0 or later
xar_subdoc_prop_get(subdoc, MAJOR_DIFF_VERSION_KEY, &value);
if (value)
majorDiffVersion = (SUBinaryDeltaMajorVersion)[@(value) intValue];
// available in version 2.0 or later
xar_subdoc_prop_get(subdoc, MINOR_DIFF_VERSION_KEY, &value);
if (value)
minorDiffVersion = (SUBinaryDeltaMinorVersion)[@(value) intValue];
}
}
return NO;
}
static BOOL shouldDeleteThenExtract(NSString * __unused key, NSDictionary* originalInfo, NSDictionary *newInfo)
{
if (!originalInfo) {
return NO;
fprintf(stdout, "%u.%u\n", majorDiffVersion, minorDiffVersion);
}
if ([originalInfo[@"type"] unsignedShortValue] != [newInfo[@"type"] unsignedShortValue]) {
return YES;
}
return NO;
return 0;
}
int main(int __unused argc, char __unused *argv[])
{
@autoreleasepool {
NSArray *args = [[NSProcessInfo processInfo] arguments];
if (args.count != 5) {
usage:
fprintf(stderr, "Usage: BinaryDelta [create | apply] before-tree after-tree patch-file\n");
NSString *programName = [args[0] lastPathComponent];
if (args.count < 2) {
printUsage(programName);
return 1;
}
NSString *command = args[1];
NSString *oldPath = args[2];
NSString *newPath = args[3];
NSString *patchFile = args[4];
NSArray *commandArguments = [args subarrayWithRange:NSMakeRange(2, args.count - 2)];
BOOL isDirectory;
[[NSFileManager defaultManager] fileExistsAtPath:oldPath isDirectory:&isDirectory];
if (!isDirectory) {
fprintf(stderr, "Usage: before-tree must be a directory\n");
return 1;
int result;
if ([command isEqualToString:CREATE_COMMAND]) {
result = runCreateCommand(programName, commandArguments);
} else if ([command isEqualToString:APPLY_COMMAND]) {
result = runApplyCommand(programName, commandArguments);
} else if ([command isEqualToString:VERSION_COMMAND] || [command isEqualToString:VERSION_ALTERNATE_COMMAND]) {
result = runVersionCommand(programName, commandArguments);
} else {
result = 1;
printUsage(programName);
}
[[NSFileManager defaultManager] fileExistsAtPath:newPath isDirectory:&isDirectory];
if (!isDirectory) {
fprintf(stderr, "Usage: after-tree must be a directory\n");
return 1;
}
if ([command isEqualToString:@"apply"]) {
int result = applyBinaryDelta(oldPath, newPath, patchFile);
return result;
}
if (![command isEqualToString:@"create"]) {
goto usage;
}
NSMutableDictionary *originalTreeState = [NSMutableDictionary dictionary];
const char *sourcePaths[] = {[oldPath fileSystemRepresentation], 0};
FTS *fts = fts_open((char* const*)sourcePaths, FTS_PHYSICAL | FTS_NOCHDIR, compareFiles);
if (!fts) {
perror("fts_open");
return 1;
}
fprintf(stderr, "Processing %s...", [oldPath UTF8String]);
FTSENT *ent = 0;
while ((ent = fts_read(fts))) {
if (ent->fts_info != FTS_F && ent->fts_info != FTS_SL && ent->fts_info != FTS_D) {
continue;
}
NSString *key = pathRelativeToDirectory(oldPath, stringWithFileSystemRepresentation(ent->fts_path));
if (![key length]) {
continue;
}
NSDictionary *info = infoForFile(ent);
originalTreeState[key] = info;
}
fts_close(fts);
NSString *beforeHash = hashOfTree(oldPath);
NSMutableDictionary *newTreeState = [NSMutableDictionary dictionary];
for (NSString *key in originalTreeState)
{
newTreeState[key] = [NSNull null];
}
fprintf(stderr, "\nProcessing %s... ", [newPath UTF8String]);
sourcePaths[0] = [newPath fileSystemRepresentation];
fts = fts_open((char* const*)sourcePaths, FTS_PHYSICAL | FTS_NOCHDIR, compareFiles);
if (!fts) {
perror("fts_open");
return 1;
}
while ((ent = fts_read(fts))) {
if (ent->fts_info != FTS_F && ent->fts_info != FTS_SL && ent->fts_info != FTS_D) {
continue;
}
NSString *key = pathRelativeToDirectory(newPath, stringWithFileSystemRepresentation(ent->fts_path));
if (![key length]) {
continue;
}
NSDictionary *info = infoForFile(ent);
NSDictionary *oldInfo = originalTreeState[key];
if ([info isEqual:oldInfo])
[newTreeState removeObjectForKey:key];
else
newTreeState[key] = info;
}
fts_close(fts);
NSString *afterHash = hashOfTree(newPath);
fprintf(stderr, "\nGenerating delta... ");
NSString *temporaryFile = temporaryPatchFile(patchFile);
xar_t x = xar_open([temporaryFile fileSystemRepresentation], WRITE);
xar_opt_set(x, XAR_OPT_COMPRESSION, "bzip2");
xar_subdoc_t attributes = xar_subdoc_new(x, "binary-delta-attributes");
xar_subdoc_prop_set(attributes, "before-sha1", [beforeHash UTF8String]);
xar_subdoc_prop_set(attributes, "after-sha1", [afterHash UTF8String]);
NSOperationQueue *deltaQueue = [[NSOperationQueue alloc] init];
NSMutableArray *deltaOperations = [NSMutableArray array];
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wselector"
// Xcode 5.1.1: compare: is clearly declared, must warn due to a compiler bug?
NSArray *keys = [[newTreeState allKeys] sortedArrayUsingSelector:@selector(compare:)];
#pragma clang diagnostic pop
for (NSString* key in keys) {
id value = [newTreeState valueForKey:key];
if ([value isEqual:[NSNull null]]) {
xar_file_t newFile = xar_add_frombuffer(x, 0, [key fileSystemRepresentation], (char *)"", 1);
assert(newFile);
xar_prop_set(newFile, "delete", "true");
continue;
}
NSDictionary *originalInfo = originalTreeState[key];
NSDictionary *newInfo = newTreeState[key];
if (shouldSkipDeltaCompression(key, originalInfo, newInfo)) {
NSString *path = [newPath stringByAppendingPathComponent:key];
xar_file_t newFile = xar_add_frompath(x, 0, [key fileSystemRepresentation], [path fileSystemRepresentation]);
assert(newFile);
if (shouldDeleteThenExtract(key, originalInfo, newInfo)) {
xar_prop_set(newFile, "delete-then-extract", "true");
}
} else {
CreateBinaryDeltaOperation *operation = [[CreateBinaryDeltaOperation alloc] initWithRelativePath:key oldTree:oldPath newTree:newPath];
[deltaQueue addOperation:operation];
[deltaOperations addObject:operation];
}
}
[deltaQueue waitUntilAllOperationsAreFinished];
for (CreateBinaryDeltaOperation *operation in deltaOperations) {
NSString *resultPath = [operation resultPath];
xar_file_t newFile = xar_add_frompath(x, 0, [[operation relativePath] fileSystemRepresentation], [resultPath fileSystemRepresentation]);
assert(newFile);
xar_prop_set(newFile, "binary-delta", "true");
unlink([resultPath fileSystemRepresentation]);
}
xar_close(x);
unlink([patchFile fileSystemRepresentation]);
link([temporaryFile fileSystemRepresentation], [patchFile fileSystemRepresentation]);
unlink([temporaryFile fileSystemRepresentation]);
fprintf(stderr, "Done!\n");
return 0;
}
}

View File

@ -17,16 +17,16 @@
+ (BOOL)canUnarchivePath:(NSString *)path
{
return binaryDeltaSupported() && [[path pathExtension] isEqualToString:@"delta"];
return [[path pathExtension] isEqualToString:@"delta"];
}
- (void)applyBinaryDelta
{
@autoreleasepool {
NSString *sourcePath = [[self.updateHost bundle] bundlePath];
NSString *sourcePath = self.updateHostBundlePath;
NSString *targetPath = [[self.archivePath stringByDeletingLastPathComponent] stringByAppendingPathComponent:[sourcePath lastPathComponent]];
int result = applyBinaryDelta(sourcePath, targetPath, self.archivePath);
int result = applyBinaryDelta(sourcePath, targetPath, self.archivePath, NO);
if (!result) {
dispatch_async(dispatch_get_main_queue(), ^{
[self notifyDelegateOfSuccess];

View File

@ -12,8 +12,10 @@
#import <Foundation/Foundation.h>
@interface SUCodeSigningVerifier : NSObject
+ (BOOL)codeSignatureIsValidAtPath:(NSString *)destinationPath error:(NSError **)error;
+ (BOOL)codeSignatureMatchesHostAndIsValidAtPath:(NSString *)applicationPath error:(NSError **)error;
+ (BOOL)codeSignatureIsValidAtPath:(NSString *)applicationPath error:(NSError **)error;
+ (BOOL)hostApplicationIsCodeSigned;
+ (BOOL)applicationAtPathIsCodeSigned:(NSString *)applicationPath;
@end
#endif

View File

@ -13,7 +13,7 @@
@implementation SUCodeSigningVerifier
+ (BOOL)codeSignatureIsValidAtPath:(NSString *)destinationPath error:(NSError *__autoreleasing *)error
+ (BOOL)codeSignatureMatchesHostAndIsValidAtPath:(NSString *)applicationPath error:(NSError *__autoreleasing *)error
{
OSStatus result;
SecRequirementRef requirement = NULL;
@ -37,20 +37,23 @@
goto finally;
}
newBundle = [NSBundle bundleWithPath:destinationPath];
newBundle = [NSBundle bundleWithPath:applicationPath];
if (!newBundle) {
SULog(@"Failed to load NSBundle for update");
result = -1;
goto finally;
}
result = SecStaticCodeCreateWithPath((__bridge CFURLRef)[newBundle executableURL], kSecCSDefaultFlags, &staticCode);
result = SecStaticCodeCreateWithPath((__bridge CFURLRef)[newBundle bundleURL], kSecCSDefaultFlags, &staticCode);
if (result != noErr) {
SULog(@"Failed to get static code %d", result);
goto finally;
}
result = SecStaticCodeCheckValidityWithErrors(staticCode, kSecCSDefaultFlags | kSecCSCheckAllArchitectures, requirement, &cfError);
// Note that kSecCSCheckNestedCode may not work with pre-Mavericks code signing.
// See https://github.com/sparkle-project/Sparkle/issues/376#issuecomment-48824267 and https://developer.apple.com/library/mac/technotes/tn2206
SecCSFlags flags = kSecCSDefaultFlags | kSecCSCheckAllArchitectures;
result = SecStaticCodeCheckValidityWithErrors(staticCode, flags, requirement, &cfError);
if (cfError) {
NSError *tmpError = CFBridgingRelease(cfError);
@ -80,6 +83,53 @@ finally:
return (result == noErr);
}
+ (BOOL)codeSignatureIsValidAtPath:(NSString *)applicationPath error:(NSError *__autoreleasing *)error
{
OSStatus result;
SecStaticCodeRef staticCode = NULL;
NSBundle *newBundle;
CFErrorRef cfError = NULL;
if (error) {
*error = nil;
}
newBundle = [NSBundle bundleWithPath:applicationPath];
if (!newBundle) {
SULog(@"Failed to load NSBundle");
result = -1;
goto finally;
}
result = SecStaticCodeCreateWithPath((__bridge CFURLRef)[newBundle bundleURL], kSecCSDefaultFlags, &staticCode);
if (result != noErr) {
SULog(@"Failed to get static code %d", result);
goto finally;
}
// Note that kSecCSCheckNestedCode may not work with pre-Mavericks code signing.
// See https://github.com/sparkle-project/Sparkle/issues/376#issuecomment-48824267 and https://developer.apple.com/library/mac/technotes/tn2206
SecCSFlags flags = kSecCSDefaultFlags | kSecCSCheckAllArchitectures;
result = SecStaticCodeCheckValidityWithErrors(staticCode, flags, NULL, &cfError);
if (cfError) {
NSError *tmpError = CFBridgingRelease(cfError);
if (error) *error = tmpError;
}
if (result != noErr) {
if (result == errSecCSUnsigned) {
SULog(@"Error: The app is not signed using Apple Code Signing. %@", applicationPath);
}
if (result == errSecCSReqFailed) {
[self logSigningInfoForCode:staticCode label:@"new info"];
}
}
finally:
if (staticCode) CFRelease(staticCode);
return (result == noErr);
}
static id valueOrNSNull(id value) {
return value ? value : [NSNull null];
}
@ -114,4 +164,35 @@ static id valueOrNSNull(id value) {
return (result == 0);
}
+ (BOOL)applicationAtPathIsCodeSigned:(NSString *)applicationPath
{
OSStatus result;
SecStaticCodeRef staticCode = NULL;
NSBundle *newBundle;
newBundle = [NSBundle bundleWithPath:applicationPath];
if (!newBundle) {
SULog(@"Failed to load NSBundle");
return NO;
}
result = SecStaticCodeCreateWithPath((__bridge CFURLRef)[newBundle bundleURL], kSecCSDefaultFlags, &staticCode);
if (result == errSecCSUnsigned) {
return NO;
}
SecRequirementRef requirement = NULL;
result = SecCodeCopyDesignatedRequirement(staticCode, kSecCSDefaultFlags, &requirement);
if (staticCode) {
CFRelease(staticCode);
}
if (requirement) {
CFRelease(requirement);
}
if (result == errSecCSUnsigned) {
return NO;
}
return (result == 0);
}
@end

View File

@ -79,33 +79,4 @@ extern NSString *const SURSSElementLink;
extern NSString *const SURSSElementPubDate;
extern NSString *const SURSSElementTitle;
// -----------------------------------------------------------------------------
// Errors:
// -----------------------------------------------------------------------------
extern NSString *const SUSparkleErrorDomain;
typedef NS_ENUM(OSStatus, SUError) {
// Appcast phase errors.
SUAppcastParseError = 1000,
SUNoUpdateError = 1001,
SUAppcastError = 1002,
SURunningFromDiskImageError = 1003,
// Downlaod phase errors.
SUTemporaryDirectoryError = 2000,
// Extraction phase errors.
SUUnarchivingError = 3000,
SUSignatureError = 3001,
// Installation phase errors.
SUFileCopyFailure = 4000,
SUAuthenticationFailure = 4001,
SUMissingUpdateError = 4002,
SUMissingInstallerToolError = 4003,
SURelaunchError = 4004,
SUInstallationError = 4005,
SUDowngradeError = 4006
};
#endif

44
Frameworks/Sparkle/Sparkle/SUErrors.h vendored Normal file
View File

@ -0,0 +1,44 @@
//
// SUErrors.h
// Sparkle
//
// Created by C.W. Betts on 10/13/14.
// Copyright (c) 2014 Sparkle Project. All rights reserved.
//
#ifndef SUERRORS_H
#define SUERRORS_H
#import <Foundation/Foundation.h>
#import "SUExport.h"
/**
* Error domain used by Sparkle
*/
SU_EXPORT extern NSString *const SUSparkleErrorDomain;
typedef NS_ENUM(OSStatus, SUError) {
// Appcast phase errors.
SUAppcastParseError = 1000,
SUNoUpdateError = 1001,
SUAppcastError = 1002,
SURunningFromDiskImageError = 1003,
// Downlaod phase errors.
SUTemporaryDirectoryError = 2000,
// Extraction phase errors.
SUUnarchivingError = 3000,
SUSignatureError = 3001,
// Installation phase errors.
SUFileCopyFailure = 4000,
SUAuthenticationFailure = 4001,
SUMissingUpdateError = 4002,
SUMissingInstallerToolError = 4003,
SURelaunchError = 4004,
SUInstallationError = 4005,
SUDowngradeError = 4006
};
#endif

View File

@ -0,0 +1,35 @@
//
// SUGuidedPackageInstaller.h
// Sparkle
//
// Created by Graham Miln on 14/05/2010.
// Copyright 2010 Dragon Systems Software Limited. All rights reserved.
//
/*!
# Sparkle Guided Installations
A guided installation allows Sparkle to download and install a package (pkg) or multi-package (mpkg) without user interaction.
The installer package is installed using Mac OS X's built-in command line installer, `/usr/sbin/installer`. No installation interface is shown to the user.
A guided installation can be started by applications other than the application being replaced. This is particularly useful where helper applications or agents are used.
## To Do
- Replace the use of `AuthorizationExecuteWithPrivilegesAndWait`. This method remains because it is well supported and tested. Ideally a helper tool or XPC would be used.
*/
#ifndef SUGUIDEDPACKAGEINSTALLER_H
#define SUGUIDEDPACKAGEINSTALLER_H
#import "Sparkle.h"
#import "SUInstaller.h"
@interface SUGuidedPackageInstaller : SUInstaller {
}
/*! Perform the guided installation */
+ (void)performInstallationToPath:(NSString *)path fromPath:(NSString *)installerGuide host:(SUHost *)host versionComparator:(id<SUVersionComparison>)comparator completionHandler:(void (^)(NSError *))completionHandler;
@end
#endif

View File

@ -0,0 +1,139 @@
//
// SUGuidedPackageInstaller.m
// Sparkle
//
// Created by Graham Miln on 14/05/2010.
// Copyright 2010 Dragon Systems Software Limited. All rights reserved.
//
#import <sys/stat.h>
#import <Security/Security.h>
#import "SUGuidedPackageInstaller.h"
static BOOL AuthorizationExecuteWithPrivilegesAndWait(AuthorizationRef authorization, const char* executablePath, AuthorizationFlags options, const char* const* arguments)
{
sig_t oldSigChildHandler = signal(SIGCHLD, SIG_DFL);
BOOL returnValue = YES;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
/* AuthorizationExecuteWithPrivileges used to support 10.4+; should be replaced with XPC or external process */
if (AuthorizationExecuteWithPrivileges(authorization, executablePath, options, (char* const*)arguments, NULL) == errAuthorizationSuccess)
#pragma clang diagnostic pop
{
int status = 0;
pid_t pid = wait(&status);
if (pid == -1 || !WIFEXITED(status) || WEXITSTATUS(status) != 0)
returnValue = NO;
}
else
returnValue = NO;
signal(SIGCHLD, oldSigChildHandler);
return returnValue;
}
@implementation SUGuidedPackageInstaller (SUGuidedPackageInstallerAuthentication)
+ (AuthorizationRef)authorizationForExecutable:(NSString*)executablePath
{
NSParameterAssert(executablePath);
// Get authorization using advice in Apple's Technical Q&A1172
// ...create authorization without specific rights
AuthorizationRef auth = NULL;
OSStatus validAuth = AuthorizationCreate(NULL,
kAuthorizationEmptyEnvironment,
kAuthorizationFlagDefaults,
&auth);
// ...then extend authorization with desired rights
if ((validAuth == errAuthorizationSuccess) &&
(auth != NULL))
{
const char* executableFileSystemRepresentation = [executablePath fileSystemRepresentation];
// Prepare a right allowing script to execute with privileges
AuthorizationItem right;
memset(&right,0,sizeof(right));
right.name = kAuthorizationRightExecute;
right.value = (void*) executableFileSystemRepresentation;
right.valueLength = strlen(executableFileSystemRepresentation);
// Package up the single right
AuthorizationRights rights;
memset(&rights,0,sizeof(rights));
rights.count = 1;
rights.items = &right;
// Extend rights to run script
validAuth = AuthorizationCopyRights(auth,
&rights,
kAuthorizationEmptyEnvironment,
kAuthorizationFlagPreAuthorize |
kAuthorizationFlagExtendRights |
kAuthorizationFlagInteractionAllowed,
NULL);
if (validAuth != errAuthorizationSuccess)
{
// Error, clean up authorization
(void) AuthorizationFree(auth,kAuthorizationFlagDefaults);
auth = NULL;
}
}
return auth;
}
@end
@implementation SUGuidedPackageInstaller
+ (void)performInstallationToPath:(NSString *)destinationPath fromPath:(NSString *)packagePath host:(SUHost *)__unused host versionComparator:(id<SUVersionComparison>)__unused comparator completionHandler:(void (^)(NSError *))completionHandler
{
NSParameterAssert(packagePath);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Preflight
NSString* installerPath = @"/usr/sbin/installer"; // Mac OS X 10.2+ command line installer tool
NSError* error = nil;
// Create authorization for installer executable
BOOL validInstallation = NO;
AuthorizationRef auth = [self authorizationForExecutable:installerPath];
if (auth != NULL)
{
// Permission was granted to execute the installer with privileges
const char* const arguments[] = {
"-pkg",
[packagePath fileSystemRepresentation],
"-target",
"/",
NULL
};
validInstallation = AuthorizationExecuteWithPrivilegesAndWait(auth,
[installerPath fileSystemRepresentation],
kAuthorizationFlagDefaults,
arguments);
// TODO: wait for communications pipe to close via fileno & CFSocketCreateWithNative
AuthorizationFree(auth,kAuthorizationFlagDefaults);
}
else
{
NSString* errorMessage = [NSString stringWithFormat:@"Sparkle Updater: Script authorization denied."];
error = [NSError errorWithDomain:SUSparkleErrorDomain code:SUInstallationError userInfo:[NSDictionary dictionaryWithObject:errorMessage forKey:NSLocalizedDescriptionKey]];
}
dispatch_async(dispatch_get_main_queue(), ^{
[self finishInstallationToPath:destinationPath
withResult:validInstallation
error:error
completionHandler:completionHandler];
});
});
}
@end

View File

@ -18,7 +18,7 @@
- (instancetype)initWithBundle:(NSBundle *)aBundle;
@property (readonly, copy) NSString *bundlePath;
@property (readonly, copy) NSString *appSupportPath;
@property (readonly, copy) NSString *appCachePath;
@property (readonly, copy) NSString *installationPath;
@property (readonly, copy) NSString *name;
@property (readonly, copy) NSString *version;

View File

@ -66,21 +66,26 @@ typedef struct {
return [self.bundle bundlePath];
}
- (NSString *)appSupportPath
- (NSString *)appCachePath
{
NSArray *appSupportPaths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES);
NSString *appSupportPath = nil;
if (!appSupportPaths || [appSupportPaths count] == 0)
{
SULog(@"Failed to find app support directory! Using ~/Library/Application Support...");
appSupportPath = [@"~/Library/Application Support" stringByExpandingTildeInPath];
NSArray *cachePaths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSString *cachePath = nil;
if ([cachePaths count]) {
cachePath = cachePaths[0];
}
else {
appSupportPath = appSupportPaths[0];
if (!cachePath) {
SULog(@"Failed to find user's cache directory! Using system default");
cachePath = NSTemporaryDirectory();
}
appSupportPath = [appSupportPath stringByAppendingPathComponent:[self name]];
appSupportPath = [appSupportPath stringByAppendingPathComponent:@".Sparkle"];
return appSupportPath;
NSString *name = [self.bundle bundleIdentifier];
if (!name) {
name = [self name];
}
cachePath = [cachePath stringByAppendingPathComponent:name];
cachePath = [cachePath stringByAppendingPathComponent:@"Sparkle"];
return cachePath;
}
- (NSString *)installationPath
@ -97,7 +102,13 @@ typedef struct {
- (NSString *)name
{
NSString *name = [self.bundle objectForInfoDictionaryKey:@"CFBundleDisplayName"];
NSString *name;
// Allow host bundle to provide a custom name
name = [self objectForInfoDictionaryKey:@"SUBundleName"];
if (name) return name;
name = [self.bundle objectForInfoDictionaryKey:@"CFBundleDisplayName"];
if (name) return name;
name = [self objectForInfoDictionaryKey:(__bridge NSString *)kCFBundleNameKey];

View File

@ -12,21 +12,14 @@
#import <Cocoa/Cocoa.h>
#import "SUVersionComparisonProtocol.h"
@protocol SUInstallerDelegate;
@class SUHost;
@interface SUInstaller : NSObject
+ (NSString *)appPathInUpdateFolder:(NSString *)updateFolder forHost:(SUHost *)host;
+ (void)installFromUpdateFolder:(NSString *)updateFolder overHost:(SUHost *)host installationPath:(NSString *)installationPath delegate:(id<SUInstallerDelegate>)delegate versionComparator:(id<SUVersionComparison>)comparator;
+ (void)finishInstallationToPath:(NSString *)installationPath withResult:(BOOL)result host:(SUHost *)host error:(NSError *)error delegate:(id<SUInstallerDelegate>)delegate;
+ (void)installFromUpdateFolder:(NSString *)updateFolder overHost:(SUHost *)host installationPath:(NSString *)installationPath versionComparator:(id<SUVersionComparison>)comparator completionHandler:(void (^)(NSError *))completionHandler;
+ (void)finishInstallationToPath:(NSString *)installationPath withResult:(BOOL)result error:(NSError *)error completionHandler:(void (^)(NSError *))completionHandler;
+ (NSString *)updateFolder;
@end
@protocol SUInstallerDelegate <NSObject>
- (void)installerFinishedForHost:(SUHost *)host;
- (void)installerForHost:(SUHost *)host failedWithError:(NSError *)error;
@end
#endif

View File

@ -9,11 +9,11 @@
#import "SUInstaller.h"
#import "SUPlainInstaller.h"
#import "SUPackageInstaller.h"
#import "SUGuidedPackageInstaller.h"
#import "SUHost.h"
#import "SUConstants.h"
#import "SULog.h"
@implementation SUInstaller
static NSString *sUpdateFolder = nil;
@ -42,50 +42,49 @@ static NSString *sUpdateFolder = nil;
return NO;
}
+ (NSString *)installSourcePathInUpdateFolder:(NSString *)inUpdateFolder forHost:(SUHost *)host isPackage:(BOOL *)isPackagePtr
+ (NSString *)installSourcePathInUpdateFolder:(NSString *)inUpdateFolder forHost:(SUHost *)host isPackage:(BOOL *)isPackagePtr isGuided:(BOOL *)isGuidedPtr
{
NSParameterAssert(inUpdateFolder);
NSParameterAssert(host);
// Search subdirectories for the application
NSString *currentFile,
*newAppDownloadPath = nil,
*bundleFileName = [[host bundlePath] lastPathComponent],
*alternateBundleFileName = [[host name] stringByAppendingPathExtension:[[host bundlePath] pathExtension]];
BOOL isPackage = NO;
BOOL isGuided = NO;
NSString *fallbackPackagePath = nil;
NSDirectoryEnumerator *dirEnum = [[NSFileManager defaultManager] enumeratorAtPath:inUpdateFolder];
NSString *bundleFileNameNoExtension = [bundleFileName stringByDeletingPathExtension];
sUpdateFolder = inUpdateFolder;
while ((currentFile = [dirEnum nextObject]))
{
while ((currentFile = [dirEnum nextObject])) {
NSString *currentPath = [inUpdateFolder stringByAppendingPathComponent:currentFile];
if ([[currentFile lastPathComponent] isEqualToString:bundleFileName] ||
[[currentFile lastPathComponent] isEqualToString:alternateBundleFileName]) // We found one!
NSString *currentFilename = [currentFile lastPathComponent];
NSString *currentExtension = [currentFile pathExtension];
NSString *currentFilenameNoExtension = [currentFilename stringByDeletingPathExtension];
if ([currentFilename isEqualToString:bundleFileName] ||
[currentFilename isEqualToString:alternateBundleFileName]) // We found one!
{
isPackage = NO;
newAppDownloadPath = currentPath;
break;
}
else if ([[currentFile pathExtension] isEqualToString:@"pkg"] ||
[[currentFile pathExtension] isEqualToString:@"mpkg"])
{
if ([[[currentFile lastPathComponent] stringByDeletingPathExtension] isEqualToString:[bundleFileName stringByDeletingPathExtension]])
{
} else if ([currentExtension isEqualToString:@"pkg"] ||
[currentExtension isEqualToString:@"mpkg"]) {
if ([currentFilenameNoExtension isEqualToString:bundleFileNameNoExtension]) {
isPackage = YES;
newAppDownloadPath = currentPath;
break;
}
else
{
} else {
// Remember any other non-matching packages we have seen should we need to use one of them as a fallback.
fallbackPackagePath = currentPath;
}
}
else
{
} else {
// Try matching on bundle identifiers in case the user has changed the name of the host app
NSBundle *incomingBundle = [NSBundle bundleWithPath:currentPath];
if(incomingBundle && [[incomingBundle bundleIdentifier] isEqualToString:[[host bundle] bundleIdentifier]])
{
if (incomingBundle && [[incomingBundle bundleIdentifier] isEqualToString:[[host bundle] bundleIdentifier]]) {
isPackage = NO;
newAppDownloadPath = currentPath;
break;
@ -99,35 +98,52 @@ static NSString *sUpdateFolder = nil;
// We don't have a valid path. Try to use the fallback package.
if (newAppDownloadPath == nil && fallbackPackagePath != nil)
{
if (newAppDownloadPath == nil && fallbackPackagePath != nil) {
isPackage = YES;
newAppDownloadPath = fallbackPackagePath;
}
if (isPackagePtr) *isPackagePtr = isPackage;
if (isPackage) {
// foo.app -> foo.sparkle_guided.pkg or foo.sparkle_guided.mpkg
if ([[[newAppDownloadPath stringByDeletingPathExtension] pathExtension] isEqualToString:@"sparkle_guided"]) {
isGuided = YES;
}
}
if (isPackagePtr)
*isPackagePtr = isPackage;
if (isGuidedPtr)
*isGuidedPtr = isGuided;
if (!newAppDownloadPath) {
SULog(@"Searched %@ for %@.(app|pkg)", inUpdateFolder, bundleFileNameNoExtension);
}
return newAppDownloadPath;
}
+ (NSString *)appPathInUpdateFolder:(NSString *)updateFolder forHost:(SUHost *)host
{
BOOL isPackage = NO;
NSString *path = [self installSourcePathInUpdateFolder:updateFolder forHost:host isPackage:&isPackage];
NSString *path = [self installSourcePathInUpdateFolder:updateFolder forHost:host isPackage:&isPackage isGuided:nil];
return isPackage ? nil : path;
}
+ (void)installFromUpdateFolder:(NSString *)inUpdateFolder overHost:(SUHost *)host installationPath:(NSString *)installationPath delegate:(id<SUInstallerDelegate>)delegate versionComparator:(id<SUVersionComparison>)comparator
+ (void)installFromUpdateFolder:(NSString *)inUpdateFolder overHost:(SUHost *)host installationPath:(NSString *)installationPath versionComparator:(id<SUVersionComparison>)comparator completionHandler:(void (^)(NSError *))completionHandler
{
BOOL isPackage = NO;
NSString *newAppDownloadPath = [self installSourcePathInUpdateFolder:inUpdateFolder forHost:host isPackage:&isPackage];
BOOL isGuided = NO;
NSString *newAppDownloadPath = [self installSourcePathInUpdateFolder:inUpdateFolder forHost:host isPackage:&isPackage isGuided:&isGuided];
if (newAppDownloadPath == nil)
{
[self finishInstallationToPath:installationPath withResult:NO host:host error:[NSError errorWithDomain:SUSparkleErrorDomain code:SUMissingUpdateError userInfo:@{ NSLocalizedDescriptionKey: @"Couldn't find an appropriate update in the downloaded package." }] delegate:delegate];
if (newAppDownloadPath == nil) {
[self finishInstallationToPath:installationPath withResult:NO error:[NSError errorWithDomain:SUSparkleErrorDomain code:SUMissingUpdateError userInfo:@{ NSLocalizedDescriptionKey: @"Couldn't find an appropriate update in the downloaded package." }] completionHandler:completionHandler];
} else {
if (isPackage && isGuided) {
[SUGuidedPackageInstaller performInstallationToPath:installationPath fromPath:newAppDownloadPath host:host versionComparator:comparator completionHandler:completionHandler];
} else if (isPackage) {
[SUPackageInstaller performInstallationToPath:installationPath fromPath:newAppDownloadPath host:host versionComparator:comparator completionHandler:completionHandler];
} else {
[SUPlainInstaller performInstallationToPath:installationPath fromPath:newAppDownloadPath host:host versionComparator:comparator completionHandler:completionHandler];
}
else
{
[(isPackage ? [SUPackageInstaller class] : [SUPlainInstaller class])performInstallationToPath:installationPath fromPath:newAppDownloadPath host:host delegate:delegate versionComparator:comparator];
}
}
@ -140,8 +156,7 @@ static NSString *sUpdateFolder = nil;
NSTask *mdimport = [[NSTask alloc] init];
[mdimport setLaunchPath:@"/usr/bin/mdimport"];
[mdimport setArguments:@[installationPath]];
@try
{
@try {
[mdimport launch];
[mdimport waitUntilExit];
}
@ -152,25 +167,21 @@ static NSString *sUpdateFolder = nil;
}
}
+ (void)finishInstallationToPath:(NSString *)installationPath withResult:(BOOL)result host:(SUHost *)host error:(NSError *)error delegate:(id<SUInstallerDelegate>)delegate
+ (void)finishInstallationToPath:(NSString *)installationPath withResult:(BOOL)result error:(NSError *)error completionHandler:(void (^)(NSError *))completionHandler
{
if (result)
{
if (result) {
[self mdimportInstallationPath:installationPath];
if ([delegate respondsToSelector:@selector(installerFinishedForHost:)]) {
dispatch_async(dispatch_get_main_queue(), ^{
[delegate installerFinishedForHost:host];
completionHandler(nil);
});
} else {
if (!error) {
error = [NSError errorWithDomain:SUSparkleErrorDomain code:SUInstallationError userInfo:nil];
}
}
else
{
if ([delegate respondsToSelector:@selector(installerForHost:failedWithError:)]) {
dispatch_async(dispatch_get_main_queue(), ^{
[delegate installerForHost:host failedWithError:error];
completionHandler(error);
});
}
}
}
@end

View File

@ -12,7 +12,8 @@
Your tech support will hug you if you tell them about this.
*/
#pragma once
#ifndef SULOG_H
#define SULOG_H
// -----------------------------------------------------------------------------
// Headers:
@ -28,4 +29,4 @@
void SUClearLog(void);
void SULog(NSString *format, ...) NS_FORMAT_FUNCTION(1, 2);
#endif

View File

@ -30,10 +30,6 @@ static NSString *const SULogFilePath = @"~/Library/Logs/SparkleUpdateLog.log";
//
// TAKES:
// sender - Object that sent this message, typically of type X.
//
// GIVES:
// param - who owns the returned value?
// result - same here.
// -----------------------------------------------------------------------------
void SUClearLog(void)
@ -41,6 +37,7 @@ void SUClearLog(void)
FILE *logfile = fopen([[SULogFilePath stringByExpandingTildeInPath] fileSystemRepresentation], "w");
if (logfile) {
fclose(logfile);
SULog(@"===== %@ =====", [[NSFileManager defaultManager] displayNameAtPath:[[NSBundle mainBundle] bundlePath]]);
}
}
@ -57,6 +54,12 @@ void SUClearLog(void)
void SULog(NSString *format, ...)
{
static BOOL loggedYet = NO;
if (!loggedYet) {
loggedYet = YES;
SUClearLog();
}
va_list ap;
va_start(ap, format);
NSString *theStr = [[NSString alloc] initWithFormat:format arguments:ap];

View File

@ -12,7 +12,7 @@
@implementation SUPackageInstaller
+ (void)performInstallationToPath:(NSString *)installationPath fromPath:(NSString *)path host:(SUHost *)host delegate:(id<SUInstallerDelegate>)delegate versionComparator:(id<SUVersionComparison>)__unused comparator
+ (void)performInstallationToPath:(NSString *)installationPath fromPath:(NSString *)path host:(SUHost *)__unused host versionComparator:(id<SUVersionComparison>)__unused comparator completionHandler:(void (^)(NSError *))completionHandler
{
// Run installer using the "open" command to ensure it is launched in front of current application.
// -W = wait until the app has quit.
@ -23,7 +23,7 @@
if (![[NSFileManager defaultManager] fileExistsAtPath:command]) {
NSError *error = [NSError errorWithDomain:SUSparkleErrorDomain code:SUMissingInstallerToolError userInfo:@{ NSLocalizedDescriptionKey: @"Couldn't find Apple's installer tool!" }];
[self finishInstallationToPath:installationPath withResult:NO host:host error:error delegate:delegate];
[self finishInstallationToPath:installationPath withResult:NO error:error completionHandler:completionHandler];
return;
}
@ -33,7 +33,7 @@
// Known bug: if the installation fails or is canceled, Sparkle goes ahead and restarts, thinking everything is fine.
dispatch_async(dispatch_get_main_queue(), ^{
[self finishInstallationToPath:installationPath withResult:YES host:host error:nil delegate:delegate];
[self finishInstallationToPath:installationPath withResult:YES error:nil completionHandler:completionHandler];
});
});
}

View File

@ -22,7 +22,10 @@
@".tar.gz": @"extractTGZ",
@".tgz": @"extractTGZ",
@".tar.bz2": @"extractTBZ",
@".tbz": @"extractTBZ" };
@".tbz": @"extractTBZ",
@".tar.xz": @"extractTXZ",
@".txz": @"extractTXZ",
@".tar.lzma": @"extractTXZ"};
NSString *lastPathComponent = [path lastPathComponent];
for (NSString *currentType in typeSelectorDictionary)
@ -45,102 +48,105 @@
}
// This method abstracts the types that use a command line tool piping data from stdin.
- (void)extractArchivePipingDataToCommand:(NSString *)command
- (void)extractArchivePipingDataToCommand:(NSString *)command args:(NSArray*)args
{
// *** GETS CALLED ON NON-MAIN THREAD!!!
@autoreleasepool {
FILE *fp = NULL, *cmdFP = NULL;
char *oldDestinationString = NULL;
// We have to declare these before a goto to prevent an error under ARC.
// No, we cannot have them in the dispatch_async calls, as the goto "jump enters
// lifetime of block which strongly captures a variable"
dispatch_block_t delegateSuccess = ^{
[self notifyDelegateOfSuccess];
};
dispatch_block_t delegateFailure = ^{
[self notifyDelegateOfFailure];
};
SULog(@"Extracting %@ using '%@'", self.archivePath, command);
NSString *destination = [self.archivePath stringByDeletingLastPathComponent];
SULog(@"Extracting using '%@' '%@' < '%@' '%@'", command, [args componentsJoinedByString:@"' '"], self.archivePath, destination);
// Get the file size.
NSNumber *fs = [[NSFileManager defaultManager] attributesOfItemAtPath:self.archivePath error:nil][NSFileSize];
if (fs == nil) goto reportError;
NSUInteger expectedLength = [[[NSFileManager defaultManager] attributesOfItemAtPath:self.archivePath error:nil][NSFileSize] unsignedIntegerValue];
if (expectedLength > 0) {
NSFileHandle *archiveInput = [NSFileHandle fileHandleForReadingAtPath:self.archivePath];
// Thank you, Allan Odgaard!
// (who wrote the following extraction alg.)
fp = fopen([self.archivePath fileSystemRepresentation], "r");
if (!fp) goto reportError;
NSPipe *pipe = [NSPipe pipe];
NSFileHandle *archiveOutput = [pipe fileHandleForWriting];
oldDestinationString = getenv("DESTINATION");
setenv("DESTINATION", [[self.archivePath stringByDeletingLastPathComponent] fileSystemRepresentation], 1);
cmdFP = popen([command fileSystemRepresentation], "w");
size_t written;
if (!cmdFP) goto reportError;
NSTask *task = [[NSTask alloc] init];
[task setStandardInput:[pipe fileHandleForReading]];
[task setStandardError:[NSFileHandle fileHandleWithStandardError]];
[task setStandardOutput:[NSFileHandle fileHandleWithStandardOutput]];
[task setLaunchPath:command];
[task setArguments:[args arrayByAddingObject:destination]];
[task launch];
char buf[32 * 1024];
size_t len;
while((len = fread(buf, 1, 32*1024, fp)))
{
written = fwrite(buf, 1, len, cmdFP);
if( written < len )
{
pclose(cmdFP);
goto reportError;
NSUInteger bytesRead = 0;
do {
NSData *data = [archiveInput readDataOfLength:256*1024];
NSUInteger len = [data length];
if (!len) {
break;
}
bytesRead += len;
[archiveOutput writeData:data];
dispatch_async(dispatch_get_main_queue(), ^{
[self notifyDelegateOfProgress:(double)bytesRead / (double)expectedLength];
});
}
while(bytesRead < expectedLength);
[archiveOutput closeFile];
[task waitUntilExit];
if ([task terminationStatus] == 0) {
if (bytesRead == expectedLength) {
dispatch_async(dispatch_get_main_queue(), ^{
[self notifyDelegateOfSuccess];
});
return;
} else {
SULog(@"Extraction failed, command '%@' got only %ld of %ld bytes", command, (long)bytesRead, (long)expectedLength);
}
} else {
SULog(@"Extraction failed, command '%@' returned %d", command, [task terminationStatus]);
}
} else {
SULog(@"Extraction failed, archive '%@' is empty", self.archivePath);
}
dispatch_async(dispatch_get_main_queue(), ^{
[self notifyDelegateOfExtractedLength:len];
[self notifyDelegateOfFailure];
});
}
pclose(cmdFP);
if (ferror(fp)) {
goto reportError;
}
dispatch_async(dispatch_get_main_queue(), delegateSuccess);
goto finally;
reportError:
dispatch_async(dispatch_get_main_queue(), delegateFailure);
finally:
if (fp)
fclose(fp);
if (oldDestinationString)
setenv("DESTINATION", oldDestinationString, 1);
else
unsetenv("DESTINATION");
}
}
- (void)extractTAR
{
// *** GETS CALLED ON NON-MAIN THREAD!!!
[self extractArchivePipingDataToCommand:@"tar -xC \"$DESTINATION\""];
[self extractArchivePipingDataToCommand:@"/usr/bin/tar" args:@[@"-xC"]];
}
- (void)extractTGZ
{
// *** GETS CALLED ON NON-MAIN THREAD!!!
[self extractArchivePipingDataToCommand:@"tar -zxC \"$DESTINATION\""];
[self extractArchivePipingDataToCommand:@"/usr/bin/tar" args:@[@"-zxC"]];
}
- (void)extractTBZ
{
// *** GETS CALLED ON NON-MAIN THREAD!!!
[self extractArchivePipingDataToCommand:@"tar -jxC \"$DESTINATION\""];
[self extractArchivePipingDataToCommand:@"/usr/bin/tar" args:@[@"-jxC"]];
}
- (void)extractZIP
{
// *** GETS CALLED ON NON-MAIN THREAD!!!
[self extractArchivePipingDataToCommand:@"ditto -x -k - \"$DESTINATION\""];
[self extractArchivePipingDataToCommand:@"/usr/bin/ditto" args:@[@"-x",@"-k",@"-"]];
}
- (void)extractTXZ
{
// *** GETS CALLED ON NON-MAIN THREAD!!!
[self extractArchivePipingDataToCommand:@"/usr/bin/tar" args:@[@"-zxC"]];
}
+ (void)load

View File

@ -21,7 +21,7 @@
@interface SUPlainInstaller : SUInstaller
+ (void)performInstallationToPath:(NSString *)installationPath fromPath:(NSString *)path host:(SUHost *)host delegate:(id<SUInstallerDelegate>)delegate versionComparator:(id<SUVersionComparison>)comparator;
+ (void)performInstallationToPath:(NSString *)installationPath fromPath:(NSString *)path host:(SUHost *)host versionComparator:(id<SUVersionComparison>)comparator completionHandler:(void (^)(NSError *))completionHandler;
@end

View File

@ -8,20 +8,22 @@
#import "SUPlainInstaller.h"
#import "SUPlainInstallerInternals.h"
#import "SUCodeSigningVerifier.h"
#import "SUConstants.h"
#import "SUHost.h"
@implementation SUPlainInstaller
+ (void)performInstallationToPath:(NSString *)installationPath fromPath:(NSString *)path host:(SUHost *)host delegate:(id<SUInstallerDelegate>)delegate versionComparator:(id<SUVersionComparison>)comparator
+ (void)performInstallationToPath:(NSString *)installationPath fromPath:(NSString *)path host:(SUHost *)host versionComparator:(id<SUVersionComparison>)comparator completionHandler:(void (^)(NSError *))completionHandler
{
NSParameterAssert(host);
// Prevent malicious downgrades
if (![[[NSBundle bundleWithIdentifier:SUBundleIdentifier] infoDictionary][SUEnableAutomatedDowngradesKey] boolValue]) {
if ([comparator compareVersion:[host version] toVersion:[[NSBundle bundleWithPath:path] objectForInfoDictionaryKey:(__bridge NSString *)kCFBundleVersionKey]] == NSOrderedDescending)
{
if ([comparator compareVersion:[host version] toVersion:[[NSBundle bundleWithPath:path] objectForInfoDictionaryKey:(__bridge NSString *)kCFBundleVersionKey]] == NSOrderedDescending) {
NSString *errorMessage = [NSString stringWithFormat:@"Sparkle Updater: Possible attack in progress! Attempting to \"upgrade\" from %@ to %@. Aborting update.", [host version], [[NSBundle bundleWithPath:path] objectForInfoDictionaryKey:(__bridge NSString *)kCFBundleVersionKey]];
NSError *error = [NSError errorWithDomain:SUSparkleErrorDomain code:SUDowngradeError userInfo:@{ NSLocalizedDescriptionKey: errorMessage }];
[self finishInstallationToPath:installationPath withResult:NO host:host error:error delegate:delegate];
[self finishInstallationToPath:installationPath withResult:NO error:error completionHandler:completionHandler];
return;
}
}
@ -30,9 +32,18 @@
NSError *error = nil;
NSString *oldPath = [host bundlePath];
NSString *tempName = [self temporaryNameForPath:[host installationPath]];
BOOL hostIsCodeSigned = [SUCodeSigningVerifier applicationAtPathIsCodeSigned:oldPath];
BOOL result = [self copyPathWithAuthentication:path overPath:installationPath temporaryName:tempName error:&error];
if (result) {
// If the host is code signed, then the replacement should be be too (and the signature should be valid).
BOOL needToCheckCodeSignature = (hostIsCodeSigned || [SUCodeSigningVerifier applicationAtPathIsCodeSigned:installationPath]);
if (needToCheckCodeSignature) {
result = [SUCodeSigningVerifier codeSignatureIsValidAtPath:installationPath error:&error];
}
}
if (result) {
BOOL haveOld = [[NSFileManager defaultManager] fileExistsAtPath:oldPath];
BOOL differentFromNew = ![oldPath isEqualToString:installationPath];
@ -42,7 +53,7 @@
}
dispatch_async(dispatch_get_main_queue(), ^{
[self finishInstallationToPath:installationPath withResult:result host:host error:error delegate:delegate];
[self finishInstallationToPath:installationPath withResult:result error:error completionHandler:completionHandler];
});
});
}

View File

@ -599,7 +599,11 @@ static BOOL AuthorizationExecuteWithPrivilegesAndWait(AuthorizationRef authoriza
}
#endif
NSURL *rootURL = [NSURL fileURLWithPath:root];
id rootResourceValue = nil;
[rootURL getResourceValue:&rootResourceValue forKey:NSURLQuarantinePropertiesKey error:NULL];
if (rootResourceValue) {
[rootURL setResourceValue:[NSNull null] forKey:NSURLQuarantinePropertiesKey error:NULL];
}
// Only recurse if it's actually a directory. Don't recurse into a
// root-level symbolic link.
@ -612,9 +616,13 @@ static BOOL AuthorizationExecuteWithPrivilegesAndWait(AuthorizationRef authoriza
NSDirectoryEnumerator *directoryEnumerator = [manager enumeratorAtURL:rootURL includingPropertiesForKeys:nil options:(NSDirectoryEnumerationOptions)0 errorHandler:nil];
for (NSURL *file in directoryEnumerator) {
id fileResourceValue = nil;
[file getResourceValue:&fileResourceValue forKey:NSURLQuarantinePropertiesKey error:NULL];
if (fileResourceValue) {
[file setResourceValue:[NSNull null] forKey:NSURLQuarantinePropertiesKey error:NULL];
}
}
}
}
@end

View File

@ -43,10 +43,18 @@
- (void)abortUpdateWithError:(NSError *)error
{
if (self.showErrors)
if (self.showErrors) {
[super abortUpdateWithError:error];
else
} else {
// Call delegate separately here because otherwise it won't know we stopped.
// Normally this gets called by the superclass
id<SUUpdaterDelegate> updaterDelegate = [self.updater delegate];
if ([updaterDelegate respondsToSelector:@selector(updater:didAbortWithError:)]) {
[updaterDelegate updater:self.updater didAbortWithError:error];
}
[self abortUpdate];
}
}
@end

View File

@ -31,7 +31,7 @@
- (instancetype)initWithHost:(SUHost *)aHost
{
self = [super initWithHost:aHost windowNibName:@"SUStatus"];
self = [super initWithWindowNibName:@"SUStatus"];
if (self)
{
self.host = aHost;

View File

@ -15,7 +15,7 @@
@class SUStatusController;
@interface SUUIBasedUpdateDriver : SUBasicUpdateDriver <SUUnarchiverDelegate, SUUpdateAlertDelegate>
@interface SUUIBasedUpdateDriver : SUBasicUpdateDriver <SUUnarchiverDelegate>
- (void)showModalAlert:(NSAlert *)alert;
- (IBAction)cancelDownload:(id)sender;

View File

@ -28,8 +28,9 @@
- (void)didFindValidUpdate
{
self.updateAlert = [[SUUpdateAlert alloc] initWithAppcastItem:self.updateItem host:self.host];
[self.updateAlert setDelegate:self];
self.updateAlert = [[SUUpdateAlert alloc] initWithAppcastItem:self.updateItem host:self.host completionBlock:^(SUUpdateAlertChoice choice) {
[self updateAlertFinishedWithChoice:choice];
}];
id<SUVersionDisplay> versDisp = nil;
if ([[self.updater delegate] respondsToSelector:@selector(versionDisplayerForUpdater:)]) {
@ -44,8 +45,7 @@
// If the app is a menubar app or the like, we need to focus it first and alter the
// update prompt to behave like a normal window. Otherwise if the window were hidden
// there may be no way for the application to be activated to make it visible again.
if ([self.host isBackgroundApplication])
{
if ([self.host isBackgroundApplication]) {
[[self.updateAlert window] setHidesOnDeactivate:NO];
[NSApp activateIgnoringOtherApps:YES];
}
@ -78,12 +78,11 @@
[[NSNotificationCenter defaultCenter] removeObserver:self name:NSApplicationDidBecomeActiveNotification object:NSApp];
}
- (void)updateAlert:(SUUpdateAlert *)__unused alert finishedWithChoice:(SUUpdateAlertChoice)choice
- (void)updateAlertFinishedWithChoice:(SUUpdateAlertChoice)choice
{
self.updateAlert = nil;
[self.host setObject:nil forUserDefaultsKey:SUSkippedVersionKey];
switch (choice)
{
switch (choice) {
case SUInstallUpdateChoice:
self.statusController = [[SUStatusController alloc] initWithHost:self.host];
[self.statusController beginActionWithTitle:SULocalizedString(@"Downloading update...", @"Take care not to overflow the status window.") maxProgressValue:0.0 statusText:nil];
@ -154,16 +153,13 @@
[super extractUpdate];
}
- (void)unarchiver:(SUUnarchiver *)__unused ua extractedLength:(unsigned long)length
- (void)unarchiver:(SUUnarchiver *)__unused ua extractedProgress:(double)progress
{
// We do this here instead of in extractUpdate so that we only have a determinate progress bar for archives with progress.
if ([self.statusController maxProgressValue] == 0.0)
{
NSDictionary *attributes;
attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:self.downloadPath error:nil];
[self.statusController setMaxProgressValue:[attributes[NSFileSize] doubleValue]];
if ([self.statusController maxProgressValue] == 0.0) {
[self.statusController setMaxProgressValue:1];
}
[self.statusController setProgressValue:[self.statusController progressValue] + (double)length];
[self.statusController setProgressValue:progress];
}
- (void)unarchiverDidFinish:(SUUnarchiver *)__unused ua

View File

@ -15,10 +15,10 @@
@interface SUUnarchiver : NSObject
@property (copy, readonly) NSString *archivePath;
@property (weak, readonly) SUHost *updateHost;
@property (copy, readonly) NSString *updateHostBundlePath;
@property (weak) id<SUUnarchiverDelegate> delegate;
+ (SUUnarchiver *)unarchiverForPath:(NSString *)path updatingHost:(SUHost *)host;
+ (SUUnarchiver *)unarchiverForPath:(NSString *)path updatingHostBundlePath:(NSString *)host;
- (void)start;
@end
@ -27,7 +27,7 @@
- (void)unarchiverDidFinish:(SUUnarchiver *)unarchiver;
- (void)unarchiverDidFail:(SUUnarchiver *)unarchiver;
@optional
- (void)unarchiver:(SUUnarchiver *)unarchiver extractedLength:(unsigned long)length;
- (void)unarchiver:(SUUnarchiver *)unarchiver extractedProgress:(double)progress;
@end
#endif

View File

@ -18,15 +18,15 @@
@implementation SUUnarchiver
@synthesize archivePath;
@synthesize updateHost;
@synthesize updateHostBundlePath;
@synthesize delegate;
+ (SUUnarchiver *)unarchiverForPath:(NSString *)path updatingHost:(SUHost *)host
+ (SUUnarchiver *)unarchiverForPath:(NSString *)path updatingHostBundlePath:(NSString *)hostPath
{
for (id current in [self unarchiverImplementations])
{
if ([current canUnarchivePath:path]) {
return [[current alloc] initWithPath:path host:host];
return [[current alloc] initWithPath:path hostBundlePath:hostPath];
}
}
return nil;
@ -39,12 +39,12 @@
// No-op
}
- (instancetype)initWithPath:(NSString *)path host:(SUHost *)host
- (instancetype)initWithPath:(NSString *)path hostBundlePath:(NSString *)hostPath
{
if ((self = [super init]))
{
archivePath = [path copy];
updateHost = host;
updateHostBundlePath = hostPath;
}
return self;
}
@ -54,10 +54,10 @@
return NO;
}
- (void)notifyDelegateOfExtractedLength:(size_t)length
- (void)notifyDelegateOfProgress:(double)progress
{
if ([self.delegate respondsToSelector:@selector(unarchiver:extractedLength:)]) {
[self.delegate unarchiver:self extractedLength:length];
if ([self.delegate respondsToSelector:@selector(unarchiver:extractedProgress:)]) {
[self.delegate unarchiver:self extractedProgress:progress];
}
}

View File

@ -16,9 +16,9 @@
+ (void)registerImplementation:(Class)implementation;
+ (NSArray *)unarchiverImplementations;
+ (BOOL)canUnarchivePath:(NSString *)path;
- (instancetype)initWithPath:(NSString *)path host:(SUHost *)host;
- (instancetype)initWithPath:(NSString *)archive hostBundlePath:(NSString *)host;
- (void)notifyDelegateOfExtractedLength:(size_t)length;
- (void)notifyDelegateOfProgress:(double)progress;
- (void)notifyDelegateOfSuccess;
- (void)notifyDelegateOfFailure;
@end

View File

@ -24,10 +24,9 @@ typedef NS_ENUM(NSInteger, SUUpdateAlertChoice) {
@class WebView, SUAppcastItem, SUHost;
@interface SUUpdateAlert : SUWindowController
@property (weak) id<SUUpdateAlertDelegate> delegate;
@property (weak) id<SUVersionDisplay> versionDisplayer;
- (instancetype)initWithAppcastItem:(SUAppcastItem *)item host:(SUHost *)host;
- (instancetype)initWithAppcastItem:(SUAppcastItem *)item host:(SUHost *)host completionBlock:(void(^)(SUUpdateAlertChoice))c;
- (IBAction)installUpdate:sender;
- (IBAction)skipThisVersion:sender;
@ -35,10 +34,4 @@ typedef NS_ENUM(NSInteger, SUUpdateAlertChoice) {
@end
@protocol SUUpdateAlertDelegate <NSObject>
- (void)updateAlert:(SUUpdateAlert *)updateAlert finishedWithChoice:(SUUpdateAlertChoice)updateChoice;
@optional
- (void)updateAlert:(SUUpdateAlert *)updateAlert shouldAllowAutoUpdate:(BOOL *)shouldAllowAutoUpdate;
@end
#endif

View File

@ -21,6 +21,7 @@
@property (strong) SUAppcastItem *updateItem;
@property (strong) SUHost *host;
@property (strong) void(^completionBlock)(SUUpdateAlertChoice);
@property (strong) NSProgressIndicator *releaseNotesSpinner;
@property (assign) BOOL webViewFinishedLoading;
@ -36,7 +37,7 @@
@implementation SUUpdateAlert
@synthesize delegate;
@synthesize completionBlock;
@synthesize versionDisplayer;
@synthesize updateItem;
@ -52,11 +53,12 @@
@synthesize skipButton;
@synthesize laterButton;
- (instancetype)initWithAppcastItem:(SUAppcastItem *)item host:(SUHost *)aHost
- (instancetype)initWithAppcastItem:(SUAppcastItem *)item host:(SUHost *)aHost completionBlock:(void (^)(SUUpdateAlertChoice))block
{
self = [super initWithHost:host windowNibName:@"SUUpdateAlert"];
self = [super initWithWindowNibName:@"SUUpdateAlert"];
if (self)
{
self.completionBlock = block;
host = aHost;
updateItem = item;
[self setShouldCascadeWindows:NO];
@ -79,8 +81,8 @@
[self.releaseNotesView setPolicyDelegate:nil];
[self.releaseNotesView removeFromSuperview]; // Otherwise it gets sent Esc presses (why?!) and gets very confused.
[self close];
if ([self.delegate respondsToSelector:@selector(updateAlert:finishedWithChoice:)])
[self.delegate updateAlert:self finishedWithChoice:choice];
self.completionBlock(choice);
self.completionBlock = nil;
}
- (IBAction)installUpdate:(id)__unused sender
@ -164,10 +166,6 @@
if ([self.host objectForInfoDictionaryKey:SUAllowsAutomaticUpdatesKey])
allowAutoUpdates = [self.host boolForInfoDictionaryKey:SUAllowsAutomaticUpdatesKey];
// Give delegate a chance to modify this choice:
if (self.delegate && [self.delegate respondsToSelector:@selector(updateAlert:shouldAllowAutoUpdate:)])
[self.delegate updateAlert:self shouldAllowAutoUpdate:&allowAutoUpdates];
return allowAutoUpdates;
}

View File

@ -45,7 +45,7 @@
- (instancetype)initWithHost:(SUHost *)aHost systemProfile:(NSArray *)profile delegate:(id<SUUpdatePermissionPromptDelegate>)d
{
self = [super initWithHost:aHost windowNibName:@"SUUpdatePermissionPrompt"];
self = [super initWithWindowNibName:@"SUUpdatePermissionPrompt"];
if (self)
{
host = aHost;
@ -65,8 +65,11 @@
// the user would not know why the application was paused.
if ([aHost isBackgroundApplication]) { [[NSApplication sharedApplication] activateIgnoringOtherApps:YES]; }
if( ![NSApp modalWindow]) // do not prompt if there is is another modal window on screen
{
SUUpdatePermissionPrompt *prompt = [[[self class] alloc] initWithHost:aHost systemProfile:profile delegate:d];
[NSApp runModalForWindow:[prompt window]];
}
}
- (NSString *)description { return [NSString stringWithFormat:@"%@ <%@>", [self class], [self.host bundlePath]]; }

View File

@ -25,7 +25,7 @@
*/
SU_EXPORT @interface SUUpdater : NSObject
@property (weak) IBOutlet id<SUUpdaterDelegate> delegate;
@property (unsafe_unretained) IBOutlet id<SUUpdaterDelegate> delegate;
+ (SUUpdater *)sharedUpdater;
+ (SUUpdater *)updaterForBundle:(NSBundle *)bundle;
@ -46,6 +46,8 @@ SU_EXPORT @interface SUUpdater : NSObject
@property (nonatomic, copy) NSString *userAgentString;
@property (copy) NSDictionary *httpHeaders;
@property BOOL sendsSystemProfile;
@property BOOL automaticallyDownloadsUpdates;
@ -312,6 +314,14 @@ SU_EXPORT extern NSString *const SUUpdaterAppcastNotificationKey;
*/
- (void)updater:(SUUpdater *)updater didCancelInstallUpdateOnQuit:(SUAppcastItem *)item;
/*!
Called after an update is aborted due to an error.
\param updater The SUUpdater instance.
\param error The error that caused the abort
*/
- (void)updater:(SUUpdater *)updater didAbortWithError:(NSError *)error;
@end
#endif

View File

@ -51,12 +51,10 @@ NSString *const SUUpdaterAppcastNotificationKey = @"SUUpdaterAppCastNotification
@synthesize delegate;
@synthesize checkTimer;
@synthesize userAgentString = customUserAgentString;
@synthesize httpHeaders;
@synthesize driver;
@synthesize host;
#pragma mark Initialization
static NSMutableDictionary *sharedUpdaters = nil;
static NSString *const SUUpdaterDefaultsObservationContext = @"SUUpdaterDefaultsObservationContext";
@ -147,38 +145,28 @@ static NSString *const SUUpdaterDefaultsObservationContext = @"SUUpdaterDefaults
- (void)startUpdateCycle
{
BOOL shouldPrompt = NO;
BOOL hasLaunchedBefore = [self.host boolForUserDefaultsKey:SUHasLaunchedBeforeKey];
// If the user has been asked about automatic checks, don't bother prompting
if ([self.host objectForUserDefaultsKey:SUEnableAutomaticChecksKey])
{
if ([self.host objectForUserDefaultsKey:SUEnableAutomaticChecksKey]) {
shouldPrompt = NO;
}
// Does the delegate want to take care of the logic for when we should ask permission to update?
else if ([self.delegate respondsToSelector:@selector(updaterShouldPromptForPermissionToCheckForUpdates:)])
{
else if ([self.delegate respondsToSelector:@selector(updaterShouldPromptForPermissionToCheckForUpdates:)]) {
shouldPrompt = [self.delegate updaterShouldPromptForPermissionToCheckForUpdates:self];
}
// Has he been asked already? And don't ask if the host has a default value set in its Info.plist.
else if ([self.host objectForKey:SUEnableAutomaticChecksKey] == nil)
{
if ([self.host objectForUserDefaultsKey:SUEnableAutomaticChecksKeyOld]) {
[self setAutomaticallyChecksForUpdates:[self.host boolForUserDefaultsKey:SUEnableAutomaticChecksKeyOld]];
}
else if ([self.host objectForKey:SUEnableAutomaticChecksKey] == nil) {
// Now, we don't want to ask the user for permission to do a weird thing on the first launch.
// We wait until the second launch, unless explicitly overridden via SUPromptUserOnFirstLaunchKey.
else if (![self.host objectForKey:SUPromptUserOnFirstLaunchKey])
{
if ([self.host boolForUserDefaultsKey:SUHasLaunchedBeforeKey] == NO)
[self.host setBool:YES forUserDefaultsKey:SUHasLaunchedBeforeKey];
else
shouldPrompt = YES;
}
else
shouldPrompt = YES;
shouldPrompt = [self.host objectForKey:SUPromptUserOnFirstLaunchKey] || hasLaunchedBefore;
}
if (shouldPrompt)
{
if (!hasLaunchedBefore) {
[self.host setBool:YES forUserDefaultsKey:SUHasLaunchedBeforeKey];
}
if (shouldPrompt) {
NSArray *profileInfo = [self.host systemProfile];
// Always say we're sending the system profile here so that the delegate displays the parameters it would send.
if ([self.delegate respondsToSelector:@selector(feedParametersForUpdater:sendingSystemProfile:)]) {
@ -186,9 +174,7 @@ static NSString *const SUUpdaterDefaultsObservationContext = @"SUUpdaterDefaults
}
[SUUpdatePermissionPrompt promptWithHost:self.host systemProfile:profileInfo delegate:self];
// We start the update checks and register as observer for changes after the prompt finishes
}
else
{
} else {
// We check if the user's said they want updates, or they haven't said anything, and the default is set to checking.
[self scheduleNextUpdateCheck];
}
@ -334,9 +320,6 @@ static NSString *const SUUpdaterDefaultsObservationContext = @"SUUpdaterDefaults
if ([self updateInProgress]) { return; }
if (self.checkTimer) { [self.checkTimer invalidate]; self.checkTimer = nil; } // Timer is non-repeating, may have invalidated itself, so we had to retain it.
SUClearLog();
SULog(@"===== %@ =====", [[NSFileManager defaultManager] displayNameAtPath:[[NSBundle mainBundle] bundlePath]]);
[self willChangeValueForKey:@"lastUpdateCheckDate"];
[self.host setObject:[NSDate date] forUserDefaultsKey:SULastCheckTimeKey];
[self didChangeValueForKey:@"lastUpdateCheckDate"];

View File

@ -78,16 +78,6 @@
[super abortUpdate];
}
- (void)appcast:(SUAppcast *)ac failedToLoadWithError:(NSError *)error
{
if (self.isCanceled)
{
[self abortUpdate];
return;
}
[super appcast:ac failedToLoadWithError:error];
}
- (BOOL)itemContainsValidUpdate:(SUAppcastItem *)ui
{
// We don't check to see if this update's been skipped, because the user explicitly *asked* if he had the latest version.

View File

@ -14,7 +14,7 @@
@class SUHost;
@interface SUWindowController : NSWindowController
// We use this instead of plain old NSWindowController initWithWindowNibName so that we'll be able to find the right path when running in a bundle loaded from another app.
- (instancetype)initWithHost:(SUHost *)host windowNibName:(NSString *)nibName;
- (instancetype)initWithWindowNibName:(NSString *)nibName;
@end
#endif

View File

@ -11,16 +11,9 @@
@implementation SUWindowController
- (instancetype)initWithHost:(SUHost *)host windowNibName:(NSString *)nibName
- (instancetype)initWithWindowNibName:(NSString *)nibName
{
NSString *path = [[NSBundle bundleForClass:[self class]] pathForResource:nibName ofType:@"nib"];
if (path == nil) // Slight hack to resolve issues with running Sparkle in debug configurations.
{
NSString *frameworkPath = [[[host bundle] sharedFrameworksPath] stringByAppendingPathComponent:@"Sparkle.framework"];
NSBundle *framework = [NSBundle bundleWithPath:frameworkPath];
path = [framework pathForResource:nibName ofType:@"nib"];
}
self = [super initWithWindowNibPath:path owner:self];
self = [super initWithWindowNibName:nibName owner:self];
return self;
}

View File

@ -18,5 +18,6 @@
#import <Sparkle/SUUpdater.h>
#import <Sparkle/SUVersionComparisonProtocol.h>
#import <Sparkle/SUVersionDisplayProtocol.h>
#import <Sparkle/SUErrors.h>
#endif

View File

@ -10,6 +10,7 @@
#import <Cocoa/Cocoa.h>
#import "SUConstants.h"
#import "SUErrors.h"
#define SULocalizedString(key, comment) NSLocalizedStringFromTableInBundle(key, @"Sparkle", [NSBundle bundleWithIdentifier:SUBundleIdentifier] ? [NSBundle bundleWithIdentifier:SUBundleIdentifier] : [NSBundle mainBundle], comment)

View File

@ -44,7 +44,7 @@ DQ
<button verticalHuggingPriority="750" id="14">
<rect key="frame" x="138" y="12" width="117" height="32"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES"/>
<buttonCell key="cell" type="push" title="Don't Check" bezelStyle="rounded" alignment="center" borderStyle="border" inset="2" id="177">
<buttonCell key="cell" type="push" title="Dont Check" bezelStyle="rounded" alignment="center" borderStyle="border" inset="2" id="177">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
<string key="keyEquivalent" base64-UTF8="YES">

View File

@ -96,7 +96,7 @@ Gw
<button id="17">
<rect key="frame" x="105" y="58" width="619" height="18"/>
<autoresizingMask key="autoresizingMask"/>
<buttonCell key="cell" type="check" title="Automatiquement télécharger et installer les mises à jour à lavenir" bezelStyle="regularSquare" imagePosition="left" alignment="left" controlSize="small" inset="2" id="43">
<buttonCell key="cell" type="check" title="Télécharger et installer automatiquement les mises à jour" bezelStyle="regularSquare" imagePosition="left" alignment="left" controlSize="small" inset="2" id="43">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="smallSystem"/>
</buttonCell>

View File

@ -173,7 +173,7 @@ DQ
</textField>
<button translatesAutoresizingMaskIntoConstraints="NO" id="117">
<rect key="frame" x="106" y="49" width="495" height="18"/>
<buttonCell key="cell" type="check" title="Automatiquement télécharger et installer les mises à jour à lavenir" bezelStyle="regularSquare" imagePosition="left" alignment="left" controlSize="small" inset="2" id="175">
<buttonCell key="cell" type="check" title="Télécharger et installer automatiquement les mises à jour" bezelStyle="regularSquare" imagePosition="left" alignment="left" controlSize="small" inset="2" id="175">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="smallSystem"/>
</buttonCell>

View File

@ -97,7 +97,7 @@ Gw
<button id="17">
<rect key="frame" x="105" y="58" width="382" height="18"/>
<autoresizingMask key="autoresizingMask"/>
<buttonCell key="cell" type="check" title="Sempre baixar e instalar atualizações automaticamente" bezelStyle="regularSquare" imagePosition="left" alignment="left" controlSize="small" inset="2" id="43">
<buttonCell key="cell" type="check" title="Transferir e instalar atualizações futuras automaticamente" bezelStyle="regularSquare" imagePosition="left" alignment="left" controlSize="small" inset="2" id="43">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="smallSystem"/>
</buttonCell>

View File

@ -24,7 +24,7 @@
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
<windowPositionMask key="initialPositionMask" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="998" y="728" width="622" height="370"/>
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1578"/>
<rect key="screenRect" x="0.0" y="0.0" width="1920" height="1057"/>
<value key="minSize" type="size" width="550" height="150"/>
<value key="maxSize" type="size" width="700" height="600"/>
<view key="contentView" id="6">
@ -57,7 +57,7 @@
<binding destination="-2" name="value" keyPath="titleText" id="11"/>
</connections>
</textField>
<textField verticalHuggingPriority="1000" horizontalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="17">
<textField verticalHuggingPriority="1000" horizontalCompressionResistancePriority="1000" misplaced="YES" translatesAutoresizingMaskIntoConstraints="NO" id="17">
<rect key="frame" x="106" y="294" width="118" height="14"/>
<constraints>
<constraint firstAttribute="width" relation="greaterThanOrEqual" priority="100" constant="100" id="gkn-FW-9aC"/>
@ -75,7 +75,7 @@
</binding>
</connections>
</textField>
<button horizontalHuggingPriority="150" verticalHuggingPriority="750" horizontalCompressionResistancePriority="997" translatesAutoresizingMaskIntoConstraints="NO" id="22">
<button horizontalHuggingPriority="150" verticalHuggingPriority="750" horizontalCompressionResistancePriority="997" misplaced="YES" translatesAutoresizingMaskIntoConstraints="NO" id="22">
<rect key="frame" x="336" y="12" width="108" height="32"/>
<constraints>
<constraint firstAttribute="width" relation="greaterThanOrEqual" priority="222" constant="90" id="2Zy-5G-GBj"/>
@ -92,8 +92,8 @@ Gw
</connections>
</button>
<button verticalHuggingPriority="750" horizontalCompressionResistancePriority="998" translatesAutoresizingMaskIntoConstraints="NO" id="23">
<rect key="frame" x="103" y="12" width="147" height="32"/>
<buttonCell key="cell" type="push" title="Pular Esta Versão" bezelStyle="rounded" alignment="center" borderStyle="border" inset="2" id="172">
<rect key="frame" x="103" y="12" width="161" height="32"/>
<buttonCell key="cell" type="push" title="Ignorar Esta Versão" bezelStyle="rounded" alignment="center" borderStyle="border" inset="2" id="172">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
@ -101,7 +101,7 @@ Gw
<action selector="skipThisVersion:" target="-2" id="33"/>
</connections>
</button>
<button horizontalHuggingPriority="20" verticalHuggingPriority="750" horizontalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="76">
<button horizontalHuggingPriority="20" verticalHuggingPriority="750" horizontalCompressionResistancePriority="1000" misplaced="YES" translatesAutoresizingMaskIntoConstraints="NO" id="76">
<rect key="frame" x="444" y="12" width="164" height="32"/>
<constraints>
<constraint firstAttribute="width" relation="greaterThanOrEqual" priority="222" constant="120" id="N94-uf-6oP"/>
@ -173,7 +173,7 @@ DQ
</textField>
<button translatesAutoresizingMaskIntoConstraints="NO" id="117">
<rect key="frame" x="106" y="49" width="497" height="18"/>
<buttonCell key="cell" type="check" title="Sempre baixar e instalar atualizações automaticamente" bezelStyle="regularSquare" imagePosition="left" alignment="left" controlSize="small" inset="2" id="175">
<buttonCell key="cell" type="check" title="Transferir e instalar atualizações futuras automaticamente" bezelStyle="regularSquare" imagePosition="left" alignment="left" controlSize="small" inset="2" id="175">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="smallSystem"/>
</buttonCell>

View File

@ -140,13 +140,13 @@ Gw
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<subviews>
<textField verticalHuggingPriority="750" id="46">
<rect key="frame" x="1" y="128" width="358" height="70"/>
<rect key="frame" x="1" y="128" width="355" height="70"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" sendsActionOnEndEditing="YES" id="183">
<font key="font" metaFont="smallSystem"/>
<string key="title">Informações anônimas do sistema são utilizadas para nos ajudar a planejar o desenvolvimento futuro do aplicativo. Caso você tenha dúvidas sobre esse procedimento, por favor contate-nos.
<string key="title">As informações anônimas do sistema são usadas para nos ajudar a planejar o desenvolvimento futuro do aplicativo. Contate-nos caso tenha dúvidas sobre este procedimento.
A seguinte informação seria enviada:</string>
As seguintes informações seriam enviadas:</string>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
@ -156,7 +156,7 @@ A seguinte informação seria enviada:</string>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<clipView key="contentView" id="Xyl-4B-4C7">
<rect key="frame" x="1" y="1" width="353" height="113"/>
<autoresizingMask key="autoresizingMask"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<tableView focusRingType="none" verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" alternatingRowBackgroundColors="YES" multipleSelection="NO" autosaveColumns="NO" typeSelect="NO" rowHeight="14" id="41">
<rect key="frame" x="0.0" y="0.0" width="353" height="113"/>

View File

@ -1,12 +1,12 @@
"%1$@ %2$@ has been downloaded and is ready to use! Would you like to install it and relaunch %1$@ now?" = "%1$@ %2$@ foi baixado e está pronto para uso! Gostaria de instalar e reiniciar o %1$@ agora?";
"%1$@ %2$@ has been downloaded and is ready to use! Would you like to install it and relaunch %1$@ now?" = "%1$@ %2$@ foi transferido e está pronto para uso! Deseja instalar e reiniciar o %1$@ agora?";
"%1$@ can't be updated when it's running from a read-only volume like a disk image or an optical drive. Move %1$@ to your Applications folder, relaunch it from there, and try again." = "%1$@ não pode ser atualizado enquanto for executado a partir de um volume somente de leitura, como uma imagem de disco ou CD/DVD. Mova %1$@ para a pasta Aplicativos, reinicie-o e tente novamente.";
"%1$@ can't be updated when it's running from a read-only volume like a disk image or an optical drive. Move %1$@ to your Applications folder, relaunch it from there, and try again." = "%1$@ não pode ser atualizado enquanto for executado a partir de um volume somente de leitura, como uma imagem de disco ou CD/DVD. Mova o %1$@ para a pasta Aplicativos, reinicie-o e tente novamente.";
"%@ %@ is currently the newest version available." = "%1$@ %2$@ é a versão mais recente disponível.";
"%@ %@ is now available--you have %@. Would you like to download it now?" = "%1$@ %2$@ está disponível--sua versão é %3$@. Gostaria de baixá-lo agora?";
"%@ %@ is now available--you have %@. Would you like to download it now?" = "%1$@ %2$@ está disponível sua versão é %3$@. Deseja transferi-lo agora?";
"%@ downloaded" = "%@ baixado";
"%@ downloaded" = "%@ transferidos";
"%@ of %@" = "%1$@ de %2$@";
@ -14,15 +14,15 @@
"A new version of %@ is ready to install!" = "Uma nova versão do %@ está pronta para ser instalada!";
"An error occurred in retrieving update information. Please try again later." = "Ocorreu um erro ao buscar atualizações. Tente novamente mais tarde.";
"An error occurred in retrieving update information. Please try again later." = "Ocorreu um erro ao obter informações da atualização. Tente novamente mais tarde.";
"An error occurred while downloading the update. Please try again later." = "Ocorreu um erro ao baixar a atualização. Tente novamente mais tarde.";
"An error occurred while downloading the update. Please try again later." = "Ocorreu um erro ao transferir a atualização. Tente novamente mais tarde.";
"An error occurred while extracting the archive. Please try again later." = "Ocorreu um erro ao extrair o arquivo. Tente novamente mais tarde.";
"An error occurred while installing the update. Please try again later." = "Ocorreu um erro ao instalar a atualização. Tente novamente mais tarde.";
"An error occurred while parsing the update feed." = "Ocorreu um erro ao analisar a fonte de atualização.";
"An error occurred while parsing the update feed." = "Ocorreu um erro ao analisar o feed de atualização.";
"An error occurred while relaunching %1$@, but the new version will be available next time you run %1$@." = "Ocorreu um erro ao reiniciar o %1$@. A nova versão estará disponível da próxima vez que você abrir o %1$@.";
@ -36,7 +36,7 @@
"Checking for updates..." = "Buscando atualizações…";
/* Take care not to overflow the status window. */
"Downloading update..." = "Baixando atualização…";
"Downloading update..." = "Transferindo atualização…";
/* Take care not to overflow the status window. */
"Extracting update..." = "Extraindo atualização…";
@ -59,9 +59,9 @@
"Ready to Install" = "Pronto para Instalar";
"Should %1$@ automatically check for updates? You can always check for updates manually from the %1$@ menu." = "Gostaria que %1$@ buscasse atualizações automaticamente? Você pode buscar por atualizações manualmente, através do menu %1$@.";
"Should %1$@ automatically check for updates? You can always check for updates manually from the %1$@ menu." = "Deseja que o %1$@ busque atualizações automaticamente? Você pode buscar atualizações manualmente, através do menu %1$@.";
"Update Error!" = "Erro na Atualização!";
"Update Error!" = "Erro de Atualização!";
"Updating %@" = "Atualizando %@";

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="5056" systemVersion="13D65" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" promptedForUpgradeToXcode5="NO">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="6245" systemVersion="13F34" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none">
<dependencies>
<development version="5100" identifier="xcode"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="5056"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="6245"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="SUAutomaticUpdateAlert">
@ -11,7 +11,7 @@
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<window allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" id="5" userLabel="Window">
<windowStyleMask key="styleMask" titled="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
@ -96,7 +96,7 @@ Gw
<button id="17">
<rect key="frame" x="105" y="58" width="619" height="18"/>
<autoresizingMask key="autoresizingMask"/>
<buttonCell key="cell" type="check" title="Ladda automatiskt ned och installera nya uppdateringar i framtiden." bezelStyle="regularSquare" imagePosition="left" alignment="left" controlSize="small" inset="2" id="43">
<buttonCell key="cell" type="check" title="Hämta och installera nya uppdateringar automatiskt i framtiden." bezelStyle="regularSquare" imagePosition="left" alignment="left" controlSize="small" inset="2" id="43">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="smallSystem"/>
</buttonCell>

View File

@ -1,9 +1,10 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="6154.17" systemVersion="13E25" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="6250" systemVersion="14A389" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies>
<deployment identifier="macosx"/>
<development version="5100" identifier="xcode"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="6154.17"/>
<plugIn identifier="com.apple.WebKitIBPlugin" version="6154.17"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="6250"/>
<plugIn identifier="com.apple.WebKitIBPlugin" version="6250"/>
<capability name="Aspect ratio constraints" minToolsVersion="5.1"/>
<capability name="Constraints with non-1.0 multipliers" minToolsVersion="5.1"/>
</dependencies>
@ -19,12 +20,12 @@
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<window identifier="SUUpdateAlert" title="Programuppdatering" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" hidesOnDeactivate="YES" releasedWhenClosed="NO" visibleAtLaunch="NO" frameAutosaveName="SUUpdateAlert" animationBehavior="default" id="5" userLabel="Update Alert (release notes)">
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
<windowPositionMask key="initialPositionMask" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="998" y="728" width="670" height="370"/>
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1578"/>
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1417"/>
<value key="minSize" type="size" width="550" height="150"/>
<value key="maxSize" type="size" width="700" height="600"/>
<view key="contentView" id="6">
@ -58,11 +59,11 @@
</connections>
</textField>
<textField verticalHuggingPriority="1000" horizontalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="17">
<rect key="frame" x="106" y="294" width="87" height="14"/>
<rect key="frame" x="106" y="294" width="120" height="14"/>
<constraints>
<constraint firstAttribute="width" relation="greaterThanOrEqual" priority="100" constant="100" id="gkn-FW-9aC"/>
</constraints>
<textFieldCell key="cell" lineBreakMode="truncatingTail" allowsUndo="NO" sendsActionOnEndEditing="YES" title="Release Notes:" usesSingleLineMode="YES" id="170">
<textFieldCell key="cell" lineBreakMode="truncatingTail" allowsUndo="NO" sendsActionOnEndEditing="YES" title="Versionsinformation:" usesSingleLineMode="YES" id="170">
<font key="font" metaFont="smallSystemBold"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
@ -76,7 +77,7 @@
</connections>
</textField>
<button horizontalHuggingPriority="150" verticalHuggingPriority="750" horizontalCompressionResistancePriority="997" translatesAutoresizingMaskIntoConstraints="NO" id="22">
<rect key="frame" x="317" y="12" width="160" height="32"/>
<rect key="frame" x="323" y="12" width="158" height="32"/>
<constraints>
<constraint firstAttribute="width" relation="greaterThanOrEqual" priority="222" constant="90" id="2Zy-5G-GBj"/>
</constraints>
@ -92,7 +93,7 @@ Gw
</connections>
</button>
<button verticalHuggingPriority="750" horizontalCompressionResistancePriority="998" translatesAutoresizingMaskIntoConstraints="NO" id="23">
<rect key="frame" x="103" y="12" width="205" height="32"/>
<rect key="frame" x="103" y="12" width="203" height="32"/>
<buttonCell key="cell" type="push" title="Hoppa över denna version" bezelStyle="rounded" alignment="center" borderStyle="border" inset="2" id="172">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
@ -102,7 +103,7 @@ Gw
</connections>
</button>
<button horizontalHuggingPriority="20" verticalHuggingPriority="750" horizontalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="76">
<rect key="frame" x="477" y="12" width="179" height="32"/>
<rect key="frame" x="481" y="12" width="175" height="32"/>
<constraints>
<constraint firstAttribute="width" relation="greaterThanOrEqual" priority="222" constant="120" id="N94-uf-6oP"/>
</constraints>
@ -173,7 +174,7 @@ DQ
</textField>
<button translatesAutoresizingMaskIntoConstraints="NO" id="117">
<rect key="frame" x="106" y="49" width="545" height="18"/>
<buttonCell key="cell" type="check" title="Ladda automatiskt ned och installera nya uppdateringar i framtiden." bezelStyle="regularSquare" imagePosition="left" alignment="left" controlSize="small" inset="2" id="175">
<buttonCell key="cell" type="check" title="Hämta och installera nya uppdateringar automatiskt i framtiden." bezelStyle="regularSquare" imagePosition="left" alignment="left" controlSize="small" inset="2" id="175">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="smallSystem"/>
</buttonCell>
@ -222,7 +223,7 @@ DQ
<connections>
<outlet property="delegate" destination="-2" id="50"/>
</connections>
<point key="canvasLocation" x="411" y="321.5"/>
<point key="canvasLocation" x="411" y="321"/>
</window>
<userDefaultsController representsSharedInstance="YES" id="93" userLabel="Shared Defaults"/>
</objects>

View File

@ -1,9 +1,8 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="5056" systemVersion="13D65" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" promptedForUpgradeToXcode5="NO">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="6245" systemVersion="13F34" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none">
<dependencies>
<deployment defaultVersion="1070" identifier="macosx"/>
<development version="5100" identifier="xcode"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="5056"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="6245"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="SUUpdatePermissionPrompt">
@ -16,7 +15,7 @@
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<window allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" id="5" userLabel="Profile Info">
<windowStyleMask key="styleMask" titled="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
@ -60,7 +59,7 @@ Gw
<autoresizingMask key="autoresizingMask" flexibleMinY="YES"/>
<textFieldCell key="cell" sendsActionOnEndEditing="YES" id="178">
<font key="font" metaFont="systemBold"/>
<string key="title">Sök efter uppdateringar automatiskt?
<string key="title">Leta efter uppdateringar automatiskt?
</string>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
@ -161,7 +160,7 @@ Detta är informationen som skulle sändas:</string>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<tableView focusRingType="none" verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" alternatingRowBackgroundColors="YES" multipleSelection="NO" autosaveColumns="NO" typeSelect="NO" rowHeight="14" id="41">
<rect key="frame" x="0.0" y="0.0" width="353" height="113"/>
<rect key="frame" x="0.0" y="0.0" width="353" height="16"/>
<autoresizingMask key="autoresizingMask"/>
<size key="intercellSpacing" width="3" height="2"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>

View File

@ -1,12 +1,12 @@
"%1$@ %2$@ has been downloaded and is ready to use! Would you like to install it and relaunch %1$@ now?" = "%1$@ %2$@ har laddats ned och är klar att använda! Vill du installera det och starta %1$@ nu?";
"%1$@ %2$@ has been downloaded and is ready to use! Would you like to install it and relaunch %1$@ now?" = "%1$@ %2$@ har hämtats och är klar att använda! Vill du installera det och starta om %1$@ nu?";
"%1$@ can't be updated when it's running from a read-only volume like a disk image or an optical drive. Move %1$@ to your Applications folder, relaunch it from there, and try again." = "%1$@ kan inte uppdateras när det körs från en skrivskyddad volym som en skivavbild eller en optisk enhet. Flytta %1$@ till mappen Program, starta om den därifrån, och försök igen.";
"%1$@ can't be updated when it's running from a read-only volume like a disk image or an optical drive. Move %1$@ to your Applications folder, relaunch it from there, and try again." = "%1$@ kan inte uppdateras när det körs från en skrivskyddad volym som en skivavbild eller en optisk enhet. Flytta %1$@ till mappen Program, starta om det därifrån, och försök igen.";
"%@ %@ is currently the newest version available." = "%1$@ %2$@ är för närvarande den senaste tillgängliga versionen.";
"%@ %@ is now available--you have %@. Would you like to download it now?" = "%1$@ %2$@ är nu tillgänglig—du har %3$@. Vill du ladda ned nu?";
"%@ %@ is now available--you have %@. Would you like to download it now?" = "%1$@ %2$@ finns nu tillgänglig—du har %3$@. Vill hämta uppdateringen nu?";
"%@ downloaded" = "%@ nedladdat";
"%@ downloaded" = "%@ hämtat";
"%@ of %@" = "%1$@ av %2$@";
@ -24,7 +24,7 @@
"An error occurred while parsing the update feed." = "Ett fel uppstod vid tolkning av uppdateringslänken.";
"An error occurred while relaunching %1$@, but the new version will be available next time you run %1$@." = "Ett fel uppstod vid omstart %1$@, men den nya versionen kommer att finnas tillgänglig nästa gång du kör %1$@.";
"An error occurred while relaunching %1$@, but the new version will be available next time you run %1$@." = "Ett fel uppstod när %1$@ skulle startas om, men den nya versionen kommer att finnas tillgänglig nästa gång du kör %1$@.";
/* the unit for bytes */
"B" = "B";
@ -33,10 +33,10 @@
"Cancel Update" = "Avbryt uppdatering";
"Checking for updates..." = "Söker efter uppdateringar…";
"Checking for updates..." = "Letar efter uppdateringar…";
/* Take care not to overflow the status window. */
"Downloading update..." = "Laddar ned uppdatering…";
"Downloading update..." = "Hämtar uppdatering…";
/* Take care not to overflow the status window. */
"Extracting update..." = "Extraherar uppdatering…";
@ -59,17 +59,17 @@
"Ready to Install" = "Redo att installera";
"Should %1$@ automatically check for updates? You can always check for updates manually from the %1$@ menu." = "Bör %1$@ automatiskt söka efter uppdateringar? Du kan alltid söka efter uppdateringar manuellt från %1$@ menyn.";
"Should %1$@ automatically check for updates? You can always check for updates manually from the %1$@ menu." = "Ska %1$@ leta efter uppdateringar automatiskt? Du kan alltid leta efter uppdateringar manuellt från %1$@-menyn.";
"Update Error!" = "Uppdateringsfel!";
"Updating %@" = "Uppdaterar %@";
"You already have the newest version of %@." = "Du har redan den nyaste versionen av %@.";
"You already have the newest version of %@." = "Du har redan den senaste versionen av %@.";
"You're up-to-date!" = "Du är uppdaterad!";
/* Alternative name for "Install" button if we have a paid update or other update
without a download but with a URL. */
"Learn More..." = "Learn More…";
"Learn More..." = "Mer info…";

View File

@ -57,7 +57,7 @@
<button verticalHuggingPriority="750" id="15">
<rect key="frame" x="502" y="12" width="164" height="32"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES"/>
<buttonCell key="cell" type="push" title="Kur ve tekrar başlat" bezelStyle="rounded" alignment="center" borderStyle="border" inset="2" id="41">
<buttonCell key="cell" type="push" title="Kur ve yeniden başlat" bezelStyle="rounded" alignment="center" borderStyle="border" inset="2" id="41">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
<string key="keyEquivalent" base64-UTF8="YES">
@ -96,7 +96,7 @@ Gw
<button id="17">
<rect key="frame" x="105" y="58" width="419" height="18"/>
<autoresizingMask key="autoresizingMask"/>
<buttonCell key="cell" type="check" title="Bundan sonra Güncellemeleri otomatik olarak indir ve kur" bezelStyle="regularSquare" imagePosition="left" alignment="left" controlSize="small" inset="2" id="43">
<buttonCell key="cell" type="check" title="Bundan sonra güncellemeleri kendiliğinden indir ve kur" bezelStyle="regularSquare" imagePosition="left" alignment="left" controlSize="small" inset="2" id="43">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="smallSystem"/>
</buttonCell>

View File

@ -173,7 +173,7 @@ DQ
</textField>
<button translatesAutoresizingMaskIntoConstraints="NO" id="117">
<rect key="frame" x="106" y="49" width="495" height="18"/>
<buttonCell key="cell" type="check" title="Bundan sonra Güncellemeleri otomatik olarak indir ve kur" bezelStyle="regularSquare" imagePosition="left" alignment="left" controlSize="small" inset="2" id="175">
<buttonCell key="cell" type="check" title="Bundan sonra güncellemeleri kendiliğinden indir ve kur" bezelStyle="regularSquare" imagePosition="left" alignment="left" controlSize="small" inset="2" id="175">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="smallSystem"/>
</buttonCell>

View File

@ -30,7 +30,7 @@
<button verticalHuggingPriority="750" tag="1" id="13">
<rect key="frame" x="256" y="12" width="168" height="32"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES"/>
<buttonCell key="cell" type="push" title="Otomatik olarak Ara" bezelStyle="rounded" alignment="center" state="on" borderStyle="border" tag="1" inset="2" id="163">
<buttonCell key="cell" type="push" title="Otomatik olarak ara" bezelStyle="rounded" alignment="center" state="on" borderStyle="border" tag="1" inset="2" id="163">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
<string key="keyEquivalent" base64-UTF8="YES">
@ -58,7 +58,7 @@ Gw
<textField verticalHuggingPriority="750" id="32">
<rect key="frame" x="104" y="114" width="315" height="34"/>
<autoresizingMask key="autoresizingMask" flexibleMinY="YES"/>
<textFieldCell key="cell" sendsActionOnEndEditing="YES" title="Otomatik olarak güncelleme Aransınmı?" id="165">
<textFieldCell key="cell" sendsActionOnEndEditing="YES" title="Güncellemeler kendiliğinden aransın mı?" id="165">
<font key="font" metaFont="systemBold"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
@ -79,7 +79,7 @@ Gw
<button focusRingType="none" id="34">
<rect key="frame" x="104" y="53" width="278" height="18"/>
<autoresizingMask key="autoresizingMask" flexibleMinY="YES"/>
<buttonCell key="cell" type="check" title="isimsiz Sistem-Bilgilerini ulaştır" bezelStyle="regularSquare" imagePosition="left" alignment="left" controlSize="small" state="on" focusRingType="none" inset="2" id="167">
<buttonCell key="cell" type="check" title="Sistem bilgilerini anonim olarak gönder" bezelStyle="regularSquare" imagePosition="left" alignment="left" controlSize="small" state="on" focusRingType="none" inset="2" id="167">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="smallSystem"/>
</buttonCell>
@ -142,9 +142,7 @@ Gw
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMinY="YES"/>
<textFieldCell key="cell" sendsActionOnEndEditing="YES" id="170">
<font key="font" metaFont="smallSystem"/>
<string key="title">Gönderdiğiniz isimsiz Sistem-Bilgileri bu Programın gelişimi için kullanılmatadır. Bu konu hakkında daha fazla Bilgi edinmek için, bizimle bağlantıya geçiniz.
Göndereceğiniz Bilgiler:</string>
<string key="title">Gönderdiğiniz anonim sistem bilgileri bu yazılımın geliştirilmesi için kullanılacaktır. Konu ile ilgili ayrıntılı bilgi için lütfen bizimle bağlantıya geçiniz. Göndereceğiniz Bilgiler:</string>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>

View File

@ -67,7 +67,7 @@
/* de_DE v0.1 - No comment provided by engineer. */
"Cancel Update" = "Güncellemeyi İptal Et";
"Cancel Update" = "Güncellemeyi iptal et";
/* de_DE v0.1 - No comment provided by engineer. */
@ -87,7 +87,7 @@
/* de_DE v0.1 - No comment provided by engineer. */
"Install and Relaunch" = "Kur ve Yeniden Başlat";
"Install and Relaunch" = "Kur ve yeniden başlat";
"Installing update..." = "Güncelleme kuruluyor...";
@ -104,13 +104,13 @@
"OK" = "Tamam";
"Ready to Install" = "Kuruluma Hazır";
"Ready to Install" = "Kuruluma hazır";
"Should %1$@ automatically check for updates? You can always check for updates manually from the %1$@ menu." = "%1$@ uygulaması için güncellemeler otomatik olarak aransın mı? Güncelleme işlemini el ile uygulama menüsünden de başlatabilirsiniz.";
"Update Error!" = "Güncelleme Hatası.";
"Update Error!" = "Güncelleme hatası.";
"Updating %@" = "%@ güncelleniyor";
@ -119,5 +119,5 @@
"You already have the newest version of %@." = "%@ uygulamasının en güncel sürümü sizde mevcut.";
"You're up to date!" = "Güncellemeye gerek yok.";
"You're up to date!" = "Uygulama güncel!";

View File

@ -1,8 +1,9 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="5056" systemVersion="13D65" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" promptedForUpgradeToXcode5="NO">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="6250" systemVersion="14A389" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none">
<dependencies>
<deployment identifier="macosx"/>
<development version="5100" identifier="xcode"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="5056"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="6250"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="SUAutomaticUpdateAlert">
@ -11,7 +12,7 @@
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<window allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" id="5" userLabel="Window">
<windowStyleMask key="styleMask" titled="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>

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="5056" systemVersion="13D65" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" promptedForUpgradeToXcode5="NO">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="6250" systemVersion="14A389" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none">
<dependencies>
<deployment defaultVersion="1070" identifier="macosx"/>
<deployment identifier="macosx"/>
<development version="5100" identifier="xcode"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="5056"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="6250"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="SUUpdatePermissionPrompt">
@ -16,7 +16,7 @@
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<window allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" id="5" userLabel="Profile Info">
<windowStyleMask key="styleMask" titled="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
@ -159,7 +159,7 @@ Gw
<autoresizingMask key="autoresizingMask"/>
<subviews>
<tableView focusRingType="none" verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnAutoresizingStyle="lastColumnOnly" alternatingRowBackgroundColors="YES" multipleSelection="NO" autosaveColumns="NO" typeSelect="NO" rowHeight="14" id="41">
<rect key="frame" x="0.0" y="0.0" width="353" height="113"/>
<rect key="frame" x="0.0" y="0.0" width="353" height="16"/>
<autoresizingMask key="autoresizingMask"/>
<size key="intercellSpacing" width="3" height="2"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>

View File

@ -14,15 +14,15 @@
"A new version of %@ is ready to install!" = "Нова версія %@ готова до встановлення!";
"An error occurred in retrieving update information. Please try again later." = "Виникла помилка при отриманні інформації про оновлення. Спробуйте ще пізніше.";
"An error occurred in retrieving update information. Please try again later." = "Виникла помилка при отриманні інформації про оновлення. Спробуйте ще раз пізніше.";
"An error occurred while downloading the update. Please try again later." = "Виникла помилка при завантаження оновлення. Спробуйте ще пізніше.";
"An error occurred while downloading the update. Please try again later." = "Виникла помилка при завантаження оновлення. Спробуйте ще раз пізніше.";
"An error occurred while extracting the archive. Please try again later." = "Виникла помилка при розпаковуванні архіву. Спробуйте ще пізніше.";
"An error occurred while extracting the archive. Please try again later." = "Виникла помилка при розпаковуванні архіву. Спробуйте ще раз пізніше.";
"An error occurred while installing the update. Please try again later." = "Виникла помилка при встановленні оновлення. Спробуйте ще пізніше.";
"An error occurred while installing the update. Please try again later." = "Виникла помилка при встановленні оновлення. Спробуйте ще раз пізніше.";
"An error occurred while parsing the update feed." = "Виникла помилка при розборі фіду оновлень.";
"An error occurred while parsing the update feed." = "Виникла помилка розбору каналу оновлень.";
"An error occurred while relaunching %1$@, but the new version will be available next time you run %1$@." = "Виникла помилка при перезавантаженні %1$@, але наступного разу при завантаження %1$@ буде доступна нова версія.";
@ -33,7 +33,7 @@
"Cancel Update" = "Відмінити оновлення";
"Checking for updates..." = "Перевіря наявність оновлень…";
"Checking for updates..." = "Перевіряю наявність оновлень…";
/* Take care not to overflow the status window. */
"Downloading update..." = "Завантажую оновлення…";
@ -65,7 +65,7 @@
"Updating %@" = "Оновлюю %@";
"You already have the newest version of %@." = "Ви вже маєте саму останню версію программи %@.";
"You already have the newest version of %@." = "Ви вже користуєтесь останньою версією програми %@.";
"You're up-to-date!" = "У вас остання версія!";

View File

@ -14,7 +14,7 @@
"A new version of %@ is ready to install!" = "新版本的 %@ 已準備安裝!";
"An error occurred in retrieving update information. Please try again later." = "擷取更新資訊時發生錯誤。請稍後再試一次。請稍後再試一次。";
"An error occurred in retrieving update information. Please try again later." = "擷取更新資訊時發生錯誤。請稍後再試一次。";
"An error occurred while downloading the update. Please try again later." = "下載更新項目時發生錯誤。請稍後再試一次。";

View File

@ -0,0 +1,283 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
5A195FB01983E85D000BE570 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 5A195FAF1983E85D000BE570 /* main.m */; };
5A195FB31983E85D000BE570 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 5A195FB21983E85D000BE570 /* AppDelegate.m */; };
5A195FB51983E85D000BE570 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5A195FB41983E85D000BE570 /* Images.xcassets */; };
5A195FB81983E85D000BE570 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5A195FB61983E85D000BE570 /* MainMenu.xib */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
5A195FAA1983E85D000BE570 /* Sparkle Updated Test App.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Sparkle Updated Test App.app"; sourceTree = BUILT_PRODUCTS_DIR; };
5A195FAE1983E85D000BE570 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
5A195FAF1983E85D000BE570 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
5A195FB11983E85D000BE570 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
5A195FB21983E85D000BE570 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
5A195FB41983E85D000BE570 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; };
5A195FB71983E85D000BE570 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
5A195FA71983E85D000BE570 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
5A195FA11983E85D000BE570 = {
isa = PBXGroup;
children = (
5A195FAC1983E85D000BE570 /* Sparkle Updated Test App */,
5A195FAB1983E85D000BE570 /* Products */,
);
sourceTree = "<group>";
};
5A195FAB1983E85D000BE570 /* Products */ = {
isa = PBXGroup;
children = (
5A195FAA1983E85D000BE570 /* Sparkle Updated Test App.app */,
);
name = Products;
sourceTree = "<group>";
};
5A195FAC1983E85D000BE570 /* Sparkle Updated Test App */ = {
isa = PBXGroup;
children = (
5A195FB11983E85D000BE570 /* AppDelegate.h */,
5A195FB21983E85D000BE570 /* AppDelegate.m */,
5A195FB61983E85D000BE570 /* MainMenu.xib */,
5A195FAD1983E85D000BE570 /* Supporting Files */,
);
path = "Sparkle Updated Test App";
sourceTree = "<group>";
};
5A195FAD1983E85D000BE570 /* Supporting Files */ = {
isa = PBXGroup;
children = (
5A195FB41983E85D000BE570 /* Images.xcassets */,
5A195FAE1983E85D000BE570 /* Info.plist */,
5A195FAF1983E85D000BE570 /* main.m */,
);
name = "Supporting Files";
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
5A195FA91983E85D000BE570 /* Sparkle Updated Test App */ = {
isa = PBXNativeTarget;
buildConfigurationList = 5A195FC71983E85D000BE570 /* Build configuration list for PBXNativeTarget "Sparkle Updated Test App" */;
buildPhases = (
5A195FA61983E85D000BE570 /* Sources */,
5A195FA71983E85D000BE570 /* Frameworks */,
5A195FA81983E85D000BE570 /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = "Sparkle Updated Test App";
productName = "Sparkle Updated Test App";
productReference = 5A195FAA1983E85D000BE570 /* Sparkle Updated Test App.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
5A195FA21983E85D000BE570 /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 0600;
ORGANIZATIONNAME = "Sparkle-project";
TargetAttributes = {
5A195FA91983E85D000BE570 = {
CreatedOnToolsVersion = 6.0;
};
};
};
buildConfigurationList = 5A195FA51983E85D000BE570 /* Build configuration list for PBXProject "Sparkle Updated Test App" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 5A195FA11983E85D000BE570;
productRefGroup = 5A195FAB1983E85D000BE570 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
5A195FA91983E85D000BE570 /* Sparkle Updated Test App */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
5A195FA81983E85D000BE570 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
5A195FB51983E85D000BE570 /* Images.xcassets in Resources */,
5A195FB81983E85D000BE570 /* MainMenu.xib in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
5A195FA61983E85D000BE570 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
5A195FB31983E85D000BE570 /* AppDelegate.m in Sources */,
5A195FB01983E85D000BE570 /* main.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXVariantGroup section */
5A195FB61983E85D000BE570 /* MainMenu.xib */ = {
isa = PBXVariantGroup;
children = (
5A195FB71983E85D000BE570 /* Base */,
);
name = MainMenu.xib;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
5A195FC51983E85D000BE570 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "-";
COPY_PHASE_STRIP = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.7;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = macosx;
};
name = Debug;
};
5A195FC61983E85D000BE570 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_IDENTITY = "-";
COPY_PHASE_STRIP = YES;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.7;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = macosx;
};
name = Release;
};
5A195FC81983E85D000BE570 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = Sparkle;
COMBINE_HIDPI_IMAGES = YES;
INFOPLIST_FILE = "Sparkle Updated Test App/Info.plist";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Debug;
};
5A195FC91983E85D000BE570 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = Sparkle;
COMBINE_HIDPI_IMAGES = YES;
INFOPLIST_FILE = "Sparkle Updated Test App/Info.plist";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks";
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
5A195FA51983E85D000BE570 /* Build configuration list for PBXProject "Sparkle Updated Test App" */ = {
isa = XCConfigurationList;
buildConfigurations = (
5A195FC51983E85D000BE570 /* Debug */,
5A195FC61983E85D000BE570 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
5A195FC71983E85D000BE570 /* Build configuration list for PBXNativeTarget "Sparkle Updated Test App" */ = {
isa = XCConfigurationList;
buildConfigurations = (
5A195FC81983E85D000BE570 /* Debug */,
5A195FC91983E85D000BE570 /* Release */,
);
defaultConfigurationIsVisible = 0;
};
/* End XCConfigurationList section */
};
rootObject = 5A195FA21983E85D000BE570 /* Project object */;
}

View File

@ -0,0 +1,15 @@
//
// AppDelegate.h
// Sparkle Updated Test App
//
// Created by Kornel on 26/07/2014.
// Copyright (c) 2014 Sparkle-project. All rights reserved.
//
#import <Cocoa/Cocoa.h>
@interface AppDelegate : NSObject <NSApplicationDelegate>
@end

View File

@ -0,0 +1,27 @@
//
// AppDelegate.m
// Sparkle Updated Test App
//
// Created by Kornel on 26/07/2014.
// Copyright (c) 2014 Sparkle-project. All rights reserved.
//
#import "AppDelegate.h"
@interface AppDelegate ()
@end
@implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
NSRunAlertPanel(@"Update succeeded!", @"This is the updated version of Sparkle Test App.\n\nDelete and rebuild the app to test updates again.", @"OK", nil, nil);
NSURL *bundleURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] bundlePath]];
if (bundleURL) {
[[NSWorkspace sharedWorkspace] activateFileViewerSelectingURLs: @[bundleURL]];
}
[NSApp terminate:self];
}
@end

View File

@ -0,0 +1,52 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="6185.7" systemVersion="14A298i" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies>
<deployment defaultVersion="1070" identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="6185.7"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="NSApplication">
<connections>
<outlet property="delegate" destination="Voe-Tx-rLC" id="GzC-gU-4Uq"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application"/>
<customObject id="Voe-Tx-rLC" customClass="AppDelegate"/>
<customObject id="YLy-65-1bz" customClass="NSFontManager"/>
<menu title="Main Menu" systemMenu="main" id="AYu-sK-qS6">
<items>
<menuItem title="Sparkle Updated Test App" id="1Xt-HY-uBw">
<modifierMask key="keyEquivalentModifierMask"/>
<menu key="submenu" title="Sparkle Updated Test App" systemMenu="apple" id="uQy-DD-JDr">
<items>
<menuItem title="Hide Sparkle Updated Test App" keyEquivalent="h" id="Olw-nP-bQN">
<connections>
<action selector="hide:" target="-1" id="PnN-Uc-m68"/>
</connections>
</menuItem>
<menuItem title="Hide Others" keyEquivalent="h" id="Vdr-fp-XzO">
<modifierMask key="keyEquivalentModifierMask" option="YES" command="YES"/>
<connections>
<action selector="hideOtherApplications:" target="-1" id="VT4-aY-XCT"/>
</connections>
</menuItem>
<menuItem title="Show All" id="Kd2-mp-pUS">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="unhideAllApplications:" target="-1" id="Dhg-Le-xox"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="kCx-OE-vgT"/>
<menuItem title="Quit Sparkle Updated Test App" keyEquivalent="q" id="4sb-4s-VLi">
<connections>
<action selector="terminate:" target="-1" id="Te7-pn-YzF"/>
</connections>
</menuItem>
</items>
</menu>
</menuItem>
</items>
</menu>
</objects>
</document>

View File

@ -0,0 +1,64 @@
{
"images" : [
{
"size" : "16x16",
"idiom" : "mac",
"filename" : "icon_16x16.png",
"scale" : "1x"
},
{
"size" : "16x16",
"idiom" : "mac",
"filename" : "icon_16x16@2x.png",
"scale" : "2x"
},
{
"size" : "32x32",
"idiom" : "mac",
"filename" : "icon_32x32.png",
"scale" : "1x"
},
{
"size" : "32x32",
"idiom" : "mac",
"filename" : "icon_32x32@2x.png",
"scale" : "2x"
},
{
"size" : "128x128",
"idiom" : "mac",
"filename" : "icon_128x128.png",
"scale" : "1x"
},
{
"size" : "128x128",
"idiom" : "mac",
"filename" : "icon_128x128@2x.png",
"scale" : "2x"
},
{
"idiom" : "mac",
"size" : "256x256",
"scale" : "1x"
},
{
"idiom" : "mac",
"size" : "256x256",
"scale" : "2x"
},
{
"idiom" : "mac",
"size" : "512x512",
"scale" : "1x"
},
{
"idiom" : "mac",
"size" : "512x512",
"scale" : "2x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 754 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Some files were not shown because too many files have changed in this diff Show More