Skip to content

Commit

Permalink
Merge branch '2.x' into channels
Browse files Browse the repository at this point in the history
  • Loading branch information
zorgiepoo committed Jun 27, 2021
2 parents c968334 + ad4ec8e commit c329319
Show file tree
Hide file tree
Showing 16 changed files with 165 additions and 40 deletions.
13 changes: 10 additions & 3 deletions Resources/SampleAppcast.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,32 +6,39 @@
<language>en</language>
<item>
<title>Version 2.0 (2 bugs fixed; 3 new features)</title>
<link>http://sparkle-project.org</link>
<sparkle:version>2.0</sparkle:version>
<sparkle:releaseNotesLink>
http://you.com/app/2.0.html
</sparkle:releaseNotesLink>
<pubDate>Wed, 09 Jan 2006 19:20:11 +0000</pubDate>
<enclosure url="http://you.com/app/Your%20Great%20App%202.0.zip" sparkle:version="2.0" length="1623481" type="application/octet-stream" sparkle:edSignature="WVyVJpOx+a5+vNWJVY79TRjFKveNk+VhGJf2iti4CZtJsJewIUGvh/1AKKEAFbH1qUwx+vro1ECuzOsMmumoBA==" />
<enclosure url="http://you.com/app/Your%20Great%20App%202.0.zip" length="1623481" type="application/octet-stream" sparkle:edSignature="WVyVJpOx+a5+vNWJVY79TRjFKveNk+VhGJf2iti4CZtJsJewIUGvh/1AKKEAFbH1qUwx+vro1ECuzOsMmumoBA==" />
<sparkle:minimumSystemVersion>10.11</sparkle:minimumSystemVersion>
</item>

<item>
<title>Version 1.5 (8 bugs fixed; 2 new features)</title>
<link>http://sparkle-project.org</link>
<sparkle:version>1.5</sparkle:version>
<sparkle:releaseNotesLink>
http://you.com/app/1.5.html
</sparkle:releaseNotesLink>
<pubDate>Wed, 01 Jan 2006 12:20:11 +0000</pubDate>
<enclosure url="http://you.com/app/Your%20Great%20App%201.5.zip" sparkle:version="1.5" length="1472893" type="application/octet-stream" sparkle:edSignature="pNFd7KbcQSu+Mq7UYrbQXTPq82luht2ACXm/r2utp1u/Uv/5hWqctdT2jwQgMejW7DRoeV/hVr6J4VdZYdwWDw==" />
<enclosure url="http://you.com/app/Your%20Great%20App%201.5.zip" length="1472893" type="application/octet-stream" sparkle:edSignature="pNFd7KbcQSu+Mq7UYrbQXTPq82luht2ACXm/r2utp1u/Uv/5hWqctdT2jwQgMejW7DRoeV/hVr6J4VdZYdwWDw==" />
<sparkle:minimumSystemVersion>10.11</sparkle:minimumSystemVersion>
</item>

<!-- Now here's an example of a version with a weird internal version number (like an SVN revision) but a human-readable external one. -->
<item>
<title>Version 1.4 (5 bugs fixed; 2 new features)</title>
<link>http://sparkle-project.org</link>
<sparkle:releaseNotesLink>
http://you.com/app/1.4.html
</sparkle:releaseNotesLink>
<sparkle:version>241</sparkle:version>
<sparkle:shortVersionString>1.4</sparkle:shortVersionString>
<pubDate>Wed, 25 Dec 2005 12:20:11 +0000</pubDate>
<enclosure url="http://you.com/app/Your%20Great%20App%201.4.zip" sparkle:version="241" sparkle:shortVersionString="1.4" sparkle:edSignature="Ody3D/ybSMH4T+P/oNj3LN4F0SA8RJGLEr1TI4UemrBAiJ9aEcDnYV3u58P75AbcFjI13jPYmHDUHXMSTFQbDw==" length="1472349" type="application/octet-stream" />
<enclosure url="http://you.com/app/Your%20Great%20App%201.4.zip" sparkle:edSignature="Ody3D/ybSMH4T+P/oNj3LN4F0SA8RJGLEr1TI4UemrBAiJ9aEcDnYV3u58P75AbcFjI13jPYmHDUHXMSTFQbDw==" length="1472349" type="application/octet-stream" />
<sparkle:minimumSystemVersion>10.11</sparkle:minimumSystemVersion>
</item>
</channel>
Expand Down
3 changes: 3 additions & 0 deletions Sparkle/Base.lproj/Sparkle.strings
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,9 @@

