Skip to content

Commit

Permalink
Fix unsteady progress when installing updates (#2072)
Browse files Browse the repository at this point in the history
Also add -showInstallingUpdateWithApplicationTerminated: to SPUUserDriver which replaces -showInstallingUpdate and -showSendingTerminationSignal
  • Loading branch information
zorgiepoo committed Jan 22, 2022
1 parent 05abb2f commit fa9bfc1
Show file tree
Hide file tree
Showing 10 changed files with 86 additions and 104 deletions.
8 changes: 4 additions & 4 deletions Autoupdate/SUDiskImageUnarchiver.m
Original file line number Diff line number Diff line change
Expand Up @@ -191,13 +191,13 @@ - (void)extractDMGWithNotifier:(SUUnarchiverNotifier *)notifier
NSString *fromPath = [mountPoint stringByAppendingPathComponent:item];
NSString *toPath = [[self.archivePath stringByDeletingLastPathComponent] stringByAppendingPathComponent:item];

// We skip any files in the DMG which are not readable.
itemsCopied += 1.0;
[notifier notifyProgress:0.5 + itemsCopied/(totalItems*2.0)];

// We skip any files in the DMG which are not readable but include the item in the progress
if (![manager isReadableFileAtPath:fromPath]) {
continue;
}

itemsCopied += 1.0;
[notifier notifyProgress:0.5 + itemsCopied/(totalItems*2.0)];

SULog(SULogLevelDefault, @"copyItemAtPath:%@ toPath:%@", fromPath, toPath);

Expand Down
4 changes: 1 addition & 3 deletions Sparkle/SPUCoreBasedUpdateDriver.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,10 @@ NS_ASSUME_NONNULL_BEGIN

- (void)coreDriverDidStartExtractingUpdate;

- (void)installerDidStartInstalling;
- (void)installerDidStartInstallingWithApplicationTerminated:(BOOL)applicationTerminated;

- (void)installerDidExtractUpdateWithProgress:(double)progress;

- (void)installerIsSendingAppTerminationSignal;

- (void)installerDidFinishInstallationAndRelaunched:(BOOL)relaunched acknowledgement:(void(^)(void))acknowledgement;

@end
Expand Down
14 changes: 3 additions & 11 deletions Sparkle/SPUCoreBasedUpdateDriver.m
Original file line number Diff line number Diff line change
Expand Up @@ -261,10 +261,10 @@ - (void)downloadDriverDidFailToDownloadFileWithError:(NSError *)error
[self.delegate coreDriverIsRequestingAbortUpdateWithError:error];
}

- (void)installerDidStartInstalling
- (void)installerDidStartInstallingWithApplicationTerminated:(BOOL)applicationTerminated
{
if ([self.delegate respondsToSelector:@selector(installerDidStartInstalling)]) {
[self.delegate installerDidStartInstalling];
if ([self.delegate respondsToSelector:@selector(installerDidStartInstallingWithApplicationTerminated:)]) {
[self.delegate installerDidStartInstallingWithApplicationTerminated:applicationTerminated];
}
}

Expand Down Expand Up @@ -324,14 +324,6 @@ - (void)installerDidFinishInstallationAndRelaunched:(BOOL)relaunched acknowledge
}
}

- (void)installerIsSendingAppTerminationSignal
{
// If they don't respond or do anything, we'll just install after the user terminates the app anyway
if ([self.delegate respondsToSelector:@selector(installerIsSendingAppTerminationSignal)]) {
[self.delegate installerIsSendingAppTerminationSignal];
}
}

