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

Commit 5114d06

Browse files
committed
Merge pull request #168 from Microsoft/android-asset-updates
Android Asset Updates 🎉
2 parents 7d9162d + 9d9b664 commit 5114d06

File tree

7 files changed

+486
-216
lines changed

7 files changed

+486
-216
lines changed

CodePush.m

Lines changed: 27 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -366,32 +366,34 @@ - (void)savePendingUpdate:(NSString *)packageHash
366366
resolver:(RCTPromiseResolveBlock)resolve
367367
rejecter:(RCTPromiseRejectBlock)reject)
368368
{
369-
[CodePushPackage downloadPackage:updatePackage
370-
// The download is progressing forward
371-
progressCallback:^(long long expectedContentLength, long long receivedContentLength) {
372-
// Notify the script-side about the progress
373-
[self.bridge.eventDispatcher
374-
sendDeviceEventWithName:@"CodePushDownloadProgress"
375-
body:@{
376-
@"totalBytes":[NSNumber numberWithLongLong:expectedContentLength],
377-
@"receivedBytes":[NSNumber numberWithLongLong:receivedContentLength]
378-
}];
379-
}
380-
// The download completed
381-
doneCallback:^{
382-
NSError *err;
383-
NSDictionary *newPackage = [CodePushPackage getPackage:updatePackage[PackageHashKey] error:&err];
384-
385-
if (err) {
386-
return reject(err);
369+
dispatch_async(dispatch_get_main_queue(), ^{
370+
[CodePushPackage downloadPackage:updatePackage
371+
// The download is progressing forward
372+
progressCallback:^(long long expectedContentLength, long long receivedContentLength) {
373+
// Notify the script-side about the progress
374+
[self.bridge.eventDispatcher
375+
sendDeviceEventWithName:@"CodePushDownloadProgress"
376+
body:@{
377+
@"totalBytes":[NSNumber numberWithLongLong:expectedContentLength],
378+
@"receivedBytes":[NSNumber numberWithLongLong:receivedContentLength]
379+
}];
387380
}
388-
389-
resolve(newPackage);
390-
}
391-
// The download failed
392-
failCallback:^(NSError *err) {
393-
reject(err);
394-
}];
381+
// The download completed
382+
doneCallback:^{
383+
NSError *err;
384+
NSDictionary *newPackage = [CodePushPackage getPackage:updatePackage[PackageHashKey] error:&err];
385+
386+
if (err) {
387+
return reject(err);
388+
}
389+
390+
resolve(newPackage);
391+
}
392+
// The download failed
393+
failCallback:^(NSError *err) {
394+
reject(err);
395+
}];
396+
});
395397
}
396398

397399
/*

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

Lines changed: 40 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -425,41 +425,50 @@ protected Void doInBackground(Object... params) {
425425
}
426426

427427
@ReactMethod
428-
public void getNewStatusReport(Promise promise) {
429-
if (needToReportRollback) {
430-
needToReportRollback = false;
431-
JSONArray failedUpdates = getFailedUpdates();
432-
if (failedUpdates != null && failedUpdates.length() > 0) {
433-
try {
434-
JSONObject lastFailedPackageJSON = failedUpdates.getJSONObject(failedUpdates.length() - 1);
435-
WritableMap lastFailedPackage = CodePushUtils.convertJsonObjectToWriteable(lastFailedPackageJSON);
436-
WritableMap failedStatusReport = codePushTelemetryManager.getRollbackReport(lastFailedPackage);
437-
if (failedStatusReport != null) {
438-
promise.resolve(failedStatusReport);
439-
return;
428+
public void getNewStatusReport(final Promise promise) {
429+
430+
AsyncTask asyncTask = new AsyncTask() {
431+
@Override
432+
protected Void doInBackground(Object... params) {
433+
if (needToReportRollback) {
434+
needToReportRollback = false;
435+
JSONArray failedUpdates = getFailedUpdates();
436+
if (failedUpdates != null && failedUpdates.length() > 0) {
437+
try {
438+
JSONObject lastFailedPackageJSON = failedUpdates.getJSONObject(failedUpdates.length() - 1);
439+
WritableMap lastFailedPackage = CodePushUtils.convertJsonObjectToWriteable(lastFailedPackageJSON);
440+
WritableMap failedStatusReport = codePushTelemetryManager.getRollbackReport(lastFailedPackage);
441+
if (failedStatusReport != null) {
442+
promise.resolve(failedStatusReport);
443+
return null;
444+
}
445+
} catch (JSONException e) {
446+
throw new CodePushUnknownException("Unable to read failed updates information stored in SharedPreferences.", e);
447+
}
448+
}
449+
} else if (didUpdate) {
450+
WritableMap currentPackage = codePushPackage.getCurrentPackage();
451+
if (currentPackage != null) {
452+
WritableMap newPackageStatusReport = codePushTelemetryManager.getUpdateReport(currentPackage);
453+
if (newPackageStatusReport != null) {
454+
promise.resolve(newPackageStatusReport);
455+
return null;
456+
}
457+
}
458+
} else if (isRunningBinaryVersion) {
459+
WritableMap newAppVersionStatusReport = codePushTelemetryManager.getBinaryUpdateReport(appVersion);
460+
if (newAppVersionStatusReport != null) {
461+
promise.resolve(newAppVersionStatusReport);
462+
return null;
440463
}
441-
} catch (JSONException e) {
442-
throw new CodePushUnknownException("Unable to read failed updates information stored in SharedPreferences.", e);
443-
}
444-
}
445-
} else if (didUpdate) {
446-
WritableMap currentPackage = codePushPackage.getCurrentPackage();
447-
if (currentPackage != null) {
448-
WritableMap newPackageStatusReport = codePushTelemetryManager.getUpdateReport(currentPackage);
449-
if (newPackageStatusReport != null) {
450-
promise.resolve(newPackageStatusReport);
451-
return;
452464
}
465+
466+
promise.resolve("");
467+
return null;
453468
}
454-
} else if (isRunningBinaryVersion) {
455-
WritableMap newAppVersionStatusReport = codePushTelemetryManager.getBinaryUpdateReport(appVersion);
456-
if (newAppVersionStatusReport != null) {
457-
promise.resolve(newAppVersionStatusReport);
458-
return;
459-
}
460-
}
469+
};
461470

462-
promise.resolve("");
471+
asyncTask.execute();
463472
}
464473

465474
@ReactMethod
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 CodePushInvalidUpdateException extends RuntimeException {
4+
public CodePushInvalidUpdateException() {
5+
super("Update is invalid - no files with extension .bundle, .js or .jsbundle were found in the update package.");
6+
}
7+
}

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

Lines changed: 98 additions & 16 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
}
@@ -52,7 +69,7 @@ public String getStatusFilePath() {
5269

5370
public WritableMap getCurrentPackageInfo() {
5471
String statusFilePath = getStatusFilePath();
55-
if (!CodePushUtils.fileAtPathExists(statusFilePath)) {
72+
if (!FileUtils.fileAtPathExists(statusFilePath)) {
5673
return new WritableNativeMap();
5774
}
5875

@@ -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,15 +155,18 @@ 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

169+
// Download the file while checking if it is a zip and notifying client of progress.
144170
try {
145171
downloadUrl = new URL(downloadUrlString);
146172
connection = (HttpURLConnection) (downloadUrl.openConnection());
@@ -149,23 +175,34 @@ public void downloadPackage(Context applicationContext, ReadableMap updatePackag
149175
long receivedBytes = 0;
150176

151177
bin = new BufferedInputStream(connection.getInputStream());
152-
File downloadFolder = new File(packageFolderPath);
178+
File downloadFolder = new File(getCodePushPath());
153179
downloadFolder.mkdirs();
154-
File downloadFile = new File(downloadFolder, UPDATE_BUNDLE_FILE_NAME);
180+
downloadFile = new File(downloadFolder, DOWNLOAD_FILE_NAME);
155181
fos = new FileOutputStream(downloadFile);
156182
bout = new BufferedOutputStream(fos, DOWNLOAD_BUFFER_SIZE);
157183
byte[] data = new byte[DOWNLOAD_BUFFER_SIZE];
184+
byte[] header = new byte[4];
185+
158186
int numBytesRead = 0;
159187
while ((numBytesRead = bin.read(data, 0, DOWNLOAD_BUFFER_SIZE)) >= 0) {
188+
if (receivedBytes < 4) {
189+
for (int i = 0; i < numBytesRead; i++) {
190+
int headerOffset = (int)(receivedBytes) + i;
191+
if (headerOffset >= 4) {
192+
break;
193+
}
194+
195+
header[headerOffset] = data[i];
196+
}
197+
}
198+
160199
receivedBytes += numBytesRead;
161200
bout.write(data, 0, numBytesRead);
162201
progressCallback.call(new DownloadProgress(totalBytes, receivedBytes));
163202
}
164203

165204
assert totalBytes == receivedBytes;
166-
167-
String bundlePath = CodePushUtils.appendPathComponent(packageFolderPath, PACKAGE_FILE_NAME);
168-
CodePushUtils.writeReadableMapToFile(updatePackage, bundlePath);
205+
isZip = ByteBuffer.wrap(header).getInt() == 0x504b0304;
169206
} catch (MalformedURLException e) {
170207
throw new CodePushMalformedDataException(downloadUrlString, e);
171208
} finally {
@@ -178,14 +215,59 @@ public void downloadPackage(Context applicationContext, ReadableMap updatePackag
178215
throw new CodePushUnknownException("Error closing IO resources.", e);
179216
}
180217
}
218+
219+
if (isZip) {
220+
// Unzip the downloaded file and then delete the zip
221+
String unzippedFolderPath = getUnzippedFolderPath();
222+
FileUtils.unzipFile(downloadFile, unzippedFolderPath);
223+
FileUtils.deleteFileSilently(downloadFile);
224+
225+
// Merge contents with current update based on the manifest
226+
String diffManifestFilePath = CodePushUtils.appendPathComponent(unzippedFolderPath,
227+
DIFF_MANIFEST_FILE_NAME);
228+
if (FileUtils.fileAtPathExists(diffManifestFilePath)) {
229+
String currentPackageFolderPath = getCurrentPackageFolderPath();
230+
CodePushUpdateUtils.copyNecessaryFilesFromCurrentPackage(diffManifestFilePath, currentPackageFolderPath, newPackageFolderPath);
231+
}
232+
233+
FileUtils.copyDirectoryContents(unzippedFolderPath, newPackageFolderPath);
234+
FileUtils.deleteFileAtPathSilently(unzippedFolderPath);
235+
236+
// For zip updates, we need to find the relative path to the jsBundle and save it in the
237+
// metadata so that we can find and run it easily the next time.
238+
String relativeBundlePath = CodePushUpdateUtils.findJSBundleInUpdateContents(newPackageFolderPath);
239+
240+
if (relativeBundlePath == null) {
241+
throw new CodePushInvalidUpdateException();
242+
} else {
243+
JSONObject updatePackageJSON = CodePushUtils.convertReadableToJsonObject(updatePackage);
244+
try {
245+
updatePackageJSON.put(RELATIVE_BUNDLE_PATH_KEY, relativeBundlePath);
246+
} catch (JSONException e) {
247+
throw new CodePushUnknownException("Unable to set key " +
248+
RELATIVE_BUNDLE_PATH_KEY + " to value " + relativeBundlePath +
249+
" in update package.", e);
250+
}
251+
252+
updatePackage = CodePushUtils.convertJsonObjectToWriteable(updatePackageJSON);
253+
}
254+
} else {
255+
// File is a jsBundle, move it to a folder with the packageHash as its name
256+
File updateBundleFile = new File(newPackageFolderPath, UPDATE_BUNDLE_FILE_NAME);
257+
downloadFile.renameTo(updateBundleFile);
258+
}
259+
260+
// Save metadata to the folder.
261+
String bundlePath = CodePushUtils.appendPathComponent(newPackageFolderPath, PACKAGE_FILE_NAME);
262+
CodePushUtils.writeReadableMapToFile(updatePackage, bundlePath);
181263
}
182264

183265
public void installPackage(ReadableMap updatePackage) throws IOException {
184266
String packageHash = CodePushUtils.tryGetString(updatePackage, PACKAGE_HASH_KEY);
185267
WritableMap info = getCurrentPackageInfo();
186268
String previousPackageHash = getPreviousPackageHash();
187269
if (previousPackageHash != null && !previousPackageHash.equals(packageHash)) {
188-
CodePushUtils.deleteDirectoryAtPath(getPackageFolderPath(previousPackageHash));
270+
FileUtils.deleteDirectoryAtPath(getPackageFolderPath(previousPackageHash));
189271
}
190272

191273
info.putString(PREVIOUS_PACKAGE_KEY, CodePushUtils.tryGetString(info, CURRENT_PACKAGE_KEY));
@@ -196,7 +278,7 @@ public void installPackage(ReadableMap updatePackage) throws IOException {
196278
public void rollbackPackage() {
197279
WritableMap info = getCurrentPackageInfo();
198280
String currentPackageFolderPath = getCurrentPackageFolderPath();
199-
CodePushUtils.deleteDirectoryAtPath(currentPackageFolderPath);
281+
FileUtils.deleteDirectoryAtPath(currentPackageFolderPath);
200282
info.putString(CURRENT_PACKAGE_KEY, CodePushUtils.tryGetString(info, PREVIOUS_PACKAGE_KEY));
201283
info.putNull(PREVIOUS_PACKAGE_KEY);
202284
updateCurrentPackageInfo(info);
@@ -238,6 +320,6 @@ public void downloadAndReplaceCurrentBundle(String remoteBundleUrl) throws IOExc
238320
public void clearUpdates() {
239321
File statusFile = new File(getStatusFilePath());
240322
statusFile.delete();
241-
CodePushUtils.deleteDirectoryAtPath(getCodePushPath());
323+
FileUtils.deleteDirectoryAtPath(getCodePushPath());
242324
}
243325
}

0 commit comments

Comments
 (0)