mirror of
https://github.com/sparkle-project/Sparkle.git
synced 2025-11-01 15:34:38 +00:00
Merge pull request #1879 from sparkle-project/channels
Add support for channels
This commit is contained in:
@@ -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 */,
|
||||
|
||||
Vendored
-3
@@ -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.";
|
||||
|
||||
Vendored
+8
-8
@@ -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,
|
||||
|
||||
Vendored
+3
-1
@@ -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;
|
||||
|
||||
Vendored
+29
@@ -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.
|
||||
|
||||
Vendored
+3
-3
@@ -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];
|
||||
|
||||
Vendored
+32
-4
@@ -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
|
||||
{
|
||||
|
||||
Vendored
+1
@@ -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;
|
||||
|
||||
Vendored
+25
@@ -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];
|
||||
|
||||
Vendored
+1
@@ -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;
|
||||
|
||||
Vendored
+1
@@ -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";
|
||||
|
||||
Vendored
-3
@@ -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." = "インストーラを始動中にエラーが発生しました。あとでやり直してください。";
|
||||
|
||||
@@ -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>
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user