Skip to content
This repository has been archived by the owner on Oct 7, 2024. It is now read-only.

Commit

Permalink
Merge pull request microsoft#51 from Microsoft/restartOnResume
Browse files Browse the repository at this point in the history
Restart on resume
  • Loading branch information
lostintangent committed Nov 16, 2015
2 parents 92112ab + ad551c2 commit ff891db
Show file tree
Hide file tree
Showing 6 changed files with 119 additions and 32 deletions.
8 changes: 7 additions & 1 deletion CodePush.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,4 +70,10 @@ failCallback:(void (^)(NSError *err))failCallback;

+ (void)rollbackPackage;

@end
@end

typedef NS_ENUM(NSInteger, CodePushRestartMode) {
CodePushRestartModeNone,
CodePushRestartModeImmediate,
CodePushRestartModeOnNextResume
};
5 changes: 5 additions & 0 deletions CodePush.ios.js
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,11 @@ var CodePush = {
notifyApplicationReady: NativeCodePush.notifyApplicationReady,
setUpTestDependencies: setUpTestDependencies,
sync: sync,
RestartMode: {
NONE: NativeCodePush.codePushRestartModeNone, // Don't artificially restart the app. Allow the update to be "picked up" on the next app restart
IMMEDIATE: NativeCodePush.codePushRestartModeImmediate, // Restart the app immediately
ON_NEXT_RESUME: NativeCodePush.codePushRestartModeOnNextResume // Restart the app the next time it is resumed from the background
},
SyncStatus: {
UP_TO_DATE: 0, // The running app is up-to-date
UPDATE_IGNORED: 1, // The app had an optional update and the end-user chose to ignore it
Expand Down
114 changes: 86 additions & 28 deletions CodePush.m
Original file line number Diff line number Diff line change
@@ -1,20 +1,28 @@
#import "RCTBridgeModule.h"
#import "RCTEventDispatcher.h"
#import "RCTConvert.h"
#import "RCTRootView.h"
#import "RCTUtils.h"
#import "CodePush.h"

@implementation CodePush
@implementation CodePush {
BOOL _resumablePendingUpdateAvailable;
}

RCT_EXPORT_MODULE()

BOOL didUpdate = NO;
NSTimer *_timer;
BOOL usingTestFolder = NO;
BOOL didUpdate = NO;

NSString * const FailedUpdatesKey = @"CODE_PUSH_FAILED_UPDATES";
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
NSString * const PendingUpdateHashKey = @"hash";
NSString * const PendingUpdateRollbackTimeoutKey = @"rollbackTimeout";

@synthesize bridge = _bridge;

// Public Obj-C API
Expand All @@ -39,7 +47,7 @@ + (NSURL *)getBundleUrl
NSDictionary *appFileAttribs = [[NSFileManager defaultManager] attributesOfItemAtPath:packageFile error:nil];
NSDate *binaryDate = [binaryFileAttributes objectForKey:NSFileModificationDate];
NSDate *packageDate = [appFileAttribs objectForKey:NSFileModificationDate];

if ([binaryDate compare:packageDate] == NSOrderedAscending) {
// Return package file because it is newer than the app store binary's JS bundle
return [[NSURL alloc] initFileURLWithPath:packageFile];
Expand All @@ -56,38 +64,83 @@ - (void)cancelRollbackTimer
});
}

- (CodePush *)init
- (void)checkForPendingUpdate:(BOOL)needsRestart
{
self = [super init];

if (self) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults];
NSDictionary *pendingUpdate = [preferences objectForKey:PendingUpdateKey];

if (pendingUpdate)
{
if (pendingUpdate) {
NSError *error;
NSString *pendingHash = pendingUpdate[@"hash"];
NSString *pendingHash = pendingUpdate[PendingUpdateHashKey];
NSString *currentHash = [CodePushPackage getCurrentPackageHash:&error];

// If the current hash is equivalent to the pending hash, then the app
// restart "picked up" the new update, but we need to kick off the
// rollback timer and ensure that the necessary state is setup.
if ([pendingHash isEqualToString:currentHash]) {
int rollbackTimeout = [pendingUpdate[@"rollbackTimeout"] intValue];
[self initializeUpdateWithRollbackTimeout:rollbackTimeout needsRestart:NO];
int rollbackTimeout = [pendingUpdate[PendingUpdateRollbackTimeoutKey] intValue];
[self initializeUpdateWithRollbackTimeout:rollbackTimeout needsRestart:needsRestart];

// Clear the pending update and sync
[preferences removeObjectForKey:PendingUpdateKey];
[preferences synchronize];
}
}
});
}

- (void)checkForPendingUpdateDuringResume
{
// In order to ensure that CodePush doesn't impact the app's
// resume experience, we're using a simple boolean check to
// check whether we need to restart, before reading the defaults store
if (_resumablePendingUpdateAvailable) {
[self checkForPendingUpdate:YES];
}
}