- (void)installerIsRequestingAbortInstallWithError:(nullable NSError *)error
{
[self.delegate coreDriverIsRequestingAbortUpdateWithError:error];
Expand Down
3 changes: 1 addition & 2 deletions Sparkle/SPUInstallerDriver.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,10 @@ NS_ASSUME_NONNULL_BEGIN

@protocol SPUInstallerDriverDelegate <NSObject>

- (void)installerDidStartInstalling;
- (void)installerDidStartInstallingWithApplicationTerminated:(BOOL)applicationTerminated;
- (void)installerDidStartExtracting;
- (void)installerDidExtractUpdateWithProgress:(double)progress;
- (void)installerDidFinishPreparationAndWillInstallImmediately:(BOOL)willInstallImmediately silently:(BOOL)willInstallSilently;
- (void)installerIsSendingAppTerminationSignal;
- (void)installerWillFinishInstallationAndRelaunch:(BOOL)relaunch;
- (void)installerDidFinishInstallationAndRelaunched:(BOOL)relaunch acknowledgement:(void(^)(void))acknowledgement;

Expand Down
15 changes: 1 addition & 14 deletions Sparkle/SPUInstallerDriver.m
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ @interface SPUInstallerDriver () <SUInstallerCommunicationProtocol>
@property (nonatomic, readonly) NSBundle *applicationBundle;
@property (nonatomic, weak, readonly) id<SPUInstallerDriverDelegate> delegate;
@property (nonatomic) SPUInstallerMessageType currentStage;
@property (nonatomic) BOOL startedInstalling;

@property (nonatomic) id<SUInstallerConnectionProtocol> installerConnection;

Expand All @@ -70,7 +69,6 @@ @implementation SPUInstallerDriver
@synthesize applicationBundle = _applicationBundle;
@synthesize delegate = _delegate;
@synthesize currentStage = _currentStage;
@synthesize startedInstalling = _startedInstalling;
@synthesize installerConnection = _installerConnection;
@synthesize extractionAttempts = _extractionAttempts;
@synthesize postponedOnce = _postponedOnce;
Expand Down Expand Up @@ -278,9 +276,6 @@ - (void)_handleMessageWithIdentifier:(int32_t)identifier data:(NSData *)data
self.currentStage = identifier;
} else if (identifier == SPUInstallationStartedStage1) {
self.currentStage = identifier;
[self.delegate installerDidStartInstalling];
self.startedInstalling = YES;

} else if (identifier == SPUInstallationFinishedStage1) {
self.currentStage = identifier;

Expand Down Expand Up @@ -308,22 +303,14 @@ - (void)_handleMessageWithIdentifier:(int32_t)identifier data:(NSData *)data
} else if (identifier == SPUInstallationFinishedStage2) {
self.currentStage = identifier;

if (!self.startedInstalling) {
// It's possible we can start from resuming to stage 2 rather than doing stage 1 again, so we should notify to start installing if we haven't done so already
self.startedInstalling = YES;
[self.delegate installerDidStartInstalling];
}

BOOL hasTargetTerminated = NO;
if (data.length >= sizeof(uint8_t)) {
hasTargetTerminated = (BOOL)*((const uint8_t *)data.bytes);
}

[self.delegate installerWillFinishInstallationAndRelaunch:self.relaunch];

if (!hasTargetTerminated) {
[self.delegate installerIsSendingAppTerminationSignal];
}
[self.delegate installerDidStartInstallingWithApplicationTerminated:hasTargetTerminated];
} else if (identifier == SPUInstallationFinishedStage3) {
self.currentStage = identifier;

Expand Down
66 changes: 36 additions & 30 deletions Sparkle/SPUStandardUserDriver.m
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ @interface SPUStandardUserDriver ()
@property (nonatomic) SUStatusController *statusController;
@property (nonatomic) SUUpdatePermissionPrompt *permissionPrompt;

@property (nonatomic) uint64_t expectedContentLength;
@property (nonatomic) uint64_t bytesDownloaded;

@end

@implementation SPUStandardUserDriver
Expand All @@ -56,6 +59,8 @@ @implementation SPUStandardUserDriver
@synthesize activeUpdateAlert = _activeUpdateAlert;
@synthesize statusController = _statusController;
@synthesize permissionPrompt = _permissionPrompt;
@synthesize expectedContentLength = _expectedContentLength;
@synthesize bytesDownloaded = _bytesDownloaded;

#pragma mark Birth

Expand Down Expand Up @@ -366,8 +371,12 @@ - (void)showDownloadInitiatedWithCancellation:(void (^)(void))cancellation
self.cancellation = cancellation;

[self createAndShowStatusController];
[self.statusController beginActionWithTitle:SULocalizedString(@"Downloading update...", @"Take care not to overflow the status window.") maxProgressValue:0.0 statusText:nil];

[self.statusController beginActionWithTitle:SULocalizedString(@"Downloading update...", @"Take care not to overflow the status window.") maxProgressValue:1.0 statusText:nil];
[self.statusController setProgressValue:0.0];
[self.statusController setButtonTitle:SULocalizedString(@"Cancel", nil) target:self action:@selector(cancelDownload:) isDefault:NO];

self.bytesDownloaded = 0;
}

