@bravemobile/react-native-code-push 13.0.0 → 13.1.0-beta.1

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.
Files changed (109) hide show
  1. package/CodePush.podspec +35 -3
  2. package/README.md +117 -3
  3. package/THIRD-PARTY-NOTICES +96 -0
  4. package/android/app/build.gradle +19 -0
  5. package/android/app/proguard-rules.pro +5 -0
  6. package/android/app/src/main/cpp/CMakeLists.txt +38 -0
  7. package/android/app/src/main/cpp/binarypatch_jni.c +181 -0
  8. package/android/app/src/main/java/com/microsoft/codepush/react/BinaryPatchAttempt.java +92 -0
  9. package/android/app/src/main/java/com/microsoft/codepush/react/BinaryPatchResult.java +64 -0
  10. package/android/app/src/main/java/com/microsoft/codepush/react/CodePush.java +1 -1
  11. package/android/app/src/main/java/com/microsoft/codepush/react/CodePushBinaryPatch.java +293 -0
  12. package/android/app/src/main/java/com/microsoft/codepush/react/CodePushConstants.java +25 -0
  13. package/android/app/src/main/java/com/microsoft/codepush/react/CodePushNativeModule.java +20 -6
  14. package/android/app/src/main/java/com/microsoft/codepush/react/CodePushUpdateManager.java +168 -4
  15. package/android/app/src/main/java/com/microsoft/codepush/react/CodePushUpdateUtils.java +13 -0
  16. package/android/app/src/main/java/com/microsoft/codepush/react/DownloadProgressCallback.java +7 -0
  17. package/android/app/src/main/java/com/microsoft/codepush/react/DownloadProgressEventDispatcher.java +25 -4
  18. package/android/app/src/main/java/com/microsoft/codepush/react/HDiffPatchNative.java +52 -0
  19. package/android/app/src/test/java/com/microsoft/codepush/react/CodePushBinaryPatchTest.java +393 -0
  20. package/android/app/src/test/java/com/microsoft/codepush/react/CodePushUpdateManagerBinaryPatchTest.java +211 -0
  21. package/android/app/src/test/java/com/microsoft/codepush/react/CodePushUpdateManagerDownloadTest.java +489 -0
  22. package/android/app/src/test/java/com/microsoft/codepush/react/DownloadProgressEventDispatcherTest.java +80 -0
  23. package/android/codepush-export.gradle +115 -0
  24. package/cli/commands/bundleCommand/bundleCodePush.ts +39 -7
  25. package/cli/commands/bundleCommand/index.ts +9 -1
  26. package/cli/commands/releaseCommand/addToReleaseHistory.ts +7 -0
  27. package/cli/commands/releaseCommand/index.test.ts +118 -0
  28. package/cli/commands/releaseCommand/index.ts +20 -2
  29. package/cli/commands/releaseCommand/release.test.ts +582 -0
  30. package/cli/commands/releaseCommand/release.ts +232 -15
  31. package/cli/dist/commands/bundleCommand/bundleCodePush.js +26 -7
  32. package/cli/dist/commands/bundleCommand/index.js +5 -1
  33. package/cli/dist/commands/releaseCommand/addToReleaseHistory.js +6 -1
  34. package/cli/dist/commands/releaseCommand/index.js +8 -1
  35. package/cli/dist/commands/releaseCommand/index.test.js +96 -0
  36. package/cli/dist/commands/releaseCommand/release.js +165 -17
  37. package/cli/dist/commands/releaseCommand/release.test.js +367 -0
  38. package/cli/dist/functions/makeBinaryPatchBundle.js +248 -0
  39. package/cli/dist/functions/makeBinaryPatchBundle.test.js +321 -0
  40. package/cli/dist/functions/makeCodePushBundle.js +4 -2
  41. package/cli/dist/functions/runHermesEmitBinaryCommand.js +48 -1
  42. package/cli/dist/functions/runHermesEmitBinaryCommand.test.js +100 -0
  43. package/cli/dist/jest.globalSetup.js +38 -0
  44. package/cli/dist/utils/binaryPatch.js +86 -0
  45. package/cli/dist/utils/binaryPatch.test.js +190 -0
  46. package/cli/functions/makeBinaryPatchBundle.test.ts +410 -0
  47. package/cli/functions/makeBinaryPatchBundle.ts +367 -0
  48. package/cli/functions/makeCodePushBundle.ts +4 -2
  49. package/cli/functions/runHermesEmitBinaryCommand.test.ts +124 -0
  50. package/cli/functions/runHermesEmitBinaryCommand.ts +57 -1
  51. package/cli/jest.globalSetup.ts +43 -0
  52. package/cli/utils/binaryPatch.test.ts +234 -0
  53. package/cli/utils/binaryPatch.ts +103 -0
  54. package/cpp/binarypatch/binarypatch_zstd_decompressor.c +187 -0
  55. package/cpp/binarypatch/binarypatch_zstd_decompressor.h +47 -0
  56. package/cpp/binarypatch/vendor/HDiffPatch/libHDiffPatch/HPatch/checksum_plugin.h +52 -0
  57. package/cpp/binarypatch/vendor/HDiffPatch/libHDiffPatch/HPatch/hpatch_mt/hpatch_mt.h +147 -0
  58. package/cpp/binarypatch/vendor/HDiffPatch/libHDiffPatch/HPatch/patch.c +3315 -0
  59. package/cpp/binarypatch/vendor/HDiffPatch/libHDiffPatch/HPatch/patch.h +268 -0
  60. package/cpp/binarypatch/vendor/HDiffPatch/libHDiffPatch/HPatch/patch_private.h +297 -0
  61. package/cpp/binarypatch/vendor/HDiffPatch/libHDiffPatch/HPatch/patch_types.h +434 -0
  62. package/cpp/binarypatch/vendor/zstd/common/allocations.h +55 -0
  63. package/cpp/binarypatch/vendor/zstd/common/bits.h +205 -0
  64. package/cpp/binarypatch/vendor/zstd/common/bitstream.h +454 -0
  65. package/cpp/binarypatch/vendor/zstd/common/compiler.h +464 -0
  66. package/cpp/binarypatch/vendor/zstd/common/cpu.h +249 -0
  67. package/cpp/binarypatch/vendor/zstd/common/debug.c +30 -0
  68. package/cpp/binarypatch/vendor/zstd/common/debug.h +107 -0
  69. package/cpp/binarypatch/vendor/zstd/common/entropy_common.c +340 -0
  70. package/cpp/binarypatch/vendor/zstd/common/error_private.c +64 -0
  71. package/cpp/binarypatch/vendor/zstd/common/error_private.h +158 -0
  72. package/cpp/binarypatch/vendor/zstd/common/fse.h +625 -0
  73. package/cpp/binarypatch/vendor/zstd/common/fse_decompress.c +315 -0
  74. package/cpp/binarypatch/vendor/zstd/common/huf.h +277 -0
  75. package/cpp/binarypatch/vendor/zstd/common/mem.h +422 -0
  76. package/cpp/binarypatch/vendor/zstd/common/pool.c +371 -0
  77. package/cpp/binarypatch/vendor/zstd/common/pool.h +81 -0
  78. package/cpp/binarypatch/vendor/zstd/common/portability_macros.h +171 -0
  79. package/cpp/binarypatch/vendor/zstd/common/threading.c +182 -0
  80. package/cpp/binarypatch/vendor/zstd/common/threading.h +142 -0
  81. package/cpp/binarypatch/vendor/zstd/common/xxhash.c +18 -0
  82. package/cpp/binarypatch/vendor/zstd/common/xxhash.h +7094 -0
  83. package/cpp/binarypatch/vendor/zstd/common/zstd_common.c +48 -0
  84. package/cpp/binarypatch/vendor/zstd/common/zstd_deps.h +123 -0
  85. package/cpp/binarypatch/vendor/zstd/common/zstd_internal.h +324 -0
  86. package/cpp/binarypatch/vendor/zstd/common/zstd_trace.h +156 -0
  87. package/cpp/binarypatch/vendor/zstd/decompress/huf_decompress.c +1944 -0
  88. package/cpp/binarypatch/vendor/zstd/decompress/zstd_ddict.c +244 -0
  89. package/cpp/binarypatch/vendor/zstd/decompress/zstd_ddict.h +44 -0
  90. package/cpp/binarypatch/vendor/zstd/decompress/zstd_decompress.c +2410 -0
  91. package/cpp/binarypatch/vendor/zstd/decompress/zstd_decompress_block.c +2209 -0
  92. package/cpp/binarypatch/vendor/zstd/decompress/zstd_decompress_block.h +73 -0
  93. package/cpp/binarypatch/vendor/zstd/decompress/zstd_decompress_internal.h +240 -0
  94. package/cpp/binarypatch/vendor/zstd/zstd.h +3206 -0
  95. package/cpp/binarypatch/vendor/zstd/zstd_errors.h +107 -0
  96. package/ios/CodePush/CodePush.h +18 -1
  97. package/ios/CodePush/CodePush.mm +24 -1
  98. package/ios/CodePush/CodePushBinaryPatch.h +80 -0
  99. package/ios/CodePush/CodePushBinaryPatch.m +473 -0
  100. package/ios/CodePush/CodePushDownloadHandler.m +6 -0
  101. package/ios/CodePush/CodePushPackage.m +183 -2
  102. package/ios/CodePush/CodePushUpdateUtils.m +49 -1
  103. package/package.json +10 -3
  104. package/scripts/binary-patch/build-hdiffpatch.sh +86 -0
  105. package/scripts/export-embedded-bundle.sh +76 -0
  106. package/src/CodePush.js +46 -3
  107. package/src/CodePush.test.js +382 -0
  108. package/src/package-mixins.js +20 -2
  109. package/typings/react-native-code-push.d.ts +80 -1
