22
33import android .content .Context ;
44
5+ import com .facebook .react .bridge .ReadableArray ;
56import com .facebook .react .bridge .ReadableMap ;
67import com .facebook .react .bridge .WritableMap ;
78import com .facebook .react .bridge .WritableNativeMap ;
89
10+ import org .json .JSONException ;
11+ import org .json .JSONObject ;
12+
913import java .io .BufferedInputStream ;
1014import java .io .BufferedOutputStream ;
1115import java .io .File ;
1418import java .net .HttpURLConnection ;
1519import java .net .MalformedURLException ;
1620import java .net .URL ;
21+ import java .nio .ByteBuffer ;
1722
1823public 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