- (void)cancelDownload:(id)__unused sender
Expand All @@ -382,7 +391,10 @@ - (void)showDownloadDidReceiveExpectedContentLength:(uint64_t)expectedContentLen
{
assert(NSThread.isMainThread);

[self.statusController setMaxProgressValue:expectedContentLength];
self.expectedContentLength = expectedContentLength;
if (expectedContentLength == 0) {
[self.statusController setMaxProgressValue:0.0];
}
}

- (NSString *)localizedStringFromByteCount:(long long)value
Expand Down Expand Up @@ -419,18 +431,17 @@ - (void)showDownloadDidReceiveDataOfLength:(uint64_t)length
{
assert(NSThread.isMainThread);

double newProgressValue = [self.statusController progressValue] + (double)length;
self.bytesDownloaded += length;

// In case our expected content length was incorrect
if (newProgressValue > [self.statusController maxProgressValue]) {
[self.statusController setMaxProgressValue:newProgressValue];
if (self.expectedContentLength > 0.0) {
double newProgressValue = (double)self.bytesDownloaded / (double)self.expectedContentLength;

[self.statusController setProgressValue:MIN(newProgressValue, 1.0)];

[self.statusController setStatusText:[NSString stringWithFormat:SULocalizedString(@"%@ of %@", nil), [self localizedStringFromByteCount:(long long)self.bytesDownloaded], [self localizedStringFromByteCount:(long long)MAX(self.bytesDownloaded, self.expectedContentLength)]]];
} else {
[self.statusController setStatusText:[NSString stringWithFormat:SULocalizedString(@"%@ downloaded", nil), [self localizedStringFromByteCount:(long long)self.bytesDownloaded]]];
}

[self.statusController setProgressValue:newProgressValue];
if ([self.statusController maxProgressValue] > 0.0)
[self.statusController setStatusText:[NSString stringWithFormat:SULocalizedString(@"%@ of %@", nil), [self localizedStringFromByteCount:(long long)self.statusController.progressValue], [self localizedStringFromByteCount:(long long)self.statusController.maxProgressValue]]];
else
[self.statusController setStatusText:[NSString stringWithFormat:SULocalizedString(@"%@ downloaded", nil), [self localizedStringFromByteCount:(long long)self.statusController.progressValue]]];
}

- (void)showDownloadDidStartExtractingUpdate
Expand All @@ -440,7 +451,8 @@ - (void)showDownloadDidStartExtractingUpdate
self.cancellation = nil;

[self createAndShowStatusController];
[self.statusController beginActionWithTitle:SULocalizedString(@"Extracting update...", @"Take care not to overflow the status window.") maxProgressValue:0.0 statusText:nil];
[self.statusController beginActionWithTitle:SULocalizedString(@"Extracting update...", @"Take care not to overflow the status window.") maxProgressValue:1.0 statusText:nil];
[self.statusController setProgressValue:0.0];
[self.statusController setButtonTitle:SULocalizedString(@"Cancel", nil) target:nil action:nil isDefault:NO];
[self.statusController setButtonEnabled:NO];
}
Expand All @@ -449,18 +461,23 @@ - (void)showExtractionReceivedProgress:(double)progress
{
assert(NSThread.isMainThread);

if ([self.statusController maxProgressValue] == 0.0) {
[self.statusController setMaxProgressValue:1];
}
[self.statusController setProgressValue:progress];
}

