Merge pull request #104 from Microsoft/remove-rollback-timeout

Remove rollback timeout
This commit is contained in:
Geoffrey Goh
2015-12-10 14:00:19 -08:00
5 changed files with 132 additions and 151 deletions

View File

@@ -22,7 +22,7 @@ static NSString *const PendingUpdateKey = @"CODE_PUSH_PENDING_UPDATE";
// These keys are already "namespaced" by the PendingUpdateKey, so
// their values don't need to be obfuscated to prevent collision with app data
static NSString *const PendingUpdateHashKey = @"hash";
static NSString *const PendingUpdateRollbackTimeoutKey = @"rollbackTimeout";
static NSString *const PendingUpdateIsLoadingKey = @"isLoading";
@synthesize bridge = _bridge;
@@ -70,20 +70,6 @@ static NSString *const PendingUpdateRollbackTimeoutKey = @"rollbackTimeout";
// Private API methods
/*
* This method cancels the currently running rollback
* timer, which has the effect of stopping an automatic
* rollback from occurring.
*
* Note: This method is safe to call from any thread.
*/
- (void)cancelRollbackTimer
{
dispatch_async(dispatch_get_main_queue(), ^{
[_timer invalidate];
});
}
/*
* This method is used by the React Native bridge to allow
* our plugin to expose constants to the JS-side. In our case
@@ -120,26 +106,27 @@ static NSString *const PendingUpdateRollbackTimeoutKey = @"rollbackTimeout";
}
/*
* This method starts the rollback protection timer
* and is used when a new update is initialized.
* This method is used when the app is started to either
* initialize a pending update or rollback a faulty update
* to the previous version.
*/
- (void)initializeUpdateAfterRestart
{
NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults];
NSDictionary *pendingUpdate = [preferences objectForKey:PendingUpdateKey];
if (pendingUpdate) {
_isFirstRunAfterUpdate = YES;
int rollbackTimeout = [pendingUpdate[PendingUpdateRollbackTimeoutKey] intValue];
if (0 != rollbackTimeout) {
dispatch_async(dispatch_get_main_queue(), ^{
[self startRollbackTimer:rollbackTimeout];
});
BOOL updateIsLoading = [pendingUpdate[PendingUpdateIsLoadingKey] boolValue];
if (updateIsLoading) {
// Pending update was initialized, but notifyApplicationReady was not called.
// Therefore, deduce that it is a broken update and rollback.
[self rollbackPackage];
} else {
// Mark that we tried to initialize the new update, so that if it crashes,
// we will know that we need to rollback when the app next starts.
[self savePendingUpdate:pendingUpdate[PendingUpdateHashKey]
isLoading:YES];
}
// Clear the pending update and sync
[preferences removeObjectForKey:PendingUpdateKey];
[preferences synchronize];
}
}
@@ -161,15 +148,19 @@ static NSString *const PendingUpdateRollbackTimeoutKey = @"rollbackTimeout";
*/
- (void)loadBundle
{
// If the current bundle URL is using http(s), then assume the dev
// is debugging and therefore, shouldn't be redirected to a local
// file (since Chrome wouldn't support it). Otherwise, update
// the current bundle URL to point at the latest update
if (![_bridge.bundleURL.scheme hasPrefix:@"http"]) {
_bridge.bundleURL = [CodePush bundleURL];
}
[_bridge reload];
// This needs to be async dispatched because the _bridge is not set on init
// when the app first starts, therefore rollbacks will not take effect.
dispatch_async(dispatch_get_main_queue(), ^{
// If the current bundle URL is using http(s), then assume the dev
// is debugging and therefore, shouldn't be redirected to a local
// file (since Chrome wouldn't support it). Otherwise, update
// the current bundle URL to point at the latest update
if (![_bridge.bundleURL.scheme hasPrefix:@"http"]) {
_bridge.bundleURL = [CodePush bundleURL];
}
[_bridge reload];
});
}
/*
@@ -187,9 +178,9 @@ static NSString *const PendingUpdateRollbackTimeoutKey = @"rollbackTimeout";
// Write the current package's hash to the "failed list"
[self saveFailedUpdate:packageHash];
// Do the actual rollback and then
// refresh the app with the previous package
// Rollback to the previous version and de-register the new update
[CodePushPackage rollbackPackage];
[self removePendingUpdate];
[self loadBundle];
}
@@ -215,39 +206,36 @@ static NSString *const PendingUpdateRollbackTimeoutKey = @"rollbackTimeout";
[preferences synchronize];
}
/*
* This method is used to register the fact that a pending
* update succeeded and therefore can be removed.
*/
- (void)removePendingUpdate
{
NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults];
[preferences removeObjectForKey:PendingUpdateKey];
[preferences synchronize];
}
/*
* When an update is installed whose mode isn't IMMEDIATE, this method
* can be called to store the pending update's metadata (e.g. rollbackTimeout)
* can be called to store the pending update's metadata (e.g. packageHash)
* so that it can be used when the actual update application occurs at a later point.
*/
- (void)savePendingUpdate:(NSString *)packageHash
rollbackTimeout:(int)rollbackTimeout
isLoading:(BOOL)isLoading
{
// Since we're not restarting, we need to store the fact that the update
// was installed, but hasn't yet become "active".
NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults];
NSDictionary *pendingUpdate = [[NSDictionary alloc] initWithObjectsAndKeys:
packageHash,PendingUpdateHashKey,
[NSNumber numberWithInt:rollbackTimeout],PendingUpdateRollbackTimeoutKey, nil];
[NSNumber numberWithBool:isLoading],PendingUpdateIsLoadingKey, nil];
[preferences setObject:pendingUpdate forKey:PendingUpdateKey];
[preferences synchronize];
}
/*
* This method handles starting the actual rollback timer
* after an update has been installed.
*/
- (void)startRollbackTimer:(int)rollbackTimeout
{
double timeoutInSeconds = rollbackTimeout / 1000;
_timer = [NSTimer scheduledTimerWithTimeInterval:timeoutInSeconds
target:self
selector:@selector(rollbackPackage)
userInfo:nil
repeats:NO];
}
// JavaScript-exported module methods
/*
@@ -318,7 +306,6 @@ RCT_EXPORT_METHOD(getCurrentPackage:(RCTPromiseResolveBlock)resolve
* This method is the native side of the LocalPackage.install method.
*/
RCT_EXPORT_METHOD(installUpdate:(NSDictionary*)updatePackage
rollbackTimeout:(int)rollbackTimeout
installMode:(CodePushInstallMode)installMode
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
@@ -332,7 +319,7 @@ RCT_EXPORT_METHOD(installUpdate:(NSDictionary*)updatePackage
reject(error);
} else {
[self savePendingUpdate:updatePackage[@"packageHash"]
rollbackTimeout:rollbackTimeout];
isLoading:NO];
if (installMode == CodePushInstallModeImmediate) {
[self loadBundle];
@@ -389,7 +376,7 @@ RCT_EXPORT_METHOD(isFirstRun:(NSString *)packageHash
RCT_EXPORT_METHOD(notifyApplicationReady:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
{
[self cancelRollbackTimer];
[self removePendingUpdate];
resolve([NSNull null]);
}