Skip to content
This repository was archived by the owner on May 20, 2025. It is now read-only.

Commit fddfcb2

Browse files
committed
android asset updates
1 parent 509cc0f commit fddfcb2

File tree

3 files changed

+235
-12
lines changed

3 files changed

+235
-12
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package com.microsoft.codepush.react;
2+
3+
public class CodePushInvalidPackageException extends RuntimeException {
4+
public CodePushInvalidPackageException() {
5+
super("Update is invalid - no files with extension .jsbundle or .bundle were found in the update package.");
6+
}
7+
}

android/app/src/main/java/com/microsoft/codepush/react/CodePushPackage.java

Lines changed: 120 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,14 @@
22

33
import android.content.Context;
44

5+
import com.facebook.react.bridge.ReadableArray;
56
import com.facebook.react.bridge.ReadableMap;
67
import com.facebook.react.bridge.WritableMap;
78
import com.facebook.react.bridge.WritableNativeMap;
89

10+
import org.json.JSONException;
11+
import org.json.JSONObject;
12+
913
import java.io.BufferedInputStream;
1014
import java.io.BufferedOutputStream;
1115
import java.io.File;
@@ -14,25 +18,38 @@
1418
import java.net.HttpURLConnection;
1519
import java.net.MalformedURLException;
1620
import java.net.URL;
21+
import java.nio.ByteBuffer;
1722

1823
public class CodePushPackage {
1924

2025
public final String CODE_PUSH_FOLDER_PREFIX = "CodePush";
21-
public final String STATUS_FILE = "codepush.json";
22-
public final String UPDATE_BUNDLE_FILE_NAME = "app.jsbundle";
2326
public final String CURRENT_PACKAGE_KEY = "currentPackage";
24-
public final String PREVIOUS_PACKAGE_KEY = "previousPackage";
27+
public final String DIFF_MANIFEST_FILE_NAME = "hotcodepush.json";
28+
public final int DOWNLOAD_BUFFER_SIZE = 1024 * 256;
29+
public final String DOWNLOAD_FILE_NAME = "download.zip";
30+
public final String DOWNLOAD_URL_KEY = "downloadUrl";
2531
public final String PACKAGE_FILE_NAME = "app.json";
2632
public final String PACKAGE_HASH_KEY = "packageHash";
27-
public final String DOWNLOAD_URL_KEY = "downloadUrl";
28-
public final int DOWNLOAD_BUFFER_SIZE = 1024 * 256;
33+
public final String PREVIOUS_PACKAGE_KEY = "previousPackage";
34+
public final String RELATIVE_BUNDLE_PATH_KEY = "bundlePath";
35+
public final String STATUS_FILE = "codepush.json";
36+
public final String UNZIPPED_FOLDER_NAME = "unzipped";
37+
public final String UPDATE_BUNDLE_FILE_NAME = "app.jsbundle";
2938

3039
private String documentsDirectory;
3140

3241
public CodePushPackage(String documentsDirectory) {
3342
this.documentsDirectory = documentsDirectory;
3443
}
3544

45+
public String getDownloadFilePath() {
46+
return CodePushUtils.appendPathComponent(getCodePushPath(), DOWNLOAD_FILE_NAME);
47+
}
48+
49+
public String getUnzippedFolderPath() {
50+
return CodePushUtils.appendPathComponent(getCodePushPath(), UNZIPPED_FOLDER_NAME);
51+
}
52+
3653
public String getDocumentsDirectory() {
3754
return documentsDirectory;
3855
}
@@ -87,7 +104,13 @@ public String getCurrentPackageBundlePath() {
87104
return null;
88105
}
89106

90-
return CodePushUtils.appendPathComponent(packageFolder, UPDATE_BUNDLE_FILE_NAME);
107+
WritableMap currentPackage = getCurrentPackage();
108+
String relativeBundlePath = CodePushUtils.tryGetString(currentPackage, RELATIVE_BUNDLE_PATH_KEY);
109+
if (relativeBundlePath == null) {
110+
return CodePushUtils.appendPathComponent(packageFolder, UPDATE_BUNDLE_FILE_NAME);
111+
} else {
112+
return CodePushUtils.appendPathComponent(packageFolder, relativeBundlePath);
113+
}
91114
}
92115

