diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/BundleDeltaClient.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/BundleDeltaClient.java new file mode 100644 index 00000000000000..68ef0ce138ae51 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/BundleDeltaClient.java @@ -0,0 +1,121 @@ +package com.facebook.react.devsupport; + +import android.util.JsonReader; +import android.util.JsonToken; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.LinkedHashMap; +import javax.annotation.Nullable; +import okio.BufferedSource; + +public class BundleDeltaClient { + + final LinkedHashMap mPreModules = new LinkedHashMap(); + final LinkedHashMap mDeltaModules = new LinkedHashMap(); + final LinkedHashMap mPostModules = new LinkedHashMap(); + @Nullable String mDeltaId; + + static boolean isDeltaUrl(String bundleUrl) { + return bundleUrl.indexOf(".delta?") != -1; + } + + public void reset() { + mDeltaId = null; + mDeltaModules.clear(); + mPreModules.clear(); + mPostModules.clear(); + } + + public String toDeltaUrl(String bundleURL) { + if (isDeltaUrl(bundleURL) && mDeltaId != null) { + return bundleURL + "&deltaBundleId=" + mDeltaId; + } + return bundleURL; + } + + public synchronized boolean storeDeltaInFile(BufferedSource body, File outputFile) + throws IOException { + + JsonReader jsonReader = new JsonReader(new InputStreamReader(body.inputStream())); + + jsonReader.beginObject(); + + int numChangedModules = 0; + + while (jsonReader.hasNext()) { + String name = jsonReader.nextName(); + if (name.equals("id")) { + mDeltaId = jsonReader.nextString(); + } else if (name.equals("pre")) { + numChangedModules += patchDelta(jsonReader, mPreModules); + } else if (name.equals("post")) { + numChangedModules += patchDelta(jsonReader, mPostModules); + } else if (name.equals("delta")) { + numChangedModules += patchDelta(jsonReader, mDeltaModules); + } else { + jsonReader.skipValue(); + } + } + + jsonReader.endObject(); + jsonReader.close(); + + if (numChangedModules == 0) { + // If we receive an empty delta, we don't need to save the file again (it'll have the + // same content). + return false; + } + + FileOutputStream fileOutputStream = new FileOutputStream(outputFile); + + try { + for (byte[] code : mPreModules.values()) { + fileOutputStream.write(code); + fileOutputStream.write('\n'); + } + + for (byte[] code : mDeltaModules.values()) { + fileOutputStream.write(code); + fileOutputStream.write('\n'); + } + + for (byte[] code : mPostModules.values()) { + fileOutputStream.write(code); + fileOutputStream.write('\n'); + } + } finally { + fileOutputStream.flush(); + fileOutputStream.close(); + } + + return true; + } + + private static int patchDelta(JsonReader jsonReader, LinkedHashMap map) + throws IOException { + jsonReader.beginArray(); + + int numModules = 0; + while (jsonReader.hasNext()) { + jsonReader.beginArray(); + + int moduleId = jsonReader.nextInt(); + + if (jsonReader.peek() == JsonToken.NULL) { + jsonReader.skipValue(); + map.remove(moduleId); + } else { + map.put(moduleId, jsonReader.nextString().getBytes()); + } + + jsonReader.endArray(); + numModules++; + } + + jsonReader.endArray(); + + return numModules; + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/BundleDownloader.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/BundleDownloader.java index ad127b7462e2f1..9217bc87b13c62 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/devsupport/BundleDownloader.java +++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/BundleDownloader.java @@ -9,8 +9,6 @@ package com.facebook.react.devsupport; -import android.util.JsonReader; -import android.util.JsonToken; import android.util.Log; import com.facebook.common.logging.FLog; import com.facebook.infer.annotation.Assertions; @@ -18,10 +16,7 @@ import com.facebook.react.common.ReactConstants; import com.facebook.react.devsupport.interfaces.DevBundleDownloadListener; import java.io.File; -import java.io.FileOutputStream; import java.io.IOException; -import java.io.InputStreamReader; -import java.util.LinkedHashMap; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -47,11 +42,8 @@ public class BundleDownloader { private final OkHttpClient mClient; - private final LinkedHashMap mPreModules = new LinkedHashMap<>(); - private final LinkedHashMap mDeltaModules = new LinkedHashMap<>(); - private final LinkedHashMap mPostModules = new LinkedHashMap<>(); + private final BundleDeltaClient mBundleDeltaClient = new BundleDeltaClient(); - private @Nullable String mDeltaId; private @Nullable Call mDownloadBundleFromURLCall; public static class BundleInfo { @@ -110,15 +102,9 @@ public void downloadBundleFromURL( final String bundleURL, final @Nullable BundleInfo bundleInfo) { - String finalUrl = bundleURL; - - if (isDeltaUrl(bundleURL) && mDeltaId != null) { - finalUrl += "&deltaBundleId=" + mDeltaId; - } - final Request request = new Request.Builder() - .url(finalUrl) + .url(mBundleDeltaClient.toDeltaUrl(bundleURL)) // FIXME: there is a bug that makes MultipartStreamReader to never find the end of the // multipart message. This temporarily disables the multipart mode to work around it, // but @@ -253,11 +239,11 @@ private void processBundleResult( boolean bundleUpdated; - if (isDeltaUrl(url)) { + if (BundleDeltaClient.isDeltaUrl(url)) { // If the bundle URL has the delta extension, we need to use the delta patching logic. - bundleUpdated = storeDeltaInFile(body, tmpFile); + bundleUpdated = mBundleDeltaClient.storeDeltaInFile(body, tmpFile); } else { - resetDeltaCache(); + mBundleDeltaClient.reset(); bundleUpdated = storePlainJSInFile(body, tmpFile); } @@ -286,101 +272,6 @@ private static boolean storePlainJSInFile(BufferedSource body, File outputFile) return true; } - private synchronized boolean storeDeltaInFile(BufferedSource body, File outputFile) throws IOException { - - JsonReader jsonReader = new JsonReader(new InputStreamReader(body.inputStream())); - - jsonReader.beginObject(); - - int numChangedModules = 0; - - while (jsonReader.hasNext()) { - String name = jsonReader.nextName(); - if (name.equals("id")) { - mDeltaId = jsonReader.nextString(); - } else if (name.equals("pre")) { - numChangedModules += patchDelta(jsonReader, mPreModules); - } else if (name.equals("post")) { - numChangedModules += patchDelta(jsonReader, mPostModules); - } else if (name.equals("delta")) { - numChangedModules += patchDelta(jsonReader, mDeltaModules); - } else { - jsonReader.skipValue(); - } - } - - jsonReader.endObject(); - jsonReader.close(); - - if (numChangedModules == 0) { - // If we receive an empty delta, we don't need to save the file again (it'll have the - // same content). - return false; - } - - FileOutputStream fileOutputStream = new FileOutputStream(outputFile); - - try { - for (byte[] code : mPreModules.values()) { - fileOutputStream.write(code); - fileOutputStream.write('\n'); - } - - for (byte[] code : mDeltaModules.values()) { - fileOutputStream.write(code); - fileOutputStream.write('\n'); - } - - for (byte[] code : mPostModules.values()) { - fileOutputStream.write(code); - fileOutputStream.write('\n'); - } - } finally { - fileOutputStream.flush(); - fileOutputStream.close(); - } - - return true; - } - - private static int patchDelta(JsonReader jsonReader, LinkedHashMap map) - throws IOException { - jsonReader.beginArray(); - - int numModules = 0; - while (jsonReader.hasNext()) { - jsonReader.beginArray(); - - int moduleId = jsonReader.nextInt(); - - if (jsonReader.peek() == JsonToken.NULL) { - jsonReader.skipValue(); - map.remove(moduleId); - } else { - map.put(moduleId, jsonReader.nextString().getBytes()); - } - - jsonReader.endArray(); - numModules++; - } - - jsonReader.endArray(); - - return numModules; - } - - private void resetDeltaCache() { - mDeltaId = null; - - mDeltaModules.clear(); - mPreModules.clear(); - mPostModules.clear(); - } - - private static boolean isDeltaUrl(String bundleUrl) { - return bundleUrl.indexOf(".delta?") != -1; - } - private static void populateBundleInfo(String url, Headers headers, BundleInfo bundleInfo) { bundleInfo.mUrl = url;