2
2
3
3
import android .content .Context ;
4
4
5
+ import com .facebook .react .bridge .ReadableArray ;
5
6
import com .facebook .react .bridge .ReadableMap ;
6
7
import com .facebook .react .bridge .WritableMap ;
7
8
import com .facebook .react .bridge .WritableNativeMap ;
8
9
10
+ import org .json .JSONException ;
11
+ import org .json .JSONObject ;
12
+
9
13
import java .io .BufferedInputStream ;
10
14
import java .io .BufferedOutputStream ;
11
15
import java .io .File ;
14
18
import java .net .HttpURLConnection ;
15
19
import java .net .MalformedURLException ;
16
20
import java .net .URL ;
21
+ import java .nio .ByteBuffer ;
17
22
18
23
public class CodePushPackage {
19
24
20
25
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" ;
23
26
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" ;
25
31
public final String PACKAGE_FILE_NAME = "app.json" ;
26
32
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" ;
29
38
30
39
private String documentsDirectory ;
31
40
32
41
public CodePushPackage (String documentsDirectory ) {
33
42
this .documentsDirectory = documentsDirectory ;
34
43
}
35
44
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
+
36
53
public String getDocumentsDirectory () {
37
54
return documentsDirectory ;
38
55
}
@@ -52,7 +69,7 @@ public String getStatusFilePath() {
52
69
53
70
public WritableMap getCurrentPackageInfo () {
54
71
String statusFilePath = getStatusFilePath ();
55
- if (!CodePushUtils .fileAtPathExists (statusFilePath )) {
72
+ if (!FileUtils .fileAtPathExists (statusFilePath )) {
56
73
return new WritableNativeMap ();
57
74
}
58
75
@@ -87,7 +104,13 @@ public String getCurrentPackageBundlePath() {
87
104
return null ;
88
105
}
89
106
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
+ }
91
114
}
92
115
93
116
public String getPackageFolderPath (String packageHash ) {
@@ -132,15 +155,18 @@ public WritableMap getPackage(String packageHash) {
132
155
public void downloadPackage (Context applicationContext , ReadableMap updatePackage ,
133
156
DownloadProgressCallback progressCallback ) throws IOException {
134
157
135
- String packageFolderPath = getPackageFolderPath (CodePushUtils .tryGetString (updatePackage , PACKAGE_HASH_KEY ));
158
+ String newPackageFolderPath = getPackageFolderPath (CodePushUtils .tryGetString (updatePackage , PACKAGE_HASH_KEY ));
136
159
String downloadUrlString = CodePushUtils .tryGetString (updatePackage , DOWNLOAD_URL_KEY );
137
160
138
161
URL downloadUrl = null ;
139
162
HttpURLConnection connection = null ;
140
163
BufferedInputStream bin = null ;
141
164
FileOutputStream fos = null ;
142
165
BufferedOutputStream bout = null ;
166
+ File downloadFile = null ;
167
+ boolean isZip = false ;
143
168
169
+ // Download the file while checking if it is a zip and notifying client of progress.
144
170
try {
145
171
downloadUrl = new URL (downloadUrlString );
146
172
connection = (HttpURLConnection ) (downloadUrl .openConnection ());
@@ -149,23 +175,34 @@ public void downloadPackage(Context applicationContext, ReadableMap updatePackag
149
175
long receivedBytes = 0 ;
150
176
151
177
bin = new BufferedInputStream (connection .getInputStream ());
152
- File downloadFolder = new File (packageFolderPath );
178
+ File downloadFolder = new File (getCodePushPath () );
153
179
downloadFolder .mkdirs ();
154
- File downloadFile = new File (downloadFolder , UPDATE_BUNDLE_FILE_NAME );
180
+ downloadFile = new File (downloadFolder , DOWNLOAD_FILE_NAME );
155
181
fos = new FileOutputStream (downloadFile );
156
182
bout = new BufferedOutputStream (fos , DOWNLOAD_BUFFER_SIZE );
157
183
byte [] data = new byte [DOWNLOAD_BUFFER_SIZE ];
184
+ byte [] header = new byte [4 ];
185
+
158
186
int numBytesRead = 0 ;
159
187
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
+
160
199
receivedBytes += numBytesRead ;
161
200
bout .write (data , 0 , numBytesRead );
162
201
progressCallback .call (new DownloadProgress (totalBytes , receivedBytes ));
163
202
}
164
203
165
204
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 ;
169
206
} catch (MalformedURLException e ) {
170
207
throw new CodePushMalformedDataException (downloadUrlString , e );
171
208
} finally {
@@ -178,14 +215,59 @@ public void downloadPackage(Context applicationContext, ReadableMap updatePackag
178
215
throw new CodePushUnknownException ("Error closing IO resources." , e );
179
216
}
180
217
}
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 );
181
263
}
182
264
183
265
public void installPackage (ReadableMap updatePackage ) throws IOException {
184
266
String packageHash = CodePushUtils .tryGetString (updatePackage , PACKAGE_HASH_KEY );
185
267
WritableMap info = getCurrentPackageInfo ();
186
268
String previousPackageHash = getPreviousPackageHash ();
187
269
if (previousPackageHash != null && !previousPackageHash .equals (packageHash )) {
188
- CodePushUtils .deleteDirectoryAtPath (getPackageFolderPath (previousPackageHash ));
270
+ FileUtils .deleteDirectoryAtPath (getPackageFolderPath (previousPackageHash ));
189
271
}
190
272
191
273
info .putString (PREVIOUS_PACKAGE_KEY , CodePushUtils .tryGetString (info , CURRENT_PACKAGE_KEY ));
@@ -196,7 +278,7 @@ public void installPackage(ReadableMap updatePackage) throws IOException {
196
278
public void rollbackPackage () {
197
279
WritableMap info = getCurrentPackageInfo ();
198
280
String currentPackageFolderPath = getCurrentPackageFolderPath ();
199
- CodePushUtils .deleteDirectoryAtPath (currentPackageFolderPath );
281
+ FileUtils .deleteDirectoryAtPath (currentPackageFolderPath );
200
282
info .putString (CURRENT_PACKAGE_KEY , CodePushUtils .tryGetString (info , PREVIOUS_PACKAGE_KEY ));
201
283
info .putNull (PREVIOUS_PACKAGE_KEY );
202
284
updateCurrentPackageInfo (info );
@@ -238,6 +320,6 @@ public void downloadAndReplaceCurrentBundle(String remoteBundleUrl) throws IOExc
238
320
public void clearUpdates () {
239
321
File statusFile = new File (getStatusFilePath ());
240
322
statusFile .delete ();
241
- CodePushUtils .deleteDirectoryAtPath (getCodePushPath ());
323
+ FileUtils .deleteDirectoryAtPath (getCodePushPath ());
242
324
}
243
325
}
0 commit comments