Skip to content

Commit

Permalink
Extract delta client
Browse files Browse the repository at this point in the history
Summary: Extracts the delta client from the bundle downloader. This will allow us to extract an interface, and provide a different implementation for C++ delta bundling (where we will pass deltas directly to native code).

Reviewed By: pakoito

Differential Revision: D6900904

fbshipit-source-id: 358705615eecc15afa0de3e50478468ad840d250
  • Loading branch information
davidaurelio authored and facebook-github-bot committed Feb 6, 2018
1 parent 6e44356 commit 1019bda
Show file tree
Hide file tree
Showing 2 changed files with 126 additions and 114 deletions.
Original file line number Diff line number Diff line change
@@ -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<Number, byte[]> mPreModules = new LinkedHashMap<Number, byte[]>();
final LinkedHashMap<Number, byte[]> mDeltaModules = new LinkedHashMap<Number, byte[]>();
final LinkedHashMap<Number, byte[]> mPostModules = new LinkedHashMap<Number, byte[]>();
@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<Number, byte[]> 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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,14 @@

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;
import com.facebook.react.common.DebugServerException;
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;
Expand All @@ -47,11 +42,8 @@ public class BundleDownloader {

private final OkHttpClient mClient;

private final LinkedHashMap<Number, byte[]> mPreModules = new LinkedHashMap<>();
private final LinkedHashMap<Number, byte[]> mDeltaModules = new LinkedHashMap<>();
private final LinkedHashMap<Number, byte[]> mPostModules = new LinkedHashMap<>();
private final BundleDeltaClient mBundleDeltaClient = new BundleDeltaClient();

private @Nullable String mDeltaId;
private @Nullable Call mDownloadBundleFromURLCall;

public static class BundleInfo {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -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<Number, byte[]> 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;

Expand Down

0 comments on commit 1019bda

Please sign in to comment.