"%1$@ %2$@ is available but your macOS version is too new for this update. This update only supports up to macOS %3$@." = "%1$@ %2$@ is available but your macOS version is too new for this update. This update only supports up to macOS %3$@.";

/* Used when a new update is not found */
"Version History" = "Version History";

/* Authorization message shown when app wants permission to update itself. */
"%1$@ wants permission to update." = "%1$@ wants permission to update.";

Expand Down
32 changes: 26 additions & 6 deletions Sparkle/SPUBasicUpdateDriver.m
Original file line number Diff line number Diff line change
Expand Up @@ -171,35 +171,46 @@ - (void)didNotFindUpdateWithLatestAppcastItem:(nullable SUAppcastItem *)latestAp
NSString *recoverySuggestion;
NSString *recoveryOption;

SPUNoUpdateFoundReason reason;
if (latestAppcastItem != nil) {
switch (hostToLatestAppcastItemComparisonResult) {
case NSOrderedDescending:
// This means the user is a 'newer than latest' version. give a slight hint to the user instead of wrongly claiming this version is identical to the latest feed version.
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.\n(You are currently running version %@.)", nil), [self.host name], latestAppcastItem.displayVersionString, [self.host displayVersion]];

reason = SPUNoUpdateFoundReasonOnNewerThanLatestVersion;
break;
case NSOrderedSame:
// No new update is available and we're on the latest
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 = SPUNoUpdateFoundReasonOnLatestVersion;
break;
case NSOrderedAscending:
// This means a new update doesn't match the OS requirements
if (!latestAppcastItem.minimumOperatingSystemVersionIsOK) {
localizedDescription = SULocalizedString(@"Your macOS version is too old", nil);

recoverySuggestion = [NSString stringWithFormat:SULocalizedString(@"%1$@ %2$@ is available but your macOS version is too old to install it. At least macOS %3$@ is required.", nil), [self.host name], latestAppcastItem.versionString, latestAppcastItem.minimumSystemVersion];

reason = SPUNoUpdateFoundReasonSystemIsTooOld;
} else if (!latestAppcastItem.maximumOperatingSystemVersionIsOK) {
localizedDescription = SULocalizedString(@"Your macOS version is too new", nil);

recoverySuggestion = [NSString stringWithFormat:SULocalizedString(@"%1$@ %2$@ is available but your macOS version is too new for this update. This update only supports up to macOS %3$@.", nil), [self.host name], latestAppcastItem.versionString, latestAppcastItem.maximumSystemVersion];

reason = SPUNoUpdateFoundReasonSystemIsTooNew;
} else {
// We shouldn't realistically get here
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;
}
break;
}
Expand All @@ -210,6 +221,20 @@ - (void)didNotFindUpdateWithLatestAppcastItem:(nullable SUAppcastItem *)latestAp
// There could be update items on channels the updater is not subscribed to for example. But we can't tell the user about them.
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;
}

NSMutableDictionary *userInfo =
[NSMutableDictionary dictionaryWithDictionary:@{
NSLocalizedDescriptionKey: localizedDescription,
NSLocalizedRecoverySuggestionErrorKey: recoverySuggestion,
NSLocalizedRecoveryOptionsErrorKey: @[recoveryOption],
SPUNoUpdateFoundReasonKey: @(reason),
}];

if (latestAppcastItem != nil) {
userInfo[SPULatestAppcastItemFoundKey] = latestAppcastItem;
}

