Merge pull request #168 from Microsoft/android-asset-updates

Android Asset Updates 🎉
This commit is contained in:
Geoffrey Goh
2016-01-29 15:45:23 -08:00
7 changed files with 494 additions and 224 deletions

View File

@@ -366,32 +366,34 @@ RCT_EXPORT_METHOD(downloadUpdate:(NSDictionary*)updatePackage
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
{
[CodePushPackage downloadPackage:updatePackage
// The download is progressing forward
progressCallback:^(long long expectedContentLength, long long receivedContentLength) {
// Notify the script-side about the progress
[self.bridge.eventDispatcher
sendDeviceEventWithName:@"CodePushDownloadProgress"
body:@{
@"totalBytes":[NSNumber numberWithLongLong:expectedContentLength],
@"receivedBytes":[NSNumber numberWithLongLong:receivedContentLength]
}];
}
// The download completed
doneCallback:^{
NSError *err;
NSDictionary *newPackage = [CodePushPackage getPackage:updatePackage[PackageHashKey] error:&err];
if (err) {
return reject(err);
dispatch_async(dispatch_get_main_queue(), ^{
[CodePushPackage downloadPackage:updatePackage
// The download is progressing forward
progressCallback:^(long long expectedContentLength, long long receivedContentLength) {
// Notify the script-side about the progress
[self.bridge.eventDispatcher
sendDeviceEventWithName:@"CodePushDownloadProgress"
body:@{
@"totalBytes":[NSNumber numberWithLongLong:expectedContentLength],
@"receivedBytes":[NSNumber numberWithLongLong:receivedContentLength]
}];
}
resolve(newPackage);
}
// The download failed
failCallback:^(NSError *err) {
reject(err);
}];
// The download completed
doneCallback:^{
NSError *err;
NSDictionary *newPackage = [CodePushPackage getPackage:updatePackage[PackageHashKey] error:&err];
if (err) {
return reject(err);
}
resolve(newPackage);
}
// The download failed
failCallback:^(NSError *err) {
reject(err);
}];
});
}
/*

View File

@@ -425,41 +425,50 @@ public class CodePush {
}
@ReactMethod
public void getNewStatusReport(Promise promise) {
if (needToReportRollback) {
needToReportRollback = false;
JSONArray failedUpdates = getFailedUpdates();
if (failedUpdates != null && failedUpdates.length() > 0) {
try {
JSONObject lastFailedPackageJSON = failedUpdates.getJSONObject(failedUpdates.length() - 1);
WritableMap lastFailedPackage = CodePushUtils.convertJsonObjectToWriteable(lastFailedPackageJSON);
WritableMap failedStatusReport = codePushTelemetryManager.getRollbackReport(lastFailedPackage);
if (failedStatusReport != null) {
promise.resolve(failedStatusReport);
return;
}
} catch (JSONException e) {
throw new CodePushUnknownException("Unable to read failed updates information stored in SharedPreferences.", e);
}
}
} else if (didUpdate) {
WritableMap currentPackage = codePushPackage.getCurrentPackage();
if (currentPackage != null) {
WritableMap newPackageStatusReport = codePushTelemetryManager.getUpdateReport(currentPackage);
if (newPackageStatusReport != null) {
promise.resolve(newPackageStatusReport);
return;
}
}
} else if (isRunningBinaryVersion) {
WritableMap newAppVersionStatusReport = codePushTelemetryManager.getBinaryUpdateReport(appVersion);
if (newAppVersionStatusReport != null) {
promise.resolve(newAppVersionStatusReport);
return;
}
}
public void getNewStatusReport(final Promise promise) {
promise.resolve("");
AsyncTask asyncTask = new AsyncTask() {
@Override
protected Void doInBackground(Object... params) {
if (needToReportRollback) {
needToReportRollback = false;
JSONArray failedUpdates = getFailedUpdates();
if (failedUpdates != null && failedUpdates.length() > 0) {
try {
JSONObject lastFailedPackageJSON = failedUpdates.getJSONObject(failedUpdates.length() - 1);
WritableMap lastFailedPackage = CodePushUtils.convertJsonObjectToWriteable(lastFailedPackageJSON);
WritableMap failedStatusReport = codePushTelemetryManager.getRollbackReport(lastFailedPackage);
if (failedStatusReport != null) {
promise.resolve(failedStatusReport);
return null;
}
} catch (JSONException e) {
throw new CodePushUnknownException("Unable to read failed updates information stored in SharedPreferences.", e);
}
}
} else if (didUpdate) {
WritableMap currentPackage = codePushPackage.getCurrentPackage();
if (currentPackage != null) {
WritableMap newPackageStatusReport = codePushTelemetryManager.getUpdateReport(currentPackage);
if (newPackageStatusReport != null) {
promise.resolve(newPackageStatusReport);
return null;
}
}
} else if (isRunningBinaryVersion) {
WritableMap newAppVersionStatusReport = codePushTelemetryManager.getBinaryUpdateReport(appVersion);
if (newAppVersionStatusReport != null) {
promise.resolve(newAppVersionStatusReport);
return null;
}
}
promise.resolve("");
return null;
}
};
asyncTask.execute();
}
@ReactMethod

View File

@@ -0,0 +1,7 @@
package com.microsoft.codepush.react;
public class CodePushInvalidUpdateException extends RuntimeException {
public CodePushInvalidUpdateException() {
super("Update is invalid - no files with extension .bundle, .js or .jsbundle were found in the update package.");
}
}

View File

@@ -2,10 +2,14 @@ package com.microsoft.codepush.react;
import android.content.Context;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.WritableNativeMap;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
@@ -14,18 +18,23 @@ import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.ByteBuffer;
public class CodePushPackage {
public final String CODE_PUSH_FOLDER_PREFIX = "CodePush";
public final String STATUS_FILE = "codepush.json";
public final String UPDATE_BUNDLE_FILE_NAME = "app.jsbundle";
public final String CURRENT_PACKAGE_KEY = "currentPackage";
public final String PREVIOUS_PACKAGE_KEY = "previousPackage";
public final String DIFF_MANIFEST_FILE_NAME = "hotcodepush.json";
public final int DOWNLOAD_BUFFER_SIZE = 1024 * 256;
public final String DOWNLOAD_FILE_NAME = "download.zip";
public final String DOWNLOAD_URL_KEY = "downloadUrl";
public final String PACKAGE_FILE_NAME = "app.json";
public final String PACKAGE_HASH_KEY = "packageHash";
public final String DOWNLOAD_URL_KEY = "downloadUrl";
public final int DOWNLOAD_BUFFER_SIZE = 1024 * 256;
public final String PREVIOUS_PACKAGE_KEY = "previousPackage";
public final String RELATIVE_BUNDLE_PATH_KEY = "bundlePath";
public final String STATUS_FILE = "codepush.json";
public final String UNZIPPED_FOLDER_NAME = "unzipped";
public final String UPDATE_BUNDLE_FILE_NAME = "app.jsbundle";
private String documentsDirectory;
@@ -33,6 +42,14 @@ public class CodePushPackage {
this.documentsDirectory = documentsDirectory;
}
public String getDownloadFilePath() {
return CodePushUtils.appendPathComponent(getCodePushPath(), DOWNLOAD_FILE_NAME);
}
public String getUnzippedFolderPath() {
return CodePushUtils.appendPathComponent(getCodePushPath(), UNZIPPED_FOLDER_NAME);
}
public String getDocumentsDirectory() {
return documentsDirectory;
}
@@ -52,7 +69,7 @@ public class CodePushPackage {
public WritableMap getCurrentPackageInfo() {
String statusFilePath = getStatusFilePath();
if (!CodePushUtils.fileAtPathExists(statusFilePath)) {
if (!FileUtils.fileAtPathExists(statusFilePath)) {
return new WritableNativeMap();
}
@@ -87,7 +104,13 @@ public class CodePushPackage {
return null;
}
return CodePushUtils.appendPathComponent(packageFolder, UPDATE_BUNDLE_FILE_NAME);
WritableMap currentPackage = getCurrentPackage();
String relativeBundlePath = CodePushUtils.tryGetString(currentPackage, RELATIVE_BUNDLE_PATH_KEY);
if (relativeBundlePath == null) {
return CodePushUtils.appendPathComponent(packageFolder, UPDATE_BUNDLE_FILE_NAME);
} else {
return CodePushUtils.appendPathComponent(packageFolder, relativeBundlePath);
}
}
public String getPackageFolderPath(String packageHash) {
@@ -132,7 +155,7 @@ public class CodePushPackage {
public void downloadPackage(Context applicationContext, ReadableMap updatePackage,
DownloadProgressCallback progressCallback) throws IOException {
String packageFolderPath = getPackageFolderPath(CodePushUtils.tryGetString(updatePackage, PACKAGE_HASH_KEY));
String newPackageFolderPath = getPackageFolderPath(CodePushUtils.tryGetString(updatePackage, PACKAGE_HASH_KEY));
String downloadUrlString = CodePushUtils.tryGetString(updatePackage, DOWNLOAD_URL_KEY);
URL downloadUrl = null;
@@ -140,7 +163,10 @@ public class CodePushPackage {
BufferedInputStream bin = null;
FileOutputStream fos = null;
BufferedOutputStream bout = null;
File downloadFile = null;
boolean isZip = false;
// Download the file while checking if it is a zip and notifying client of progress.
try {
downloadUrl = new URL(downloadUrlString);
connection = (HttpURLConnection) (downloadUrl.openConnection());
@@ -149,23 +175,34 @@ public class CodePushPackage {
long receivedBytes = 0;
bin = new BufferedInputStream(connection.getInputStream());
File downloadFolder = new File(packageFolderPath);
File downloadFolder = new File(getCodePushPath());
downloadFolder.mkdirs();
File downloadFile = new File(downloadFolder, UPDATE_BUNDLE_FILE_NAME);
downloadFile = new File(downloadFolder, DOWNLOAD_FILE_NAME);
fos = new FileOutputStream(downloadFile);
bout = new BufferedOutputStream(fos, DOWNLOAD_BUFFER_SIZE);
byte[] data = new byte[DOWNLOAD_BUFFER_SIZE];
byte[] header = new byte[4];
int numBytesRead = 0;
while ((numBytesRead = bin.read(data, 0, DOWNLOAD_BUFFER_SIZE)) >= 0) {
if (receivedBytes < 4) {
for (int i = 0; i < numBytesRead; i++) {
int headerOffset = (int)(receivedBytes) + i;
if (headerOffset >= 4) {
break;
}
header[headerOffset] = data[i];
}
}
receivedBytes += numBytesRead;
bout.write(data, 0, numBytesRead);
progressCallback.call(new DownloadProgress(totalBytes, receivedBytes));
}
assert totalBytes == receivedBytes;
String bundlePath = CodePushUtils.appendPathComponent(packageFolderPath, PACKAGE_FILE_NAME);
CodePushUtils.writeReadableMapToFile(updatePackage, bundlePath);
isZip = ByteBuffer.wrap(header).getInt() == 0x504b0304;
} catch (MalformedURLException e) {
throw new CodePushMalformedDataException(downloadUrlString, e);
} finally {
@@ -178,6 +215,51 @@ public class CodePushPackage {
throw new CodePushUnknownException("Error closing IO resources.", e);
}
}
if (isZip) {
// Unzip the downloaded file and then delete the zip
String unzippedFolderPath = getUnzippedFolderPath();
FileUtils.unzipFile(downloadFile, unzippedFolderPath);
FileUtils.deleteFileSilently(downloadFile);
// Merge contents with current update based on the manifest
String diffManifestFilePath = CodePushUtils.appendPathComponent(unzippedFolderPath,
DIFF_MANIFEST_FILE_NAME);
if (FileUtils.fileAtPathExists(diffManifestFilePath)) {
String currentPackageFolderPath = getCurrentPackageFolderPath();
CodePushUpdateUtils.copyNecessaryFilesFromCurrentPackage(diffManifestFilePath, currentPackageFolderPath, newPackageFolderPath);
}
FileUtils.copyDirectoryContents(unzippedFolderPath, newPackageFolderPath);
FileUtils.deleteFileAtPathSilently(unzippedFolderPath);
// For zip updates, we need to find the relative path to the jsBundle and save it in the
// metadata so that we can find and run it easily the next time.
String relativeBundlePath = CodePushUpdateUtils.findJSBundleInUpdateContents(newPackageFolderPath);
if (relativeBundlePath == null) {
throw new CodePushInvalidUpdateException();
} else {
JSONObject updatePackageJSON = CodePushUtils.convertReadableToJsonObject(updatePackage);
try {
updatePackageJSON.put(RELATIVE_BUNDLE_PATH_KEY, relativeBundlePath);
} catch (JSONException e) {
throw new CodePushUnknownException("Unable to set key " +
RELATIVE_BUNDLE_PATH_KEY + " to value " + relativeBundlePath +
" in update package.", e);
}
updatePackage = CodePushUtils.convertJsonObjectToWriteable(updatePackageJSON);
}
} else {
// File is a jsBundle, move it to a folder with the packageHash as its name
File updateBundleFile = new File(newPackageFolderPath, UPDATE_BUNDLE_FILE_NAME);
downloadFile.renameTo(updateBundleFile);
}
// Save metadata to the folder.
String bundlePath = CodePushUtils.appendPathComponent(newPackageFolderPath, PACKAGE_FILE_NAME);
CodePushUtils.writeReadableMapToFile(updatePackage, bundlePath);
}
public void installPackage(ReadableMap updatePackage) throws IOException {
@@ -185,7 +267,7 @@ public class CodePushPackage {
WritableMap info = getCurrentPackageInfo();
String previousPackageHash = getPreviousPackageHash();
if (previousPackageHash != null && !previousPackageHash.equals(packageHash)) {
CodePushUtils.deleteDirectoryAtPath(getPackageFolderPath(previousPackageHash));
FileUtils.deleteDirectoryAtPath(getPackageFolderPath(previousPackageHash));
}
info.putString(PREVIOUS_PACKAGE_KEY, CodePushUtils.tryGetString(info, CURRENT_PACKAGE_KEY));
@@ -196,7 +278,7 @@ public class CodePushPackage {
public void rollbackPackage() {
WritableMap info = getCurrentPackageInfo();
String currentPackageFolderPath = getCurrentPackageFolderPath();
CodePushUtils.deleteDirectoryAtPath(currentPackageFolderPath);
FileUtils.deleteDirectoryAtPath(currentPackageFolderPath);
info.putString(CURRENT_PACKAGE_KEY, CodePushUtils.tryGetString(info, PREVIOUS_PACKAGE_KEY));
info.putNull(PREVIOUS_PACKAGE_KEY);
updateCurrentPackageInfo(info);
@@ -238,6 +320,6 @@ public class CodePushPackage {
public void clearUpdates() {
File statusFile = new File(getStatusFilePath());
statusFile.delete();
CodePushUtils.deleteDirectoryAtPath(getCodePushPath());
FileUtils.deleteDirectoryAtPath(getCodePushPath());
}
}

View File

@@ -0,0 +1,46 @@
package com.microsoft.codepush.react;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.WritableMap;
import java.io.File;
import java.io.IOException;
public class CodePushUpdateUtils {
public static void copyNecessaryFilesFromCurrentPackage(String diffManifestFilePath, String currentPackageFolderPath, String newPackageFolderPath) throws IOException{
FileUtils.copyDirectoryContents(currentPackageFolderPath, newPackageFolderPath);
WritableMap diffManifest = CodePushUtils.getWritableMapFromFile(diffManifestFilePath);
ReadableArray deletedFiles = diffManifest.getArray("deletedFiles");
for (int i = 0; i < deletedFiles.size(); i++) {
String fileNameToDelete = deletedFiles.getString(i);
File fileToDelete = new File(newPackageFolderPath, fileNameToDelete);
FileUtils.deleteFileSilently(fileToDelete);
}
}
public static String findJSBundleInUpdateContents(String folderPath) {
File folder = new File(folderPath);
File[] folderFiles = folder.listFiles();
for (File file : folderFiles) {
String fullFilePath = CodePushUtils.appendPathComponent(folderPath, file.getName());
if (file.isDirectory()) {
String mainBundlePathInSubFolder = findJSBundleInUpdateContents(fullFilePath);
if (mainBundlePathInSubFolder != null) {
return CodePushUtils.appendPathComponent(file.getName(), mainBundlePathInSubFolder);
}
} else {
String fileName = file.getName();
int dotIndex = fileName.lastIndexOf(".");
if (dotIndex >= 0) {
String fileExtension = fileName.substring(dotIndex + 1);
if (fileExtension.equals("bundle") || fileExtension.equals("js") || fileExtension.equals("jsbundle")) {
return fileName;
}
}
}
}
return null;
}
}

View File

@@ -15,99 +15,57 @@ import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.Iterator;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
public class CodePushUtils {
public static final String CODE_PUSH_TAG = "CodePush";
public static final String REACT_NATIVE_LOG_TAG = "ReactNative";
public static String appendPathComponent(String basePath, String appendPathComponent) {
return new File(basePath, appendPathComponent).getAbsolutePath();
}
public static boolean fileAtPathExists(String filePath) {
return new File(filePath).exists();
}
public static String readFileToString(String filePath) throws IOException {
FileInputStream fin = null;
BufferedReader reader = null;
try {
File fl = new File(filePath);
fin = new FileInputStream(fl);
reader = new BufferedReader(new InputStreamReader(fin));
StringBuilder sb = new StringBuilder();
String line = null;
while ((line = reader.readLine()) != null) {
sb.append(line).append("\n");
public static WritableArray convertJsonArrayToWriteable(JSONArray jsonArr) {
WritableArray arr = Arguments.createArray();
for (int i=0; i<jsonArr.length(); i++) {
Object obj = null;
try {
obj = jsonArr.get(i);
} catch (JSONException jsonException) {
// Should not happen.
throw new CodePushUnknownException(i + " should be within bounds of array " + jsonArr.toString(), jsonException);
}
return sb.toString();
} finally {
if (reader != null) reader.close();
if (fin != null) fin.close();
}
}
public static void writeStringToFile(String content, String filePath) throws IOException {
PrintWriter out = null;
try {
out = new PrintWriter(filePath);
out.print(content);
} finally {
if (out != null) out.close();
}
}
public static void deleteDirectoryAtPath(String directoryPath) {
deleteDirectory(new File(directoryPath));
}
public static void deleteDirectory(File directory) {
if (directory.exists()) {
File[] files = directory.listFiles();
if (files != null) {
for (int i=0; i<files.length; i++) {
if(files[i].isDirectory()) {
deleteDirectory(files[i]);
}
else {
files[i].delete();
}
}
}
}
directory.delete();
}
public static boolean createFolderAtPath(String filePath) {
File file = new File(filePath);
return file.mkdir();
}
public static WritableMap getWritableMapFromFile(String filePath) throws IOException {
String content = CodePushUtils.readFileToString(filePath);
JSONObject json = null;
try {
json = new JSONObject(content);
return convertJsonObjectToWriteable(json);
} catch (JSONException jsonException) {
throw new CodePushMalformedDataException(filePath, jsonException);
if (obj instanceof JSONObject)
arr.pushMap(convertJsonObjectToWriteable((JSONObject) obj));
else if (obj instanceof JSONArray)
arr.pushArray(convertJsonArrayToWriteable((JSONArray) obj));
else if (obj instanceof String)
arr.pushString((String) obj);
else if (obj instanceof Double)
arr.pushDouble((Double) obj);
else if (obj instanceof Integer)
arr.pushInt((Integer) obj);
else if (obj instanceof Boolean)
arr.pushBoolean((Boolean) obj);
else if (obj == null)
arr.pushNull();
else
throw new CodePushUnknownException("Unrecognized object: " + obj);
}
}
public static void writeReadableMapToFile(ReadableMap map, String filePath) throws IOException {
JSONObject json = CodePushUtils.convertReadableToJsonObject(map);
String jsonString = json.toString();
CodePushUtils.writeStringToFile(jsonString, filePath);
return arr;
}
public static WritableMap convertJsonObjectToWriteable(JSONObject jsonObj) {
@@ -144,73 +102,9 @@ public class CodePushUtils {
return map;
}
public static WritableArray convertJsonArrayToWriteable(JSONArray jsonArr) {
WritableArray arr = Arguments.createArray();
for (int i=0; i<jsonArr.length(); i++) {
Object obj = null;
try {
obj = jsonArr.get(i);
} catch (JSONException jsonException) {
// Should not happen.
throw new CodePushUnknownException(i + " should be within bounds of array " + jsonArr.toString(), jsonException);
}
if (obj instanceof JSONObject)
arr.pushMap(convertJsonObjectToWriteable((JSONObject) obj));
else if (obj instanceof JSONArray)
arr.pushArray(convertJsonArrayToWriteable((JSONArray) obj));
else if (obj instanceof String)
arr.pushString((String) obj);
else if (obj instanceof Double)
arr.pushDouble((Double) obj);
else if (obj instanceof Integer)
arr.pushInt((Integer) obj);
else if (obj instanceof Boolean)
arr.pushBoolean((Boolean) obj);
else if (obj == null)
arr.pushNull();
else
throw new CodePushUnknownException("Unrecognized object: " + obj);
}
return arr;
}
public static JSONObject convertReadableToJsonObject(ReadableMap map) {
JSONObject jsonObj = new JSONObject();
ReadableMapKeySetIterator it = map.keySetIterator();
while (it.hasNextKey()) {
String key = it.nextKey();
ReadableType type = map.getType(key);
try {
switch (type) {
case Map:
jsonObj.put(key, convertReadableToJsonObject(map.getMap(key)));
break;
case Array:
jsonObj.put(key, convertReadableToJsonArray(map.getArray(key)));
break;
case String:
jsonObj.put(key, map.getString(key));
break;
case Number:
jsonObj.put(key, map.getDouble(key));
break;
case Boolean:
jsonObj.put(key, map.getBoolean(key));
break;
case Null:
jsonObj.put(key, null);
break;
default:
throw new CodePushUnknownException("Unrecognized type: " + type + " of key: " + key);
}
} catch (JSONException jsonException) {
throw new CodePushUnknownException("Error setting key: " + key + " in JSONObject", jsonException);
}
}
return jsonObj;
public static WritableMap convertReadableMapToWritableMap(ReadableMap map) {
JSONObject mapJSON = convertReadableToJsonObject(map);
return convertJsonObjectToWriteable(mapJSON);
}
public static JSONArray convertReadableToJsonArray(ReadableArray arr) {
@@ -252,9 +146,62 @@ public class CodePushUtils {
return jsonArr;
}
public static WritableMap convertReadableMapToWritableMap(ReadableMap map) {
JSONObject mapJSON = convertReadableToJsonObject(map);
return convertJsonObjectToWriteable(mapJSON);
public static JSONObject convertReadableToJsonObject(ReadableMap map) {
JSONObject jsonObj = new JSONObject();
ReadableMapKeySetIterator it = map.keySetIterator();
while (it.hasNextKey()) {
String key = it.nextKey();
ReadableType type = map.getType(key);
try {
switch (type) {
case Map:
jsonObj.put(key, convertReadableToJsonObject(map.getMap(key)));
break;
case Array:
jsonObj.put(key, convertReadableToJsonArray(map.getArray(key)));
break;
case String:
jsonObj.put(key, map.getString(key));
break;
case Number:
jsonObj.put(key, map.getDouble(key));
break;
case Boolean:
jsonObj.put(key, map.getBoolean(key));
break;
case Null:
jsonObj.put(key, null);
break;
default:
throw new CodePushUnknownException("Unrecognized type: " + type + " of key: " + key);
}
} catch (JSONException jsonException) {
throw new CodePushUnknownException("Error setting key: " + key + " in JSONObject", jsonException);
}
}
return jsonObj;
}
public static WritableMap getWritableMapFromFile(String filePath) throws IOException {
String content = FileUtils.readFileToString(filePath);
JSONObject json = null;
try {
json = new JSONObject(content);
return convertJsonObjectToWriteable(json);
} catch (JSONException jsonException) {
throw new CodePushMalformedDataException(filePath, jsonException);
}
}
public static void log(String message) {
Log.d(REACT_NATIVE_LOG_TAG, "[CodePush] " + message);
}
public static void logBundleUrl(String path) {
log("Loading JS bundle from \"" + path + "\"");
}
public static String tryGetString(ReadableMap map, String key) {
@@ -265,11 +212,9 @@ public class CodePushUtils {
}
}
public static void log(String message) {
Log.d(REACT_NATIVE_LOG_TAG, "[CodePush] " + message);
}
public static void logBundleUrl(String path) {
log("Loading JS bundle from \"" + path + "\"");
public static void writeReadableMapToFile(ReadableMap map, String filePath) throws IOException {
JSONObject json = CodePushUtils.convertReadableToJsonObject(map);
String jsonString = json.toString();
FileUtils.writeStringToFile(jsonString, filePath);
}
}

View File

@@ -0,0 +1,179 @@
package com.microsoft.codepush.react;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
public class FileUtils {
public static final int WRITE_BUFFER_SIZE = 1024 * 8;
public static void copyDirectoryContents(String sourceDirectoryPath, String destinationDirectoryPath) throws IOException {
File sourceDir = new File(sourceDirectoryPath);
File destDir = new File(destinationDirectoryPath);
if (!destDir.exists()) {
destDir.mkdir();
}
for (File sourceFile : sourceDir.listFiles()) {
if (sourceFile.isDirectory()) {
copyDirectoryContents(
CodePushUtils.appendPathComponent(sourceDirectoryPath, sourceFile.getName()),
CodePushUtils.appendPathComponent(destinationDirectoryPath, sourceFile.getName()));
} else {
File destFile = new File(destDir, sourceFile.getName());
FileInputStream fromFileStream = null;
BufferedInputStream fromBufferedStream = null;
FileOutputStream destStream = null;
byte[] buffer = new byte[WRITE_BUFFER_SIZE];
try {
fromFileStream = new FileInputStream(sourceFile);
fromBufferedStream = new BufferedInputStream(fromFileStream);
destStream = new FileOutputStream(destFile);
int bytesRead;
while ((bytesRead = fromBufferedStream.read(buffer)) > 0) {
destStream.write(buffer, 0, bytesRead);
}
} finally {
try {
if (fromFileStream != null) fromFileStream.close();
if (fromBufferedStream != null) fromBufferedStream.close();
if (destStream != null) destStream.close();
} catch (IOException e) {
throw new CodePushUnknownException("Error closing IO resources.", e);
}
}
}
}
}
public static boolean createFolderAtPath(String filePath) {
File file = new File(filePath);
return file.mkdir();
}
public static void deleteDirectory(File directory) {
if (directory.exists()) {
File[] files = directory.listFiles();
if (files != null) {
for (int i=0; i<files.length; i++) {
if(files[i].isDirectory()) {
deleteDirectory(files[i]);
}
else {
files[i].delete();
}
}
}
}
directory.delete();
}
public static void deleteDirectoryAtPath(String directoryPath) {
deleteDirectory(new File(directoryPath));
}
public static void deleteFileAtPathSilently(String path) {
deleteFileSilently(new File(path));
}
public static void deleteFileSilently(File file) {
if (!file.delete()) {
CodePushUtils.log("Error deleting file " + file.getName());
}
}
public static boolean fileAtPathExists(String filePath) {
return new File(filePath).exists();
}
public static String readFileToString(String filePath) throws IOException {
FileInputStream fin = null;
BufferedReader reader = null;
try {
File fl = new File(filePath);
fin = new FileInputStream(fl);
reader = new BufferedReader(new InputStreamReader(fin));
StringBuilder sb = new StringBuilder();
String line = null;
while ((line = reader.readLine()) != null) {
sb.append(line).append("\n");
}
return sb.toString();
} finally {
if (reader != null) reader.close();
if (fin != null) fin.close();
}
}
public static void unzipFile(File zipFile, String destination) throws IOException {
FileInputStream fileStream = null;
BufferedInputStream bufferedStream = null;
ZipInputStream zipStream = null;
try {
fileStream = new FileInputStream(zipFile);
bufferedStream = new BufferedInputStream(fileStream);
zipStream = new ZipInputStream(bufferedStream);
ZipEntry entry;
File destinationFolder = new File(destination);
if (!destinationFolder.exists()) {
destinationFolder.mkdirs();
}
byte[] buffer = new byte[WRITE_BUFFER_SIZE];
while ((entry = zipStream.getNextEntry()) != null) {
String fileName = entry.getName();
File file = new File(destinationFolder, fileName);
if (entry.isDirectory()) {
file.mkdirs();
} else {
File parent = file.getParentFile();
if (!parent.exists()) {
parent.mkdirs();
}
FileOutputStream fout = new FileOutputStream(file);
try {
int numBytesRead;
while ((numBytesRead = zipStream.read(buffer)) != -1) {
fout.write(buffer, 0, numBytesRead);
}
} finally {
fout.close();
}
}
long time = entry.getTime();
if (time > 0) {
file.setLastModified(time);
}
}
} finally {
try {
if (zipStream != null) zipStream.close();
if (bufferedStream != null) bufferedStream.close();
if (fileStream != null) fileStream.close();
} catch (IOException e) {
throw new CodePushUnknownException("Error closing IO resources.", e);
}
}
}
public static void writeStringToFile(String content, String filePath) throws IOException {
PrintWriter out = null;
try {
out = new PrintWriter(filePath);
out.print(content);
} finally {
if (out != null) out.close();
}
}
}