mirror of
https://github.com/zhigang1992/react-native-code-push.git
synced 2026-05-19 19:39:54 +08:00
Merge branch 'master' of github.com:Microsoft/react-native-code-push
This commit is contained in:
92
CodePush.js
92
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 = {
|
||||
|
||||
@@ -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;
|
||||
@@ -236,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 {
|
||||
@@ -251,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),
|
||||
@@ -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<Void, Void, Void> asyncTask = new AsyncTask<Void, Void, Void>() {
|
||||
@Override
|
||||
protected Void doInBackground(Void... params) {
|
||||
@@ -447,11 +449,48 @@ 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 hasScheduledNextFrame = false;
|
||||
private DownloadProgress latestDownloadProgress = null;
|
||||
|
||||
@Override
|
||||
public void call(DownloadProgress downloadProgress) {
|
||||
if (!notifyProgress) {
|
||||
return;
|
||||
}
|
||||
|
||||
latestDownloadProgress = downloadProgress;
|
||||
// If the download is completed, synchronously send the last event.
|
||||
if (latestDownloadProgress.isCompleted()) {
|
||||
dispatchDownloadProgressEvent();
|
||||
return;
|
||||
}
|
||||
|
||||
if (hasScheduledNextFrame) {
|
||||
return;
|
||||
}
|
||||
|
||||
hasScheduledNextFrame = true;
|
||||
getReactApplicationContext().runOnUiQueueThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
ReactChoreographer.getInstance().postFrameCallback(ReactChoreographer.CallbackType.TIMERS_EVENTS, new Choreographer.FrameCallback() {
|
||||
@Override
|
||||
public void doFrame(long frameTimeNanos) {
|
||||
if (!latestDownloadProgress.isCompleted()) {
|
||||
dispatchDownloadProgressEvent();
|
||||
}
|
||||
|
||||
hasScheduledNextFrame = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void dispatchDownloadProgressEvent() {
|
||||
getReactApplicationContext()
|
||||
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
|
||||
.emit(DOWNLOAD_PROGRESS_EVENT_NAME, downloadProgress.createWritableMap());
|
||||
.emit(DOWNLOAD_PROGRESS_EVENT_NAME, latestDownloadProgress.createWritableMap());
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -23,4 +23,8 @@ class DownloadProgress {
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
public boolean isCompleted() {
|
||||
return this.totalBytes == this.receivedBytes;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,11 +54,13 @@
|
||||
@property (strong) NSOutputStream *outputFileStream;
|
||||
@property long long expectedContentLength;
|
||||
@property long long receivedContentLength;
|
||||
@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
|
||||
operationQueue:(dispatch_queue_t)operationQueue
|
||||
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
|
||||
operationQueue:(dispatch_queue_t)operationQueue
|
||||
progressCallback:(void (^)(long long, long long))progressCallback
|
||||
doneCallback:(void (^)())doneCallback
|
||||
failCallback:(void (^)(NSError *err))failCallback;
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
#import "CodePush.h"
|
||||
|
||||
@interface CodePush () <RCTBridgeModule>
|
||||
@interface CodePush () <RCTBridgeModule, RCTFrameUpdateObserver>
|
||||
@end
|
||||
|
||||
@implementation CodePush {
|
||||
@@ -15,6 +15,11 @@
|
||||
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;
|
||||
}
|
||||
|
||||
RCT_EXPORT_MODULE()
|
||||
@@ -171,6 +176,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
|
||||
@@ -223,6 +230,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.
|
||||
@@ -273,7 +291,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 +497,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,44 +507,53 @@ RCT_EXPORT_METHOD(downloadUpdate:(NSDictionary*)updatePackage
|
||||
[mutableUpdatePackage setValue:[CodePushUpdateUtils modifiedDateStringOfFileAtURL:binaryBundleURL]
|
||||
forKey:BinaryBundleDateKey];
|
||||
}
|
||||
|
||||
|
||||
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;
|
||||
_paused = NO;
|
||||
}
|
||||
|
||||
[CodePushPackage
|
||||
downloadPackage:mutableUpdatePackage
|
||||
expectedBundleFileName:[bundleResourceName stringByAppendingPathExtension:bundleResourceExtension]
|
||||
operationQueue:_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]
|
||||
}];
|
||||
});
|
||||
// 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) {
|
||||
_didUpdateProgress = NO;
|
||||
_paused = YES;
|
||||
[self dispatchDownloadProgressEvent];
|
||||
}
|
||||
}
|
||||
// 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);
|
||||
}
|
||||
|
||||
resolve(newPackage);
|
||||
});
|
||||
if (err) {
|
||||
return reject([NSString stringWithFormat: @"%lu", (long)err.code], err.localizedDescription, err);
|
||||
}
|
||||
resolve(newPackage);
|
||||
}
|
||||
// The download failed
|
||||
failCallback:^(NSError *err) {
|
||||
dispatch_async(_methodQueue, ^{
|
||||
if ([CodePushErrorUtils isCodePushError:err]) {
|
||||
[self saveFailedUpdate:mutableUpdatePackage];
|
||||
}
|
||||
|
||||
reject([NSString stringWithFormat: @"%lu", (long)err.code], err.localizedDescription, err);
|
||||
});
|
||||
if ([CodePushErrorUtils isCodePushError:err]) {
|
||||
[self saveFailedUpdate:mutableUpdatePackage];
|
||||
}
|
||||
|
||||
// Stop observing frame updates if the download fails.
|
||||
_didUpdateProgress = NO;
|
||||
_paused = YES;
|
||||
reject([NSString stringWithFormat: @"%lu", (long)err.code], err.localizedDescription, err);
|
||||
}];
|
||||
}
|
||||
|
||||
@@ -760,4 +788,16 @@ RCT_EXPORT_METHOD(getNewStatusReport:(RCTPromiseResolveBlock)resolve
|
||||
resolve(nil);
|
||||
}
|
||||
|
||||
#pragma mark - RCTFrameUpdateObserver Methods
|
||||
|
||||
- (void)didUpdateFrame:(RCTFrameUpdate *)update
|
||||
{
|
||||
if (!_didUpdateProgress) {
|
||||
return;
|
||||
}
|
||||
|
||||
[self dispatchDownloadProgressEvent];
|
||||
_didUpdateProgress = NO;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -6,12 +6,14 @@
|
||||
}
|
||||
|
||||
- (id)init:(NSString *)downloadFilePath
|
||||
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.operationQueue = operationQueue;
|
||||
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];
|
||||
|
||||
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request
|
||||
delegate:self
|
||||
startImmediately:NO];
|
||||
[connection scheduleInRunLoop:[NSRunLoop mainRunLoop]
|
||||
forMode:NSDefaultRunLoopMode];
|
||||
NSOperationQueue *delegateQueue = [NSOperationQueue new];
|
||||
delegateQueue.underlyingQueue = self.operationQueue;
|
||||
[connection setDelegateQueue:delegateQueue];
|
||||
[connection start];
|
||||
}
|
||||
|
||||
@@ -51,31 +53,31 @@ failCallback:(void (^)(NSError *err))failCallback {
|
||||
if (headerOffset >= 4) {
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
const char *bytes = [data bytes];
|
||||
_header[headerOffset] = bytes[i];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
self.receivedContentLength = self.receivedContentLength + [data length];
|
||||
|
||||
|
||||
NSInteger bytesLeft = [data length];
|
||||
|
||||
|
||||
do {
|
||||
NSInteger bytesWritten = [self.outputFileStream write:[data bytes]
|
||||
maxLength:bytesLeft];
|
||||
if (bytesWritten == -1) {
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
bytesLeft -= bytesWritten;
|
||||
} while (bytesLeft > 0);
|
||||
|
||||
|
||||
self.progressCallback(self.expectedContentLength, self.receivedContentLength);
|
||||
|
||||
|
||||
// bytesLeft should not be negative.
|
||||
assert(bytesLeft >= 0);
|
||||
|
||||
|
||||
if (bytesLeft) {
|
||||
[self.outputFileStream close];
|
||||
[connection cancel];
|
||||
|
||||
@@ -41,6 +41,7 @@ static NSString *const UnzippedFolderName = @"unzipped";
|
||||
|
||||
+ (void)downloadPackage:(NSDictionary *)updatePackage
|
||||
expectedBundleFileName:(NSString *)expectedBundleFileName
|
||||
operationQueue:(dispatch_queue_t)operationQueue
|
||||
progressCallback:(void (^)(long long, long long))progressCallback
|
||||
doneCallback:(void (^)())doneCallback
|
||||
failCallback:(void (^)(NSError *err))failCallback
|
||||
@@ -76,6 +77,7 @@ static NSString *const UnzippedFolderName = @"unzipped";
|
||||
|
||||
CodePushDownloadHandler *downloadHandler = [[CodePushDownloadHandler alloc]
|
||||
init:downloadFilePath
|
||||
operationQueue:operationQueue
|
||||
progressCallback:progressCallback
|
||||
doneCallback:^(BOOL isZip) {
|
||||
NSError *error = nil;
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user