@bravemobile/react-native-code-push 12.4.1 → 13.1.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CodePush.podspec +47 -2
- package/README.md +118 -3
- package/THIRD-PARTY-NOTICES +96 -0
- package/android/app/build.gradle +26 -0
- package/android/app/proguard-rules.pro +5 -0
- package/android/app/src/main/cpp/CMakeLists.txt +38 -0
- package/android/app/src/main/cpp/binarypatch_jni.c +181 -0
- package/android/app/src/main/java/com/microsoft/codepush/react/BinaryPatchAttempt.java +92 -0
- package/android/app/src/main/java/com/microsoft/codepush/react/BinaryPatchResult.java +64 -0
- package/android/app/src/main/java/com/microsoft/codepush/react/CodePush.java +46 -11
- package/android/app/src/main/java/com/microsoft/codepush/react/CodePushBinaryPatch.java +293 -0
- package/android/app/src/main/java/com/microsoft/codepush/react/CodePushConstants.java +25 -0
- package/android/app/src/main/java/com/microsoft/codepush/react/CodePushNativeModule.java +79 -88
- package/android/app/src/main/java/com/microsoft/codepush/react/CodePushUpdateManager.java +159 -4
- package/android/app/src/main/java/com/microsoft/codepush/react/CodePushUpdateUtils.java +13 -0
- package/android/app/src/main/java/com/microsoft/codepush/react/HDiffPatchNative.java +52 -0
- package/android/app/src/test/java/com/microsoft/codepush/react/CodePushBinaryPatchTest.java +393 -0
- package/android/app/src/test/java/com/microsoft/codepush/react/CodePushUpdateManagerBinaryPatchTest.java +207 -0
- package/android/app/src/test/java/com/microsoft/codepush/react/CodePushUpdateManagerDownloadTest.java +449 -0
- package/android/codepush-export.gradle +115 -0
- package/cli/commands/bundleCommand/bundleCodePush.ts +39 -7
- package/cli/commands/bundleCommand/index.ts +9 -1
- package/cli/commands/releaseCommand/addToReleaseHistory.ts +7 -0
- package/cli/commands/releaseCommand/index.test.ts +118 -0
- package/cli/commands/releaseCommand/index.ts +20 -2
- package/cli/commands/releaseCommand/release.test.ts +582 -0
- package/cli/commands/releaseCommand/release.ts +232 -15
- package/cli/dist/commands/bundleCommand/bundleCodePush.js +26 -7
- package/cli/dist/commands/bundleCommand/index.js +5 -1
- package/cli/dist/commands/releaseCommand/addToReleaseHistory.js +6 -1
- package/cli/dist/commands/releaseCommand/index.js +8 -1
- package/cli/dist/commands/releaseCommand/index.test.js +96 -0
- package/cli/dist/commands/releaseCommand/release.js +165 -17
- package/cli/dist/commands/releaseCommand/release.test.js +367 -0
- package/cli/dist/functions/makeBinaryPatchBundle.js +248 -0
- package/cli/dist/functions/makeBinaryPatchBundle.test.js +321 -0
- package/cli/dist/functions/makeCodePushBundle.js +4 -2
- package/cli/dist/functions/runHermesEmitBinaryCommand.js +98 -27
- package/cli/dist/functions/runHermesEmitBinaryCommand.test.js +100 -0
- package/cli/dist/jest.globalSetup.js +38 -0
- package/cli/dist/utils/binaryPatch.js +86 -0
- package/cli/dist/utils/binaryPatch.test.js +190 -0
- package/cli/functions/makeBinaryPatchBundle.test.ts +410 -0
- package/cli/functions/makeBinaryPatchBundle.ts +367 -0
- package/cli/functions/makeCodePushBundle.ts +4 -2
- package/cli/functions/runHermesEmitBinaryCommand.test.ts +124 -0
- package/cli/functions/runHermesEmitBinaryCommand.ts +112 -26
- package/cli/jest.globalSetup.ts +43 -0
- package/cli/utils/binaryPatch.test.ts +234 -0
- package/cli/utils/binaryPatch.ts +103 -0
- package/cpp/binarypatch/binarypatch_zstd_decompressor.c +187 -0
- package/cpp/binarypatch/binarypatch_zstd_decompressor.h +47 -0
- package/cpp/binarypatch/vendor/HDiffPatch/libHDiffPatch/HPatch/checksum_plugin.h +52 -0
- package/cpp/binarypatch/vendor/HDiffPatch/libHDiffPatch/HPatch/hpatch_mt/hpatch_mt.h +147 -0
- package/cpp/binarypatch/vendor/HDiffPatch/libHDiffPatch/HPatch/patch.c +3315 -0
- package/cpp/binarypatch/vendor/HDiffPatch/libHDiffPatch/HPatch/patch.h +268 -0
- package/cpp/binarypatch/vendor/HDiffPatch/libHDiffPatch/HPatch/patch_private.h +297 -0
- package/cpp/binarypatch/vendor/HDiffPatch/libHDiffPatch/HPatch/patch_types.h +434 -0
- package/cpp/binarypatch/vendor/zstd/common/allocations.h +55 -0
- package/cpp/binarypatch/vendor/zstd/common/bits.h +205 -0
- package/cpp/binarypatch/vendor/zstd/common/bitstream.h +454 -0
- package/cpp/binarypatch/vendor/zstd/common/compiler.h +464 -0
- package/cpp/binarypatch/vendor/zstd/common/cpu.h +249 -0
- package/cpp/binarypatch/vendor/zstd/common/debug.c +30 -0
- package/cpp/binarypatch/vendor/zstd/common/debug.h +107 -0
- package/cpp/binarypatch/vendor/zstd/common/entropy_common.c +340 -0
- package/cpp/binarypatch/vendor/zstd/common/error_private.c +64 -0
- package/cpp/binarypatch/vendor/zstd/common/error_private.h +158 -0
- package/cpp/binarypatch/vendor/zstd/common/fse.h +625 -0
- package/cpp/binarypatch/vendor/zstd/common/fse_decompress.c +315 -0
- package/cpp/binarypatch/vendor/zstd/common/huf.h +277 -0
- package/cpp/binarypatch/vendor/zstd/common/mem.h +422 -0
- package/cpp/binarypatch/vendor/zstd/common/pool.c +371 -0
- package/cpp/binarypatch/vendor/zstd/common/pool.h +81 -0
- package/cpp/binarypatch/vendor/zstd/common/portability_macros.h +171 -0
- package/cpp/binarypatch/vendor/zstd/common/threading.c +182 -0
- package/cpp/binarypatch/vendor/zstd/common/threading.h +142 -0
- package/cpp/binarypatch/vendor/zstd/common/xxhash.c +18 -0
- package/cpp/binarypatch/vendor/zstd/common/xxhash.h +7094 -0
- package/cpp/binarypatch/vendor/zstd/common/zstd_common.c +48 -0
- package/cpp/binarypatch/vendor/zstd/common/zstd_deps.h +123 -0
- package/cpp/binarypatch/vendor/zstd/common/zstd_internal.h +324 -0
- package/cpp/binarypatch/vendor/zstd/common/zstd_trace.h +156 -0
- package/cpp/binarypatch/vendor/zstd/decompress/huf_decompress.c +1944 -0
- package/cpp/binarypatch/vendor/zstd/decompress/zstd_ddict.c +244 -0
- package/cpp/binarypatch/vendor/zstd/decompress/zstd_ddict.h +44 -0
- package/cpp/binarypatch/vendor/zstd/decompress/zstd_decompress.c +2410 -0
- package/cpp/binarypatch/vendor/zstd/decompress/zstd_decompress_block.c +2209 -0
- package/cpp/binarypatch/vendor/zstd/decompress/zstd_decompress_block.h +73 -0
- package/cpp/binarypatch/vendor/zstd/decompress/zstd_decompress_internal.h +240 -0
- package/cpp/binarypatch/vendor/zstd/zstd.h +3206 -0
- package/cpp/binarypatch/vendor/zstd/zstd_errors.h +107 -0
- package/ios/CodePush/CodePush.h +58 -9
- package/ios/CodePush/{CodePush.m → CodePush.mm} +77 -63
- package/ios/CodePush/CodePushBinaryPatch.h +80 -0
- package/ios/CodePush/CodePushBinaryPatch.m +473 -0
- package/ios/CodePush/CodePushPackage.m +183 -2
- package/ios/CodePush/CodePushUpdateUtils.m +49 -1
- package/ios/CodePush.xcodeproj/project.pbxproj +6 -6
- package/package.json +28 -4
- package/react-native.config.js +3 -1
- package/scripts/binary-patch/build-hdiffpatch.sh +86 -0
- package/scripts/export-embedded-bundle.sh +76 -0
- package/src/CodePush.js +190 -96
- package/src/CodePush.test.js +382 -0
- package/src/native/NativeCodePush.ts +162 -0
- package/src/package-mixins.js +24 -11
- package/src/specs/NativeCodePush.ts +43 -0
- package/typings/react-native-code-push.d.ts +77 -0
- package/android/app/src/main/java/com/microsoft/codepush/react/CodePushDialog.java +0 -102
- package/src/AlertAdapter.js +0 -24
|
@@ -2,7 +2,6 @@ package com.microsoft.codepush.react;
|
|
|
2
2
|
|
|
3
3
|
import android.app.Activity;
|
|
4
4
|
import android.content.SharedPreferences;
|
|
5
|
-
import android.os.AsyncTask;
|
|
6
5
|
import android.os.Handler;
|
|
7
6
|
import android.os.Looper;
|
|
8
7
|
import android.view.View;
|
|
@@ -20,11 +19,11 @@ import com.facebook.react.bridge.JSBundleLoader;
|
|
|
20
19
|
import com.facebook.react.bridge.LifecycleEventListener;
|
|
21
20
|
import com.facebook.react.bridge.Promise;
|
|
22
21
|
import com.facebook.react.bridge.ReactApplicationContext;
|
|
23
|
-
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
|
24
22
|
import com.facebook.react.bridge.ReactMethod;
|
|
25
23
|
import com.facebook.react.bridge.ReadableMap;
|
|
26
24
|
import com.facebook.react.bridge.WritableMap;
|
|
27
25
|
import com.facebook.react.common.annotations.UnstableReactNativeAPI;
|
|
26
|
+
import com.facebook.react.module.annotations.ReactModule;
|
|
28
27
|
import com.facebook.react.modules.core.DeviceEventManagerModule;
|
|
29
28
|
import com.facebook.react.modules.core.ReactChoreographer;
|
|
30
29
|
import com.facebook.react.runtime.ReactHostDelegate;
|
|
@@ -37,15 +36,19 @@ import java.io.IOException;
|
|
|
37
36
|
import java.lang.reflect.Field;
|
|
38
37
|
import java.util.ArrayList;
|
|
39
38
|
import java.util.Date;
|
|
40
|
-
import java.util.HashMap;
|
|
41
|
-
import java.util.Map;
|
|
42
39
|
import java.util.UUID;
|
|
40
|
+
import java.util.concurrent.ExecutorService;
|
|
41
|
+
import java.util.concurrent.Executors;
|
|
42
|
+
|
|
43
|
+
@ReactModule(name = CodePushNativeModule.NAME)
|
|
44
|
+
public class CodePushNativeModule extends NativeCodePushSpec {
|
|
45
|
+
public static final String NAME = "CodePush";
|
|
43
46
|
|
|
44
|
-
public class CodePushNativeModule extends ReactContextBaseJavaModule {
|
|
45
47
|
private String mBinaryContentsHash = null;
|
|
46
48
|
private String mClientUniqueId = null;
|
|
47
49
|
private LifecycleEventListener mLifecycleEventListener = null;
|
|
48
50
|
private int mMinimumBackgroundDuration = 0;
|
|
51
|
+
private final ExecutorService mBackgroundExecutor = Executors.newSingleThreadExecutor();
|
|
49
52
|
|
|
50
53
|
private CodePush mCodePush;
|
|
51
54
|
private SettingsManager mSettingsManager;
|
|
@@ -76,24 +79,15 @@ public class CodePushNativeModule extends ReactContextBaseJavaModule {
|
|
|
76
79
|
}
|
|
77
80
|
|
|
78
81
|
@Override
|
|
79
|
-
public
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
constants.put("codePushInstallModeImmediate", CodePushInstallMode.IMMEDIATE.getValue());
|
|
83
|
-
constants.put("codePushInstallModeOnNextRestart", CodePushInstallMode.ON_NEXT_RESTART.getValue());
|
|
84
|
-
constants.put("codePushInstallModeOnNextResume", CodePushInstallMode.ON_NEXT_RESUME.getValue());
|
|
85
|
-
constants.put("codePushInstallModeOnNextSuspend", CodePushInstallMode.ON_NEXT_SUSPEND.getValue());
|
|
86
|
-
|
|
87
|
-
constants.put("codePushUpdateStateRunning", CodePushUpdateState.RUNNING.getValue());
|
|
88
|
-
constants.put("codePushUpdateStatePending", CodePushUpdateState.PENDING.getValue());
|
|
89
|
-
constants.put("codePushUpdateStateLatest", CodePushUpdateState.LATEST.getValue());
|
|
90
|
-
|
|
91
|
-
return constants;
|
|
82
|
+
public String getName() {
|
|
83
|
+
return NAME;
|
|
92
84
|
}
|
|
93
85
|
|
|
94
86
|
@Override
|
|
95
|
-
public
|
|
96
|
-
|
|
87
|
+
public void invalidate() {
|
|
88
|
+
clearLifecycleEventListener();
|
|
89
|
+
mBackgroundExecutor.shutdownNow();
|
|
90
|
+
super.invalidate();
|
|
97
91
|
}
|
|
98
92
|
|
|
99
93
|
private void loadBundleLegacy() {
|
|
@@ -265,6 +259,21 @@ public class CodePushNativeModule extends ReactContextBaseJavaModule {
|
|
|
265
259
|
return null;
|
|
266
260
|
}
|
|
267
261
|
|
|
262
|
+
private void executeInBackground(Runnable runnable) {
|
|
263
|
+
mBackgroundExecutor.execute(runnable);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
private void emitDownloadProgressEvent(DownloadProgress downloadProgress) {
|
|
267
|
+
if (mEventEmitterCallback != null) {
|
|
268
|
+
emitOnDownloadProgress(downloadProgress.createWritableMap());
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
getReactApplicationContext()
|
|
273
|
+
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
|
|
274
|
+
.emit(CodePushConstants.DOWNLOAD_PROGRESS_EVENT_NAME, downloadProgress.createWritableMap());
|
|
275
|
+
}
|
|
276
|
+
|
|
268
277
|
private void restartAppInternal(boolean onlyIfUpdateIsPending) {
|
|
269
278
|
if (this._restartInProgress) {
|
|
270
279
|
CodePushUtils.log("Restart request queued until the current restart is completed");
|
|
@@ -292,7 +301,7 @@ public class CodePushNativeModule extends ReactContextBaseJavaModule {
|
|
|
292
301
|
}
|
|
293
302
|
|
|
294
303
|
@ReactMethod
|
|
295
|
-
public void allow(
|
|
304
|
+
public void allow() {
|
|
296
305
|
CodePushUtils.log("Re-allowing restarts");
|
|
297
306
|
this._allowed = true;
|
|
298
307
|
|
|
@@ -302,54 +311,39 @@ public class CodePushNativeModule extends ReactContextBaseJavaModule {
|
|
|
302
311
|
this._restartQueue.remove(0);
|
|
303
312
|
this.restartAppInternal(buf);
|
|
304
313
|
}
|
|
305
|
-
|
|
306
|
-
promise.resolve(null);
|
|
307
|
-
return;
|
|
308
314
|
}
|
|
309
315
|
|
|
310
316
|
@ReactMethod
|
|
311
|
-
public void clearPendingRestart(
|
|
317
|
+
public void clearPendingRestart() {
|
|
312
318
|
this._restartQueue.clear();
|
|
313
|
-
promise.resolve(null);
|
|
314
|
-
return;
|
|
315
319
|
}
|
|
316
320
|
|
|
317
321
|
@ReactMethod
|
|
318
|
-
public void disallow(
|
|
322
|
+
public void disallow() {
|
|
319
323
|
CodePushUtils.log("Disallowing restarts");
|
|
320
324
|
this._allowed = false;
|
|
321
|
-
promise.resolve(null);
|
|
322
|
-
return;
|
|
323
325
|
}
|
|
324
326
|
|
|
325
327
|
@ReactMethod
|
|
326
|
-
public void restartApp(boolean onlyIfUpdateIsPending
|
|
327
|
-
|
|
328
|
-
restartAppInternal(onlyIfUpdateIsPending);
|
|
329
|
-
promise.resolve(null);
|
|
330
|
-
} catch(CodePushUnknownException e) {
|
|
331
|
-
CodePushUtils.log(e);
|
|
332
|
-
promise.reject(e);
|
|
333
|
-
}
|
|
328
|
+
public void restartApp(boolean onlyIfUpdateIsPending) {
|
|
329
|
+
restartAppInternal(onlyIfUpdateIsPending);
|
|
334
330
|
}
|
|
335
331
|
|
|
336
332
|
@ReactMethod
|
|
337
333
|
public void downloadUpdate(final ReadableMap updatePackage, final boolean notifyProgress, final Promise promise) {
|
|
338
|
-
|
|
334
|
+
executeInBackground(new Runnable() {
|
|
339
335
|
@Override
|
|
340
|
-
|
|
336
|
+
public void run() {
|
|
341
337
|
try {
|
|
342
338
|
JSONObject mutableUpdatePackage = CodePushUtils.convertReadableToJsonObject(updatePackage);
|
|
343
|
-
mUpdateManager.downloadPackage(mutableUpdatePackage, mCodePush.getAssetsBundleFileName(), new DownloadProgressCallback() {
|
|
339
|
+
JSONObject binaryPatchResult = mUpdateManager.downloadPackage(mutableUpdatePackage, mCodePush.getAssetsBundleFileName(), new DownloadProgressCallback() {
|
|
344
340
|
private volatile boolean hasScheduledNextFrame = false;
|
|
345
341
|
private volatile DownloadProgress latestDownloadProgress = null;
|
|
346
342
|
private final DownloadProgressEventDispatcher progressEventDispatcher = new DownloadProgressEventDispatcher(
|
|
347
343
|
new DownloadProgressEventDispatcher.EventEmitter() {
|
|
348
344
|
@Override
|
|
349
345
|
public void emit(DownloadProgress downloadProgress) {
|
|
350
|
-
|
|
351
|
-
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
|
|
352
|
-
.emit(CodePushConstants.DOWNLOAD_PROGRESS_EVENT_NAME, downloadProgress.createWritableMap());
|
|
346
|
+
emitDownloadProgressEvent(downloadProgress);
|
|
353
347
|
}
|
|
354
348
|
});
|
|
355
349
|
|
|
@@ -390,7 +384,17 @@ public class CodePushNativeModule extends ReactContextBaseJavaModule {
|
|
|
390
384
|
});
|
|
391
385
|
|
|
392
386
|
JSONObject newPackage = mUpdateManager.getPackage(CodePushUtils.tryGetString(updatePackage, CodePushConstants.PACKAGE_HASH_KEY));
|
|
393
|
-
|
|
387
|
+
WritableMap downloadedPackage = CodePushUtils.convertJsonObjectToWritable(newPackage);
|
|
388
|
+
if (binaryPatchResult != null) {
|
|
389
|
+
// The result describes this download rather than the update, so it is
|
|
390
|
+
// put on what this call resolves with and nowhere else: the metadata
|
|
391
|
+
// the update manager has already written knows nothing about it, and
|
|
392
|
+
// whoever asked for the download decides what the result is worth.
|
|
393
|
+
downloadedPackage.putMap(CodePushConstants.BINARY_PATCH_RESULT_KEY,
|
|
394
|
+
CodePushUtils.convertJsonObjectToWritable(binaryPatchResult));
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
promise.resolve(downloadedPackage);
|
|
394
398
|
} catch (CodePushInvalidUpdateException e) {
|
|
395
399
|
CodePushUtils.log(e);
|
|
396
400
|
mSettingsManager.saveFailedUpdate(CodePushUtils.convertReadableToJsonObject(updatePackage));
|
|
@@ -399,12 +403,8 @@ public class CodePushNativeModule extends ReactContextBaseJavaModule {
|
|
|
399
403
|
CodePushUtils.log(e);
|
|
400
404
|
promise.reject(e);
|
|
401
405
|
}
|
|
402
|
-
|
|
403
|
-
return null;
|
|
404
406
|
}
|
|
405
|
-
};
|
|
406
|
-
|
|
407
|
-
asyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
|
407
|
+
});
|
|
408
408
|
}
|
|
409
409
|
|
|
410
410
|
@ReactMethod
|
|
@@ -429,16 +429,17 @@ public class CodePushNativeModule extends ReactContextBaseJavaModule {
|
|
|
429
429
|
}
|
|
430
430
|
|
|
431
431
|
@ReactMethod
|
|
432
|
-
public void getUpdateMetadata(final
|
|
433
|
-
|
|
432
|
+
public void getUpdateMetadata(final double updateStateValue, final Promise promise) {
|
|
433
|
+
executeInBackground(new Runnable() {
|
|
434
434
|
@Override
|
|
435
|
-
|
|
435
|
+
public void run() {
|
|
436
436
|
try {
|
|
437
|
+
int updateState = (int) updateStateValue;
|
|
437
438
|
JSONObject currentPackage = mUpdateManager.getCurrentPackage();
|
|
438
439
|
|
|
439
440
|
if (currentPackage == null) {
|
|
440
441
|
promise.resolve(null);
|
|
441
|
-
return
|
|
442
|
+
return;
|
|
442
443
|
}
|
|
443
444
|
|
|
444
445
|
Boolean currentUpdateIsPending = false;
|
|
@@ -459,7 +460,7 @@ public class CodePushNativeModule extends ReactContextBaseJavaModule {
|
|
|
459
460
|
|
|
460
461
|
if (previousPackage == null) {
|
|
461
462
|
promise.resolve(null);
|
|
462
|
-
return
|
|
463
|
+
return;
|
|
463
464
|
}
|
|
464
465
|
|
|
465
466
|
promise.resolve(CodePushUtils.convertJsonObjectToWritable(previousPackage));
|
|
@@ -488,19 +489,15 @@ public class CodePushNativeModule extends ReactContextBaseJavaModule {
|
|
|
488
489
|
CodePushUtils.log(e);
|
|
489
490
|
promise.reject(e);
|
|
490
491
|
}
|
|
491
|
-
|
|
492
|
-
return null;
|
|
493
492
|
}
|
|
494
|
-
};
|
|
495
|
-
|
|
496
|
-
asyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
|
493
|
+
});
|
|
497
494
|
}
|
|
498
495
|
|
|
499
496
|
@ReactMethod
|
|
500
497
|
public void getNewStatusReport(final Promise promise) {
|
|
501
|
-
|
|
498
|
+
executeInBackground(new Runnable() {
|
|
502
499
|
@Override
|
|
503
|
-
|
|
500
|
+
public void run() {
|
|
504
501
|
try {
|
|
505
502
|
if (mCodePush.needToReportRollback()) {
|
|
506
503
|
mCodePush.setNeedToReportRollback(false);
|
|
@@ -512,7 +509,7 @@ public class CodePushNativeModule extends ReactContextBaseJavaModule {
|
|
|
512
509
|
WritableMap failedStatusReport = mTelemetryManager.getRollbackReport(lastFailedPackage);
|
|
513
510
|
if (failedStatusReport != null) {
|
|
514
511
|
promise.resolve(failedStatusReport);
|
|
515
|
-
return
|
|
512
|
+
return;
|
|
516
513
|
}
|
|
517
514
|
} catch (JSONException e) {
|
|
518
515
|
throw new CodePushUnknownException("Unable to read failed updates information stored in SharedPreferences.", e);
|
|
@@ -524,41 +521,40 @@ public class CodePushNativeModule extends ReactContextBaseJavaModule {
|
|
|
524
521
|
WritableMap newPackageStatusReport = mTelemetryManager.getUpdateReport(CodePushUtils.convertJsonObjectToWritable(currentPackage));
|
|
525
522
|
if (newPackageStatusReport != null) {
|
|
526
523
|
promise.resolve(newPackageStatusReport);
|
|
527
|
-
return
|
|
524
|
+
return;
|
|
528
525
|
}
|
|
529
526
|
}
|
|
530
527
|
} else if (mCodePush.isRunningBinaryVersion()) {
|
|
531
528
|
WritableMap newAppVersionStatusReport = mTelemetryManager.getBinaryUpdateReport(mCodePush.getAppVersion());
|
|
532
529
|
if (newAppVersionStatusReport != null) {
|
|
533
530
|
promise.resolve(newAppVersionStatusReport);
|
|
534
|
-
return
|
|
531
|
+
return;
|
|
535
532
|
}
|
|
536
533
|
} else {
|
|
537
534
|
WritableMap retryStatusReport = mTelemetryManager.getRetryStatusReport();
|
|
538
535
|
if (retryStatusReport != null) {
|
|
539
536
|
promise.resolve(retryStatusReport);
|
|
540
|
-
return
|
|
537
|
+
return;
|
|
541
538
|
}
|
|
542
539
|
}
|
|
543
540
|
|
|
544
|
-
promise.resolve(
|
|
541
|
+
promise.resolve(null);
|
|
545
542
|
} catch(CodePushUnknownException e) {
|
|
546
543
|
CodePushUtils.log(e);
|
|
547
544
|
promise.reject(e);
|
|
548
545
|
}
|
|
549
|
-
return null;
|
|
550
546
|
}
|
|
551
|
-
};
|
|
552
|
-
|
|
553
|
-
asyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
|
547
|
+
});
|
|
554
548
|
}
|
|
555
549
|
|
|
556
550
|
@ReactMethod
|
|
557
|
-
public void installUpdate(final ReadableMap updatePackage, final
|
|
558
|
-
|
|
551
|
+
public void installUpdate(final ReadableMap updatePackage, final double installModeValue, final double minimumBackgroundDurationValue, final Promise promise) {
|
|
552
|
+
executeInBackground(new Runnable() {
|
|
559
553
|
@Override
|
|
560
|
-
|
|
554
|
+
public void run() {
|
|
561
555
|
try {
|
|
556
|
+
int installMode = (int) installModeValue;
|
|
557
|
+
int minimumBackgroundDuration = (int) minimumBackgroundDurationValue;
|
|
562
558
|
mUpdateManager.installPackage(CodePushUtils.convertReadableToJsonObject(updatePackage), mSettingsManager.isPendingUpdate(null));
|
|
563
559
|
|
|
564
560
|
String pendingHash = CodePushUtils.tryGetString(updatePackage, CodePushConstants.PACKAGE_HASH_KEY);
|
|
@@ -568,13 +564,12 @@ public class CodePushNativeModule extends ReactContextBaseJavaModule {
|
|
|
568
564
|
mSettingsManager.savePendingUpdate(pendingHash, /* isLoading */false);
|
|
569
565
|
}
|
|
570
566
|
|
|
571
|
-
if (installMode == CodePushInstallMode.ON_NEXT_RESUME.getValue()
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
567
|
+
if (installMode == CodePushInstallMode.ON_NEXT_RESUME.getValue()
|
|
568
|
+
// We also add the resume listener if the installMode is IMMEDIATE, because
|
|
569
|
+
// if the current activity is backgrounded, we want to reload the bundle when
|
|
570
|
+
// it comes back into the foreground.
|
|
571
|
+
|| installMode == CodePushInstallMode.IMMEDIATE.getValue()
|
|
572
|
+
|| installMode == CodePushInstallMode.ON_NEXT_SUSPEND.getValue()) {
|
|
578
573
|
// Store the minimum duration on the native module as an instance
|
|
579
574
|
// variable instead of relying on a closure below, so that any
|
|
580
575
|
// subsequent resume-based installs could override it.
|
|
@@ -628,17 +623,13 @@ public class CodePushNativeModule extends ReactContextBaseJavaModule {
|
|
|
628
623
|
}
|
|
629
624
|
}
|
|
630
625
|
|
|
631
|
-
promise.resolve(
|
|
626
|
+
promise.resolve(null);
|
|
632
627
|
} catch(CodePushUnknownException e) {
|
|
633
628
|
CodePushUtils.log(e);
|
|
634
629
|
promise.reject(e);
|
|
635
630
|
}
|
|
636
|
-
|
|
637
|
-
return null;
|
|
638
631
|
}
|
|
639
|
-
};
|
|
640
|
-
|
|
641
|
-
asyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
|
632
|
+
});
|
|
642
633
|
}
|
|
643
634
|
|
|
644
635
|
@ReactMethod
|
|
@@ -695,7 +686,7 @@ public class CodePushNativeModule extends ReactContextBaseJavaModule {
|
|
|
695
686
|
public void notifyApplicationReady(Promise promise) {
|
|
696
687
|
try {
|
|
697
688
|
mSettingsManager.removePendingUpdate();
|
|
698
|
-
promise.resolve(
|
|
689
|
+
promise.resolve(null);
|
|
699
690
|
} catch(CodePushUnknownException e) {
|
|
700
691
|
CodePushUtils.log(e);
|
|
701
692
|
promise.reject(e);
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
package com.microsoft.codepush.react;
|
|
2
2
|
|
|
3
|
+
import android.content.Context;
|
|
3
4
|
import android.os.Build;
|
|
4
5
|
|
|
5
6
|
import org.json.JSONObject;
|
|
6
7
|
|
|
7
8
|
import java.io.BufferedInputStream;
|
|
8
9
|
import java.io.BufferedOutputStream;
|
|
10
|
+
import java.io.ByteArrayOutputStream;
|
|
9
11
|
import java.io.File;
|
|
10
12
|
import java.io.FileOutputStream;
|
|
11
13
|
import java.io.IOException;
|
|
14
|
+
import java.io.InputStream;
|
|
12
15
|
import java.net.HttpURLConnection;
|
|
13
16
|
import java.net.MalformedURLException;
|
|
14
17
|
import java.net.URL;
|
|
@@ -19,9 +22,15 @@ import javax.net.ssl.HttpsURLConnection;
|
|
|
19
22
|
public class CodePushUpdateManager {
|
|
20
23
|
|
|
21
24
|
private String mDocumentsDirectory;
|
|
25
|
+
private final CodePushBinaryPatch mBinaryPatch;
|
|
22
26
|
|
|
23
|
-
public CodePushUpdateManager(String documentsDirectory) {
|
|
27
|
+
public CodePushUpdateManager(String documentsDirectory, Context context) {
|
|
28
|
+
this(documentsDirectory, new CodePushBinaryPatch(new AssetsBaseBundleProvider(context), new HDiffPatchNative()));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
CodePushUpdateManager(String documentsDirectory, CodePushBinaryPatch binaryPatch) {
|
|
24
32
|
mDocumentsDirectory = documentsDirectory;
|
|
33
|
+
mBinaryPatch = binaryPatch;
|
|
25
34
|
}
|
|
26
35
|
|
|
27
36
|
private String getDownloadFilePath() {
|
|
@@ -32,6 +41,10 @@ public class CodePushUpdateManager {
|
|
|
32
41
|
return CodePushUtils.appendPathComponent(getCodePushPath(), CodePushConstants.UNZIPPED_FOLDER_NAME);
|
|
33
42
|
}
|
|
34
43
|
|
|
44
|
+
private String getBinaryPatchFolderPath() {
|
|
45
|
+
return CodePushUtils.appendPathComponent(getCodePushPath(), CodePushConstants.BINARY_PATCH_FOLDER_NAME);
|
|
46
|
+
}
|
|
47
|
+
|
|
35
48
|
private String getDocumentsDirectory() {
|
|
36
49
|
return mDocumentsDirectory;
|
|
37
50
|
}
|
|
@@ -143,8 +156,92 @@ public class CodePushUpdateManager {
|
|
|
143
156
|
}
|
|
144
157
|
}
|
|
145
158
|
|
|
146
|
-
|
|
147
|
-
|
|
159
|
+
/**
|
|
160
|
+
* @return what the attempt at installing the update from its binary patch archive ended
|
|
161
|
+
* in, or null when the update had no patch archive to attempt. The result belongs
|
|
162
|
+
* to this download alone: the metadata written for the update never carries it.
|
|
163
|
+
*/
|
|
164
|
+
public JSONObject downloadPackage(JSONObject updatePackage, String expectedBundleFileName,
|
|
165
|
+
DownloadProgressCallback progressCallback) throws IOException {
|
|
166
|
+
// A release that was published with a binary patch offers two archives of the same
|
|
167
|
+
// update. The patch is worth trying because it is a fraction of the size, and the
|
|
168
|
+
// full archive is always there when it does not work out.
|
|
169
|
+
String binaryPatchDownloadUrl = updatePackage.optString(CodePushConstants.BINARY_PATCH_DOWNLOAD_URL_KEY, null);
|
|
170
|
+
BinaryPatchAttempt patchAttempt = null;
|
|
171
|
+
if (binaryPatchDownloadUrl != null) {
|
|
172
|
+
patchAttempt = new BinaryPatchAttempt();
|
|
173
|
+
if (tryDownloadBinaryPatchPackage(updatePackage, expectedBundleFileName, progressCallback,
|
|
174
|
+
binaryPatchDownloadUrl, patchAttempt)) {
|
|
175
|
+
return patchAttempt.result();
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
downloadAndInstallPackage(updatePackage, expectedBundleFileName, progressCallback,
|
|
180
|
+
updatePackage.optString(CodePushConstants.DOWNLOAD_URL_KEY, null), false, null);
|
|
181
|
+
|
|
182
|
+
return patchAttempt == null ? null : patchAttempt.result();
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Installs the update from its binary patch archive.
|
|
187
|
+
*
|
|
188
|
+
* Every way this can fail ends the same way, with the update being downloaded in full
|
|
189
|
+
* instead, so none of them is reported to the caller as an error. The fallback happens
|
|
190
|
+
* exactly once without anything having to count it: the full archive is downloaded by
|
|
191
|
+
* a call that is not allowed to take the patch path, so it has no failure of its own to
|
|
192
|
+
* fall back from.
|
|
193
|
+
*
|
|
194
|
+
* @param patchAttempt records what the attempt ended in, for the caller to hand to
|
|
195
|
+
* whoever asked for the download
|
|
196
|
+
* @return true when the update was installed, false when the caller has to download the
|
|
197
|
+
* full archive instead
|
|
198
|
+
*/
|
|
199
|
+
private boolean tryDownloadBinaryPatchPackage(JSONObject updatePackage, String expectedBundleFileName,
|
|
200
|
+
DownloadProgressCallback progressCallback,
|
|
201
|
+
String binaryPatchDownloadUrl, BinaryPatchAttempt patchAttempt) {
|
|
202
|
+
try {
|
|
203
|
+
BinaryPatchResult patchResult = downloadAndInstallPackage(updatePackage, expectedBundleFileName,
|
|
204
|
+
progressCallback, binaryPatchDownloadUrl, true, patchAttempt);
|
|
205
|
+
if (patchResult.succeeded()) {
|
|
206
|
+
return true;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
patchAttempt.recordFallback(patchResult.getFailureReason());
|
|
210
|
+
CodePushUtils.log("Binary patch update failed (" + patchResult.getFailureReason()
|
|
211
|
+
+ "). Downloading the full update instead.");
|
|
212
|
+
} catch (Exception | OutOfMemoryError e) {
|
|
213
|
+
// Applying a patch is the one path that holds a whole bundle in memory, so
|
|
214
|
+
// running out of it is a failure this has to absorb like any other: by the time
|
|
215
|
+
// it lands here the arrays are unreachable, and the full archive is downloaded
|
|
216
|
+
// to disk in chunks rather than held.
|
|
217
|
+
patchAttempt.recordFallbackAfterError();
|
|
218
|
+
CodePushUtils.log(e);
|
|
219
|
+
CodePushUtils.log("The binary patch update could not be completed. Downloading the full update instead.");
|
|
220
|
+
} finally {
|
|
221
|
+
FileUtils.deleteDirectoryAtPath(getBinaryPatchFolderPath());
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return false;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Downloads an update from one of its archives and installs it.
|
|
229
|
+
*
|
|
230
|
+
* @param isBinaryPatchUpdate whether the archive holds a binary patch of the JS bundle,
|
|
231
|
+
* which has to be applied before the contents are the update.
|
|
232
|
+
* Only an archive downloaded from the binary patch URL is
|
|
233
|
+
* treated that way, so an update being downloaded in full can
|
|
234
|
+
* never end up on the patch path.
|
|
235
|
+
* @param patchAttempt the record the patch is timed into, or null for a full download,
|
|
236
|
+
* which has no patch to time
|
|
237
|
+
* @return the outcome of the patch: a failed result means the update was not installed
|
|
238
|
+
* and the caller has to fall back to the full archive. Downloading the full
|
|
239
|
+
* archive always succeeds or throws.
|
|
240
|
+
*/
|
|
241
|
+
BinaryPatchResult downloadAndInstallPackage(JSONObject updatePackage, String expectedBundleFileName,
|
|
242
|
+
DownloadProgressCallback progressCallback,
|
|
243
|
+
String downloadUrlString, boolean isBinaryPatchUpdate,
|
|
244
|
+
BinaryPatchAttempt patchAttempt) throws IOException {
|
|
148
245
|
String newUpdateHash = updatePackage.optString(CodePushConstants.PACKAGE_HASH_KEY, null);
|
|
149
246
|
String newUpdateFolderPath = getPackageFolderPath(newUpdateHash);
|
|
150
247
|
String newUpdateMetadataPath = CodePushUtils.appendPathComponent(newUpdateFolderPath, CodePushConstants.PACKAGE_FILE_NAME);
|
|
@@ -154,7 +251,6 @@ public class CodePushUpdateManager {
|
|
|
154
251
|
FileUtils.deleteDirectoryAtPath(newUpdateFolderPath);
|
|
155
252
|
}
|
|
156
253
|
|
|
157
|
-
String downloadUrlString = updatePackage.optString(CodePushConstants.DOWNLOAD_URL_KEY, null);
|
|
158
254
|
HttpURLConnection connection = null;
|
|
159
255
|
BufferedInputStream bin = null;
|
|
160
256
|
FileOutputStream fos = null;
|
|
@@ -232,6 +328,21 @@ public class CodePushUpdateManager {
|
|
|
232
328
|
FileUtils.unzipFile(downloadFile, unzippedFolderPath);
|
|
233
329
|
FileUtils.deleteFileOrFolderSilently(downloadFile);
|
|
234
330
|
|
|
331
|
+
// Rebuild the JS bundle the archive only carries a patch of, which leaves the
|
|
332
|
+
// contents identical to the ones the full archive would have delivered.
|
|
333
|
+
if (isBinaryPatchUpdate) {
|
|
334
|
+
long patchStartTime = System.currentTimeMillis();
|
|
335
|
+
BinaryPatchResult patchResult = mBinaryPatch.restoreBundle(unzippedFolderPath,
|
|
336
|
+
getBinaryPatchFolderPath(), expectedBundleFileName);
|
|
337
|
+
if (!patchResult.succeeded()) {
|
|
338
|
+
return patchResult;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
long applyDurationMs = System.currentTimeMillis() - patchStartTime;
|
|
342
|
+
patchAttempt.recordBundleRestored(applyDurationMs);
|
|
343
|
+
CodePushUtils.log("Restored the update from its binary patch in " + applyDurationMs + " ms.");
|
|
344
|
+
}
|
|
345
|
+
|
|
235
346
|
// Merge contents with current update based on the manifest
|
|
236
347
|
String diffManifestFilePath = CodePushUtils.appendPathComponent(unzippedFolderPath,
|
|
237
348
|
CodePushConstants.DIFF_MANIFEST_FILE_NAME);
|
|
@@ -268,12 +379,22 @@ public class CodePushUpdateManager {
|
|
|
268
379
|
CodePushUtils.setJSONValueForKey(updatePackage, CodePushConstants.RELATIVE_BUNDLE_PATH_KEY, relativeBundlePath);
|
|
269
380
|
}
|
|
270
381
|
} else {
|
|
382
|
+
if (isBinaryPatchUpdate) {
|
|
383
|
+
// Whatever the patch URL served, it is not a patch archive - an error page
|
|
384
|
+
// answered with a 200 looks like this too. Moving it into place would
|
|
385
|
+
// install bytes no hash has ever been checked against, so the full archive
|
|
386
|
+
// is downloaded instead.
|
|
387
|
+
return BinaryPatchResult.failure(BinaryPatchResult.REASON_INVALID_MANIFEST);
|
|
388
|
+
}
|
|
389
|
+
|
|
271
390
|
// File is a jsbundle, move it to a folder with the packageHash as its name
|
|
272
391
|
FileUtils.moveFile(downloadFile, newUpdateFolderPath, expectedBundleFileName);
|
|
273
392
|
}
|
|
274
393
|
|
|
275
394
|
// Save metadata to the folder.
|
|
276
395
|
CodePushUtils.writeJsonToFile(updatePackage, newUpdateMetadataPath);
|
|
396
|
+
|
|
397
|
+
return BinaryPatchResult.success();
|
|
277
398
|
}
|
|
278
399
|
|
|
279
400
|
public void installPackage(JSONObject updatePackage, boolean removePendingUpdate) {
|
|
@@ -349,4 +470,38 @@ public class CodePushUpdateManager {
|
|
|
349
470
|
public void clearUpdates() {
|
|
350
471
|
FileUtils.deleteDirectoryAtPath(getCodePushPath());
|
|
351
472
|
}
|
|
473
|
+
|
|
474
|
+
/** Reads the JS bundle that shipped inside the app binary out of the APK's assets. */
|
|
475
|
+
private static class AssetsBaseBundleProvider implements CodePushBinaryPatch.BaseBundleProvider {
|
|
476
|
+
|
|
477
|
+
private final Context mContext;
|
|
478
|
+
|
|
479
|
+
AssetsBaseBundleProvider(Context context) {
|
|
480
|
+
mContext = context;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
@Override
|
|
484
|
+
public byte[] readBaseBundle(String bundleFileName) throws IOException {
|
|
485
|
+
// The bundle is stored uncompressed in the APK, so it is read straight into
|
|
486
|
+
// memory: a copy on disk would buy nothing and cost the space the restored
|
|
487
|
+
// bundle needs.
|
|
488
|
+
InputStream assetStream = mContext.getAssets().open(bundleFileName);
|
|
489
|
+
try {
|
|
490
|
+
// An uncompressed asset knows its whole length up front, so the buffer is
|
|
491
|
+
// sized for it: growing one would repeatedly hold two copies of a bundle
|
|
492
|
+
// that is already the largest allocation on this path.
|
|
493
|
+
ByteArrayOutputStream bundleBytes = new ByteArrayOutputStream(
|
|
494
|
+
Math.max(assetStream.available(), CodePushConstants.DOWNLOAD_BUFFER_SIZE));
|
|
495
|
+
byte[] buffer = new byte[CodePushConstants.DOWNLOAD_BUFFER_SIZE];
|
|
496
|
+
int bytesRead;
|
|
497
|
+
while ((bytesRead = assetStream.read(buffer)) > 0) {
|
|
498
|
+
bundleBytes.write(buffer, 0, bytesRead);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
return bundleBytes.toByteArray();
|
|
502
|
+
} finally {
|
|
503
|
+
assetStream.close();
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
}
|
|
352
507
|
}
|
|
@@ -86,6 +86,19 @@ public class CodePushUpdateUtils {
|
|
|
86
86
|
return String.format("%064x", new java.math.BigInteger(1, hash));
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
+
/**
|
|
90
|
+
* SHA-256 of a single file's bytes, as lowercase hex - the way a binary patch manifest
|
|
91
|
+
* records the bundle it was computed against and the bundle it produces.
|
|
92
|
+
*/
|
|
93
|
+
public static String computeHashForFile(File file) throws IOException {
|
|
94
|
+
return computeHash(new FileInputStream(file));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** SHA-256 of the bytes in memory, in the same form as {@link #computeHashForFile}. */
|
|
98
|
+
public static String computeHashForBytes(byte[] data) {
|
|
99
|
+
return computeHash(new ByteArrayInputStream(data));
|
|
100
|
+
}
|
|
101
|
+
|
|
89
102
|
public static void copyNecessaryFilesFromCurrentPackage(String diffManifestFilePath, String currentPackageFolderPath, String newPackageFolderPath) throws IOException {
|
|
90
103
|
if (currentPackageFolderPath == null || !new File(currentPackageFolderPath).exists()) {
|
|
91
104
|
CodePushUtils.log("Unable to copy files from current package during diff update, because currentPackageFolderPath is invalid.");
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
package com.microsoft.codepush.react;
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The native HDiffPatch applier, which is what actually turns the bundle inside the app
|
|
5
|
+
* binary into the bundle an update wants to run.
|
|
6
|
+
*
|
|
7
|
+
* The library is loaded on first use rather than when the class is loaded, and a load
|
|
8
|
+
* failure is reported as a result code instead of an error: an app whose build did not
|
|
9
|
+
* produce the library still has to install its updates, it just has to download them in
|
|
10
|
+
* full.
|
|
11
|
+
*/
|
|
12
|
+
public class HDiffPatchNative implements CodePushBinaryPatch.PatchApplier {
|
|
13
|
+
|
|
14
|
+
private static final String LIBRARY_NAME = "codepush-binarypatch";
|
|
15
|
+
|
|
16
|
+
private static boolean sLibraryLoadAttempted = false;
|
|
17
|
+
private static boolean sLibraryLoaded = false;
|
|
18
|
+
|
|
19
|
+
@Override
|
|
20
|
+
public int apply(byte[] base, byte[] patch, String outputPath, long expectedTargetSize) {
|
|
21
|
+
if (!loadLibrary()) {
|
|
22
|
+
return RESULT_LIBRARY_UNAVAILABLE;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return applyPatch(base, patch, outputPath, expectedTargetSize);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
private static synchronized boolean loadLibrary() {
|
|
29
|
+
if (!sLibraryLoadAttempted) {
|
|
30
|
+
sLibraryLoadAttempted = true;
|
|
31
|
+
try {
|
|
32
|
+
System.loadLibrary(LIBRARY_NAME);
|
|
33
|
+
sLibraryLoaded = true;
|
|
34
|
+
} catch (UnsatisfiedLinkError e) {
|
|
35
|
+
CodePushUtils.log("Unable to load the binary patch library: " + e.getMessage());
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return sLibraryLoaded;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Writes the bundle that `patch` produces from `base` to `outputPath`.
|
|
44
|
+
*
|
|
45
|
+
* The base bundle and the patch are passed as arrays because the applier needs random
|
|
46
|
+
* access to both, while the restored bundle is written straight to a file, so the two
|
|
47
|
+
* bundles are never in memory at the same time.
|
|
48
|
+
*
|
|
49
|
+
* @return one of the RESULT_* codes of {@link CodePushBinaryPatch.PatchApplier}
|
|
50
|
+
*/
|
|
51
|
+
private static native int applyPatch(byte[] base, byte[] patch, String outputPath, long expectedTargetSize);
|
|
52
|
+
}
|