Updated Sparkle framework
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,2 +1,4 @@
|
|||
#include "ConfigFramework.xcconfig"
|
||||
|
||||
// Unit tests need access to non-public classes
|
||||
GCC_SYMBOLS_PRIVATE_EXTERN = NO
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 37 KiB |
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 754 B |
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 37 KiB |
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 5.9 KiB |
After Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 10 KiB |
|
@ -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"
|
||||
|
|
|
@ -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 */,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0610"
|
||||
LastUpgradeVersion = "0630"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0610"
|
||||
LastUpgradeVersion = "0630"
|
||||
version = "2.0">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
#import "SUBasicUpdateDriver.h"
|
||||
#import "SUAutomaticUpdateAlert.h"
|
||||
|
||||
@interface SUAutomaticUpdateDriver : SUBasicUpdateDriver <SUUnarchiverDelegate, SUAutomaticUpdateAlertDelegate>
|
||||
@interface SUAutomaticUpdateDriver : SUBasicUpdateDriver <SUUnarchiverDelegate>
|
||||
|
||||
@end
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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;
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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];
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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];
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
|
||||
- (instancetype)initWithHost:(SUHost *)aHost
|
||||
{
|
||||
self = [super initWithHost:aHost windowNibName:@"SUStatus"];
|
||||
self = [super initWithWindowNibName:@"SUStatus"];
|
||||
if (self)
|
||||
{
|
||||
self.host = aHost;
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
|
||||
@class SUStatusController;
|
||||
|
||||
@interface SUUIBasedUpdateDriver : SUBasicUpdateDriver <SUUnarchiverDelegate, SUUpdateAlertDelegate>
|
||||
@interface SUUIBasedUpdateDriver : SUBasicUpdateDriver <SUUnarchiverDelegate>
|
||||
|
||||
- (void)showModalAlert:(NSAlert *)alert;
|
||||
- (IBAction)cancelDownload:(id)sender;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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]]; }
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"];
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -18,5 +18,6 @@
|
|||
#import <Sparkle/SUUpdater.h>
|
||||
#import <Sparkle/SUVersionComparisonProtocol.h>
|
||||
#import <Sparkle/SUVersionDisplayProtocol.h>
|
||||
#import <Sparkle/SUErrors.h>
|
||||
|
||||
#endif
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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="Don’t 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">
|
||||
|
|
|
@ -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 à l’avenir" 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>
|
||||
|
|
|
@ -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 à l’avenir" 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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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"/>
|
||||
|
|
|
@ -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 %@";
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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"/>
|
||||
|
|
|
@ -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…";
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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!";
|
||||
|
||||
|
|
|
@ -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"/>
|
||||
|
|
|
@ -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"/>
|
||||
|
|
|
@ -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!" = "У вас остання версія!";
|
||||
|
||||
|
|
|
@ -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." = "下載更新項目時發生錯誤。請稍後再試一次。";
|
||||
|
||||
|
|
283
Frameworks/Sparkle/TestApplication/After Update/Sparkle Updated Test App.xcodeproj/project.pbxproj
vendored
Normal 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 */;
|
||||
}
|
15
Frameworks/Sparkle/TestApplication/After Update/Sparkle Updated Test App/AppDelegate.h
vendored
Normal 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
|
||||
|
27
Frameworks/Sparkle/TestApplication/After Update/Sparkle Updated Test App/AppDelegate.m
vendored
Normal 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
|
52
Frameworks/Sparkle/TestApplication/After Update/Sparkle Updated Test App/Base.lproj/MainMenu.xib
vendored
Normal 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>
|
|
@ -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"
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 37 KiB |
After Width: | Height: | Size: 754 B |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 2.1 KiB |