@@ -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
- public void downloadPackage(JSONObject updatePackage, String expectedBundleFileName,
147
- DownloadProgressCallback progressCallback) throws IOException {
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;
@@ -179,9 +275,18 @@ public class CodePushUpdateManager {
179
275
  connection.setRequestProperty("Accept-Encoding", "identity");
180
276
  bin = new BufferedInputStream(connection.getInputStream());
181
277
 
278
+ // Announced only once the response is flowing, so a connection that fails to
279
+ // open never announces an empty stream.
280
+ progressCallback.onDownloadStart();
182
281
  long totalBytes = connection.getContentLength();
183
282
  long receivedBytes = 0;
184
283
 
284
+ if (totalBytes > 0) {
285
+ // The zero-received event hands the listener this download's total up front,
286
+ // so a fallback download shows as restarting rather than running backwards.
287
+ progressCallback.call(new DownloadProgress(totalBytes, 0));
288
+ }
289
+
185
290
  File downloadFolder = new File(getCodePushPath());
186
291
  downloadFolder.mkdirs();
187
292
  downloadFile = new File(downloadFolder, CodePushConstants.DOWNLOAD_FILE_NAME);
@@ -232,6 +337,21 @@ public class CodePushUpdateManager {
232
337
  FileUtils.unzipFile(downloadFile, unzippedFolderPath);
233
338
  FileUtils.deleteFileOrFolderSilently(downloadFile);
234
339
 
340
+ // Rebuild the JS bundle the archive only carries a patch of, which leaves the
341
+ // contents identical to the ones the full archive would have delivered.
342
+ if (isBinaryPatchUpdate) {
343
+ long patchStartTime = System.currentTimeMillis();
344
+ BinaryPatchResult patchResult = mBinaryPatch.restoreBundle(unzippedFolderPath,
345
+ getBinaryPatchFolderPath(), expectedBundleFileName);
346
+ if (!patchResult.succeeded()) {
347
+ return patchResult;
348
+ }
349
+
350
+ long applyDurationMs = System.currentTimeMillis() - patchStartTime;
351
+ patchAttempt.recordBundleRestored(applyDurationMs);
352
+ CodePushUtils.log("Restored the update from its binary patch in " + applyDurationMs + " ms.");
353
+ }
354
+
235
355
  // Merge contents with current update based on the manifest
236
356
  String diffManifestFilePath = CodePushUtils.appendPathComponent(unzippedFolderPath,
237
357
  CodePushConstants.DIFF_MANIFEST_FILE_NAME);
@@ -268,12 +388,22 @@ public class CodePushUpdateManager {
268
388
  CodePushUtils.setJSONValueForKey(updatePackage, CodePushConstants.RELATIVE_BUNDLE_PATH_KEY, relativeBundlePath);
269
389
  }
270
390
  } else {
391
+ if (isBinaryPatchUpdate) {
392
+ // Whatever the patch URL served, it is not a patch archive - an error page
393
+ // answered with a 200 looks like this too. Moving it into place would
394
+ // install bytes no hash has ever been checked against, so the full archive
395
+ // is downloaded instead.
396
+ return BinaryPatchResult.failure(BinaryPatchResult.REASON_INVALID_MANIFEST);
397
+ }
398
+
271
399
  // File is a jsbundle, move it to a folder with the packageHash as its name
272
400
  FileUtils.moveFile(downloadFile, newUpdateFolderPath, expectedBundleFileName);
273
401
  }
274
402
 
275
403
  // Save metadata to the folder.
276
404
  CodePushUtils.writeJsonToFile(updatePackage, newUpdateMetadataPath);
405
+
406
+ return BinaryPatchResult.success();
277
407
  }
278
408
 
279
409
  public void installPackage(JSONObject updatePackage, boolean removePendingUpdate) {
@@ -349,4 +479,38 @@ public class CodePushUpdateManager {
349
479
  public void clearUpdates() {
350
480
  FileUtils.deleteDirectoryAtPath(getCodePushPath());
351
481
  }
482
+
483
+ /** Reads the JS bundle that shipped inside the app binary out of the APK's assets. */
484
+ private static class AssetsBaseBundleProvider implements CodePushBinaryPatch.BaseBundleProvider {
485
+
486
+ private final Context mContext;
487
+
488
+ AssetsBaseBundleProvider(Context context) {
489
+ mContext = context;
490
+ }
491
+
492
+ @Override
493
+ public byte[] readBaseBundle(String bundleFileName) throws IOException {
494
+ // The bundle is stored uncompressed in the APK, so it is read straight into
495
+ // memory: a copy on disk would buy nothing and cost the space the restored
496
+ // bundle needs.
497
+ InputStream assetStream = mContext.getAssets().open(bundleFileName);
498
+ try {
499
+ // An uncompressed asset knows its whole length up front, so the buffer is
500
+ // sized for it: growing one would repeatedly hold two copies of a bundle
501
+ // that is already the largest allocation on this path.
502
+ ByteArrayOutputStream bundleBytes = new ByteArrayOutputStream(
503
+ Math.max(assetStream.available(), CodePushConstants.DOWNLOAD_BUFFER_SIZE));
504
+ byte[] buffer = new byte[CodePushConstants.DOWNLOAD_BUFFER_SIZE];
505
+ int bytesRead;
506
+ while ((bytesRead = assetStream.read(buffer)) > 0) {
507
+ bundleBytes.write(buffer, 0, bytesRead);
508
+ }
509
+
510
+ return bundleBytes.toByteArray();
511
+ } finally {
512
+ assetStream.close();
513
+ }
514
+ }
515
+ }
352
516
  }
@@ -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.");
@@ -1,5 +1,12 @@
1
1
  package com.microsoft.codepush.react;
2
2
 
3
3
  interface DownloadProgressCallback {
4
+ /**
5
+ * A download is starting its own progress stream: received bytes count from zero again,
6
+ * against this download's own total. A binary patch fallback makes this happen twice
7
+ * within one downloaded update.
8
+ */
9
+ void onDownloadStart();
10
+
4
11
  void call(DownloadProgress downloadProgress);
5
12
  }
@@ -1,23 +1,44 @@
1
1
  package com.microsoft.codepush.react;
2
2
 
3
+ /**
4
+ * Serializes progress events from the download thread and the UI frame callback into one
5
+ * monotonic stream per download: whatever order the two threads dispatch in, the JS side
6
+ * never sees the same download run backwards. The latest progress lives here rather than
7
+ * with the caller so that reading it and dispatching it happen under one lock.
8
+ */
3
9
  class DownloadProgressEventDispatcher {
4
10
  interface EventEmitter {
5
11
  void emit(DownloadProgress downloadProgress);
6
12
  }
7
13
 
8
14
  private final EventEmitter mEventEmitter;
15
+ private DownloadProgress mLatestDownloadProgress = null;
9
16
  private long mLastDispatchedBytes = -1;
10
17
 
11
18
  DownloadProgressEventDispatcher(EventEmitter eventEmitter) {
12
19
  mEventEmitter = eventEmitter;
13
20
  }
14
21
 
15
- synchronized void dispatch(DownloadProgress downloadProgress) {
16
- if (downloadProgress == null || downloadProgress.getReceivedBytes() <= mLastDispatchedBytes) {
22
+ synchronized void record(DownloadProgress downloadProgress) {
23
+ mLatestDownloadProgress = downloadProgress;
24
+ }
25
+
26
+ synchronized void dispatchLatest() {
27
+ if (mLatestDownloadProgress == null || mLatestDownloadProgress.getReceivedBytes() <= mLastDispatchedBytes) {
17
28
  return;
18
29
  }
19
30
 
20
- mEventEmitter.emit(downloadProgress);
21
- mLastDispatchedBytes = downloadProgress.getReceivedBytes();
31
+ mEventEmitter.emit(mLatestDownloadProgress);
32
+ mLastDispatchedBytes = mLatestDownloadProgress.getReceivedBytes();
33
+ }
34
+
35
+ /**
36
+ * Forgets the download dispatched so far. The next download counts its bytes from zero,
37
+ * and without the reset the monotonic guard would swallow its events as the previous
38
+ * download running backwards.
39
+ */
40
+ synchronized void reset() {
41
+ mLatestDownloadProgress = null;
42
+ mLastDispatchedBytes = -1;
22
43
  }
23
44
  }
@@ -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
+ }