Merge pull request #1879 from sparkle-project/channels

Add support for channels
This commit is contained in:
Mayur Pawashe
2021-06-28 19:57:58 -07:00
committed by GitHub
18 changed files with 256 additions and 28 deletions
+6 -2
View File
@@ -81,7 +81,7 @@
5D1AF5900FD767AD0065DB48 /* libxml2.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 5D1AF58F0FD767AD0065DB48 /* libxml2.dylib */; };
5D1AF59A0FD767E50065DB48 /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 5D1AF5990FD767E50065DB48 /* libz.dylib */; };
5F1510A21C96E591006E1629 /* testnamespaces.xml in Resources */ = {isa = PBXBuildFile; fileRef = 5F1510A11C96E591006E1629 /* testnamespaces.xml */; };
61299A5C09CA6D4500B7442F /* SUConstants.h in Headers */ = {isa = PBXBuildFile; fileRef = 61299A5B09CA6D4500B7442F /* SUConstants.h */; settings = {ATTRIBUTES = (); }; };
61299A5C09CA6D4500B7442F /* SUConstants.h in Headers */ = {isa = PBXBuildFile; fileRef = 61299A5B09CA6D4500B7442F /* SUConstants.h */; };
61299A6009CA6EB100B7442F /* SUConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = 61299A5F09CA6EB100B7442F /* SUConstants.m */; };
61299B3609CB04E000B7442F /* Sparkle.h in Headers */ = {isa = PBXBuildFile; fileRef = 61299B3509CB04E000B7442F /* Sparkle.h */; settings = {ATTRIBUTES = (Public, ); }; };
612DCBB00D488BC60015DBEA /* SUUpdatePermissionPrompt.m in Sources */ = {isa = PBXBuildFile; fileRef = 612DCBAE0D488BC60015DBEA /* SUUpdatePermissionPrompt.m */; };
@@ -188,6 +188,7 @@
723C8A541E2D60DB00C14942 /* SUTouchBarButtonGroup.h in Headers */ = {isa = PBXBuildFile; fileRef = 723C8A521E2D60DB00C14942 /* SUTouchBarButtonGroup.h */; };
723C8A551E2D60DB00C14942 /* SUTouchBarButtonGroup.m in Sources */ = {isa = PBXBuildFile; fileRef = 723C8A531E2D60DB00C14942 /* SUTouchBarButtonGroup.m */; };
723C8A561E2D60DB00C14942 /* SUTouchBarButtonGroup.m in Sources */ = {isa = PBXBuildFile; fileRef = 723C8A531E2D60DB00C14942 /* SUTouchBarButtonGroup.m */; };
723EDC3F26885A8E000BCBA4 /* testappcast_channels.xml in Resources */ = {isa = PBXBuildFile; fileRef = 723EDC3E26885A8E000BCBA4 /* testappcast_channels.xml */; };
72464F701E1F31E000FB341C /* SUOperatingSystem.m in Sources */ = {isa = PBXBuildFile; fileRef = 726F2CE41BC9C33D001971A4 /* SUOperatingSystem.m */; };
72464F711E1F326A00FB341C /* SUOperatingSystem.m in Sources */ = {isa = PBXBuildFile; fileRef = 726F2CE41BC9C33D001971A4 /* SUOperatingSystem.m */; };
72464F721E1F327C00FB341C /* SUOperatingSystem.m in Sources */ = {isa = PBXBuildFile; fileRef = 726F2CE41BC9C33D001971A4 /* SUOperatingSystem.m */; };
@@ -1082,6 +1083,7 @@
723B5DA51CF7AB0100365F95 /* SPUDownloaderProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SPUDownloaderProtocol.h; path = Downloader/SPUDownloaderProtocol.h; sourceTree = SOURCE_ROOT; };
723C8A521E2D60DB00C14942 /* SUTouchBarButtonGroup.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SUTouchBarButtonGroup.h; sourceTree = "<group>"; };
723C8A531E2D60DB00C14942 /* SUTouchBarButtonGroup.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SUTouchBarButtonGroup.m; sourceTree = "<group>"; };
723EDC3E26885A8E000BCBA4 /* testappcast_channels.xml */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = testappcast_channels.xml; sourceTree = "<group>"; };
7246E0A11C83B685003B4E75 /* SPUStandardUpdaterController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPUStandardUpdaterController.h; sourceTree = "<group>"; };
7246E0A21C83B685003B4E75 /* SPUStandardUpdaterController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPUStandardUpdaterController.m; sourceTree = "<group>"; };
724BB36C1D31D0B7005D534A /* org.sparkle-project.InstallerConnection.xpc */ = {isa = PBXFileReference; explicitFileType = "wrapper.xpc-service"; includeInIndex = 0; path = "org.sparkle-project.InstallerConnection.xpc"; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -1685,6 +1687,7 @@
14958C6C19AEBC610061B14F /* test-pubkey.pem */,
5AF6C74E1AEA46D10014A3AB /* test.pkg */,
5AD0FA7E1C73F2E2004BCEFF /* testappcast.xml */,
723EDC3E26885A8E000BCBA4 /* testappcast_channels.xml */,
725B3A81263FBF0C0041AB8E /* testappcast_minimumAutoupdateVersion.xml */,
722545B526805FF80036465C /* testappcast_info_updates.xml */,
5F1510A11C96E591006E1629 /* testnamespaces.xml */,
@@ -2336,6 +2339,7 @@
726E4A371C89116000C57C6A /* SPUStandardUserDriverDelegate.h in Headers */,
72EF30BE2675CF38008CE987 /* SPUAppcastItemStateResolver.h in Headers */,
72EF30C7267C716A008CE987 /* SUAppcastItem+Private.h in Headers */,
61299A5C09CA6D4500B7442F /* SUConstants.h in Headers */,
726DF88E1C84277600188804 /* SPUUserUpdateState.h in Headers */,
72B767DA1C9CD2E400A07552 /* SPUUIBasedUpdateDriver.h in Headers */,
7229E1B61C97C91100CB50D0 /* SPUUpdateDriver.h in Headers */,
@@ -2357,7 +2361,6 @@
725602D51C83551C00DAA70E /* SUApplicationInfo.h in Headers */,
7214B8811D456A8500CB5CED /* SUBundleIcon.h in Headers */,
720767D31E2EE9C200F9A850 /* SUTouchBarForwardDeclarations.h in Headers */,
61299A5C09CA6D4500B7442F /* SUConstants.h in Headers */,
55E6F33319EC9F6C00005E76 /* SUErrors.h in Headers */,
14652F8419A978C200959E44 /* SUExport.h in Headers */,
7267E5E51D3D90AA00D1BF90 /* SUFileManager.h in Headers */,
@@ -2936,6 +2939,7 @@
files = (
725B3A82263FBF0C0041AB8E /* testappcast_minimumAutoupdateVersion.xml in Resources */,
14958C6E19AEBC950061B14F /* signed-test-file.txt in Resources */,
723EDC3F26885A8E000BCBA4 /* testappcast_channels.xml in Resources */,
72AC6B2E1B9B218C00F62325 /* SparkleTestCodeSignApp.dmg in Resources */,
C23E885B1BE7B24F0050BB73 /* SparkleTestCodeSignApp.enc.dmg in Resources */,
72AC6B281B9AAD6700F62325 /* SparkleTestCodeSignApp.tar in Resources */,
-3
View File
@@ -14,9 +14,6 @@
"%@ %@ is currently the newest version available.\n(You are currently running version %@.)" = "%1$@ %2$@ is currently the newest version available.\n(You are currently running version %3$@.)";
/* An appcast feed error when checking for updates and no appcast items are retrieved */
"No valid update information could be loaded." = "No valid update information could be loaded.";
"An error occurred while running the updater. Please try again later." = "An error occurred while running the updater. Please try again later.";
"An error occurred while starting the installer. Please try again later." = "An error occurred while starting the installer. Please try again later.";
+8 -8
View File
@@ -169,7 +169,6 @@
NSString *localizedDescription;
NSString *recoverySuggestion;
NSString *recoveryOption;
SPUNoUpdateFoundReason reason;
if (latestAppcastItem != nil) {
@@ -214,19 +213,20 @@
}
break;
}
recoveryOption = SULocalizedString(@"OK", nil);
} else {
// When no updates are found in the appcast, or latest appcast item info
// was not provided (i.e, for a background update check)
// In the case no info was provided for a background check, the error isn't shown anywhere
localizedDescription = SULocalizedString(@"Update Error!", nil);
recoverySuggestion = SULocalizedString(@"No valid update information could be loaded.", nil);
recoveryOption = SULocalizedString(@"Cancel Update", nil);
// We will need to assume the user is up to date if the feed doen't have any applicable update items
// There could be update items on channels the updater is not subscribed to for example. But we can't tell the user about them.
// There could also only be update items available for other platforms or none at all.
localizedDescription = SULocalizedString(@"You're up-to-date!", "Status message shown when the user checks for updates but is already current or the feed doesn't contain any updates.");
recoverySuggestion = [NSString stringWithFormat:SULocalizedString(@"%@ %@ is currently the newest version available.", nil), [self.host name], [self.host displayVersion]];
reason = SPUNoUpdateFoundReasonUnknown;
reason = SPUNoUpdateFoundReasonOnLatestVersion;
}
NSString *recoveryOption = SULocalizedString(@"OK", nil);
NSMutableDictionary *userInfo =
[NSMutableDictionary dictionaryWithDictionary:@{
NSLocalizedDescriptionKey: localizedDescription,
+3 -1
View File
@@ -186,10 +186,12 @@ SU_EXPORT @interface SPUUpdater : NSObject
Setting this property will persist in the host bundle's user defaults.
To avoid this, you should consider instead implementing
-[SPUUpdaterDelegate feedURLStringForUpdater:] or -[SPUUpdaterDelegate feedParametersForUpdater:sendingSystemProfile:]
-[SPUUpdaterDelegate feedURLStringForUpdater:]
Passing nil will remove any feed URL that has been set in the host bundle's user defaults.
For beta updates, you may consider migrating to -[SPUUpdaterDelegate allowedChannelsForUpdater:] in the future.
This method must be called on the main thread; calls from background threads will have no effect.
*/
- (void)setFeedURL:(NSURL * _Nullable)feedURL;
+29
View File
@@ -160,9 +160,38 @@ typedef NS_ENUM(NSInteger, SPUUpdateCheck)
*/
- (void)updater:(SPUUpdater *)updater didFinishLoadingAppcast:(SUAppcast *)appcast;
/*!
Returns the set of Sparkle channels the updater is allowed to find new updates from.
An appcast item can specify a channel the update is posted to. Without specifying a channel, the appcast item is posted to the default channel.
For instance:
<item>
<sparkle:version>2.0 Beta 1</sparkle:version>
<sparkle:channel>beta</sparkle:channel>
</item>
This example posts an update to the 'beta' channel, so only updaters that are allowed to use the 'beta' channel can find this update.
If the <sparkle:channel> is not present, the update item is posted to the default channel and can be found by any updater.
You can pick any name you'd like for the channel. The valid characters for channel names are letters, numbers, dashes, underscores, and periods.
Note to use this feature, all app versions that your users may update from in your feed must use a version of Sparkle that supports this feature.
This feature was added in Sparkle 2.
\return The set of channel names the updater is allowed to find new updates in. An empty set is the default behavior,
which means the updater will only look for updates in the default channel.
*/
- (NSSet<NSString *> *)allowedChannelsForUpdater:(SPUUpdater *)updater;
/*!
Returns the item in the appcast corresponding to the update that should be installed.
Please consider using or migrating to other supported features before adopting this method.
Specifically:
If you want to filter out certain tagged updates (like beta updates), consider -[SPUUpdaterDelegate allowedChannelsForUpdater:] instead.
If you want to treat certain updates as informational-only, consider supplying <sparkle:informationalUpdate> with a set of affected versions users are updating from.
If you're using special logic or extensions in your appcast,
implement this to use your own logic for finding a valid update, if any,
in the given appcast.
+3 -3
View File
@@ -145,15 +145,15 @@
[dict setObject:deltas forKey:name];
}
else if ([name isEqualToString:SUAppcastElementTags]) {
NSMutableArray *tags = [NSMutableArray array];
NSMutableArray *names = [NSMutableArray array];
NSEnumerator *childEnum = [[node children] objectEnumerator];
for (NSXMLNode *child in childEnum) {
NSString *childName = child.name;
if (childName) {
[tags addObject:childName];
[names addObject:childName];
}
}
[dict setObject:tags forKey:name];
[dict setObject:names forKey:name];
}
else if ([name isEqualToString:SUAppcastElementInformationalUpdate]) {
NSMutableSet *informationalUpdateVersions = [NSMutableSet set];
+32 -4
View File
@@ -166,10 +166,18 @@
NSDictionary *userInfo = @{ SUUpdaterAppcastNotificationKey: loadedAppcast };
[[NSNotificationCenter defaultCenter] postNotificationName:SUUpdaterDidFinishLoadingAppCastNotification object:self.updater userInfo:userInfo];
// We will never care about other OS's
SUAppcast *macOSAppcast = [loadedAppcast copyByFilteringItems:^(SUAppcastItem *item) {
return (BOOL)[item isMacOsUpdate];
}];
NSSet<NSString *> *allowedChannels;
if ([self.updaterDelegate respondsToSelector:@selector(allowedChannelsForUpdater:)]) {
allowedChannels = [self.updaterDelegate allowedChannelsForUpdater:self.updater];
if (allowedChannels == nil) {
SULog(SULogLevelError, @"Error: -allowedChannelsForUpdater: cannot return nil. Treating this as an empty set.");
allowedChannels = [NSSet set];
}
} else {
allowedChannels = [NSSet set];
}
SUAppcast *macOSAppcast = [[self class] filterAppcast:loadedAppcast forMacOSAndAllowedChannels:allowedChannels];
id<SUVersionComparison> applicationVersionComparator = [self versionComparator];
@@ -221,6 +229,26 @@
}
}
// This method is used by unit tests
+ (SUAppcast *)filterAppcast:(SUAppcast *)appcast forMacOSAndAllowedChannels:(NSSet<NSString *> *)allowedChannels
{
return [appcast copyByFilteringItems:^(SUAppcastItem *item) {
// We will never care about other OS's
BOOL macOSUpdate = [item isMacOsUpdate];
if (!macOSUpdate) {
return NO;
}
NSString *channel = item.channel;
if (channel == nil) {
// Item is on the default channel
return YES;
}
return [allowedChannels containsObject:channel];
}];
}
// This method is used by unit tests
+ (SUAppcast *)filterSupportedAppcast:(SUAppcast *)appcast phasedUpdateGroup:(NSNumber * _Nullable)phasedUpdateGroup skippedUpdate:(SPUSkippedUpdate * _Nullable)skippedUpdate hostVersion:(NSString *)hostVersion versionComparator:(id<SUVersionComparison>)versionComparator testOSVersion:(BOOL)testOSVersion testMinimumAutoupdateVersion:(BOOL)testMinimumAutoupdateVersion
{
+1
View File
@@ -52,6 +52,7 @@ SU_EXPORT @interface SUAppcastItem : NSObject<NSSecureCoding>
@property (copy, readonly, nullable) NSNumber* phasedRolloutInterval;
@property (nonatomic, copy, readonly) NSString *installationType;
@property (copy, readonly, nullable) NSString *minimumAutoupdateVersion;
@property (nonatomic, readonly, nullable) NSString *channel;
@property (getter=isDeltaUpdate, readonly) BOOL deltaUpdate;
@property (getter=isCriticalUpdate, readonly) BOOL criticalUpdate;
+25
View File
@@ -73,6 +73,7 @@ static NSString *SUAppcastItemStateKey = @"SUAppcastItemState";
@synthesize state = _state;
@synthesize hasCriticalInformation = _hasCriticalInformation;
@synthesize informationalUpdateVersions = _informationalUpdateVersions;
@synthesize channel = _channel;
+ (BOOL)supportsSecureCoding
{
@@ -130,6 +131,8 @@ static NSString *SUAppcastItemStateKey = @"SUAppcastItemState";
_propertiesDictionary = propertiesDictionary;
_phasedRolloutInterval = [decoder decodeObjectOfClass:[NSNumber class] forKey:SUAppcastElementPhasedRolloutInterval];
_channel = [(NSString *)[decoder decodeObjectOfClass:[NSString class] forKey:SUAppcastElementChannel] copy];
}
return self;
@@ -206,6 +209,10 @@ static NSString *SUAppcastItemStateKey = @"SUAppcastItemState";
if (self.phasedRolloutInterval != nil) {
[encoder encodeObject:self.phasedRolloutInterval forKey:SUAppcastElementPhasedRolloutInterval];
}
if (self.channel != nil) {
[encoder encodeObject:self.channel forKey:SUAppcastElementChannel];
}
}
- (BOOL)isDeltaUpdate
@@ -421,6 +428,24 @@ static NSString *SUAppcastItemStateKey = @"SUAppcastItemState";
_maximumSystemVersion = [(NSString *)[dict objectForKey:SUAppcastElementMaximumSystemVersion] copy];
_minimumAutoupdateVersion = [(NSString *)[dict objectForKey:SUAppcastElementMinimumAutoupdateVersion] copy];
NSString *channel = [dict objectForKey:SUAppcastElementChannel];
if (channel != nil) {
if (channel.length == 0) {
SULog(SULogLevelError, @"warning: Item with version '%@' has zero-length channel; this will be ignored.", newVersion);
_channel = nil;
} else {
// Reject characters in the channel name that may cause parsing problems in tools later
NSMutableCharacterSet *allowedCharacterSet = [NSMutableCharacterSet alphanumericCharacterSet];
[allowedCharacterSet addCharactersInString:@"_.-"];
if ([channel rangeOfCharacterFromSet:allowedCharacterSet.invertedSet].location != NSNotFound) {
SULog(SULogLevelError, @"warning: Item with version '%@' has channel with invalid name. This channel will be ignored. Only [a-zA-Z0-9._-] is allowed.", newVersion);
_channel = nil;
} else {
_channel = [channel copy];
}
}
}
// Grab critical update information
NSDictionary * _Nullable criticalUpdateDictionaryFromAppcast = (NSDictionary *)[dict objectForKey:SUAppcastElementCriticalUpdate];
NSArray *tags = [dict objectForKey:SUAppcastElementTags];
+1
View File
@@ -88,6 +88,7 @@ extern NSString *const SUAppcastElementReleaseNotesLink;
extern NSString *const SUAppcastElementTags;
extern NSString *const SUAppcastElementPhasedRolloutInterval;
extern NSString *const SUAppcastElementInformationalUpdate;
extern NSString *const SUAppcastElementChannel;
extern NSString *const SURSSAttributeURL;
extern NSString *const SURSSAttributeLength;
+1
View File
@@ -84,6 +84,7 @@ NSString *const SUAppcastElementReleaseNotesLink = @"sparkle:releaseNotesLink";
NSString *const SUAppcastElementTags = @"sparkle:tags";
NSString *const SUAppcastElementPhasedRolloutInterval = @"sparkle:phasedRolloutInterval";
NSString *const SUAppcastElementInformationalUpdate = @"sparkle:informationalUpdate";
NSString *const SUAppcastElementChannel = @"sparkle:channel";
NSString *const SURSSAttributeURL = @"url";
NSString *const SURSSAttributeLength = @"length";
-3
View File
@@ -14,9 +14,6 @@
"%@ %@ is currently the newest version available.\n(You are currently running version %@.)" = "%1$@ %2$@は現在入手できる最新バージョンです。\n(現在使用中のバージョンは%3$@です。)";
/* An appcast feed error when checking for updates and no appcast items are retrieved */
"No valid update information could be loaded." = "有効なアップデート情報が取得できませんでした。";
"An error occurred while running the updater. Please try again later." = "アップデータを実行中にエラーが発生しました。あとでやり直してください。";
"An error occurred while starting the installer. Please try again later." = "インストーラを始動中にエラーが発生しました。あとでやり直してください。";
+59
View File
@@ -0,0 +1,59 @@
<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:sparkle="http://www.andymatuschak.org/xml-namespaces/sparkle">
<channel>
<title>For unit test only</title>
<!-- Implicitly no channels -->
<item>
<title>Version 2.0</title>
<description>desc</description>
<pubDate>Sat, 26 Jul 2014 15:20:11 +0000</pubDate>
<enclosure url="http://localhost:1337/Sparkle_Test_App.zip" sparkle:version="2.0" />
<sparkle:criticalUpdate sparkle:version="1.5" />
</item>
<!-- Invalid channel name -->
<item>
<title>Version 3.0</title>
<sparkle:phasedRolloutInterval>86400</sparkle:phasedRolloutInterval>
<sparkle:tags><sparkle:criticalUpdate /></sparkle:tags>
<enclosure url="http://localhost:1337/Sparkle_Test_App.zip" sparkle:version="3.0" length="1346234" />
<sparkle:deltas>
<enclosure url="http://localhost:1337/3.0_from_2.0.patch"
sparkle:version="3.0"
sparkle:deltaFrom="2.0"
length="1235"
type="application/octet-stream"
sparkle:edSignature="..." />
<enclosure url="http://localhost:1337/3.0_from_1.0.patch"
sparkle:version="3.0"
sparkle:deltaFrom="1.0"
length="1485"
type="application/octet-stream"
sparkle:edSignature="..." />
</sparkle:deltas>
<sparkle:channel></sparkle:channel>
</item>
<!-- Beta channel -->
<item>
<title>Version 4.0</title>
<sparkle:version>4.0</sparkle:version>
<pubDate>Sat, 26 Jul 2014 15:20:13 +0000</pubDate>
<enclosure url="http://localhost:1337/Sparkle_Test_App.zip" length="1346234" />
<sparkle:channel>beta</sparkle:channel>
</item>
<!-- Nightly channel -->
<item>
<title>Version 5.0</title>
<sparkle:version>5.0</sparkle:version>
<enclosure url="http://localhost:1337/Sparkle_Test_App.zip" length="1346234" />
<sparkle:channel>nightly</sparkle:channel>
</item>
<!-- A Windows release -->
<item>
<title>Version 6.0</title>
<sparkle:version>6.0</sparkle:version>
<enclosure url="http://localhost:1337/Sparkle_Test_App.zip" length="1346234" sparkle:os="windows" />
</item>
</channel>
</rss>
+58
View File
@@ -79,6 +79,64 @@ class SUAppcastTest: XCTestCase {
}
}
func testChannelsAndMacOSReleases() {
let testURL = Bundle(for: SUAppcastTest.self).url(forResource: "testappcast_channels", withExtension: "xml")!
do {
let testData = try Data(contentsOf: testURL)
let versionComparator = SUStandardVersionComparator.default()
let hostVersion = "1.0"
let stateResolver = SPUAppcastItemStateResolver(hostVersion: hostVersion, applicationVersionComparator: versionComparator, standardVersionComparator: versionComparator)
let appcast = try SUAppcast(xmlData: testData, relativeTo: nil, stateResolver: stateResolver)
XCTAssertEqual(5, appcast.items.count)
do {
let filteredAppcast = SUAppcastDriver.filterAppcast(appcast, forMacOSAndAllowedChannels: ["beta", "nightly"])
XCTAssertEqual(4, filteredAppcast.items.count)
XCTAssertEqual("2.0", filteredAppcast.items[0].versionString)
XCTAssertEqual("3.0", filteredAppcast.items[1].versionString)
XCTAssertEqual("4.0", filteredAppcast.items[2].versionString)
XCTAssertEqual("5.0", filteredAppcast.items[3].versionString)
}
do {
let filteredAppcast = SUAppcastDriver.filterAppcast(appcast, forMacOSAndAllowedChannels: [])
XCTAssertEqual(2, filteredAppcast.items.count)
XCTAssertEqual("2.0", filteredAppcast.items[0].versionString)
XCTAssertEqual("3.0", filteredAppcast.items[1].versionString)
}
do {
let filteredAppcast = SUAppcastDriver.filterAppcast(appcast, forMacOSAndAllowedChannels: ["beta"])
XCTAssertEqual(3, filteredAppcast.items.count)
XCTAssertEqual("2.0", filteredAppcast.items[0].versionString)
XCTAssertEqual("3.0", filteredAppcast.items[1].versionString)
XCTAssertEqual("4.0", filteredAppcast.items[2].versionString)
}
do {
let filteredAppcast = SUAppcastDriver.filterAppcast(appcast, forMacOSAndAllowedChannels: ["nightly"])
XCTAssertEqual(3, filteredAppcast.items.count)
XCTAssertEqual("2.0", filteredAppcast.items[0].versionString)
XCTAssertEqual("3.0", filteredAppcast.items[1].versionString)
XCTAssertEqual("5.0", filteredAppcast.items[2].versionString)
}
do {
let filteredAppcast = SUAppcastDriver.filterAppcast(appcast, forMacOSAndAllowedChannels: ["madeup"])
XCTAssertEqual("2.0", filteredAppcast.items[0].versionString)
XCTAssertEqual("3.0", filteredAppcast.items[1].versionString)
XCTAssertEqual(2, filteredAppcast.items.count)
}
} catch let err as NSError {
NSLog("%@", err)
XCTFail(err.localizedDescription)
}
}
func testCriticalUpdateVersion() {
let testURL = Bundle(for: SUAppcastTest.self).url(forResource: "testappcast", withExtension: "xml")!
@@ -42,6 +42,8 @@ static const char *SUAppleQuarantineIdentifier = "com.apple.quarantine";
+ (SUAppcast *)filterSupportedAppcast:(SUAppcast *)appcast phasedUpdateGroup:(NSNumber * _Nullable)phasedUpdateGroup skippedUpdate:(SPUSkippedUpdate * _Nullable)skippedUpdate hostVersion:(NSString *)hostVersion versionComparator:(id<SUVersionComparison>)versionComparator testOSVersion:(BOOL)testOSVersion testMinimumAutoupdateVersion:(BOOL)testMinimumAutoupdateVersion;
+ (SUAppcast *)filterAppcast:(SUAppcast *)appcast forMacOSAndAllowedChannels:(NSSet<NSString *> *)allowedChannels;
@end
@interface SUBinaryDeltaUnarchiver (Private)
+1 -1
View File
@@ -14,7 +14,7 @@ NS_ASSUME_NONNULL_BEGIN
@interface SPUCommandLineDriver : NSObject
- (nullable instancetype)initWithUpdateBundlePath:(NSString *)updateBundlePath applicationBundlePath:(nullable NSString *)applicationBundlePath customFeedURL:(nullable NSString *)customFeedURL updatePermissionResponse:(nullable SUUpdatePermissionResponse *)updatePermissionResponse deferInstallation:(BOOL)deferInstallation interactiveInstallation:(BOOL)interactiveInstallation verbose:(BOOL)verbose;
- (nullable instancetype)initWithUpdateBundlePath:(NSString *)updateBundlePath applicationBundlePath:(nullable NSString *)applicationBundlePath allowedChannels:(NSSet<NSString *> *)allowedChannels customFeedURL:(nullable NSString *)customFeedURL updatePermissionResponse:(nullable SUUpdatePermissionResponse *)updatePermissionResponse deferInstallation:(BOOL)deferInstallation interactiveInstallation:(BOOL)interactiveInstallation verbose:(BOOL)verbose;
- (void)runAndCheckForUpdatesNow:(BOOL)checkForUpdatesNow;
+9 -1
View File
@@ -16,6 +16,7 @@
@property (nonatomic, readonly) BOOL verbose;
@property (nonatomic) BOOL probingForUpdates;
@property (nonatomic, readonly) BOOL interactive;
@property (nonatomic, readonly) NSSet<NSString *> *allowedChannels;
@property (nonatomic, copy, readonly, nullable) NSString *customFeedURL;
@end
@@ -26,9 +27,10 @@
@synthesize verbose = _verbose;
@synthesize probingForUpdates = _probingForUpdates;
@synthesize interactive = _interactive;
@synthesize allowedChannels = _allowedChannels;
@synthesize customFeedURL = _customFeedURL;
- (instancetype)initWithUpdateBundlePath:(NSString *)updateBundlePath applicationBundlePath:(nullable NSString *)applicationBundlePath customFeedURL:(nullable NSString *)customFeedURL updatePermissionResponse:(nullable SUUpdatePermissionResponse *)updatePermissionResponse deferInstallation:(BOOL)deferInstallation interactiveInstallation:(BOOL)interactiveInstallation verbose:(BOOL)verbose
- (instancetype)initWithUpdateBundlePath:(NSString *)updateBundlePath applicationBundlePath:(nullable NSString *)applicationBundlePath allowedChannels:(NSSet<NSString *> *)allowedChannels customFeedURL:(nullable NSString *)customFeedURL updatePermissionResponse:(nullable SUUpdatePermissionResponse *)updatePermissionResponse deferInstallation:(BOOL)deferInstallation interactiveInstallation:(BOOL)interactiveInstallation verbose:(BOOL)verbose
{
self = [super init];
if (self != nil) {
@@ -49,6 +51,7 @@
_verbose = verbose;
_interactive = interactiveInstallation;
_allowedChannels = allowedChannels;
_customFeedURL = [customFeedURL copy];
id <SPUUserDriver> userDriver = [[SPUCommandLineUserDriver alloc] initWithUpdatePermissionResponse:updatePermissionResponse deferInstallation:deferInstallation verbose:verbose];
@@ -84,6 +87,11 @@
}
}
- (NSSet<NSString *> *)allowedChannelsForUpdater:(SPUUpdater *)__unused updater
{
return self.allowedChannels;
}
- (nullable NSString *)feedURLStringForUpdater:(SPUUpdater *)__unused updater
{
return self.customFeedURL;
+18 -2
View File
@@ -20,10 +20,11 @@
#define PROBE_FLAG "probe"
#define INTERACTIVE_FLAG "interactive"
#define FEED_URL_FLAG "feed-url"
#define CHANNELS_FLAG "channels"
static void printUsage(char **argv)
{
fprintf(stderr, "Usage: %s bundle [--%s <app-path>] [--%s] [--%s] [--%s] [--%s] [--%s] [--%s] [--%s]\n", argv[0], APPLICATION_FLAG, CHECK_NOW_FLAG, PROBE_FLAG, GRANT_AUTOMATIC_CHECKING_FLAG, SEND_PROFILE_FLAG, DEFER_FLAG, INTERACTIVE_FLAG, VERBOSE_FLAG);
fprintf(stderr, "Usage: %s bundle [--%s app-path] [--%s] [--%s] [--%s chan1,chan2,…] [--%s feed-url] [--%s] [--%s] [--%s] [--%s] [--%s]\n", argv[0], APPLICATION_FLAG, CHECK_NOW_FLAG, PROBE_FLAG, CHANNELS_FLAG, FEED_URL_FLAG, GRANT_AUTOMATIC_CHECKING_FLAG, SEND_PROFILE_FLAG, DEFER_FLAG, INTERACTIVE_FLAG, VERBOSE_FLAG);
fprintf(stderr, "Description:\n");
fprintf(stderr, " Check if any new updates for a Sparkle supported bundle need to be installed.\n\n");
fprintf(stderr, " If any new updates need to be installed, the user application\n is terminated and the update is installed immediately unless --%s\n is specified. If the application was alive, then it will be relaunched after.\n\n", DEFER_FLAG);
@@ -36,6 +37,7 @@ static void printUsage(char **argv)
fprintf(stderr, " --%s\n Path to the application to watch for termination and to relaunch.\n If not provided, this is assumed to be the same as the bundle.\n", APPLICATION_FLAG);
fprintf(stderr, " --%s\n Immediately checks for updates to install.\n Without this, updates are checked only when needed on a scheduled basis.\n", CHECK_NOW_FLAG);
fprintf(stderr, " --%s\n Probe for updates. Check if any updates are available but do not install.\n An exit status of 0 is returned if a new update is available.\n", PROBE_FLAG);
fprintf(stderr, " --%s\n List of allowed Sparkle channels to look for updates in. By default,\n only the default channel is used.\n", CHANNELS_FLAG);
fprintf(stderr, " --%s\n URL for appcast feed. This URL will be used for the feed instead of the one\n in the bundle's Info.plist or in the bundle's user defaults.\n", FEED_URL_FLAG);
fprintf(stderr, " --%s\n Allows prompting the user for an authorization dialog prompt if the\n installer needs elevated privileges, or allows performing an interactive\n installer package.\n", INTERACTIVE_FLAG);
fprintf(stderr, " --%s\n If update permission is requested, this enables automatic update checks.\n Note that this behavior may overwrite the user's defaults for the bundle.\n This option has no effect if --%s is passed, or if the\n user has replied to this request already, or if the developer configured\n to skip it.\n", GRANT_AUTOMATIC_CHECKING_FLAG, CHECK_NOW_FLAG);
@@ -55,6 +57,7 @@ int main(int argc, char **argv)
struct option longOptions[] = {
{APPLICATION_FLAG, required_argument, NULL, 0},
{CHANNELS_FLAG, required_argument, NULL, 0},
{FEED_URL_FLAG, required_argument, NULL, 0},
{DEFER_FLAG, no_argument, NULL, 0},
{VERBOSE_FLAG, no_argument, NULL, 0},
@@ -68,6 +71,7 @@ int main(int argc, char **argv)
NSString *applicationPath = nil;
NSString *feedURL = nil;
NSSet<NSString *> *channels = [NSSet set];
BOOL deferInstall = NO;
BOOL verbose = NO;
BOOL checkForUpdatesNow = NO;
@@ -100,6 +104,18 @@ int main(int argc, char **argv)
printUsage(argv);
return EXIT_FAILURE;
}
} else if (strcmp(CHANNELS_FLAG, longOptions[optionIndex].name) == 0) {
assert(optarg != NULL);
NSString *channelsString = [[NSString alloc] initWithUTF8String:optarg];
if (channelsString == nil) {
printUsage(argv);
return EXIT_FAILURE;
}
if (channelsString.length > 0) {
channels = [NSSet setWithArray:[channelsString componentsSeparatedByString:@","]];
}
} else if (strcmp(DEFER_FLAG, longOptions[optionIndex].name) == 0) {
deferInstall = YES;
} else if (strcmp(VERBOSE_FLAG, longOptions[optionIndex].name) == 0) {
@@ -146,7 +162,7 @@ int main(int argc, char **argv)
updatePermissionResponse = [[SUUpdatePermissionResponse alloc] initWithAutomaticUpdateChecks:YES sendSystemProfile:sendProfile];
}
SPUCommandLineDriver *driver = [[SPUCommandLineDriver alloc] initWithUpdateBundlePath:updatePath applicationBundlePath:applicationPath customFeedURL:feedURL updatePermissionResponse:updatePermissionResponse deferInstallation:deferInstall interactiveInstallation:interactive verbose:verbose];
SPUCommandLineDriver *driver = [[SPUCommandLineDriver alloc] initWithUpdateBundlePath:updatePath applicationBundlePath:applicationPath allowedChannels:channels customFeedURL:feedURL updatePermissionResponse:updatePermissionResponse deferInstallation:deferInstall interactiveInstallation:interactive verbose:verbose];
if (driver == nil) {
fprintf(stderr, "Error: Failed to initialize updater. Are the bundle paths provided valid?\n");
return EXIT_FAILURE;