- (void)showInstallingUpdate
- (void)showInstallingUpdateWithApplicationTerminated:(BOOL)applicationTerminated
{
assert(NSThread.isMainThread);

[self.statusController beginActionWithTitle:SULocalizedString(@"Installing update...", @"Take care not to overflow the status window.") maxProgressValue:0.0 statusText:nil];
[self.statusController setButtonEnabled:NO];
if (applicationTerminated) {
[self.statusController beginActionWithTitle:SULocalizedString(@"Installing update...", @"Take care not to overflow the status window.") maxProgressValue:0.0 statusText:nil];
[self.statusController setButtonEnabled:NO];
} else {
// The "quit" event can always be canceled or delayed by the application we're updating
// So we can't easily predict how long the installation will take or if it won't happen right away
// We close our status window because we don't want it persisting for too long and have it obscure other windows
[self.statusController close];
self.statusController = nil;
}
}

- (void)showUpdateInstalledAndRelaunched:(BOOL)relaunched acknowledgement:(void (^)(void))acknowledgement
Expand Down Expand Up @@ -504,17 +521,6 @@ - (void)showUpdateInstalledAndRelaunched:(BOOL)relaunched acknowledgement:(void

#pragma mark Aborting Everything

- (void)showSendingTerminationSignal
{
assert(NSThread.isMainThread);

// The "quit" event can always be canceled or delayed by the application we're updating
// So we can't easily predict how long the installation will take or if it won't happen right away
// We close our status window because we don't want it persisting for too long and have it obscure other windows
[self.statusController close];
self.statusController = nil;
}

- (void)dismissUpdateInstallation
{
assert(NSThread.isMainThread);
Expand Down
24 changes: 17 additions & 7 deletions Sparkle/SPUUIBasedUpdateDriver.m
Original file line number Diff line number Diff line change
Expand Up @@ -310,9 +310,24 @@ - (void)coreDriverDidStartExtractingUpdate
[self.userDriver showDownloadDidStartExtractingUpdate];
}

- (void)installerDidStartInstalling
- (void)installerDidStartInstallingWithApplicationTerminated:(BOOL)applicationTerminated
{
[self.userDriver showInstallingUpdate];
if ([self.userDriver respondsToSelector:@selector(showInstallingUpdateWithApplicationTerminated:)]) {
[self.userDriver showInstallingUpdateWithApplicationTerminated:applicationTerminated];
} else {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
if ([self.userDriver respondsToSelector:@selector(showInstallingUpdate)]) {
[self.userDriver showInstallingUpdate];
}

if (!applicationTerminated) {
if ([self.userDriver respondsToSelector:@selector(showSendingTerminationSignal)]) {
[self.userDriver showSendingTerminationSignal];
}
}
#pragma clang diagnostic pop
}
}

- (void)installerDidExtractUpdateWithProgress:(double)progress
Expand All @@ -331,11 +346,6 @@ - (void)installerDidFinishPreparationAndWillInstallImmediately:(BOOL)willInstall
}
}

- (void)installerIsSendingAppTerminationSignal
{
[self.userDriver showSendingTerminationSignal];
}