recoveryOption = SULocalizedString(@"OK", nil);
Expand All @@ -218,12 +243,7 @@ - (void)didNotFindUpdateWithLatestAppcastItem:(nullable SUAppcastItem *)latestAp
[NSError
errorWithDomain:SUSparkleErrorDomain
code:SUNoUpdateError
userInfo:@{
NSLocalizedDescriptionKey: localizedDescription,
NSLocalizedRecoverySuggestionErrorKey: recoverySuggestion,
NSLocalizedRecoveryOptionsErrorKey: @[recoveryOption]
}
];
userInfo:[userInfo copy]];
[self.delegate basicDriverIsRequestingAbortUpdateWithError:notFoundError];
}
}
Expand Down
52 changes: 47 additions & 5 deletions Sparkle/SPUStandardUserDriver.m
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#import "SUApplicationInfo.h"
#import "SUOperatingSystem.h"
#import "SPUUserUpdateState.h"
#import "SUErrors.h"

#import <AppKit/AppKit.h>

Expand Down Expand Up @@ -250,7 +251,7 @@ - (void)showUpdaterError:(NSError *)error acknowledgement:(void (^)(void))acknow
alert.messageText = SULocalizedString(@"Update Error!", nil);
alert.informativeText = [NSString stringWithFormat:@"%@", [error localizedDescription]];
[alert addButtonWithTitle:SULocalizedString(@"Cancel Update", nil)];
[self showAlert:alert];
[self showAlert:alert secondaryAction:nil];