- (NSDictionary *)constantsToExport
{
// Export the values of the CodePushRestartMode enum
// so that the script-side can easily stay in sync
return @{ @"codePushRestartModeNone": @(CodePushRestartModeNone),
@"codePushRestartModeImmediate": @(CodePushRestartModeImmediate),
@"codePushRestartModeOnNextResume": @(CodePushRestartModeOnNextResume)
};
};

- (void)dealloc
{
// Ensure the global resume handler is cleared, so that
// this object isn't kept alive unnecessarily
[[NSNotificationCenter defaultCenter] removeObserver:self];
}

- (CodePush *)init
{
self = [super init];

if (self) {
// Do an async check to see whether
// we need to start the rollback timer
// due to a pending update being applied at start
[self checkForPendingUpdate:NO];

// Register for app resume notifications so that we
// can check for pending updates which support "restart on resume"
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(checkForPendingUpdateDuringResume)
name:UIApplicationWillEnterForegroundNotification
object:[UIApplication sharedApplication]];
}

return self;
}

- (void)initializeUpdateWithRollbackTimeout:(int)rollbackTimeout needsRestart:(BOOL)needsRestart {
- (void)initializeUpdateWithRollbackTimeout:(int)rollbackTimeout
needsRestart:(BOOL)needsRestart
{
didUpdate = YES;

if (needsRestart) {
Expand All @@ -101,7 +154,8 @@ - (void)initializeUpdateWithRollbackTimeout:(int)rollbackTimeout needsRestart:(B
}
}

- (BOOL)isFailedHash:(NSString*)packageHash {
- (BOOL)isFailedHash:(NSString*)packageHash
{
NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults];
NSMutableArray *failedUpdates = [preferences objectForKey:FailedUpdatesKey];
return (failedUpdates != nil && [failedUpdates containsObject:packageHash]);
Expand Down Expand Up @@ -129,7 +183,8 @@ - (void)rollbackPackage
[self loadBundle];
}

- (void)saveFailedUpdate:(NSString *)packageHash {
- (void)saveFailedUpdate:(NSString *)packageHash
{
NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults];
NSMutableArray *failedUpdates = [preferences objectForKey:FailedUpdatesKey];
if (failedUpdates == nil) {
Expand All @@ -146,13 +201,14 @@ - (void)saveFailedUpdate:(NSString *)packageHash {
}

- (void)savePendingUpdate:(NSString *)packageHash
rollbackTimeout:(int)rollbackTimeout {
rollbackTimeout:(int)rollbackTimeout
{
// Since we're not restarting, we need to store the fact that the update
// was applied, but hasn't yet become "active".
NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults];
NSDictionary *pendingUpdate = [[NSDictionary alloc] initWithObjectsAndKeys:
packageHash,@"hash",
rollbackTimeout,@"rollbackTimeout", nil];
packageHash,PendingUpdateHashKey,
[NSNumber numberWithInt:rollbackTimeout],PendingUpdateRollbackTimeoutKey, nil];

[preferences setObject:pendingUpdate forKey:PendingUpdateKey];
[preferences synchronize];
Expand All @@ -170,10 +226,10 @@ - (void)startRollbackTimer:(int)rollbackTimeout

// JavaScript-exported module methods
RCT_EXPORT_METHOD(applyUpdate:(NSDictionary*)updatePackage
rollbackTimeout:(int)rollbackTimeout
restartImmediately:(BOOL)restartImmediately
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
rollbackTimeout:(int)rollbackTimeout
restartMode:(CodePushRestartMode)restartMode
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSError *error;
Expand All @@ -183,10 +239,12 @@ - (void)startRollbackTimer:(int)rollbackTimeout
if (error) {
reject(error);
} else {
if (restartImmediately) {
if (restartMode == CodePushRestartModeImmediate) {
[self initializeUpdateWithRollbackTimeout:rollbackTimeout needsRestart:YES];
} else {
[self savePendingUpdate:updatePackage[@"packageHash"] rollbackTimeout:rollbackTimeout];
_resumablePendingUpdateAvailable = (restartMode == CodePushRestartModeOnNextResume);
[self savePendingUpdate:updatePackage[@"packageHash"]
rollbackTimeout:rollbackTimeout];
}
}
});
Expand Down Expand Up @@ -256,10 +314,10 @@ - (void)startRollbackTimer:(int)rollbackTimeout
{
NSError *error;
BOOL isFirstRun = didUpdate
&& nil != packageHash
&& [packageHash length] > 0
&& [packageHash isEqualToString:[CodePushPackage getCurrentPackageHash:&error]];
&& nil != packageHash
&& [packageHash length] > 0
&& [packageHash isEqualToString:[CodePushPackage getCurrentPackageHash:&error]];

resolve(@(isFirstRun));
}

Expand Down
6 changes: 5 additions & 1 deletion CodePush.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@

/* Begin PBXBuildFile section */
13BE3DEE1AC21097009241FE /* CodePush.m in Sources */ = {isa = PBXBuildFile; fileRef = 13BE3DED1AC21097009241FE /* CodePush.m */; };
54FFEDE01BF550630061DD23 /* CodePushDownloadHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 54FFEDDF1BF550630061DD23 /* CodePushDownloadHandler.m */; settings = {ASSET_TAGS = (); }; };
1B23B9141BF9267B000BB2F0 /* RCTConvert+CodePushRestartMode.m in Sources */ = {isa = PBXBuildFile; fileRef = 1B23B9131BF9267B000BB2F0 /* RCTConvert+CodePushRestartMode.m */; };
54FFEDE01BF550630061DD23 /* CodePushDownloadHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 54FFEDDF1BF550630061DD23 /* CodePushDownloadHandler.m */; };
810D4E6D1B96935000B397E9 /* CodePushPackage.m in Sources */ = {isa = PBXBuildFile; fileRef = 810D4E6C1B96935000B397E9 /* CodePushPackage.m */; };
81D51F3A1B6181C2000DA084 /* CodePushConfig.m in Sources */ = {isa = PBXBuildFile; fileRef = 81D51F391B6181C2000DA084 /* CodePushConfig.m */; };
/* End PBXBuildFile section */
Expand All @@ -29,6 +30,7 @@
134814201AA4EA6300B7C361 /* libCodePush.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libCodePush.a; sourceTree = BUILT_PRODUCTS_DIR; };
13BE3DEC1AC21097009241FE /* CodePush.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CodePush.h; sourceTree = "<group>"; };
13BE3DED1AC21097009241FE /* CodePush.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CodePush.m; sourceTree = "<group>"; };
1B23B9131BF9267B000BB2F0 /* RCTConvert+CodePushRestartMode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "RCTConvert+CodePushRestartMode.m"; sourceTree = "<group>"; };
54FFEDDF1BF550630061DD23 /* CodePushDownloadHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CodePushDownloadHandler.m; sourceTree = "<group>"; };
810D4E6C1B96935000B397E9 /* CodePushPackage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CodePushPackage.m; sourceTree = "<group>"; };
81D51F391B6181C2000DA084 /* CodePushConfig.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CodePushConfig.m; sourceTree = "<group>"; };
Expand Down Expand Up @@ -56,6 +58,7 @@
58B511D21A9E6C8500147676 = {
isa = PBXGroup;
children = (
1B23B9131BF9267B000BB2F0 /* RCTConvert+CodePushRestartMode.m */,
54FFEDDF1BF550630061DD23 /* CodePushDownloadHandler.m */,
810D4E6C1B96935000B397E9 /* CodePushPackage.m */,
81D51F391B6181C2000DA084 /* CodePushConfig.m */,
Expand Down Expand Up @@ -121,6 +124,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
1B23B9141BF9267B000BB2F0 /* RCTConvert+CodePushRestartMode.m in Sources */,
81D51F3A1B6181C2000DA084 /* CodePushConfig.m in Sources */,
54FFEDE01BF550630061DD23 /* CodePushDownloadHandler.m in Sources */,
13BE3DEE1AC21097009241FE /* CodePush.m in Sources */,
Expand Down
14 changes: 14 additions & 0 deletions RCTConvert+CodePushRestartMode.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#import "CodePush.h"
#import "RCTConvert.h"

// Extending the RCTConvert class allows the React Native
// bridge to handle args of type "CodePushRestartMode"
@implementation RCTConvert (CodePushRestartMode)

RCT_ENUM_CONVERTER(CodePushRestartMode, (@{ @"codePushRestartModeNone": @(CodePushRestartModeNone),
@"codePushRestartModeImmediate": @(CodePushRestartModeImmediate),
@"codePushRestartModeOnNextResume": @(CodePushRestartModeOnNextResume) }),
CodePushRestartModeImmediate, // Default enum value
integerValue)

@end
4 changes: 2 additions & 2 deletions package-mixins.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ module.exports = (NativeCodePush) => {
};

var local = {
apply: function apply(rollbackTimeout = 0, restartImmediately = true) {
return NativeCodePush.applyUpdate(this, rollbackTimeout, restartImmediately);
apply: function apply(rollbackTimeout = 0, restartMode = NativeCodePush.codePushRestartModeImmediate) {
return NativeCodePush.applyUpdate(this, rollbackTimeout, restartMode);
}
};

Expand Down

0 comments on commit ff891db

Please sign in to comment.