- (void)installerDidFinishInstallationAndRelaunched:(BOOL)relaunched acknowledgement:(void(^)(void))acknowledgement
{
if ([self.userDriver respondsToSelector:@selector(showUpdateInstalledAndRelaunched:acknowledgement:)]) {
Expand Down
34 changes: 15 additions & 19 deletions Sparkle/SPUUserDriver.h
Original file line number Diff line number Diff line change
Expand Up @@ -197,15 +197,6 @@ SU_EXPORT @protocol SPUUserDriver <NSObject>
*/
- (void)showExtractionReceivedProgress:(double)progress;

/**
* Show the user that the update is installing
*
* Let the user know that the update is currently installing. Sparkle uses this to show an indeterminate progress bar.
*
* Before this point, `-showExtractionReceivedProgress:` may be called.
*/
- (void)showInstallingUpdate;

/**
* Show the user that the update is ready to install & relaunch
*
Expand All @@ -218,24 +209,25 @@ SU_EXPORT @protocol SPUUserDriver <NSObject>
*
* A reply of `SPUUserUpdateChoiceSkip` cancels the current update that has begun installing and dismisses the update. In this circumstance, the update is canceled but this update version is not skipped in the future.
*
* Before this point, `-showInstallingUpdate` will be called.
* Before this point, `-showExtractionReceivedProgress:` or `-showUpdateFoundWithAppcastItem:state:reply:` may be called.
*
* @param reply The reply which indicates if the update should be installed, dismissed, or skipped. See above discussion for more details.
*/
- (void)showReadyToInstallAndRelaunch:(void (^)(SPUUserUpdateChoice))reply;

/**
* Show or dismiss progress while a termination signal is being sent to the application from Sparkle's installer
* Show the user that the update is installing
*
* Terminating and relaunching the application (if requested to be relaunched) may happen quickly,
* or it may take some time to perform the final installation, or the termination signal can be canceled or delayed by the application or user.
* Let the user know that the update is currently installing.
*
* It is up to the implementor whether or not to decide to continue showing installation progress
* or dismissing UI that won't remain obscuring other parts of the user interface.
* Before this point, `-showReadyToInstallAndRelaunch:` or `-showUpdateFoundWithAppcastItem:state:reply:` will be called.
*
* This will not be invoked if the application that is being updated is already terminated.
* @param applicationTerminated Indicates if the application has been terminated already.
* If the application hasn't been terminated, a quit event is sent to the running application before installing the update.
* If the application or user delays or cancels termination, there may be an indefinite period of time before the application fully quits.
* It is up to the implementor whether or not to decide to continue showing installation progress in this case.
*/
- (void)showSendingTerminationSignal;
- (void)showInstallingUpdateWithApplicationTerminated:(BOOL)applicationTerminated;

/**
* Show the user that the update installation finished
Expand All @@ -246,7 +238,7 @@ SU_EXPORT @protocol SPUUserDriver <NSObject>
* the updater's lifetime is tied to the application it is updating. This implementation must not try to reference
* the old bundle prior to the installation, which will no longer be around.
*
* Before this point, `-showSendingTerminationSignal` or `-showReadyToInstallAndRelaunch:` may be called.
* Before this point, `-showInstallingUpdateWithApplicationTerminated:` will be called.
*
* @param relaunched Indicates if the update was relaunched.
* @param acknowledgement Acknowledge to the updater that the finished installation was shown.
Expand Down Expand Up @@ -284,7 +276,11 @@ SU_EXPORT @protocol SPUUserDriver <NSObject>

- (void)showUpdateInstallationDidFinishWithAcknowledgement:(void (^)(void))acknowledgement __deprecated_msg("Implement -showUpdateInstalledAndRelaunched:acknowledgement: instead");

- (void)dismissUserInitiatedUpdateCheck __deprecated_msg("Transition to new UI appropriately when a new update is shown, when no update is found, or when an update error occurs.");;
- (void)dismissUserInitiatedUpdateCheck __deprecated_msg("Transition to new UI appropriately when a new update is shown, when no update is found, or when an update error occurs.");

- (void)showInstallingUpdate __deprecated_msg("Implement -showInstallingUpdateWithApplicationTerminated: instead.");

- (void)showSendingTerminationSignal __deprecated_msg("Implement -showInstallingUpdateWithApplicationTerminated: instead.");

@end

Expand Down
15 changes: 7 additions & 8 deletions TestApplication/SUPopUpTitlebarUserDriver.m
Original file line number Diff line number Diff line change
Expand Up @@ -251,15 +251,14 @@ - (void)showExtractionReceivedProgress:(double)progress
[self addUpdateButtonWithTitle:[NSString stringWithFormat:@"Extracting (%d%%)…", (int)(progress * 100)]];
}

- (void)showInstallingUpdate
- (void)showInstallingUpdateWithApplicationTerminated:(BOOL)applicationTerminated
{
[self addUpdateButtonWithTitle:@"Installing…"];
}

- (void)showSendingTerminationSignal
{
// In case our termination request fails or is delayed
[self removeUpdateButton];
if (applicationTerminated) {
[self addUpdateButtonWithTitle:@"Installing…"];
} else {
// In case our termination request fails or is delayed
[self removeUpdateButton];
}
}

- (void)showUpdateInstalledAndRelaunched:(BOOL)__unused relaunched acknowledgement:(void (^)(void))acknowledgement
Expand Down
Loading

0 comments on commit fa9bfc1

Please sign in to comment.