93116
public String getPackageFolderPath(String packageHash) {
@@ -132,14 +155,16 @@ public WritableMap getPackage(String packageHash) {
132155
public void downloadPackage(Context applicationContext, ReadableMap updatePackage,
133156
DownloadProgressCallback progressCallback) throws IOException {
134157

135-
String packageFolderPath = getPackageFolderPath(CodePushUtils.tryGetString(updatePackage, PACKAGE_HASH_KEY));
158+
String newPackageFolderPath = getPackageFolderPath(CodePushUtils.tryGetString(updatePackage, PACKAGE_HASH_KEY));
136159
String downloadUrlString = CodePushUtils.tryGetString(updatePackage, DOWNLOAD_URL_KEY);
137160

138161
URL downloadUrl = null;
139162
HttpURLConnection connection = null;
140163
BufferedInputStream bin = null;
141164
FileOutputStream fos = null;
142165
BufferedOutputStream bout = null;
166+
File downloadFile = null;
167+
boolean isZip = false;
143168

144169
try {
145170
downloadUrl = new URL(downloadUrlString);
@@ -149,23 +174,34 @@ public void downloadPackage(Context applicationContext, ReadableMap updatePackag
149174
long receivedBytes = 0;
150175

151176
bin = new BufferedInputStream(connection.getInputStream());
152-
File downloadFolder = new File(packageFolderPath);
177+
File downloadFolder = new File(getCodePushPath());
153178
downloadFolder.mkdirs();
154-
File downloadFile = new File(downloadFolder, UPDATE_BUNDLE_FILE_NAME);
179+
downloadFile = new File(downloadFolder, DOWNLOAD_FILE_NAME);
155180
fos = new FileOutputStream(downloadFile);
156181
bout = new BufferedOutputStream(fos, DOWNLOAD_BUFFER_SIZE);
157182
byte[] data = new byte[DOWNLOAD_BUFFER_SIZE];
183+
byte[] header = new byte[4];
184+
158185
int numBytesRead = 0;
159186
while ((numBytesRead = bin.read(data, 0, DOWNLOAD_BUFFER_SIZE)) >= 0) {
187+
if (receivedBytes < 4) {
188+
for (int i = 0; i < numBytesRead; i++) {
189+
int headerOffset = (int)(receivedBytes) + i;
190+
if (headerOffset >= 4) {
191+
break;
192+
}
193+
194+
header[headerOffset] = data[i];
195+
}
196+
}
197+
160198
receivedBytes += numBytesRead;
161199
bout.write(data, 0, numBytesRead);
162200
progressCallback.call(new DownloadProgress(totalBytes, receivedBytes));
163201
}
164202

165203
assert totalBytes == receivedBytes;
166-
167-
String bundlePath = CodePushUtils.appendPathComponent(packageFolderPath, PACKAGE_FILE_NAME);
168-
CodePushUtils.writeReadableMapToFile(updatePackage, bundlePath);
204+
isZip = ByteBuffer.wrap(header).getInt() == 0x504b0304;
169205
} catch (MalformedURLException e) {
170206
throw new CodePushMalformedDataException(downloadUrlString, e);
171207
} finally {
@@ -178,6 +214,78 @@ public void downloadPackage(Context applicationContext, ReadableMap updatePackag
178214
throw new CodePushUnknownException("Error closing IO resources.", e);
179215
}
180216
}
217+
218+
if (isZip) {
219+
System.err.println("THIS IS A ZIP!");
220+
String unzippedFolderPath = getUnzippedFolderPath();
221+
CodePushUtils.unzipFile(downloadFile, unzippedFolderPath);
222+
CodePushUtils.deleteFileSilently(downloadFile);
223+
String diffManifestFilePath = CodePushUtils.appendPathComponent(unzippedFolderPath,
224+
DIFF_MANIFEST_FILE_NAME);
225+
File diffManifestFile = new File(unzippedFolderPath, DIFF_MANIFEST_FILE_NAME);
226+
if (diffManifestFile.exists()) {
227+
String currentPackageFolderPath = getCurrentPackageFolderPath();
228+
CodePushUtils.mergeEntriesInFolder(currentPackageFolderPath, newPackageFolderPath);
229+
WritableMap diffManifest = CodePushUtils.getWritableMapFromFile(diffManifestFilePath);
230+
ReadableArray deletedFiles = diffManifest.getArray("deletedFiles");
231+
for (int i = 0; i < deletedFiles.size(); i++) {
232+
String fileNameToDelete = deletedFiles.getString(i);
233+
File fileToDelete = new File(newPackageFolderPath, fileNameToDelete);
234+
CodePushUtils.deleteFileSilently(fileToDelete);
235+
}
236+
}
237+
238+
CodePushUtils.mergeEntriesInFolder(unzippedFolderPath, newPackageFolderPath);
239+
CodePushUtils.deleteFileAtPathSilently(unzippedFolderPath);
240+
String relativeBundlePath = findMainBundleInFolder(newPackageFolderPath);
241+
242+
if (relativeBundlePath == null) {
243+
throw new CodePushInvalidPackageException();
244+
} else {
245+
JSONObject updatePackageJSON = CodePushUtils.convertReadableToJsonObject(updatePackage);
246+
try {
247+
updatePackageJSON.put(RELATIVE_BUNDLE_PATH_KEY, relativeBundlePath);
248+
} catch (JSONException e) {
249+
throw new CodePushUnknownException("Unable to set key " +
250+
RELATIVE_BUNDLE_PATH_KEY + " to value " + relativeBundlePath +
251+
" in update package.", e);
252+
}
253+
updatePackage = CodePushUtils.convertJsonObjectToWriteable(updatePackageJSON);
254+
}
255+
} else {
256+
System.err.println("THIS IS NOT A ZIP!");
257+
// File is not a zip.
258+
File updateBundleFile = new File(newPackageFolderPath, UPDATE_BUNDLE_FILE_NAME);
259+
downloadFile.renameTo(updateBundleFile);
260+
}
261+
262+
String bundlePath = CodePushUtils.appendPathComponent(newPackageFolderPath, PACKAGE_FILE_NAME);
263+
CodePushUtils.writeReadableMapToFile(updatePackage, bundlePath);
264+
}
265+
266+
public String findMainBundleInFolder(String folderPath) {
267+
File folder = new File(folderPath);
268+
File[] folderFiles = folder.listFiles();
269+
for (File file : folderFiles) {
270+
String fullFilePath = CodePushUtils.appendPathComponent(folderPath, file.getName());
271+
if (file.isDirectory()) {
272+
String mainBundlePathInSubFolder = findMainBundleInFolder(fullFilePath);
273+
if (mainBundlePathInSubFolder != null) {
274+
return CodePushUtils.appendPathComponent(file.getName(), mainBundlePathInSubFolder);
275+
}
276+
} else {
277+
String fileName = file.getName();
278+
int dotIndex = fileName.lastIndexOf(".");
279+
if (dotIndex >= 0) {
280+
String fileExtension = fileName.substring(dotIndex + 1);
281+
if (fileExtension.equals("bundle") || fileExtension.equals("js") || fileExtension.equals("jsbundle")) {
282+
return fileName;
283+
}
284+
}
285+
}
286+
}
287+
288+
return null;
181289
}
182290

183291
public void installPackage(ReadableMap updatePackage) throws IOException {

android/app/src/main/java/com/microsoft/codepush/react/CodePushUtils.java

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,23 @@
1515
import org.json.JSONException;
1616
import org.json.JSONObject;
1717

18+
import java.io.BufferedInputStream;
1819
import java.io.BufferedReader;
1920
import java.io.File;
2021
import java.io.FileInputStream;
22+
import java.io.FileOutputStream;
2123
import java.io.IOException;
2224
import java.io.InputStreamReader;
2325
import java.io.PrintWriter;
2426
import java.util.Iterator;
27+
import java.util.zip.ZipEntry;
28+
import java.util.zip.ZipInputStream;
2529

2630
public class CodePushUtils {
2731

32+
public static final String CODE_PUSH_TAG = "CodePush";
2833
public static final String REACT_NATIVE_LOG_TAG = "ReactNative";
34+
public static final int WRITE_BUFFER_SIZE = 1024 * 8;
2935

3036
public static String appendPathComponent(String basePath, String appendPathComponent) {
3137
return new File(basePath, appendPathComponent).getAbsolutePath();
@@ -265,6 +271,108 @@ public static String tryGetString(ReadableMap map, String key) {
265271
}
266272
}
267273

274+
public static void unzipFile(File zipFile, String destination) throws IOException {
275+
FileInputStream fis = null;
276+
BufferedInputStream bis = null;
277+
ZipInputStream zis = null;
278+
try {
279+
fis = new FileInputStream(zipFile);
280+
bis = new BufferedInputStream(fis);
281+
zis = new ZipInputStream(bis);
282+
ZipEntry entry;
283+
284+
File destinationFolder = new File(destination);
285+
if (!destinationFolder.exists()) {
286+
destinationFolder.mkdirs();
287+
}
288+
289+
byte[] buffer = new byte[WRITE_BUFFER_SIZE];
290+
while ((entry = zis.getNextEntry()) != null) {
291+
String fileName = entry.getName();
292+
File file = new File(destinationFolder, fileName);
293+
if (entry.isDirectory()) {
294+
file.mkdirs();
295+
} else {
296+
File parent = file.getParentFile();
297+
if (!parent.exists()) {
298+
parent.mkdirs();
299+
}
300+
301+
FileOutputStream fout = new FileOutputStream(file);
302+
try {
303+
int numBytesRead;
304+
while ((numBytesRead = zis.read(buffer)) != -1) {
305+
fout.write(buffer, 0, numBytesRead);
306+
}
307+
} finally {
308+
fout.close();
309+
}
310+
}
311+
long time = entry.getTime();
312+
if (time > 0) {
313+
file.setLastModified(time);
314+
}
315+
}
316+
} finally {
317+
try {
318+
if (zis != null) zis.close();
319+
if (bis != null) bis.close();
320+
if (fis != null) fis.close();
321+
} catch (IOException e) {
322+
throw new CodePushUnknownException("Error closing IO resources.", e);
323+
}
324+
}
325+
}
326+
327+
public static void mergeEntriesInFolder(String fromPath, String destinationPath) throws IOException {
328+
File fromDir = new File(fromPath);
329+
File destDir = new File(destinationPath);
330+
if (!destDir.exists()) {
331+
destDir.mkdir();
332+
}
333+
334+
for (File fromFile : fromDir.listFiles()) {
335+
if (fromFile.isDirectory()) {
336+
mergeEntriesInFolder(
337+
CodePushUtils.appendPathComponent(fromPath, fromFile.getName()),
338+
CodePushUtils.appendPathComponent(destinationPath, fromFile.getName()));
339+
} else {
340+
File destFile = new File(destDir, fromFile.getName());
341+
FileInputStream fromFileStream = null;
342+
BufferedInputStream fromBufferedStream = null;
343+
FileOutputStream destStream = null;
344+
byte[] buffer = new byte[WRITE_BUFFER_SIZE];
345+
try {
346+
fromFileStream = new FileInputStream(fromFile);
347+
fromBufferedStream = new BufferedInputStream(fromFileStream);
348+
destStream = new FileOutputStream(destFile);
349+
int bytesRead;
350+
while ((bytesRead = fromBufferedStream.read(buffer)) > 0) {
351+
destStream.write(buffer, 0, bytesRead);
352+
}
353+
} finally {
354+
try {
355+
if (fromFileStream != null) fromFileStream.close();
356+
if (fromBufferedStream != null) fromBufferedStream.close();
357+
if (destStream != null) destStream.close();
358+
} catch (IOException e) {
359+
throw new CodePushUnknownException("Error closing IO resources.", e);
360+
}
361+
}
362+
}
363+
}
364+
}
365+
366+
public static void deleteFileAtPathSilently(String path) {
367+
deleteFileSilently(new File(path));
368+
}
369+
370+
public static void deleteFileSilently(File file) {
371+
if (!file.delete()) {
372+
Log.e(CODE_PUSH_TAG, "Error deleting file " + file.getName());
373+
}
374+
}
375+
268376
public static void log(String message) {
269377
Log.d(REACT_NATIVE_LOG_TAG, "[CodePush] " + message);
270378
}

0 commit comments

Comments
 (0)