Extract delta client

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
This commit is contained in:
David Aurelio 2018-02-06 15:03:11 -08:00 committed by Facebook Github Bot
parent 6e44356c9b
commit 1019bda930
2 changed files with 126 additions and 114 deletions

View File

@ -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;
}
}

View File

@ -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.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;
@ -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 {
@ -110,15 +102,9 @@ public class BundleDownloader {
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 @@ public class BundleDownloader {
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 @@ public class BundleDownloader {
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;