Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expose why new update is unavailable #1877

Merged
merged 4 commits into from
Jun 27, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Resources/SampleAppcast.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
<sparkle:releaseNotesLink>
http://you.com/app/2.0.html
</sparkle:releaseNotesLink>
<link>http://sparkle-project.org</link>
<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==" />
<sparkle:minimumSystemVersion>10.11</sparkle:minimumSystemVersion>
Expand All @@ -19,6 +20,7 @@
<sparkle:releaseNotesLink>
http://you.com/app/1.5.html
</sparkle:releaseNotesLink>
<link>http://sparkle-project.org</link>
<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==" />
<sparkle:minimumSystemVersion>10.11</sparkle:minimumSystemVersion>
Expand All @@ -30,6 +32,7 @@
<sparkle:releaseNotesLink>
http://you.com/app/1.4.html
</sparkle:releaseNotesLink>
<link>http://sparkle-project.org</link>
<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" />
<sparkle:minimumSystemVersion>10.11</sparkle:minimumSystemVersion>
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 @@ -146,6 +146,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
34 changes: 27 additions & 7 deletions Sparkle/SPUBasicUpdateDriver.m
Original file line number Diff line number Diff line change
Expand Up @@ -171,59 +171,79 @@ - (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;
}

recoveryOption = @"OK";
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);

reason = SPUNoUpdateFoundReasonUnknown;
}

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

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

NSError *notFoundError =
[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
2 changes: 2 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 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