acknowledgement();
}
Expand All @@ -263,12 +264,49 @@ - (void)showUpdateNotFoundWithError:(NSError *)error acknowledgement:(void (^)(v

NSAlert *alert = [NSAlert alertWithError:error];
alert.alertStyle = NSAlertStyleInformational;
[self showAlert:alert];

// Can we give more information to the user?
SPUNoUpdateFoundReason reason = (SPUNoUpdateFoundReason)[(NSNumber *)error.userInfo[SPUNoUpdateFoundReasonKey] integerValue];

void (^secondaryAction)(void) = nil;
SUAppcastItem *latestAppcastItem = error.userInfo[SPULatestAppcastItemFoundKey];
if (latestAppcastItem != nil) {
switch (reason) {
case SPUNoUpdateFoundReasonOnLatestVersion:
case SPUNoUpdateFoundReasonOnNewerThanLatestVersion: {
if (latestAppcastItem.releaseNotesURL != nil) {
// Show the user the past version history if available
[alert addButtonWithTitle:SULocalizedString(@"Version History", nil)];

secondaryAction = ^{
[[NSWorkspace sharedWorkspace] openURL:(NSURL * _Nonnull)latestAppcastItem.releaseNotesURL];
};
}

break;
}
case SPUNoUpdateFoundReasonSystemIsTooOld:
case SPUNoUpdateFoundReasonSystemIsTooNew:
if (latestAppcastItem.infoURL != nil) {
// Show the user the product's link if available
[alert addButtonWithTitle:SULocalizedString(@"Learn More...", nil)];

secondaryAction = ^{
[[NSWorkspace sharedWorkspace] openURL:(NSURL * _Nonnull)latestAppcastItem.infoURL];
};
}
break;
case SPUNoUpdateFoundReasonUnknown:
break;
}
}

[self showAlert:alert secondaryAction:secondaryAction];

acknowledgement();
}

- (void)showAlert:(NSAlert *)alert
- (void)showAlert:(NSAlert *)alert secondaryAction:(void (^ _Nullable)(void))secondaryAction
{
id <SPUStandardUserDriverDelegate> delegate = self.delegate;

Expand All @@ -281,7 +319,11 @@ - (void)showAlert:(NSAlert *)alert
if ([SUApplicationInfo isBackgroundApplication:[NSApplication sharedApplication]]) { [[NSApplication sharedApplication] activateIgnoringOtherApps:YES]; }

[alert setIcon:[SUApplicationInfo bestIconForHost:self.host]];
[alert runModal];

NSModalResponse response = [alert runModal];
if (response == NSAlertSecondButtonReturn && secondaryAction != nil) {
secondaryAction();
}

if ([delegate respondsToSelector:@selector(standardUserDriverDidShowModalAlert)]) {
[delegate standardUserDriverDidShowModalAlert];
Expand Down Expand Up @@ -435,7 +477,7 @@ - (void)showUpdateInstalledAndRelaunched:(BOOL)relaunched acknowledgement:(void
} else {
alert.informativeText = [NSString stringWithFormat:SULocalizedString(@"%@ is now updated!", nil), hostName];
}
[self showAlert:alert];
[self showAlert:alert secondaryAction:nil];
}

acknowledgement();
Expand Down
8 changes: 8 additions & 0 deletions Sparkle/SPUUserDriver.h
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,14 @@ SU_EXPORT @protocol SPUUserDriver <NSObject>
* Before this point, -showUserInitiatedUpdateCheckWithCancellation: may be called.
*
* @param error The error associated with why a new update was not found.
* There are various reasons a new update is unavailable and can't be installed.
* This error object is populated with recovery and suggestion strings suitable to be shown in an alert.
*
* The userInfo dictionary is also populated with two keys:
* SPULatestAppcastItemFoundKey: if available, this may provide the latest SUAppcastItem that was found.
* SPUNoUpdateFoundReasonKey: if available, this will provide the SUNoUpdateFoundReason. For example the reason could be because
* the latest version in the feed requires a newer OS version or could be because the user is already on the latest version.
*
* @param acknowledgement Acknowledge to the updater that no update found error was shown.
*/
- (void)showUpdateNotFoundWithError:(NSError *)error acknowledgement:(void (^)(void))acknowledgement;
Expand Down
18 changes: 11 additions & 7 deletions Sparkle/SUAppcastItem.m
Original file line number Diff line number Diff line change
Expand Up @@ -319,23 +319,28 @@ - (nullable instancetype)initWithDictionary:(NSDictionary *)dict relativeToURL:(
{
self = [super init];
if (self) {
_title = [(NSString *)[dict objectForKey:SURSSElementTitle] copy];

NSDictionary *enclosure = [dict objectForKey:SURSSElementEnclosure];

// Try to find a version string.
// Finding the new version number from the RSS feed is a little bit hacky. There are two ways:
// Finding the new version number from the RSS feed is a little bit hacky. There are a few ways:
// 1. A "sparkle:version" attribute on the enclosure tag, an extension from the RSS spec.
// 2. If there isn't a version attribute, Sparkle will parse the path in the enclosure, expecting
// 2. If there isn't a version attribute, see if there is a version element (this is now the recommended path).
// 3. If there isn't a version element, Sparkle will parse the path in the enclosure, expecting
// that it will look like this: http://something.com/YourApp_0.5.zip. It'll read whatever's between the last
// underscore and the last period as the version number. So name your packages like this: APPNAME_VERSION.extension.
// The big caveat with this is that you can't have underscores in your version strings, as that'll confuse Sparkle.
// Feel free to change the separator string to a hyphen or something more suited to your needs if you like.
NSString *newVersion = [enclosure objectForKey:SUAppcastAttributeVersion];
if (newVersion == nil) {
newVersion = [dict objectForKey:SUAppcastAttributeVersion]; // Get version from the item, in case it's a download-less item (i.e. paid upgrade).
// Get version from the item
newVersion = [dict objectForKey:SUAppcastElementVersion];
}
if (newVersion == nil) // no sparkle:version attribute anywhere?
if (newVersion == nil)
{
SULog(SULogLevelError, @"warning: <%@> for URL '%@' is missing %@ attribute. Version comparison may be unreliable. Please always specify %@", SURSSElementEnclosure, [enclosure objectForKey:SURSSAttributeURL], SUAppcastAttributeVersion, SUAppcastAttributeVersion);
// No sparkle:version element/attribute anywhere?
SULog(SULogLevelError, @"warning: Item '%@' is missing '<%@>' element. Version comparison may be unreliable. Please always specify %@", _title, SUAppcastElementVersion, SUAppcastElementVersion);

// Separate the url by underscores and take the last component, as that'll be closest to the end,
// then we remove the extension. Hopefully, this will be the version.
Expand All @@ -347,7 +352,7 @@ - (nullable instancetype)initWithDictionary:(NSDictionary *)dict relativeToURL:(

if (!newVersion) {
if (error) {
*error = [NSString stringWithFormat:@"Feed item lacks %@ attribute, and version couldn't be deduced from file name (would have used last component of a file name like AppName_1.3.4.zip)", SUAppcastAttributeVersion];
*error = [NSString stringWithFormat:@"Feed item lacks %@ element, and version couldn't be deduced from file name (would have used last component of a file name like AppName_1.3.4.zip)", SUAppcastElementVersion];
}
return nil;
}
Expand All @@ -360,7 +365,6 @@ - (nullable instancetype)initWithDictionary:(NSDictionary *)dict relativeToURL:(
}

_propertiesDictionary = [[NSDictionary alloc] initWithDictionary:dict];
_title = [(NSString *)[dict objectForKey:SURSSElementTitle] copy];
_dateString = [(NSString *)[dict objectForKey:SURSSElementPubDate] copy];
_itemDescription = [(NSString *)[dict objectForKey:SURSSElementDescription] copy];

Expand Down
2 changes: 2 additions & 0 deletions Sparkle/SUConstants.h
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ extern NSString *const SUAppcastAttributeVersion;
extern NSString *const SUAppcastAttributeOsType;
extern NSString *const SUAppcastAttributeInstallationType;

extern NSString *const SUAppcastElementVersion;
extern NSString *const SUAppcastElementShortVersionString;
extern NSString *const SUAppcastElementCriticalUpdate;
extern NSString *const SUAppcastElementDeltas;
extern NSString *const SUAppcastElementMinimumAutoupdateVersion;
Expand Down
4 changes: 4 additions & 0 deletions Sparkle/SUConstants.m
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@
NSString *const SUFixedHTMLDisplaySizeKey = @"SUFixedHTMLDisplaySize";
NSString *const SUDefaultsDomainKey = @"SUDefaultsDomain";
NSString *const SUSparkleErrorDomain = @"SUSparkleErrorDomain";
NSString *const SPUNoUpdateFoundReasonKey = @"SUNoUpdateFoundReason";
NSString *const SPULatestAppcastItemFoundKey = @"SULatestAppcastItemFound";

NSString *const SUAppendVersionNumberKey = @"SUAppendVersionNumber";
NSString *const SUEnableAutomatedDowngradesKey = @"SUEnableAutomatedDowngrades";
Expand All @@ -71,6 +73,8 @@
NSString *const SUAppcastAttributeOsType = @"sparkle:os";
NSString *const SUAppcastAttributeInstallationType = @"sparkle:installationType";

NSString *const SUAppcastElementVersion = SUAppcastAttributeVersion;
NSString *const SUAppcastElementShortVersionString = SUAppcastAttributeShortVersionString;
NSString *const SUAppcastElementCriticalUpdate = @"sparkle:criticalUpdate";
NSString *const SUAppcastElementDeltas = @"sparkle:deltas";
NSString *const SUAppcastElementMinimumAutoupdateVersion = @"sparkle:minimumAutoupdateVersion";
Expand Down
11 changes: 11 additions & 0 deletions Sparkle/SUErrors.h
Original file line number Diff line number Diff line change
Expand Up @@ -78,4 +78,15 @@ typedef NS_ENUM(OSStatus, SUError) {
SUIncorrectAPIUsageError = 5000
};

typedef NS_ENUM(OSStatus, SPUNoUpdateFoundReason) {
SPUNoUpdateFoundReasonUnknown,
SPUNoUpdateFoundReasonOnLatestVersion,
SPUNoUpdateFoundReasonOnNewerThanLatestVersion,
SPUNoUpdateFoundReasonSystemIsTooOld,
SPUNoUpdateFoundReasonSystemIsTooNew
};

SU_EXPORT extern NSString *const SPUNoUpdateFoundReasonKey;
SU_EXPORT extern NSString *const SPULatestAppcastItemFoundKey;

#endif
Loading

0 comments on commit c329319

Please sign in to comment.