diff --git a/Sparkle/SPUAutomaticUpdateDriver.m b/Sparkle/SPUAutomaticUpdateDriver.m index c6626fa344..8d2ce908fc 100644 --- a/Sparkle/SPUAutomaticUpdateDriver.m +++ b/Sparkle/SPUAutomaticUpdateDriver.m @@ -77,15 +77,15 @@ - (void)resumeUpdate:(id)__unused resumableUpdate completion SULog(SULogLevelError, @"Error: resumeDownloadedUpdate:completion: called on SPUAutomaticUpdateDriver"); } -- (void)basicDriverDidFindUpdateWithAppcastItem:(SUAppcastItem *)updateItem preventsAutoupdate:(BOOL)preventsAutoupdate +- (void)basicDriverDidFindUpdateWithAppcastItem:(SUAppcastItem *)updateItem secondaryAppcastItem:(SUAppcastItem *)secondaryUpdateItem preventsAutoupdate:(BOOL)preventsAutoupdate { self.updateItem = updateItem; if (updateItem.isInformationOnlyUpdate || preventsAutoupdate) { - [self.coreDriver deferInformationalUpdate:updateItem preventsAutoupdate:preventsAutoupdate]; + [self.coreDriver deferInformationalUpdate:updateItem secondaryUpdate:secondaryUpdateItem preventsAutoupdate:preventsAutoupdate]; [self abortUpdate]; } else { - [self.coreDriver downloadUpdateFromAppcastItem:updateItem inBackground:YES]; + [self.coreDriver downloadUpdateFromAppcastItem:updateItem secondaryAppcastItem:secondaryUpdateItem inBackground:YES]; } } diff --git a/Sparkle/SPUBasicUpdateDriver.h b/Sparkle/SPUBasicUpdateDriver.h index 8598024fc8..dcde2abfef 100644 --- a/Sparkle/SPUBasicUpdateDriver.h +++ b/Sparkle/SPUBasicUpdateDriver.h @@ -16,7 +16,7 @@ NS_ASSUME_NONNULL_BEGIN @protocol SPUBasicUpdateDriverDelegate -- (void)basicDriverDidFindUpdateWithAppcastItem:(SUAppcastItem *)appcastItem preventsAutoupdate:(BOOL)preventsAutoupdate systemDomain:(NSNumber * _Nullable)systemDomain; +- (void)basicDriverDidFindUpdateWithAppcastItem:(SUAppcastItem *)appcastItem secondaryAppcastItem:(SUAppcastItem *)secondaryAppcastItem preventsAutoupdate:(BOOL)preventsAutoupdate systemDomain:(NSNumber * _Nullable)systemDomain; - (void)basicDriverIsRequestingAbortUpdateWithError:(nullable NSError *)error; @@ -38,8 +38,6 @@ NS_ASSUME_NONNULL_BEGIN - (void)resumeUpdate:(id)resumableUpdate completion:(SPUUpdateDriverCompletion)completionBlock; -@property (nullable, nonatomic, readonly) SUAppcastItem *nonDeltaUpdateItem; - - (void)abortUpdateAndShowNextUpdateImmediately:(BOOL)shouldSignalShowingUpdate resumableUpdate:(id _Nullable)resumableUpdate error:(nullable NSError *)error; @end diff --git a/Sparkle/SPUBasicUpdateDriver.m b/Sparkle/SPUBasicUpdateDriver.m index 49929c1af5..c6427f1ca8 100644 --- a/Sparkle/SPUBasicUpdateDriver.m +++ b/Sparkle/SPUBasicUpdateDriver.m @@ -79,7 +79,7 @@ - (void)checkForUpdatesAtAppcastURL:(NSURL *)appcastURL withUserAgent:(NSString } } -- (void)notifyResumableUpdateItem:(SUAppcastItem *)updateItem systemDomain:(NSNumber * _Nullable)systemDomain +- (void)notifyResumableUpdateItem:(SUAppcastItem *)updateItem secondaryUpdateItem:(SUAppcastItem *)secondaryUpdateItem systemDomain:(NSNumber * _Nullable)systemDomain { if (updateItem == nil) { [self.delegate basicDriverIsRequestingAbortUpdateWithError:[NSError errorWithDomain:SUSparkleErrorDomain code:SUResumeAppcastError userInfo:@{ NSLocalizedDescriptionKey: SULocalizedString(@"Failed to resume installing update.", nil) }]]; @@ -88,7 +88,7 @@ - (void)notifyResumableUpdateItem:(SUAppcastItem *)updateItem systemDomain:(NSNu [self notifyFinishLoadingAppcast]; SUAppcastItem *nonNullUpdateItem = updateItem; - [self notifyFoundValidUpdateWithAppcastItem:nonNullUpdateItem preventsAutoupdate:NO systemDomain:systemDomain]; + [self notifyFoundValidUpdateWithAppcastItem:nonNullUpdateItem secondaryAppcastItem:secondaryUpdateItem preventsAutoupdate:NO systemDomain:systemDomain]; } } @@ -100,7 +100,7 @@ - (void)resumeInstallingUpdateWithCompletion:(SPUUpdateDriverCompletion)completi assert(hostBundleIdentifier != nil); [SPUProbeInstallStatus probeInstallerUpdateItemForHostBundleIdentifier:hostBundleIdentifier completion:^(SPUInstallationInfo * _Nullable installationInfo) { dispatch_async(dispatch_get_main_queue(), ^{ - [self notifyResumableUpdateItem:installationInfo.appcastItem systemDomain:@(installationInfo.systemDomain)]; + [self notifyResumableUpdateItem:installationInfo.appcastItem secondaryUpdateItem:nil systemDomain:@(installationInfo.systemDomain)]; }); }]; } @@ -109,12 +109,7 @@ - (void)resumeUpdate:(id)resumableUpdate completion:(SPUUpda { self.completionBlock = completionBlock; - [self notifyResumableUpdateItem:resumableUpdate.updateItem systemDomain:nil]; -} - -- (SUAppcastItem *)nonDeltaUpdateItem -{ - return self.appcastDriver.nonDeltaUpdateItem; + [self notifyResumableUpdateItem:resumableUpdate.updateItem secondaryUpdateItem:resumableUpdate.secondaryUpdateItem systemDomain:nil]; } - (void)didFailToFetchAppcastWithError:(NSError *)error @@ -142,7 +137,7 @@ - (void)didFinishLoadingAppcast:(SUAppcast *)appcast } } -- (void)notifyFoundValidUpdateWithAppcastItem:(SUAppcastItem *)updateItem preventsAutoupdate:(BOOL)preventsAutoupdate systemDomain:(NSNumber * _Nullable)systemDomain +- (void)notifyFoundValidUpdateWithAppcastItem:(SUAppcastItem *)updateItem secondaryAppcastItem:(SUAppcastItem *)secondaryUpdateItem preventsAutoupdate:(BOOL)preventsAutoupdate systemDomain:(NSNumber * _Nullable)systemDomain { if (!self.aborted) { [[NSNotificationCenter defaultCenter] postNotificationName:SUUpdaterDidFindValidUpdateNotification @@ -153,13 +148,13 @@ - (void)notifyFoundValidUpdateWithAppcastItem:(SUAppcastItem *)updateItem preven [self.updaterDelegate updater:self.updater didFindValidUpdate:updateItem]; } - [self.delegate basicDriverDidFindUpdateWithAppcastItem:updateItem preventsAutoupdate:preventsAutoupdate systemDomain:systemDomain]; + [self.delegate basicDriverDidFindUpdateWithAppcastItem:updateItem secondaryAppcastItem:secondaryUpdateItem preventsAutoupdate:preventsAutoupdate systemDomain:systemDomain]; } } -- (void)didFindValidUpdateWithAppcastItem:(SUAppcastItem *)updateItem preventsAutoupdate:(BOOL)preventsAutoupdate +- (void)didFindValidUpdateWithAppcastItem:(SUAppcastItem *)updateItem secondaryAppcastItem:(SUAppcastItem *)secondaryAppcastItem preventsAutoupdate:(BOOL)preventsAutoupdate { - [self notifyFoundValidUpdateWithAppcastItem:updateItem preventsAutoupdate:preventsAutoupdate systemDomain:nil]; + [self notifyFoundValidUpdateWithAppcastItem:updateItem secondaryAppcastItem:secondaryAppcastItem preventsAutoupdate:preventsAutoupdate systemDomain:nil]; } - (void)didNotFindUpdateWithLatestAppcastItem:(nullable SUAppcastItem *)latestAppcastItem hostToLatestAppcastItemComparisonResult:(NSComparisonResult)hostToLatestAppcastItemComparisonResult diff --git a/Sparkle/SPUCoreBasedUpdateDriver.h b/Sparkle/SPUCoreBasedUpdateDriver.h index 57806ee8e4..f936e9b00c 100644 --- a/Sparkle/SPUCoreBasedUpdateDriver.h +++ b/Sparkle/SPUCoreBasedUpdateDriver.h @@ -17,7 +17,7 @@ NS_ASSUME_NONNULL_BEGIN @protocol SPUCoreBasedUpdateDriverDelegate -- (void)basicDriverDidFindUpdateWithAppcastItem:(SUAppcastItem *)updateItem preventsAutoupdate:(BOOL)preventsAutoupdate; +- (void)basicDriverDidFindUpdateWithAppcastItem:(SUAppcastItem *)updateItem secondaryAppcastItem:(SUAppcastItem *)secondaryAppcastItem preventsAutoupdate:(BOOL)preventsAutoupdate; - (void)installerDidFinishPreparationAndWillInstallImmediately:(BOOL)willInstallImmediately silently:(BOOL)willInstallSilently; @@ -61,9 +61,9 @@ NS_ASSUME_NONNULL_BEGIN - (void)resumeUpdate:(id)resumableUpdate completion:(SPUUpdateDriverCompletion)completionBlock; -- (void)downloadUpdateFromAppcastItem:(SUAppcastItem *)updateItem inBackground:(BOOL)background; +- (void)downloadUpdateFromAppcastItem:(SUAppcastItem *)updateItem secondaryAppcastItem:(SUAppcastItem * _Nullable)secondaryUpdateItem inBackground:(BOOL)background; -- (void)deferInformationalUpdate:(SUAppcastItem *)updateItem preventsAutoupdate:(BOOL)preventsAutoupdate; +- (void)deferInformationalUpdate:(SUAppcastItem *)updateItem secondaryUpdate:(SUAppcastItem *)secondaryUpdateItem preventsAutoupdate:(BOOL)preventsAutoupdate; - (void)extractDownloadedUpdate; diff --git a/Sparkle/SPUCoreBasedUpdateDriver.m b/Sparkle/SPUCoreBasedUpdateDriver.m index a769fe08fe..b095524ffe 100644 --- a/Sparkle/SPUCoreBasedUpdateDriver.m +++ b/Sparkle/SPUCoreBasedUpdateDriver.m @@ -32,6 +32,7 @@ @interface SPUCoreBasedUpdateDriver () delegate; @property (nonatomic) SUAppcastItem *updateItem; +@property (nonatomic) SUAppcastItem *secondaryUpdateItem; @property (nonatomic) id resumableUpdate; @property (nonatomic) SPUDownloadedUpdate *downloadedUpdateForRemoval; @@ -53,6 +54,7 @@ @implementation SPUCoreBasedUpdateDriver @synthesize installerDriver = _installerDriver; @synthesize delegate = _delegate; @synthesize updateItem = _updateItem; +@synthesize secondaryUpdateItem = _secondaryUpdateItem; @synthesize host = _host; @synthesize resumingInstallingUpdate = _resumingInstallingUpdate; @synthesize silentInstall = _silentInstall; @@ -147,33 +149,34 @@ - (void)basicDriverDidFinishLoadingAppcast } } -- (void)basicDriverDidFindUpdateWithAppcastItem:(SUAppcastItem *)updateItem preventsAutoupdate:(BOOL)preventsAutoupdate systemDomain:(NSNumber * _Nullable)systemDomain +- (void)basicDriverDidFindUpdateWithAppcastItem:(SUAppcastItem *)updateItem secondaryAppcastItem:(SUAppcastItem *)secondaryUpdateItem preventsAutoupdate:(BOOL)preventsAutoupdate systemDomain:(NSNumber * _Nullable)systemDomain { self.updateItem = updateItem; + self.secondaryUpdateItem = secondaryUpdateItem; if (self.resumingInstallingUpdate) { assert(systemDomain != nil); [self.installerDriver resumeInstallingUpdateWithUpdateItem:updateItem systemDomain:systemDomain.boolValue]; - [self.delegate basicDriverDidFindUpdateWithAppcastItem:updateItem preventsAutoupdate:preventsAutoupdate]; + [self.delegate basicDriverDidFindUpdateWithAppcastItem:updateItem secondaryAppcastItem:secondaryUpdateItem preventsAutoupdate:preventsAutoupdate]; } else { if (!self.preventsInstallerInteraction) { // Simple case - delegate allows interaction, so we should continue along - [self.delegate basicDriverDidFindUpdateWithAppcastItem:updateItem preventsAutoupdate:preventsAutoupdate]; + [self.delegate basicDriverDidFindUpdateWithAppcastItem:updateItem secondaryAppcastItem:secondaryUpdateItem preventsAutoupdate:preventsAutoupdate]; } else { // Package type installations will always require installer interaction as long as we don't support running as root // If it's not a package type installation, we should be okay since we did an auth check before checking for updates above if (![updateItem.installationType isEqualToString:SPUInstallationTypeApplication]) { [self.delegate coreDriverIsRequestingAbortUpdateWithError:[NSError errorWithDomain:SUSparkleErrorDomain code:SUNotAllowedInteractionError userInfo:@{ NSLocalizedDescriptionKey: [NSString stringWithFormat:@"A new update is available but cannot be installed because interaction has been prevented."] }]]; } else { - [self.delegate basicDriverDidFindUpdateWithAppcastItem:updateItem preventsAutoupdate:preventsAutoupdate]; + [self.delegate basicDriverDidFindUpdateWithAppcastItem:updateItem secondaryAppcastItem:secondaryUpdateItem preventsAutoupdate:preventsAutoupdate]; } } } } -- (void)downloadUpdateFromAppcastItem:(SUAppcastItem *)updateItem inBackground:(BOOL)background +- (void)downloadUpdateFromAppcastItem:(SUAppcastItem *)updateItem secondaryAppcastItem:(SUAppcastItem * _Nullable)secondaryUpdateItem inBackground:(BOOL)background { - self.downloadDriver = [[SPUDownloadDriver alloc] initWithUpdateItem:updateItem host:self.host userAgent:self.userAgent httpHeaders:self.httpHeaders inBackground:background delegate:self]; + self.downloadDriver = [[SPUDownloadDriver alloc] initWithUpdateItem:updateItem secondaryUpdateItem:secondaryUpdateItem host:self.host userAgent:self.userAgent httpHeaders:self.httpHeaders inBackground:background delegate:self]; if ([self.updaterDelegate respondsToSelector:@selector((updater:willDownloadUpdate:withRequest:))]) { [self.updaterDelegate updater:self.updater @@ -222,9 +225,9 @@ - (void)downloadDriverDidDownloadUpdate:(SPUDownloadedUpdate *)downloadedUpdate [self extractUpdate:downloadedUpdate]; } -- (void)deferInformationalUpdate:(SUAppcastItem *)updateItem preventsAutoupdate:(BOOL)preventsAutoupdate +- (void)deferInformationalUpdate:(SUAppcastItem *)updateItem secondaryUpdate:(SUAppcastItem *)secondaryUpdateItem preventsAutoupdate:(BOOL)preventsAutoupdate { - self.resumableUpdate = [[SPUInformationalUpdate alloc] initWithAppcastItem:updateItem preventsAutoupdate:preventsAutoupdate]; + self.resumableUpdate = [[SPUInformationalUpdate alloc] initWithAppcastItem:updateItem secondaryAppcastItem:secondaryUpdateItem preventsAutoupdate:preventsAutoupdate]; } - (void)extractDownloadedUpdate @@ -381,16 +384,18 @@ - (void)basicDriverIsRequestingAbortUpdateWithError:(nullable NSError *)error - (void)installerDidFailToApplyDeltaUpdate { - SUAppcastItem *nonDeltaUpdateItem = self.basicDriver.nonDeltaUpdateItem; - assert(nonDeltaUpdateItem != nil); + SUAppcastItem *secondaryUpdateItem = self.secondaryUpdateItem; + assert(secondaryUpdateItem != nil); BOOL backgroundDownload = self.downloadDriver.inBackground; [self clearDownloadedUpdate]; // Fall back to the non-delta update. Note that we don't want to trigger another update was found event. - self.updateItem = nonDeltaUpdateItem; - [self downloadUpdateFromAppcastItem:nonDeltaUpdateItem inBackground:backgroundDownload]; + self.updateItem = secondaryUpdateItem; + self.secondaryUpdateItem = nil; + + [self downloadUpdateFromAppcastItem:secondaryUpdateItem secondaryAppcastItem:nil inBackground:backgroundDownload]; } - (void)abortUpdateAndShowNextUpdateImmediately:(BOOL)shouldShowUpdateImmediately error:(nullable NSError *)error diff --git a/Sparkle/SPUDownloadDriver.h b/Sparkle/SPUDownloadDriver.h index 43fd6667af..14934806b5 100644 --- a/Sparkle/SPUDownloadDriver.h +++ b/Sparkle/SPUDownloadDriver.h @@ -28,7 +28,7 @@ NS_ASSUME_NONNULL_BEGIN @interface SPUDownloadDriver : NSObject -- (instancetype)initWithUpdateItem:(SUAppcastItem *)updateItem host:(SUHost *)host userAgent:(NSString *)userAgent httpHeaders:(NSDictionary * _Nullable)httpHeaders inBackground:(BOOL)background delegate:(id)delegate; +- (instancetype)initWithUpdateItem:(SUAppcastItem *)updateItem secondaryUpdateItem:(SUAppcastItem * _Nullable)secondaryUpdateItem host:(SUHost *)host userAgent:(NSString *)userAgent httpHeaders:(NSDictionary * _Nullable)httpHeaders inBackground:(BOOL)background delegate:(id)delegate; - (instancetype)initWithHost:(SUHost *)host; diff --git a/Sparkle/SPUDownloadDriver.m b/Sparkle/SPUDownloadDriver.m index 7624510aad..7b7c3c039f 100644 --- a/Sparkle/SPUDownloadDriver.m +++ b/Sparkle/SPUDownloadDriver.m @@ -28,6 +28,7 @@ @interface SPUDownloadDriver () @property (nonatomic) id downloader; @property (nonatomic) NSXPCConnection *connection; @property (nonatomic, readonly) SUAppcastItem *updateItem; +@property (nonatomic, readonly) SUAppcastItem *secondaryUpdateItem; @property (nonatomic, readonly) SUHost *host; @property (nonatomic, copy) NSString *temporaryDirectory; @property (nonatomic, copy) NSString *downloadName; @@ -43,6 +44,7 @@ @implementation SPUDownloadDriver @synthesize downloader = _downloader; @synthesize connection = _connection; @synthesize updateItem = _updateItem; +@synthesize secondaryUpdateItem = _secondaryUpdateItem; @synthesize request = _request; @synthesize inBackground = _inBackground; @synthesize host = _host; @@ -106,11 +108,12 @@ - (instancetype)initWithHost:(SUHost *)host return self; } -- (instancetype)initWithUpdateItem:(SUAppcastItem *)updateItem host:(SUHost *)host userAgent:(NSString *)userAgent httpHeaders:(NSDictionary * _Nullable)httpHeaders inBackground:(BOOL)background delegate:(id)delegate +- (instancetype)initWithUpdateItem:(SUAppcastItem *)updateItem secondaryUpdateItem:(SUAppcastItem * _Nullable)secondaryUpdateItem host:(SUHost *)host userAgent:(NSString *)userAgent httpHeaders:(NSDictionary * _Nullable)httpHeaders inBackground:(BOOL)background delegate:(id)delegate { self = [self initWithHost:host]; if (self != nil) { _updateItem = updateItem; + _secondaryUpdateItem = secondaryUpdateItem; _delegate = delegate; _inBackground = background; @@ -188,7 +191,7 @@ - (void)downloaderDidFinishWithTemporaryDownloadData:(SPUDownloadData * _Nullabl SULog(SULogLevelError, @"Warning: Downloader's expected content length (%llu) != Appcast item's length (%llu)", self.expectedContentLength, self.updateItem.contentLength); } - SPUDownloadedUpdate *downloadedUpdate = [[SPUDownloadedUpdate alloc] initWithAppcastItem:self.updateItem downloadName:self.downloadName temporaryDirectory:self.temporaryDirectory]; + SPUDownloadedUpdate *downloadedUpdate = [[SPUDownloadedUpdate alloc] initWithAppcastItem:self.updateItem secondaryAppcastItem:self.secondaryUpdateItem downloadName:self.downloadName temporaryDirectory:self.temporaryDirectory]; [self.delegate downloadDriverDidDownloadUpdate:downloadedUpdate]; }); diff --git a/Sparkle/SPUDownloadedUpdate.h b/Sparkle/SPUDownloadedUpdate.h index 349d03a48a..2dc0a842c4 100644 --- a/Sparkle/SPUDownloadedUpdate.h +++ b/Sparkle/SPUDownloadedUpdate.h @@ -13,7 +13,7 @@ NS_ASSUME_NONNULL_BEGIN @interface SPUDownloadedUpdate : NSObject -- (instancetype)initWithAppcastItem:(SUAppcastItem *)updateItem downloadName:(NSString *)downloadName temporaryDirectory:(NSString *)temporaryDirectory; +- (instancetype)initWithAppcastItem:(SUAppcastItem *)updateItem secondaryAppcastItem:(SUAppcastItem * _Nullable)secondaryItem downloadName:(NSString *)downloadName temporaryDirectory:(NSString *)temporaryDirectory; @property (nonatomic, copy, readonly) NSString *downloadName; @property (nonatomic, copy, readonly) NSString *temporaryDirectory; diff --git a/Sparkle/SPUDownloadedUpdate.m b/Sparkle/SPUDownloadedUpdate.m index 07b8cdb804..f3d7b520c1 100644 --- a/Sparkle/SPUDownloadedUpdate.m +++ b/Sparkle/SPUDownloadedUpdate.m @@ -16,15 +16,17 @@ @implementation SPUDownloadedUpdate // If we ever enable auto-synthesize in the future, we'll still need this synthesize // because the property is declared in a protocol @synthesize updateItem = _updateItem; +@synthesize secondaryUpdateItem = _secondaryUpdateItem; @synthesize downloadName = _downloadName; @synthesize temporaryDirectory = _temporaryDirectory; -- (instancetype)initWithAppcastItem:(SUAppcastItem *)updateItem downloadName:(NSString *)downloadName temporaryDirectory:(NSString *)temporaryDirectory +- (instancetype)initWithAppcastItem:(SUAppcastItem *)updateItem secondaryAppcastItem:(SUAppcastItem * _Nullable)secondaryUpdateItem downloadName:(NSString *)downloadName temporaryDirectory:(NSString *)temporaryDirectory { self = [super init]; if (self != nil) { _updateItem = updateItem; + _secondaryUpdateItem = secondaryUpdateItem; _downloadName = [downloadName copy]; _temporaryDirectory = [temporaryDirectory copy]; } diff --git a/Sparkle/SPUInformationalUpdate.h b/Sparkle/SPUInformationalUpdate.h index c26b03f48f..0d54f99d09 100644 --- a/Sparkle/SPUInformationalUpdate.h +++ b/Sparkle/SPUInformationalUpdate.h @@ -13,7 +13,7 @@ NS_ASSUME_NONNULL_BEGIN @interface SPUInformationalUpdate : NSObject -- (instancetype)initWithAppcastItem:(SUAppcastItem *)updateItem preventsAutoupdate:(BOOL)preventsAutoupdate; +- (instancetype)initWithAppcastItem:(SUAppcastItem *)updateItem secondaryAppcastItem:(SUAppcastItem *)secondaryUpdateItem preventsAutoupdate:(BOOL)preventsAutoupdate; @end diff --git a/Sparkle/SPUInformationalUpdate.m b/Sparkle/SPUInformationalUpdate.m index 6dd3b15c51..e9eec62f3d 100644 --- a/Sparkle/SPUInformationalUpdate.m +++ b/Sparkle/SPUInformationalUpdate.m @@ -16,13 +16,15 @@ @implementation SPUInformationalUpdate // If we ever enable auto-synthesize in the future, we'll still need this synthesize // because the property is declared in a protocol @synthesize updateItem = _updateItem; +@synthesize secondaryUpdateItem = _secondaryUpdateItem; @synthesize preventsAutoupdate = _preventsAutoupdate; -- (instancetype)initWithAppcastItem:(SUAppcastItem *)updateItem preventsAutoupdate:(BOOL)preventsAutoupdate +- (instancetype)initWithAppcastItem:(SUAppcastItem *)updateItem secondaryAppcastItem:(SUAppcastItem *)secondaryUpdateItem preventsAutoupdate:(BOOL)preventsAutoupdate { self = [super init]; if (self != nil) { _updateItem = updateItem; + _secondaryUpdateItem = secondaryUpdateItem; _preventsAutoupdate = preventsAutoupdate; } return self; diff --git a/Sparkle/SPUProbingUpdateDriver.m b/Sparkle/SPUProbingUpdateDriver.m index 6cd8fc3b30..bb377ef7da 100644 --- a/Sparkle/SPUProbingUpdateDriver.m +++ b/Sparkle/SPUProbingUpdateDriver.m @@ -54,7 +54,7 @@ - (void)resumeUpdate:(id)resumableUpdate completion:(SPUUpda [self.basicDriver resumeUpdate:resumableUpdate completion:completionBlock]; } -- (void)basicDriverDidFindUpdateWithAppcastItem:(SUAppcastItem *)__unused appcastItem preventsAutoupdate:(BOOL)__unused preventsAutoupdate systemDomain:(NSNumber * _Nullable)__unused systemDomain +- (void)basicDriverDidFindUpdateWithAppcastItem:(SUAppcastItem *)__unused appcastItem secondaryAppcastItem:(SUAppcastItem *)__unused secondaryAppcastItem preventsAutoupdate:(BOOL)__unused preventsAutoupdate systemDomain:(NSNumber * _Nullable)__unused systemDomain { // Stop as soon as we have an answer [self abortUpdate]; diff --git a/Sparkle/SPUResumableUpdate.h b/Sparkle/SPUResumableUpdate.h index 65ad7db66d..895a80c888 100644 --- a/Sparkle/SPUResumableUpdate.h +++ b/Sparkle/SPUResumableUpdate.h @@ -15,6 +15,7 @@ NS_ASSUME_NONNULL_BEGIN @protocol SPUResumableUpdate @property (nonatomic, readonly) SUAppcastItem *updateItem; +@property (nonatomic, readonly, nullable) SUAppcastItem *secondaryUpdateItem; @property (nonatomic, readonly) BOOL preventsAutoupdate; @end diff --git a/Sparkle/SPUUIBasedUpdateDriver.m b/Sparkle/SPUUIBasedUpdateDriver.m index ea29f6d0f6..54ae8de44b 100644 --- a/Sparkle/SPUUIBasedUpdateDriver.m +++ b/Sparkle/SPUUIBasedUpdateDriver.m @@ -105,7 +105,7 @@ - (void)basicDriverDidFinishLoadingAppcast } } -- (void)basicDriverDidFindUpdateWithAppcastItem:(SUAppcastItem *)updateItem preventsAutoupdate:(BOOL)preventsAutoupdate +- (void)basicDriverDidFindUpdateWithAppcastItem:(SUAppcastItem *)updateItem secondaryAppcastItem:(SUAppcastItem *)secondaryUpdateItem preventsAutoupdate:(BOOL)preventsAutoupdate { id updaterDelegate = self.updaterDelegate; if ([self.userDriver respondsToSelector:@selector(showUpdateFoundWithAppcastItem:userInitiated:state:reply:)]) { @@ -143,7 +143,7 @@ - (void)basicDriverDidFindUpdateWithAppcastItem:(SUAppcastItem *)updateItem prev [self.coreDriver finishInstallationWithResponse:validatedChoice displayingUserInterface:!self.preventsInstallerInteraction]; break; case SPUUserUpdateStateNotDownloaded: - [self.coreDriver downloadUpdateFromAppcastItem:updateItem inBackground:NO]; + [self.coreDriver downloadUpdateFromAppcastItem:updateItem secondaryAppcastItem:secondaryUpdateItem inBackground:NO]; break; case SPUUserUpdateStateInformational: assert(false); @@ -235,7 +235,7 @@ - (void)basicDriverDidFindUpdateWithAppcastItem:(SUAppcastItem *)updateItem prev [self.host setObject:nil forUserDefaultsKey:SUSkippedVersionKey]; switch (choice) { case SPUInstallUpdateChoice: - [self.coreDriver downloadUpdateFromAppcastItem:updateItem inBackground:NO]; + [self.coreDriver downloadUpdateFromAppcastItem:updateItem secondaryAppcastItem:secondaryUpdateItem inBackground:NO]; break; case SPUSkipThisVersionChoice: [self.host setObject:[updateItem versionString] forUserDefaultsKey:SUSkippedVersionKey]; diff --git a/Sparkle/SUAppcast.h b/Sparkle/SUAppcast.h index 5ae0f0fabc..7030dff314 100644 --- a/Sparkle/SUAppcast.h +++ b/Sparkle/SUAppcast.h @@ -28,10 +28,9 @@ SU_EXPORT @interface SUAppcast : NSObject @property (copy, nullable) NSDictionary *httpHeaders; - (void)fetchAppcastFromURL:(NSURL *)url inBackground:(BOOL)bg completionBlock:(void (^)(NSError *_Nullable))err; -- (SUAppcast *)copyWithoutDeltaUpdates; - (SUAppcast *)copyByFilteringItems:(BOOL (^)(SUAppcastItem *))filterBlock; -@property (readonly, copy, nullable) NSArray *items; +@property (readonly, copy, nullable) NSArray *items; @end NS_ASSUME_NONNULL_END diff --git a/Sparkle/SUAppcast.m b/Sparkle/SUAppcast.m index 640960e833..467854a73f 100644 --- a/Sparkle/SUAppcast.m +++ b/Sparkle/SUAppcast.m @@ -47,7 +47,7 @@ - (NSDictionary *)attributesAsDictionary @interface SUAppcast () @property (strong) void (^completionBlock)(NSError *); -@property (copy) NSArray *items; +@property (copy) NSArray *items; - (void)reportError:(NSError *)error; - (NSXMLNode *)bestNodeInNodes:(NSArray *)nodes; @end @@ -306,12 +306,6 @@ - (NSXMLNode *)bestNodeInNodes:(NSArray *)nodes return [nodes objectAtIndex:i]; } -- (SUAppcast *)copyWithoutDeltaUpdates { - return [self copyByFilteringItems:^(SUAppcastItem *item) { - return (BOOL)![item isDeltaUpdate]; - }]; -} - - (SUAppcast *)copyByFilteringItems:(BOOL (^)(SUAppcastItem *))filterBlock { SUAppcast *other = [SUAppcast new]; diff --git a/Sparkle/SUAppcastDriver.h b/Sparkle/SUAppcastDriver.h index b5b2dbf280..e96686783d 100644 --- a/Sparkle/SUAppcastDriver.h +++ b/Sparkle/SUAppcastDriver.h @@ -17,7 +17,7 @@ NS_ASSUME_NONNULL_BEGIN - (void)didFailToFetchAppcastWithError:(NSError *)error; - (void)didFinishLoadingAppcast:(SUAppcast *)appcast; -- (void)didFindValidUpdateWithAppcastItem:(SUAppcastItem *)appcastItem preventsAutoupdate:(BOOL)preventsAutoupdate; +- (void)didFindValidUpdateWithAppcastItem:(SUAppcastItem *)appcastItem secondaryAppcastItem:(SUAppcastItem *)secondaryAppcastItem preventsAutoupdate:(BOOL)preventsAutoupdate; - (void)didNotFindUpdateWithLatestAppcastItem:(nullable SUAppcastItem *)latestAppcastItem hostToLatestAppcastItemComparisonResult:(NSComparisonResult)hostToLatestAppcastItemComparisonResult; @end @@ -28,8 +28,6 @@ NS_ASSUME_NONNULL_BEGIN - (void)loadAppcastFromURL:(NSURL *)appcastURL userAgent:(NSString *)userAgent httpHeaders:(NSDictionary * _Nullable)httpHeaders inBackground:(BOOL)background includesSkippedUpdates:(BOOL)includesSkippedUpdates; -@property (nullable, nonatomic, readonly) SUAppcastItem *nonDeltaUpdateItem; - @end NS_ASSUME_NONNULL_END diff --git a/Sparkle/SUAppcastDriver.m b/Sparkle/SUAppcastDriver.m index 24a0e69296..64366002e4 100644 --- a/Sparkle/SUAppcastDriver.m +++ b/Sparkle/SUAppcastDriver.m @@ -16,6 +16,7 @@ #import "SUHost.h" #import "SUConstants.h" #import "SUPhasedUpdateGroupInfo.h" +#import "SULog.h" #include "AppKitPrevention.h" @@ -26,7 +27,6 @@ @interface SUAppcastDriver () @property (nonatomic, copy) NSString *userAgent; @property (nullable, nonatomic, readonly, weak) id updater; @property (nullable, nonatomic, readonly, weak) id updaterDelegate; -@property (nullable, nonatomic) SUAppcastItem *nonDeltaUpdateItem; @property (nullable, nonatomic, readonly, weak) id delegate; @end @@ -37,7 +37,6 @@ @implementation SUAppcastDriver @synthesize userAgent = _userAgent; @synthesize updater = _updater; @synthesize updaterDelegate = _updaterDelegate; -@synthesize nonDeltaUpdateItem = _nonDeltaUpdateItem; @synthesize delegate = _delegate; - (instancetype)initWithHost:(SUHost *)host updater:(id)updater updaterDelegate:(id )updaterDelegate delegate:(id )delegate @@ -68,6 +67,23 @@ - (void)loadAppcastFromURL:(NSURL *)appcastURL userAgent:(NSString *)userAgent h }]; } +- (SUAppcastItem * _Nullable)preferredUpdateForRegularAppcastItem:(SUAppcastItem * _Nullable)regularItem secondaryUpdate:(SUAppcastItem * __autoreleasing _Nullable *)secondaryUpdate +{ + SUAppcastItem *deltaItem = (regularItem != nil) ? [[self class] deltaUpdateFromAppcastItem:regularItem hostVersion:self.host.version] : nil; + + if (deltaItem != nil) { + if (secondaryUpdate != NULL) { + *secondaryUpdate = regularItem; + } + return deltaItem; + } else { + if (secondaryUpdate != NULL) { + *secondaryUpdate = nil; + } + return regularItem; + } +} + - (void)appcastDidFinishLoading:(SUAppcast *)loadedAppcast inBackground:(BOOL)background includesSkippedUpdates:(BOOL)includesSkippedUpdates { [self.delegate didFinishLoadingAppcast:loadedAppcast]; @@ -78,40 +94,43 @@ - (void)appcastDidFinishLoading:(SUAppcast *)loadedAppcast inBackground:(BOOL)ba NSNumber *phasedUpdateGroup = background ? @([SUPhasedUpdateGroupInfo updateGroupForHost:self.host]) : nil; SUAppcast *supportedAppcast = [[self class] filterSupportedAppcast:loadedAppcast phasedUpdateGroup:phasedUpdateGroup]; - SUAppcastItem *item = nil; - SUAppcastItem *nonDeltaUpdateItem = nil; - - // Now we have to find the best valid update in the appcast. - if ([self.updaterDelegate respondsToSelector:@selector((bestValidUpdateInAppcast:forUpdater:))]) - { - item = [self.updaterDelegate bestValidUpdateInAppcast:supportedAppcast forUpdater:(id _Nonnull)self.updater]; - } - - if (item != nil) - { - // Does the delegate want to handle it? - if ([item isDeltaUpdate]) { - nonDeltaUpdateItem = [self.updaterDelegate bestValidUpdateInAppcast:[supportedAppcast copyWithoutDeltaUpdates] forUpdater:(id _Nonnull)self.updater]; - } - } - else // If not, we'll take care of it ourselves. - { - // Find the best supported update - SUAppcastItem *deltaUpdateItem = nil; - item = [[self class] bestItemFromAppcastItems:supportedAppcast.items getDeltaItem:&deltaUpdateItem withHostVersion:self.host.version comparator:[self versionComparator]]; + // Find the best valid update in the appcast by asking the delegate + SUAppcastItem *regularItemFromDelegate; + if ([self.updaterDelegate respondsToSelector:@selector((bestValidUpdateInAppcast:forUpdater:))]) { + SUAppcastItem *candidateItem = [self.updaterDelegate bestValidUpdateInAppcast:supportedAppcast forUpdater:(id _Nonnull)self.updater]; - if (item && deltaUpdateItem) { - nonDeltaUpdateItem = item; - item = deltaUpdateItem; + assert(!candidateItem.deltaUpdate); + if (candidateItem.deltaUpdate) { + // Client would have to go out of their way to examine the .deltaUpdates to return one + // This is very unlikely, and we need them to give us a regular update item back + SULog(SULogLevelError, @"Error: -bestValidUpdateInAppcast:forUpdater: cannot return a delta update item"); + regularItemFromDelegate = nil; + } else { + regularItemFromDelegate = candidateItem; } + } else { + regularItemFromDelegate = nil; + } + + // Take care of finding best appcast item ourselves if delegate does not + SUAppcastItem *regularItem; + if (regularItemFromDelegate == nil) { + regularItem = [[self class] bestItemFromAppcastItems:supportedAppcast.items comparator:[self versionComparator]]; + } else { + regularItem = regularItemFromDelegate; } - if ([self itemContainsValidUpdate:item inBackground:background includesSkippedUpdates:includesSkippedUpdates]) { - self.nonDeltaUpdateItem = nonDeltaUpdateItem; - [self.delegate didFindValidUpdateWithAppcastItem:item preventsAutoupdate:[self itemPreventsAutoupdate:item]]; + // Retrieve the preferred primary and secondary update items + // In the case of a delta update, the preferred primary item will be the delta update, + // and the secondary item will be the regular update. + SUAppcastItem *secondaryItem = nil; + SUAppcastItem *primaryItem = [self preferredUpdateForRegularAppcastItem:regularItem secondaryUpdate:&secondaryItem]; + + if ([self itemContainsValidUpdate:primaryItem inBackground:background includesSkippedUpdates:includesSkippedUpdates]) { + [self.delegate didFindValidUpdateWithAppcastItem:primaryItem secondaryAppcastItem:secondaryItem preventsAutoupdate:[self itemPreventsAutoupdate:primaryItem]]; } else { - NSComparisonResult hostToLatestAppcastItemComparisonResult = (item != nil) ? [[self versionComparator] compareVersion:[self.host version] toVersion:[item versionString]] : 0; - [self.delegate didNotFindUpdateWithLatestAppcastItem:item hostToLatestAppcastItemComparisonResult:hostToLatestAppcastItemComparisonResult]; + NSComparisonResult hostToLatestAppcastItemComparisonResult = (primaryItem != nil) ? [[self versionComparator] compareVersion:[self.host version] toVersion:[primaryItem versionString]] : 0; + [self.delegate didNotFindUpdateWithLatestAppcastItem:primaryItem hostToLatestAppcastItemComparisonResult:hostToLatestAppcastItemComparisonResult]; } } @@ -125,9 +144,12 @@ + (SUAppcast *)filterSupportedAppcast:(SUAppcast *)appcast phasedUpdateGroup:(NS }]; } -// This method is used by unit tests -// This method should not do *any* filtering, only version comparing -+ (SUAppcastItem *)bestItemFromAppcastItems:(NSArray *)appcastItems getDeltaItem:(SUAppcastItem * __autoreleasing *)deltaItem withHostVersion:(NSString *)hostVersion comparator:(id)comparator ++ (SUAppcastItem * _Nullable)deltaUpdateFromAppcastItem:(SUAppcastItem *)appcastItem hostVersion:(NSString *)hostVersion +{ + return appcastItem.deltaUpdates[hostVersion]; +} + ++ (SUAppcastItem * _Nullable)bestItemFromAppcastItems:(NSArray *)appcastItems comparator:(id)comparator { SUAppcastItem *item = nil; for(SUAppcastItem *candidate in appcastItems) { @@ -135,14 +157,17 @@ + (SUAppcastItem *)bestItemFromAppcastItems:(NSArray *)appcastItems getDeltaItem item = candidate; } } - - if (item && deltaItem) { - SUAppcastItem *deltaUpdateItem = [item deltaUpdates][hostVersion]; - if (deltaUpdateItem) { - *deltaItem = deltaUpdateItem; - } + return item; +} + +// This method is used by unit tests +// This method should not do *any* filtering, only version comparing ++ (SUAppcastItem *)bestItemFromAppcastItems:(NSArray *)appcastItems getDeltaItem:(SUAppcastItem * __autoreleasing *)deltaItem withHostVersion:(NSString *)hostVersion comparator:(id)comparator +{ + SUAppcastItem *item = [self bestItemFromAppcastItems:appcastItems comparator:comparator]; + if (item != nil && deltaItem != NULL) { + *deltaItem = [self deltaUpdateFromAppcastItem:item hostVersion:hostVersion]; } - return item; }