From 4508f630480ff871af8b20a1100398210bdf77cd Mon Sep 17 00:00:00 2001 From: Geoffrey Goh Date: Wed, 27 Apr 2016 14:29:38 -0700 Subject: [PATCH 01/10] improve download progress perf --- CodePush.js | 92 +++++++++---------- .../microsoft/codepush/react/CodePush.java | 34 ++++++- ios/CodePush/CodePush.h | 3 + ios/CodePush/CodePush.m | 49 +++++++--- ios/CodePush/CodePushDownloadHandler.m | 8 +- ios/CodePush/CodePushPackage.m | 2 + package-mixins.js | 12 +-- 7 files changed, 126 insertions(+), 74 deletions(-) diff --git a/CodePush.js b/CodePush.js index e31558c..eee79d8 100644 --- a/CodePush.js +++ b/CodePush.js @@ -9,7 +9,7 @@ const PackageMixins = require("./package-mixins")(NativeCodePush); async function checkForUpdate(deploymentKey = null) { /* * Before we ask the server if an update exists, we - * need to retrieve three pieces of information from the + * need to retrieve three pieces of information from the * native side: deployment key, app version (e.g. 1.0.1) * and the hash of the currently running update (if there is one). * This allows the client to only receive updates which are targetted @@ -17,7 +17,7 @@ async function checkForUpdate(deploymentKey = null) { * different from the CodePush update they have already installed. */ const nativeConfig = await getConfiguration(); - + /* * If a deployment key was explicitly provided, * then let's override the one we retrieved @@ -30,7 +30,7 @@ async function checkForUpdate(deploymentKey = null) { // Use dynamically overridden getCurrentPackage() during tests. const localPackage = await module.exports.getCurrentPackage(); - + /* * If the app has a previously installed update, and that update * was targetted at the same app version that is currently running, @@ -48,9 +48,9 @@ async function checkForUpdate(deploymentKey = null) { queryPackage.packageHash = config.packageHash; } } - + const update = await sdk.queryUpdateWithCurrentPackage(queryPackage); - + /* * There are four cases where checkForUpdate will resolve to null: * ---------------------------------------------------------------- @@ -69,13 +69,13 @@ async function checkForUpdate(deploymentKey = null) { * because we want to avoid having to install diff updates against the binary's * version, which we can't do yet on Android. */ - if (!update || update.updateAppVersion || - localPackage && (update.packageHash === localPackage.packageHash) || + if (!update || update.updateAppVersion || + localPackage && (update.packageHash === localPackage.packageHash) || (!localPackage || localPackage._isDebugOnly) && config.packageHash === update.packageHash) { if (update && update.updateAppVersion) { log("An update is available but it is targeting a newer binary version than you are currently running."); } - + return null; } else { const remotePackage = { ...update, ...PackageMixins.remote(sdk.reportStatusDownload) }; @@ -93,7 +93,7 @@ const getConfiguration = (() => { } else if (testConfig) { return testConfig; } else { - config = await NativeCodePush.getConfiguration(); + config = await NativeCodePush.getConfiguration(); return config; } } @@ -123,7 +123,7 @@ function getPromisifiedSdk(requestFetchAdapter, config) { } else { resolve(update); } - }); + }); }); }; @@ -135,7 +135,7 @@ function getPromisifiedSdk(requestFetchAdapter, config) { } else { resolve(); } - }); + }); }); }; @@ -147,7 +147,7 @@ function getPromisifiedSdk(requestFetchAdapter, config) { } else { resolve(); } - }); + }); }); }; @@ -159,7 +159,7 @@ function log(message) { console.log(`[CodePush] ${message}`) } -// This ensures that notifyApplicationReadyInternal is only called once +// This ensures that notifyApplicationReadyInternal is only called once // in the lifetime of this module instance. const notifyApplicationReady = (() => { let notifyApplicationReadyPromise; @@ -167,13 +167,13 @@ const notifyApplicationReady = (() => { if (!notifyApplicationReadyPromise) { notifyApplicationReadyPromise = notifyApplicationReadyInternal(); } - + return notifyApplicationReadyPromise; }; })(); async function notifyApplicationReadyInternal() { - await NativeCodePush.notifyApplicationReady(); + await NativeCodePush.notifyApplicationReady(); const statusReport = await NativeCodePush.getNewStatusReport(); if (statusReport) { const config = await getConfiguration(); @@ -208,15 +208,15 @@ function setUpTestDependencies(testSdk, providedTestConfig, testNativeBridge) { const sync = (() => { let syncInProgress = false; const setSyncCompleted = () => { syncInProgress = false; }; - + return (options = {}, syncStatusChangeCallback, downloadProgressCallback) => { if (syncInProgress) { typeof syncStatusChangeCallback === "function" ? syncStatusChangeCallback(CodePush.SyncStatus.SYNC_IN_PROGRESS) : log("Sync already in progress."); return Promise.resolve(CodePush.SyncStatus.SYNC_IN_PROGRESS); - } - + } + syncInProgress = true; const syncPromise = syncInternal(options, syncStatusChangeCallback, downloadProgressCallback); syncPromise @@ -230,7 +230,7 @@ const sync = (() => { /* * The syncInternal method provides a simple, one-line experience for * incorporating the check, download and installation of an update. - * + * * It simply composes the existing API methods together and adds additional * support for respecting mandatory updates, ignoring previously failed * releases, and displaying a standard confirmation UI to the end-user @@ -245,9 +245,9 @@ async function syncInternal(options = {}, syncStatusChangeCallback, downloadProg mandatoryInstallMode: CodePush.InstallMode.IMMEDIATE, minimumBackgroundDuration: 0, updateDialog: null, - ...options + ...options }; - + syncStatusChangeCallback = typeof syncStatusChangeCallback === "function" ? syncStatusChangeCallback : (syncStatus) => { @@ -271,7 +271,7 @@ async function syncInternal(options = {}, syncStatusChangeCallback, downloadProg log("User cancelled the update."); break; case CodePush.SyncStatus.UPDATE_INSTALLED: - /* + /* * If the install mode is IMMEDIATE, this will not get returned as the * app will be restarted to a new Javascript context. */ @@ -290,40 +290,34 @@ async function syncInternal(options = {}, syncStatusChangeCallback, downloadProg break; } }; - - downloadProgressCallback = typeof downloadProgressCallback === "function" - ? downloadProgressCallback - : (downloadProgress) => { - log(`Expecting ${downloadProgress.totalBytes} bytes, received ${downloadProgress.receivedBytes} bytes.`); - }; - + try { await CodePush.notifyApplicationReady(); - + syncStatusChangeCallback(CodePush.SyncStatus.CHECKING_FOR_UPDATE); const remotePackage = await checkForUpdate(syncOptions.deploymentKey); - + const doDownloadAndInstall = async () => { syncStatusChangeCallback(CodePush.SyncStatus.DOWNLOADING_PACKAGE); const localPackage = await remotePackage.download(downloadProgressCallback); - + // Determine the correct install mode based on whether the update is mandatory or not. resolvedInstallMode = localPackage.isMandatory ? syncOptions.mandatoryInstallMode : syncOptions.installMode; - + syncStatusChangeCallback(CodePush.SyncStatus.INSTALLING_UPDATE); await localPackage.install(resolvedInstallMode, syncOptions.minimumBackgroundDuration, () => { syncStatusChangeCallback(CodePush.SyncStatus.UPDATE_INSTALLED); }); - + return CodePush.SyncStatus.UPDATE_INSTALLED; }; - + const updateShouldBeIgnored = remotePackage && (remotePackage.failedInstall && syncOptions.ignoreFailedUpdates); if (!remotePackage || updateShouldBeIgnored) { if (updateShouldBeIgnored) { log("An update is available, but it is being ignored due to having been previously rolled back."); } - + syncStatusChangeCallback(CodePush.SyncStatus.UP_TO_DATE); return CodePush.SyncStatus.UP_TO_DATE; } else if (syncOptions.updateDialog) { @@ -334,24 +328,24 @@ async function syncInternal(options = {}, syncStatusChangeCallback, downloadProg } else { syncOptions.updateDialog = { ...CodePush.DEFAULT_UPDATE_DIALOG, ...syncOptions.updateDialog }; } - - return await new Promise((resolve, reject) => { + + return await new Promise((resolve, reject) => { let message = null; const dialogButtons = [{ text: null, - onPress: async () => { + onPress: async () => { resolve(await doDownloadAndInstall()); } }]; - + if (remotePackage.isMandatory) { message = syncOptions.updateDialog.mandatoryUpdateMessage; dialogButtons[0].text = syncOptions.updateDialog.mandatoryContinueButtonLabel; } else { message = syncOptions.updateDialog.optionalUpdateMessage; - dialogButtons[0].text = syncOptions.updateDialog.optionalInstallButtonLabel; + dialogButtons[0].text = syncOptions.updateDialog.optionalInstallButtonLabel; // Since this is an optional update, add another button - // to allow the end-user to ignore it + // to allow the end-user to ignore it dialogButtons.push({ text: syncOptions.updateDialog.optionalIgnoreButtonLabel, onPress: () => { @@ -360,13 +354,13 @@ async function syncInternal(options = {}, syncStatusChangeCallback, downloadProg } }); } - + // If the update has a description, and the developer // explicitly chose to display it, then set that as the message if (syncOptions.updateDialog.appendReleaseDescription && remotePackage.description) { - message += `${syncOptions.updateDialog.descriptionPrefix} ${remotePackage.description}`; + message += `${syncOptions.updateDialog.descriptionPrefix} ${remotePackage.description}`; } - + syncStatusChangeCallback(CodePush.SyncStatus.AWAITING_USER_ACTION); Alert.alert(syncOptions.updateDialog.title, message, dialogButtons); }); @@ -375,16 +369,16 @@ async function syncInternal(options = {}, syncStatusChangeCallback, downloadProg } } catch (error) { syncStatusChangeCallback(CodePush.SyncStatus.UNKNOWN_ERROR); - log(error.message); + log(error.message); throw error; - } + } }; let CodePush; -// If the "NativeCodePush" variable isn't defined, then +// If the "NativeCodePush" variable isn't defined, then // the app didn't properly install the native module, -// and therefore, it doesn't make sense initializing +// and therefore, it doesn't make sense initializing // the JS interface when it wouldn't work anyways. if (NativeCodePush) { CodePush = { diff --git a/android/app/src/main/java/com/microsoft/codepush/react/CodePush.java b/android/app/src/main/java/com/microsoft/codepush/react/CodePush.java index 8e4e555..24d9a38 100644 --- a/android/app/src/main/java/com/microsoft/codepush/react/CodePush.java +++ b/android/app/src/main/java/com/microsoft/codepush/react/CodePush.java @@ -13,6 +13,7 @@ import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.WritableMap; import com.facebook.react.bridge.WritableNativeMap; import com.facebook.react.modules.core.DeviceEventManagerModule; +import com.facebook.react.uimanager.ReactChoreographer; import com.facebook.react.uimanager.ViewManager; import com.facebook.soloader.SoLoader; @@ -25,6 +26,7 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.os.AsyncTask; import android.provider.Settings; +import android.view.Choreographer; import org.json.JSONArray; import org.json.JSONException; @@ -439,7 +441,7 @@ public class CodePush implements ReactPackage { } @ReactMethod - public void downloadUpdate(final ReadableMap updatePackage, final Promise promise) { + public void downloadUpdate(final ReadableMap updatePackage, final boolean notifyProgress, final Promise promise) { AsyncTask asyncTask = new AsyncTask() { @Override protected Void doInBackground(Void... params) { @@ -447,11 +449,35 @@ public class CodePush implements ReactPackage { WritableMap mutableUpdatePackage = CodePushUtils.convertReadableMapToWritableMap(updatePackage); mutableUpdatePackage.putString(BINARY_MODIFIED_TIME_KEY, "" + getBinaryResourcesModifiedTime()); codePushPackage.downloadPackage(mutableUpdatePackage, CodePush.this.assetsBundleFileName, new DownloadProgressCallback() { + private boolean nextFrameBusy = false; + private DownloadProgress latestDownloadProgress = null; + @Override public void call(DownloadProgress downloadProgress) { - getReactApplicationContext() - .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) - .emit(DOWNLOAD_PROGRESS_EVENT_NAME, downloadProgress.createWritableMap()); + if (!notifyProgress) { + return; + } + + this.latestDownloadProgress = downloadProgress; + if (nextFrameBusy) { + return; + } + + nextFrameBusy = true; + mainActivity.runOnUiThread(new Runnable() { + @Override + public void run() { + ReactChoreographer.getInstance().postFrameCallback(ReactChoreographer.CallbackType.TIMERS_EVENTS, new Choreographer.FrameCallback() { + @Override + public void doFrame(long frameTimeNanos) { + getReactApplicationContext() + .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) + .emit(DOWNLOAD_PROGRESS_EVENT_NAME, latestDownloadProgress.createWritableMap()); + nextFrameBusy = false; + } + }); + } + }); } }); diff --git a/ios/CodePush/CodePush.h b/ios/CodePush/CodePush.h index b56561e..f94d684 100644 --- a/ios/CodePush/CodePush.h +++ b/ios/CodePush/CodePush.h @@ -54,11 +54,13 @@ @property (strong) NSOutputStream *outputFileStream; @property long long expectedContentLength; @property long long receivedContentLength; +@property dispatch_queue_t usingQueue; @property (copy) void (^progressCallback)(long long, long long); @property (copy) void (^doneCallback)(BOOL); @property (copy) void (^failCallback)(NSError *err); - (id)init:(NSString *)downloadFilePath +usingQueue:(dispatch_queue_t)usingQueue progressCallback:(void (^)(long long, long long))progressCallback doneCallback:(void (^)(BOOL))doneCallback failCallback:(void (^)(NSError *err))failCallback; @@ -78,6 +80,7 @@ failCallback:(void (^)(NSError *err))failCallback; + (void)downloadPackage:(NSDictionary *)updatePackage expectedBundleFileName:(NSString *)expectedBundleFileName + usingQueue:(dispatch_queue_t)usingQueue progressCallback:(void (^)(long long, long long))progressCallback doneCallback:(void (^)())doneCallback failCallback:(void (^)(NSError *err))failCallback; diff --git a/ios/CodePush/CodePush.m b/ios/CodePush/CodePush.m index 689f943..a91120d 100644 --- a/ios/CodePush/CodePush.m +++ b/ios/CodePush/CodePush.m @@ -7,7 +7,7 @@ #import "CodePush.h" -@interface CodePush () +@interface CodePush () @end @implementation CodePush { @@ -171,6 +171,8 @@ static NSString *bundleResourceName = @"main"; @synthesize bridge = _bridge; @synthesize methodQueue = _methodQueue; +@synthesize pauseCallback = _pauseCallback; +@synthesize paused = _paused; /* * This method is used to clear updates that are installed @@ -273,7 +275,7 @@ static NSString *bundleResourceName = @"main"; #ifdef DEBUG [self clearDebugUpdates]; #endif - + _paused = YES; NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults]; NSDictionary *pendingUpdate = [preferences objectForKey:PendingUpdateKey]; if (pendingUpdate) { @@ -479,6 +481,7 @@ static NSString *bundleResourceName = @"main"; * This is native-side of the RemotePackage.download method */ RCT_EXPORT_METHOD(downloadUpdate:(NSDictionary*)updatePackage + notifyProgress:(BOOL)notifyProgress resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { @@ -488,21 +491,18 @@ RCT_EXPORT_METHOD(downloadUpdate:(NSDictionary*)updatePackage [mutableUpdatePackage setValue:[CodePushUpdateUtils modifiedDateStringOfFileAtURL:binaryBundleURL] forKey:BinaryBundleDateKey]; } - + [CodePushPackage downloadPackage:mutableUpdatePackage expectedBundleFileName:[bundleResourceName stringByAppendingPathExtension:bundleResourceExtension] + usingQueue:_methodQueue // The download is progressing forward progressCallback:^(long long expectedContentLength, long long receivedContentLength) { - dispatch_async(_methodQueue, ^{ - // Notify the script-side about the progress - [self.bridge.eventDispatcher - sendDeviceEventWithName:@"CodePushDownloadProgress" - body:@{ - @"totalBytes":[NSNumber numberWithLongLong:expectedContentLength], - @"receivedBytes":[NSNumber numberWithLongLong:receivedContentLength] - }]; - }); + // Notify the script-side about the progress + if (notifyProgress) { + [self sendDownloadProgressDuringNextFrame:expectedContentLength + receivedContentLength:receivedContentLength]; + } } // The download completed doneCallback:^{ @@ -760,4 +760,29 @@ RCT_EXPORT_METHOD(getNewStatusReport:(RCTPromiseResolveBlock)resolve resolve(nil); } +#pragma mark - Methods for handling dispatching of download progress events to JS (Private) + +long long latestExpectedContentLength = -1; +long long latestReceivedConentLength = -1; + +- (void)didUpdateFrame:(RCTFrameUpdate *)update +{ + // Notify the script-side about the progress + [self.bridge.eventDispatcher + sendDeviceEventWithName:@"CodePushDownloadProgress" + body:@{ + @"totalBytes":[NSNumber numberWithLongLong:latestExpectedContentLength], + @"receivedBytes":[NSNumber numberWithLongLong:latestReceivedConentLength] + }]; + _paused = YES; +} + +- (void)sendDownloadProgressDuringNextFrame:(long long)expectedContentLength + receivedContentLength:(long long)receivedContentLength +{ + latestExpectedContentLength = expectedContentLength; + latestReceivedConentLength = receivedContentLength; + _paused = NO; +} + @end \ No newline at end of file diff --git a/ios/CodePush/CodePushDownloadHandler.m b/ios/CodePush/CodePushDownloadHandler.m index 62fd6f4..69c245e 100644 --- a/ios/CodePush/CodePushDownloadHandler.m +++ b/ios/CodePush/CodePushDownloadHandler.m @@ -6,12 +6,14 @@ } - (id)init:(NSString *)downloadFilePath +usingQueue:(dispatch_queue_t)usingQueue progressCallback:(void (^)(long long, long long))progressCallback doneCallback:(void (^)(BOOL))doneCallback failCallback:(void (^)(NSError *err))failCallback { self.outputFileStream = [NSOutputStream outputStreamToFileAtPath:downloadFilePath append:NO]; self.receivedContentLength = 0; + self.usingQueue = usingQueue; self.progressCallback = progressCallback; self.doneCallback = doneCallback; self.failCallback = failCallback; @@ -22,12 +24,12 @@ failCallback:(void (^)(NSError *err))failCallback { NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:url] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:60.0]; - + NSOperationQueue *delegateQueue = [NSOperationQueue new]; + delegateQueue.underlyingQueue = self.usingQueue; NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO]; - [connection scheduleInRunLoop:[NSRunLoop mainRunLoop] - forMode:NSDefaultRunLoopMode]; + [connection setDelegateQueue:delegateQueue]; [connection start]; } diff --git a/ios/CodePush/CodePushPackage.m b/ios/CodePush/CodePushPackage.m index d72f30b..63f5250 100644 --- a/ios/CodePush/CodePushPackage.m +++ b/ios/CodePush/CodePushPackage.m @@ -41,6 +41,7 @@ static NSString *const UnzippedFolderName = @"unzipped"; + (void)downloadPackage:(NSDictionary *)updatePackage expectedBundleFileName:(NSString *)expectedBundleFileName + usingQueue:(dispatch_queue_t)usingQueue progressCallback:(void (^)(long long, long long))progressCallback doneCallback:(void (^)())doneCallback failCallback:(void (^)(NSError *err))failCallback @@ -71,6 +72,7 @@ static NSString *const UnzippedFolderName = @"unzipped"; CodePushDownloadHandler *downloadHandler = [[CodePushDownloadHandler alloc] init:downloadFilePath + usingQueue:usingQueue progressCallback:progressCallback doneCallback:^(BOOL isZip) { NSError *error = nil; diff --git a/package-mixins.js b/package-mixins.js index 4c68404..0357c41 100644 --- a/package-mixins.js +++ b/package-mixins.js @@ -14,24 +14,24 @@ module.exports = (NativeCodePush) => { let downloadProgressSubscription; if (downloadProgressCallback) { - // Use event subscription to obtain download progress. + // Use event subscription to obtain download progress. downloadProgressSubscription = DeviceEventEmitter.addListener( "CodePushDownloadProgress", downloadProgressCallback ); } - + // Use the downloaded package info. Native code will save the package info // so that the client knows what the current package version is. - try { - const downloadedPackage = await NativeCodePush.downloadUpdate(this); + try { + const downloadedPackage = await NativeCodePush.downloadUpdate(this, !!downloadProgressCallback); reportStatusDownload && reportStatusDownload(this); return { ...downloadedPackage, ...local }; } finally { downloadProgressSubscription && downloadProgressSubscription.remove(); } }, - + isPending: false // A remote package could never be in a pending state }; }; @@ -47,7 +47,7 @@ module.exports = (NativeCodePush) => { localPackage.isPending = true; // Mark the package as pending since it hasn't been applied yet } }, - + isPending: false // A local package wouldn't be pending until it was installed }; From 58d5d89c78c73b7d57912c896e766f3fd70291e7 Mon Sep 17 00:00:00 2001 From: Geoffrey Goh Date: Wed, 27 Apr 2016 15:20:42 -0700 Subject: [PATCH 02/10] Move delegateQueue creation --- ios/CodePush/CodePushDownloadHandler.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ios/CodePush/CodePushDownloadHandler.m b/ios/CodePush/CodePushDownloadHandler.m index 69c245e..2268020 100644 --- a/ios/CodePush/CodePushDownloadHandler.m +++ b/ios/CodePush/CodePushDownloadHandler.m @@ -24,11 +24,11 @@ failCallback:(void (^)(NSError *err))failCallback { NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:url] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:60.0]; - NSOperationQueue *delegateQueue = [NSOperationQueue new]; - delegateQueue.underlyingQueue = self.usingQueue; NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO]; + NSOperationQueue *delegateQueue = [NSOperationQueue new]; + delegateQueue.underlyingQueue = self.usingQueue; [connection setDelegateQueue:delegateQueue]; [connection start]; } From 99da3bf1b8fda9ef15be7af17e888d81dbf06452 Mon Sep 17 00:00:00 2001 From: Geoffrey Goh Date: Wed, 27 Apr 2016 16:00:15 -0700 Subject: [PATCH 03/10] download-progress-perf --- ios/CodePush/CodePush.h | 6 ++--- ios/CodePush/CodePush.m | 34 ++++++++++++-------------- ios/CodePush/CodePushDownloadHandler.m | 6 ++--- ios/CodePush/CodePushPackage.m | 4 +-- 4 files changed, 23 insertions(+), 27 deletions(-) diff --git a/ios/CodePush/CodePush.h b/ios/CodePush/CodePush.h index f94d684..06ac51b 100644 --- a/ios/CodePush/CodePush.h +++ b/ios/CodePush/CodePush.h @@ -54,13 +54,13 @@ @property (strong) NSOutputStream *outputFileStream; @property long long expectedContentLength; @property long long receivedContentLength; -@property dispatch_queue_t usingQueue; +@property dispatch_queue_t operationQueue; @property (copy) void (^progressCallback)(long long, long long); @property (copy) void (^doneCallback)(BOOL); @property (copy) void (^failCallback)(NSError *err); - (id)init:(NSString *)downloadFilePath -usingQueue:(dispatch_queue_t)usingQueue +operationQueue:(dispatch_queue_t)operationQueue progressCallback:(void (^)(long long, long long))progressCallback doneCallback:(void (^)(BOOL))doneCallback failCallback:(void (^)(NSError *err))failCallback; @@ -80,7 +80,7 @@ failCallback:(void (^)(NSError *err))failCallback; + (void)downloadPackage:(NSDictionary *)updatePackage expectedBundleFileName:(NSString *)expectedBundleFileName - usingQueue:(dispatch_queue_t)usingQueue + operationQueue:(dispatch_queue_t)operationQueue progressCallback:(void (^)(long long, long long))progressCallback doneCallback:(void (^)())doneCallback failCallback:(void (^)(NSError *err))failCallback; diff --git a/ios/CodePush/CodePush.m b/ios/CodePush/CodePush.m index a91120d..c0cce17 100644 --- a/ios/CodePush/CodePush.m +++ b/ios/CodePush/CodePush.m @@ -495,37 +495,33 @@ RCT_EXPORT_METHOD(downloadUpdate:(NSDictionary*)updatePackage [CodePushPackage downloadPackage:mutableUpdatePackage expectedBundleFileName:[bundleResourceName stringByAppendingPathExtension:bundleResourceExtension] - usingQueue:_methodQueue + operationQueue:_methodQueue // The download is progressing forward progressCallback:^(long long expectedContentLength, long long receivedContentLength) { // Notify the script-side about the progress if (notifyProgress) { - [self sendDownloadProgressDuringNextFrame:expectedContentLength - receivedContentLength:receivedContentLength]; + [self updateDownloadProgressForNextFrame:expectedContentLength + receivedContentLength:receivedContentLength]; } } // The download completed doneCallback:^{ - dispatch_async(_methodQueue, ^{ - NSError *err; - NSDictionary *newPackage = [CodePushPackage getPackage:mutableUpdatePackage[PackageHashKey] error:&err]; + NSError *err; + NSDictionary *newPackage = [CodePushPackage getPackage:mutableUpdatePackage[PackageHashKey] error:&err]; - if (err) { - return reject([NSString stringWithFormat: @"%lu", (long)err.code], err.localizedDescription, err); - } + if (err) { + return reject([NSString stringWithFormat: @"%lu", (long)err.code], err.localizedDescription, err); + } - resolve(newPackage); - }); + resolve(newPackage); } // The download failed failCallback:^(NSError *err) { - dispatch_async(_methodQueue, ^{ - if ([CodePushErrorUtils isCodePushError:err]) { - [self saveFailedUpdate:mutableUpdatePackage]; - } + if ([CodePushErrorUtils isCodePushError:err]) { + [self saveFailedUpdate:mutableUpdatePackage]; + } - reject([NSString stringWithFormat: @"%lu", (long)err.code], err.localizedDescription, err); - }); + reject([NSString stringWithFormat: @"%lu", (long)err.code], err.localizedDescription, err); }]; } @@ -777,8 +773,8 @@ long long latestReceivedConentLength = -1; _paused = YES; } -- (void)sendDownloadProgressDuringNextFrame:(long long)expectedContentLength - receivedContentLength:(long long)receivedContentLength +- (void)updateDownloadProgressForNextFrame:(long long)expectedContentLength + receivedContentLength:(long long)receivedContentLength { latestExpectedContentLength = expectedContentLength; latestReceivedConentLength = receivedContentLength; diff --git a/ios/CodePush/CodePushDownloadHandler.m b/ios/CodePush/CodePushDownloadHandler.m index 69c245e..3e69381 100644 --- a/ios/CodePush/CodePushDownloadHandler.m +++ b/ios/CodePush/CodePushDownloadHandler.m @@ -6,14 +6,14 @@ } - (id)init:(NSString *)downloadFilePath -usingQueue:(dispatch_queue_t)usingQueue +operationQueue:(dispatch_queue_t)operationQueue progressCallback:(void (^)(long long, long long))progressCallback doneCallback:(void (^)(BOOL))doneCallback failCallback:(void (^)(NSError *err))failCallback { self.outputFileStream = [NSOutputStream outputStreamToFileAtPath:downloadFilePath append:NO]; self.receivedContentLength = 0; - self.usingQueue = usingQueue; + self.operationQueue = operationQueue; self.progressCallback = progressCallback; self.doneCallback = doneCallback; self.failCallback = failCallback; @@ -25,7 +25,7 @@ failCallback:(void (^)(NSError *err))failCallback { cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:60.0]; NSOperationQueue *delegateQueue = [NSOperationQueue new]; - delegateQueue.underlyingQueue = self.usingQueue; + delegateQueue.underlyingQueue = self.operationQueue; NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO]; diff --git a/ios/CodePush/CodePushPackage.m b/ios/CodePush/CodePushPackage.m index 63f5250..2d15a28 100644 --- a/ios/CodePush/CodePushPackage.m +++ b/ios/CodePush/CodePushPackage.m @@ -41,7 +41,7 @@ static NSString *const UnzippedFolderName = @"unzipped"; + (void)downloadPackage:(NSDictionary *)updatePackage expectedBundleFileName:(NSString *)expectedBundleFileName - usingQueue:(dispatch_queue_t)usingQueue + operationQueue:(dispatch_queue_t)operationQueue progressCallback:(void (^)(long long, long long))progressCallback doneCallback:(void (^)())doneCallback failCallback:(void (^)(NSError *err))failCallback @@ -72,7 +72,7 @@ static NSString *const UnzippedFolderName = @"unzipped"; CodePushDownloadHandler *downloadHandler = [[CodePushDownloadHandler alloc] init:downloadFilePath - usingQueue:usingQueue + operationQueue:operationQueue progressCallback:progressCallback doneCallback:^(BOOL isZip) { NSError *error = nil; From 6a8ce4d041a96a14fa61e4985ecdbf42df11a146 Mon Sep 17 00:00:00 2001 From: Geoffrey Goh Date: Wed, 27 Apr 2016 16:04:07 -0700 Subject: [PATCH 04/10] rename pragma mark --- ios/CodePush/CodePush.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ios/CodePush/CodePush.m b/ios/CodePush/CodePush.m index c0cce17..a4c87bd 100644 --- a/ios/CodePush/CodePush.m +++ b/ios/CodePush/CodePush.m @@ -756,7 +756,7 @@ RCT_EXPORT_METHOD(getNewStatusReport:(RCTPromiseResolveBlock)resolve resolve(nil); } -#pragma mark - Methods for handling dispatching of download progress events to JS (Private) +#pragma mark - RCTFrameUpdateObserver Methods long long latestExpectedContentLength = -1; long long latestReceivedConentLength = -1; From 951d6ca564e4905656f1baae9255f120491f740a Mon Sep 17 00:00:00 2001 From: Geoffrey Goh Date: Wed, 27 Apr 2016 17:41:15 -0700 Subject: [PATCH 05/10] nextFrameBusy -> hasScheduledNextFrame --- .../java/com/microsoft/codepush/react/CodePush.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/android/app/src/main/java/com/microsoft/codepush/react/CodePush.java b/android/app/src/main/java/com/microsoft/codepush/react/CodePush.java index 24d9a38..486b1ed 100644 --- a/android/app/src/main/java/com/microsoft/codepush/react/CodePush.java +++ b/android/app/src/main/java/com/microsoft/codepush/react/CodePush.java @@ -238,7 +238,7 @@ public class CodePush implements ReactPackage { // Reset the state which indicates that // the app was just freshly updated. didUpdate = false; - + JSONObject pendingUpdate = getPendingUpdate(); if (pendingUpdate != null) { try { @@ -253,7 +253,7 @@ public class CodePush implements ReactPackage { // There is in fact a new update running for the first // time, so update the local state to ensure the client knows. didUpdate = true; - + // 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. savePendingUpdate(pendingUpdate.getString(PENDING_UPDATE_HASH_KEY), @@ -449,7 +449,7 @@ public class CodePush implements ReactPackage { WritableMap mutableUpdatePackage = CodePushUtils.convertReadableMapToWritableMap(updatePackage); mutableUpdatePackage.putString(BINARY_MODIFIED_TIME_KEY, "" + getBinaryResourcesModifiedTime()); codePushPackage.downloadPackage(mutableUpdatePackage, CodePush.this.assetsBundleFileName, new DownloadProgressCallback() { - private boolean nextFrameBusy = false; + private boolean hasScheduledNextFrame = false; private DownloadProgress latestDownloadProgress = null; @Override @@ -459,11 +459,11 @@ public class CodePush implements ReactPackage { } this.latestDownloadProgress = downloadProgress; - if (nextFrameBusy) { + if (hasScheduledNextFrame) { return; } - nextFrameBusy = true; + hasScheduledNextFrame = true; mainActivity.runOnUiThread(new Runnable() { @Override public void run() { @@ -473,7 +473,7 @@ public class CodePush implements ReactPackage { getReactApplicationContext() .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) .emit(DOWNLOAD_PROGRESS_EVENT_NAME, latestDownloadProgress.createWritableMap()); - nextFrameBusy = false; + hasScheduledNextFrame = false; } }); } From b1a176ace51a27982ae91dcba4ea3c9c14ed1dde Mon Sep 17 00:00:00 2001 From: Geoffrey Goh Date: Thu, 28 Apr 2016 11:29:38 -0700 Subject: [PATCH 06/10] CR feedback --- ios/CodePush/CodePush.m | 36 +++++++++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/ios/CodePush/CodePush.m b/ios/CodePush/CodePush.m index a4c87bd..a3b2cd5 100644 --- a/ios/CodePush/CodePush.m +++ b/ios/CodePush/CodePush.m @@ -492,6 +492,10 @@ RCT_EXPORT_METHOD(downloadUpdate:(NSDictionary*)updatePackage forKey:BinaryBundleDateKey]; } + if (notifyProgress) { + [self setupFrameObserverForDownloadProgress]; + } + [CodePushPackage downloadPackage:mutableUpdatePackage expectedBundleFileName:[bundleResourceName stringByAppendingPathExtension:bundleResourceExtension] @@ -499,10 +503,8 @@ RCT_EXPORT_METHOD(downloadUpdate:(NSDictionary*)updatePackage // The download is progressing forward progressCallback:^(long long expectedContentLength, long long receivedContentLength) { // Notify the script-side about the progress - if (notifyProgress) { - [self updateDownloadProgressForNextFrame:expectedContentLength - receivedContentLength:receivedContentLength]; - } + [self updateDownloadProgressForNextFrame:expectedContentLength + receivedContentLength:receivedContentLength]; } // The download completed doneCallback:^{ @@ -512,7 +514,8 @@ RCT_EXPORT_METHOD(downloadUpdate:(NSDictionary*)updatePackage if (err) { return reject([NSString stringWithFormat: @"%lu", (long)err.code], err.localizedDescription, err); } - + + [self pauseFrameObserver]; resolve(newPackage); } // The download failed @@ -520,7 +523,8 @@ RCT_EXPORT_METHOD(downloadUpdate:(NSDictionary*)updatePackage if ([CodePushErrorUtils isCodePushError:err]) { [self saveFailedUpdate:mutableUpdatePackage]; } - + + [self pauseFrameObserver]; reject([NSString stringWithFormat: @"%lu", (long)err.code], err.localizedDescription, err); }]; } @@ -760,9 +764,14 @@ RCT_EXPORT_METHOD(getNewStatusReport:(RCTPromiseResolveBlock)resolve long long latestExpectedContentLength = -1; long long latestReceivedConentLength = -1; +BOOL didUpdateProgress = NO; - (void)didUpdateFrame:(RCTFrameUpdate *)update { + if (!didUpdateProgress) { + return; + } + // Notify the script-side about the progress [self.bridge.eventDispatcher sendDeviceEventWithName:@"CodePushDownloadProgress" @@ -770,7 +779,8 @@ long long latestReceivedConentLength = -1; @"totalBytes":[NSNumber numberWithLongLong:latestExpectedContentLength], @"receivedBytes":[NSNumber numberWithLongLong:latestReceivedConentLength] }]; - _paused = YES; + didUpdateProgress = NO; + } - (void)updateDownloadProgressForNextFrame:(long long)expectedContentLength @@ -778,7 +788,19 @@ long long latestReceivedConentLength = -1; { latestExpectedContentLength = expectedContentLength; latestReceivedConentLength = receivedContentLength; + didUpdateProgress = YES; +} + + +- (void)setupFrameObserverForDownloadProgress +{ + didUpdateProgress = NO; _paused = NO; } +- (void)pauseFrameObserver +{ + _paused = YES; +} + @end \ No newline at end of file From a995a3c64d48911638a0e17826e94ecad1b8e5b8 Mon Sep 17 00:00:00 2001 From: Geoffrey Goh Date: Thu, 28 Apr 2016 14:26:09 -0700 Subject: [PATCH 07/10] CR feedback --- .../microsoft/codepush/react/CodePush.java | 23 +++++++++++++++---- .../codepush/react/DownloadProgress.java | 4 ++++ ios/CodePush/CodePush.m | 21 +++++++++++------ 3 files changed, 36 insertions(+), 12 deletions(-) diff --git a/android/app/src/main/java/com/microsoft/codepush/react/CodePush.java b/android/app/src/main/java/com/microsoft/codepush/react/CodePush.java index 486b1ed..49f0c12 100644 --- a/android/app/src/main/java/com/microsoft/codepush/react/CodePush.java +++ b/android/app/src/main/java/com/microsoft/codepush/react/CodePush.java @@ -458,27 +458,40 @@ public class CodePush implements ReactPackage { return; } - this.latestDownloadProgress = downloadProgress; + latestDownloadProgress = downloadProgress; + // If the download is completed, synchronously send the last event. + if (latestDownloadProgress.isCompleted()) { + dispatchDownloadProgressEvent(); + return; + } + if (hasScheduledNextFrame) { return; } hasScheduledNextFrame = true; - mainActivity.runOnUiThread(new Runnable() { + getReactApplicationContext().runOnUiQueueThread(new Runnable() { @Override public void run() { ReactChoreographer.getInstance().postFrameCallback(ReactChoreographer.CallbackType.TIMERS_EVENTS, new Choreographer.FrameCallback() { @Override public void doFrame(long frameTimeNanos) { - getReactApplicationContext() - .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) - .emit(DOWNLOAD_PROGRESS_EVENT_NAME, latestDownloadProgress.createWritableMap()); + if (!latestDownloadProgress.isCompleted()) { + dispatchDownloadProgressEvent(); + } + hasScheduledNextFrame = false; } }); } }); } + + public void dispatchDownloadProgressEvent() { + getReactApplicationContext() + .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) + .emit(DOWNLOAD_PROGRESS_EVENT_NAME, latestDownloadProgress.createWritableMap()); + } }); WritableMap newPackage = codePushPackage.getPackage(CodePushUtils.tryGetString(updatePackage, PACKAGE_HASH_KEY)); diff --git a/android/app/src/main/java/com/microsoft/codepush/react/DownloadProgress.java b/android/app/src/main/java/com/microsoft/codepush/react/DownloadProgress.java index ff4add9..45b5026 100644 --- a/android/app/src/main/java/com/microsoft/codepush/react/DownloadProgress.java +++ b/android/app/src/main/java/com/microsoft/codepush/react/DownloadProgress.java @@ -23,4 +23,8 @@ class DownloadProgress { } return map; } + + public boolean isCompleted() { + return this.totalBytes == this.receivedBytes; + } } diff --git a/ios/CodePush/CodePush.m b/ios/CodePush/CodePush.m index a3b2cd5..0c27c75 100644 --- a/ios/CodePush/CodePush.m +++ b/ios/CodePush/CodePush.m @@ -275,7 +275,7 @@ static NSString *bundleResourceName = @"main"; #ifdef DEBUG [self clearDebugUpdates]; #endif - _paused = YES; + [self pauseFrameObserver]; NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults]; NSDictionary *pendingUpdate = [preferences objectForKey:PendingUpdateKey]; if (pendingUpdate) { @@ -502,9 +502,13 @@ RCT_EXPORT_METHOD(downloadUpdate:(NSDictionary*)updatePackage operationQueue:_methodQueue // The download is progressing forward progressCallback:^(long long expectedContentLength, long long receivedContentLength) { - // Notify the script-side about the progress [self updateDownloadProgressForNextFrame:expectedContentLength receivedContentLength:receivedContentLength]; + // If the download is completed, stop observing frame updates and synchronously send the last event. + if (expectedContentLength == receivedContentLength) { + [self pauseFrameObserver]; + [self dispatchDownloadProgressEvent]; + } } // The download completed doneCallback:^{ @@ -514,8 +518,6 @@ RCT_EXPORT_METHOD(downloadUpdate:(NSDictionary*)updatePackage if (err) { return reject([NSString stringWithFormat: @"%lu", (long)err.code], err.localizedDescription, err); } - - [self pauseFrameObserver]; resolve(newPackage); } // The download failed @@ -772,15 +774,19 @@ BOOL didUpdateProgress = NO; return; } + [self dispatchDownloadProgressEvent]; + didUpdateProgress = NO; +} + +- (void)dispatchDownloadProgressEvent +{ // Notify the script-side about the progress [self.bridge.eventDispatcher sendDeviceEventWithName:@"CodePushDownloadProgress" body:@{ @"totalBytes":[NSNumber numberWithLongLong:latestExpectedContentLength], @"receivedBytes":[NSNumber numberWithLongLong:latestReceivedConentLength] - }]; - didUpdateProgress = NO; - + }]; } - (void)updateDownloadProgressForNextFrame:(long long)expectedContentLength @@ -800,6 +806,7 @@ BOOL didUpdateProgress = NO; - (void)pauseFrameObserver { + didUpdateProgress = NO; _paused = YES; } From 3b050738fc69a7868adaddfaa137e2fa8f88e423 Mon Sep 17 00:00:00 2001 From: Geoffrey Goh Date: Thu, 28 Apr 2016 16:57:06 -0700 Subject: [PATCH 08/10] inline progress update methods --- ios/CodePush/CodePush.m | 74 +++++++++++++++++------------------------ 1 file changed, 31 insertions(+), 43 deletions(-) diff --git a/ios/CodePush/CodePush.m b/ios/CodePush/CodePush.m index 0c27c75..2666edc 100644 --- a/ios/CodePush/CodePush.m +++ b/ios/CodePush/CodePush.m @@ -15,6 +15,9 @@ BOOL _isFirstRunAfterUpdate; int _minimumBackgroundDuration; NSDate *_lastResignedDate; + long long latestExpectedContentLength; + long long latestReceivedConentLength; + BOOL didUpdateProgress; } RCT_EXPORT_MODULE() @@ -225,6 +228,17 @@ static NSString *bundleResourceName = @"main"; [[NSNotificationCenter defaultCenter] removeObserver:self]; } +- (void)dispatchDownloadProgressEvent +{ + // Notify the script-side about the progress + [self.bridge.eventDispatcher + sendDeviceEventWithName:@"CodePushDownloadProgress" + body:@{ + @"totalBytes":[NSNumber numberWithLongLong:latestExpectedContentLength], + @"receivedBytes":[NSNumber numberWithLongLong:latestReceivedConentLength] + }]; +} + /* * This method ensures that the app was packaged with a JS bundle * file, and if not, it throws the appropriate exception. @@ -275,7 +289,7 @@ static NSString *bundleResourceName = @"main"; #ifdef DEBUG [self clearDebugUpdates]; #endif - [self pauseFrameObserver]; + _paused = YES; NSUserDefaults *preferences = [NSUserDefaults standardUserDefaults]; NSDictionary *pendingUpdate = [preferences objectForKey:PendingUpdateKey]; if (pendingUpdate) { @@ -493,7 +507,10 @@ RCT_EXPORT_METHOD(downloadUpdate:(NSDictionary*)updatePackage } if (notifyProgress) { - [self setupFrameObserverForDownloadProgress]; + // Set up and unpause the frame observer so that it can emit + // progress events every frame if the progress is updated. + didUpdateProgress = NO; + _paused = NO; } [CodePushPackage @@ -502,11 +519,16 @@ RCT_EXPORT_METHOD(downloadUpdate:(NSDictionary*)updatePackage operationQueue:_methodQueue // The download is progressing forward progressCallback:^(long long expectedContentLength, long long receivedContentLength) { - [self updateDownloadProgressForNextFrame:expectedContentLength - receivedContentLength:receivedContentLength]; - // If the download is completed, stop observing frame updates and synchronously send the last event. + // Update the download progress so that the frame observer can notify the JS side + latestExpectedContentLength = expectedContentLength; + latestReceivedConentLength = receivedContentLength; + didUpdateProgress = YES; + + // If the download is completed, stop observing frame + // updates and synchronously send the last event. if (expectedContentLength == receivedContentLength) { - [self pauseFrameObserver]; + didUpdateProgress = NO; + _paused = YES; [self dispatchDownloadProgressEvent]; } } @@ -526,7 +548,9 @@ RCT_EXPORT_METHOD(downloadUpdate:(NSDictionary*)updatePackage [self saveFailedUpdate:mutableUpdatePackage]; } - [self pauseFrameObserver]; + // Stop observing frame updates if the download fails. + didUpdateProgress = NO; + _paused = YES; reject([NSString stringWithFormat: @"%lu", (long)err.code], err.localizedDescription, err); }]; } @@ -764,10 +788,6 @@ RCT_EXPORT_METHOD(getNewStatusReport:(RCTPromiseResolveBlock)resolve #pragma mark - RCTFrameUpdateObserver Methods -long long latestExpectedContentLength = -1; -long long latestReceivedConentLength = -1; -BOOL didUpdateProgress = NO; - - (void)didUpdateFrame:(RCTFrameUpdate *)update { if (!didUpdateProgress) { @@ -778,36 +798,4 @@ BOOL didUpdateProgress = NO; didUpdateProgress = NO; } -- (void)dispatchDownloadProgressEvent -{ - // Notify the script-side about the progress - [self.bridge.eventDispatcher - sendDeviceEventWithName:@"CodePushDownloadProgress" - body:@{ - @"totalBytes":[NSNumber numberWithLongLong:latestExpectedContentLength], - @"receivedBytes":[NSNumber numberWithLongLong:latestReceivedConentLength] - }]; -} - -- (void)updateDownloadProgressForNextFrame:(long long)expectedContentLength - receivedContentLength:(long long)receivedContentLength -{ - latestExpectedContentLength = expectedContentLength; - latestReceivedConentLength = receivedContentLength; - didUpdateProgress = YES; -} - - -- (void)setupFrameObserverForDownloadProgress -{ - didUpdateProgress = NO; - _paused = NO; -} - -- (void)pauseFrameObserver -{ - didUpdateProgress = NO; - _paused = YES; -} - @end \ No newline at end of file From 5cc0556d1a66ed2b97c4f26b13ced745b1106751 Mon Sep 17 00:00:00 2001 From: Geoffrey Goh Date: Thu, 28 Apr 2016 17:00:27 -0700 Subject: [PATCH 09/10] add underscore prefix --- ios/CodePush/CodePush.m | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/ios/CodePush/CodePush.m b/ios/CodePush/CodePush.m index 2666edc..d696de8 100644 --- a/ios/CodePush/CodePush.m +++ b/ios/CodePush/CodePush.m @@ -15,9 +15,9 @@ BOOL _isFirstRunAfterUpdate; int _minimumBackgroundDuration; NSDate *_lastResignedDate; - long long latestExpectedContentLength; - long long latestReceivedConentLength; - BOOL didUpdateProgress; + long long _latestExpectedContentLength; + long long _latestReceivedConentLength; + BOOL _didUpdateProgress; } RCT_EXPORT_MODULE() @@ -174,7 +174,6 @@ static NSString *bundleResourceName = @"main"; @synthesize bridge = _bridge; @synthesize methodQueue = _methodQueue; -@synthesize pauseCallback = _pauseCallback; @synthesize paused = _paused; /* @@ -234,8 +233,8 @@ static NSString *bundleResourceName = @"main"; [self.bridge.eventDispatcher sendDeviceEventWithName:@"CodePushDownloadProgress" body:@{ - @"totalBytes":[NSNumber numberWithLongLong:latestExpectedContentLength], - @"receivedBytes":[NSNumber numberWithLongLong:latestReceivedConentLength] + @"totalBytes":[NSNumber numberWithLongLong:_latestExpectedContentLength], + @"receivedBytes":[NSNumber numberWithLongLong:_latestReceivedConentLength] }]; } @@ -509,7 +508,7 @@ RCT_EXPORT_METHOD(downloadUpdate:(NSDictionary*)updatePackage if (notifyProgress) { // Set up and unpause the frame observer so that it can emit // progress events every frame if the progress is updated. - didUpdateProgress = NO; + _didUpdateProgress = NO; _paused = NO; } @@ -520,14 +519,14 @@ RCT_EXPORT_METHOD(downloadUpdate:(NSDictionary*)updatePackage // The download is progressing forward progressCallback:^(long long expectedContentLength, long long receivedContentLength) { // Update the download progress so that the frame observer can notify the JS side - latestExpectedContentLength = expectedContentLength; - latestReceivedConentLength = receivedContentLength; - didUpdateProgress = YES; + _latestExpectedContentLength = expectedContentLength; + _latestReceivedConentLength = receivedContentLength; + _didUpdateProgress = YES; // If the download is completed, stop observing frame // updates and synchronously send the last event. if (expectedContentLength == receivedContentLength) { - didUpdateProgress = NO; + _didUpdateProgress = NO; _paused = YES; [self dispatchDownloadProgressEvent]; } @@ -549,7 +548,7 @@ RCT_EXPORT_METHOD(downloadUpdate:(NSDictionary*)updatePackage } // Stop observing frame updates if the download fails. - didUpdateProgress = NO; + _didUpdateProgress = NO; _paused = YES; reject([NSString stringWithFormat: @"%lu", (long)err.code], err.localizedDescription, err); }]; @@ -790,12 +789,12 @@ RCT_EXPORT_METHOD(getNewStatusReport:(RCTPromiseResolveBlock)resolve - (void)didUpdateFrame:(RCTFrameUpdate *)update { - if (!didUpdateProgress) { + if (!_didUpdateProgress) { return; } [self dispatchDownloadProgressEvent]; - didUpdateProgress = NO; + _didUpdateProgress = NO; } @end \ No newline at end of file From 27e9f349911f96c253dc478324ed448b89f4f91f Mon Sep 17 00:00:00 2001 From: Geoffrey Goh Date: Thu, 28 Apr 2016 17:10:23 -0700 Subject: [PATCH 10/10] add comment and resynthesize pauseCallback --- ios/CodePush/CodePush.m | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ios/CodePush/CodePush.m b/ios/CodePush/CodePush.m index d696de8..10ece53 100644 --- a/ios/CodePush/CodePush.m +++ b/ios/CodePush/CodePush.m @@ -15,6 +15,8 @@ BOOL _isFirstRunAfterUpdate; int _minimumBackgroundDuration; NSDate *_lastResignedDate; + + // Used to coordinate the dispatching of download progress events to JS. long long _latestExpectedContentLength; long long _latestReceivedConentLength; BOOL _didUpdateProgress; @@ -174,6 +176,7 @@ static NSString *bundleResourceName = @"main"; @synthesize bridge = _bridge; @synthesize methodQueue = _methodQueue; +@synthesize pauseCallback = _pauseCallback; @synthesize paused = _paused; /*