diff --git a/CodePush.m b/CodePush.m index 2cc25fa..2059fac 100644 --- a/CodePush.m +++ b/CodePush.m @@ -384,14 +384,14 @@ RCT_EXPORT_METHOD(downloadUpdate:(NSDictionary*)updatePackage NSDictionary *newPackage = [CodePushPackage getPackage:updatePackage[PackageHashKey] error:&err]; if (err) { - return reject(err); + return reject([NSString stringWithFormat: @"%lu", (long)err.code], err.localizedDescription, err); } resolve(newPackage); } // The download failed failCallback:^(NSError *err) { - reject(err); + reject([NSString stringWithFormat: @"%lu", (long)err.code], err.localizedDescription, err); }]; }); } @@ -419,7 +419,7 @@ RCT_EXPORT_METHOD(getCurrentPackage:(RCTPromiseResolveBlock)resolve NSMutableDictionary *package = [[CodePushPackage getCurrentPackage:&error] mutableCopy]; if (error) { - reject(error); + reject([NSString stringWithFormat: @"%lu", (long)error.code], error.localizedDescription, error); } // Add the "isPending" virtual property to the package at this point, so that @@ -445,7 +445,7 @@ RCT_EXPORT_METHOD(installUpdate:(NSDictionary*)updatePackage error:&error]; if (error) { - reject(error); + reject([NSString stringWithFormat: @"%lu", (long)error.code], error.localizedDescription, error); } else { [self savePendingUpdate:updatePackage[PackageHashKey] isLoading:NO]; diff --git a/Examples/CodePushDemoApp/.flowconfig b/Examples/CodePushDemoApp/.flowconfig index a86e0f8..245c23a 100644 --- a/Examples/CodePushDemoApp/.flowconfig +++ b/Examples/CodePushDemoApp/.flowconfig @@ -7,22 +7,38 @@ # Some modules have their own node_modules with overlap .*/node_modules/node-haste/.* -# Ignore react-tools where there are overlaps, but don't ignore anything that -# react-native relies on -.*/node_modules/react-tools/src/vendor/core/ExecutionEnvironment.js -.*/node_modules/react-tools/src/browser/eventPlugins/ResponderEventPlugin.js -.*/node_modules/react-tools/src/browser/ui/React.js -.*/node_modules/react-tools/src/core/ReactInstanceHandles.js -.*/node_modules/react-tools/src/event/EventPropagators.js +# Ugh +.*/node_modules/babel.* +.*/node_modules/babylon.* +.*/node_modules/invariant.* + +# Ignore react and fbjs where there are overlaps, but don't ignore +# anything that react-native relies on +.*/node_modules/fbjs/lib/Map.js +.*/node_modules/fbjs/lib/Promise.js +.*/node_modules/fbjs/lib/fetch.js +.*/node_modules/fbjs/lib/ExecutionEnvironment.js +.*/node_modules/fbjs/lib/isEmpty.js +.*/node_modules/fbjs/lib/crc32.js +.*/node_modules/fbjs/lib/ErrorUtils.js + +# Flow has a built-in definition for the 'react' module which we prefer to use +# over the currently-untyped source +.*/node_modules/react/react.js +.*/node_modules/react/lib/React.js +.*/node_modules/react/lib/ReactDOM.js # Ignore commoner tests -.*/node_modules/react-tools/node_modules/commoner/test/.* +.*/node_modules/commoner/test/.* # See https://github.com/facebook/flow/issues/442 .*/react-tools/node_modules/commoner/lib/reader.js # Ignore jest -.*/react-native/node_modules/jest-cli/.* +.*/node_modules/jest-cli/.* + +# Ignore Website +.*/website/.* [include] @@ -32,5 +48,18 @@ node_modules/react-native/Libraries/react-native/react-native-interface.js [options] module.system=haste +munge_underscores=true + +module.name_mapper='^image![a-zA-Z0-9$_-]+$' -> 'GlobalImageStub' +module.name_mapper='^[./a-zA-Z0-9$_-]+\.png$' -> 'RelativeImageStub' + +suppress_type=$FlowIssue +suppress_type=$FlowFixMe +suppress_type=$FixMe + +suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(2[0-0]\\|1[0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\) +suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(2[0-0]\\|1[0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+ +suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy + [version] -0.11.0 +0.20.1 diff --git a/Examples/CodePushDemoApp/android/app/build.gradle b/Examples/CodePushDemoApp/android/app/build.gradle index 630bf38..e82e2a2 100644 --- a/Examples/CodePushDemoApp/android/app/build.gradle +++ b/Examples/CodePushDemoApp/android/app/build.gradle @@ -1,7 +1,80 @@ -apply plugin: 'com.android.application' +apply plugin: "com.android.application" + +import com.android.build.OutputFile + +/** + * The react.gradle file registers a task for each build variant (e.g. bundleDebugJsAndAssets + * and bundleReleaseJsAndAssets). + * These basically call `react-native bundle` with the correct arguments during the Android build + * cycle. By default, bundleDebugJsAndAssets is skipped, as in debug/dev mode we prefer to load the + * bundle directly from the development server. Below you can see all the possible configurations + * and their defaults. If you decide to add a configuration block, make sure to add it before the + * `apply from: "react.gradle"` line. + * + * project.ext.react = [ + * // the name of the generated asset file containing your JS bundle + * bundleAssetName: "index.android.bundle", + * + * // the entry file for bundle generation + * entryFile: "index.android.js", + * + * // whether to bundle JS and assets in debug mode + * bundleInDebug: false, + * + * // whether to bundle JS and assets in release mode + * bundleInRelease: true, + * + * // whether to bundle JS and assets in another build variant (if configured). + * // See http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Variants + * // The configuration property is in the format 'bundleIn${productFlavor}${buildType}' + * // bundleInFreeDebug: true, + * // bundleInPaidRelease: true, + * // bundleInBeta: true, + * + * // the root of your project, i.e. where "package.json" lives + * root: "../../", + * + * // where to put the JS bundle asset in debug mode + * jsBundleDirDebug: "$buildDir/intermediates/assets/debug", + * + * // where to put the JS bundle asset in release mode + * jsBundleDirRelease: "$buildDir/intermediates/assets/release", + * + * // where to put drawable resources / React Native assets, e.g. the ones you use via + * // require('./image.png')), in debug mode + * resourcesDirDebug: "$buildDir/intermediates/res/merged/debug", + * + * // where to put drawable resources / React Native assets, e.g. the ones you use via + * // require('./image.png')), in release mode + * resourcesDirRelease: "$buildDir/intermediates/res/merged/release", + * + * // by default the gradle tasks are skipped if none of the JS files or assets change; this means + * // that we don't look at files in android/ or ios/ to determine whether the tasks are up to + * // date; if you have any other folders that you want to ignore for performance reasons (gradle + * // indexes the entire tree), add them here. Alternatively, if you have JS files in android/ + * // for example, you might want to remove it from here. + * inputExcludes: ["android/**", "ios/**"] + * ] + */ apply from: "react.gradle" +/** + * Set this to true to create three separate APKs instead of one: + * - A universal APK that works on all devices + * - An APK that only works on ARM devices + * - An APK that only works on x86 devices + * The advantage is the size of the APK is reduced by about 4MB. + * Upload all the APKs to the Play Store and people will download + * the correct one based on the CPU architecture of their device. + */ +def enableSeparateBuildPerCPUArchitecture = false + +/** + * Run Proguard to shrink the Java bytecode in release builds. + */ +def enableProguardInReleaseBuilds = true + android { compileSdkVersion 23 buildToolsVersion "23.0.1" @@ -16,17 +89,38 @@ android { abiFilters "armeabi-v7a", "x86" } } + splits { + abi { + enable enableSeparateBuildPerCPUArchitecture + universalApk true + reset() + include "armeabi-v7a", "x86" + } + } buildTypes { release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + minifyEnabled enableProguardInReleaseBuilds + proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" + } + } + // applicationVariants are e.g. debug, release + applicationVariants.all { variant -> + variant.outputs.each { output -> + // For each separate APK per architecture, set a unique version code as described here: + // http://tools.android.com/tech-docs/new-build-system/user-guide/apk-splits + def versionCodes = ["armeabi-v7a":1, "x86":2] + def abi = output.getFilter(OutputFile.ABI) + if (abi != null) { // null for the universal-debug, universal-release variants + output.versionCodeOverride = + versionCodes.get(abi) * 1048576 + defaultConfig.versionCode + } } } } dependencies { - compile fileTree(dir: 'libs', include: ['*.jar']) - compile 'com.android.support:appcompat-v7:23.0.0' - compile 'com.facebook.react:react-native:0.15.1' - compile project(':react-native-code-push') + compile fileTree(dir: "libs", include: ["*.jar"]) + compile "com.android.support:appcompat-v7:23.0.1" + compile "com.facebook.react:react-native:0.19.+" + compile project(":react-native-code-push") } diff --git a/Examples/CodePushDemoApp/android/app/proguard-rules.pro b/Examples/CodePushDemoApp/android/app/proguard-rules.pro index a92fa17..3584026 100644 --- a/Examples/CodePushDemoApp/android/app/proguard-rules.pro +++ b/Examples/CodePushDemoApp/android/app/proguard-rules.pro @@ -15,3 +15,53 @@ #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} + +# Disabling obfuscation is useful if you collect stack traces from production crashes +# (unless you are using a system that supports de-obfuscate the stack traces). +-dontobfuscate + +# React Native + +# Keep our interfaces so they can be used by other ProGuard rules. +# See http://sourceforge.net/p/proguard/bugs/466/ +-keep,allowobfuscation @interface com.facebook.proguard.annotations.DoNotStrip +-keep,allowobfuscation @interface com.facebook.proguard.annotations.KeepGettersAndSetters + +# Do not strip any method/class that is annotated with @DoNotStrip +-keep @com.facebook.proguard.annotations.DoNotStrip class * +-keepclassmembers class * { + @com.facebook.proguard.annotations.DoNotStrip *; +} + +-keepclassmembers @com.facebook.proguard.annotations.KeepGettersAndSetters class * { + void set*(***); + *** get*(); +} + +-keep class * extends com.facebook.react.bridge.JavaScriptModule { *; } +-keep class * extends com.facebook.react.bridge.NativeModule { *; } +-keepclassmembers,includedescriptorclasses class * { native ; } +-keepclassmembers class * { @com.facebook.react.uimanager.UIProp ; } +-keepclassmembers class * { @com.facebook.react.uimanager.ReactProp ; } +-keepclassmembers class * { @com.facebook.react.uimanager.ReactPropGroup ; } + +-dontwarn com.facebook.react.** + +# okhttp + +-keepattributes Signature +-keepattributes *Annotation* +-keep class com.squareup.okhttp.** { *; } +-keep interface com.squareup.okhttp.** { *; } +-dontwarn com.squareup.okhttp.** + +# okio + +-keep class sun.misc.Unsafe { *; } +-dontwarn java.nio.file.* +-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement +-dontwarn okio.** + +# stetho + +-dontwarn com.facebook.stetho.** diff --git a/Examples/CodePushDemoApp/android/app/react.gradle b/Examples/CodePushDemoApp/android/app/react.gradle index e6e16d3..a540d46 100644 --- a/Examples/CodePushDemoApp/android/app/react.gradle +++ b/Examples/CodePushDemoApp/android/app/react.gradle @@ -1,74 +1,96 @@ +import org.apache.tools.ant.taskdefs.condition.Os + def config = project.hasProperty("react") ? project.react : []; def bundleAssetName = config.bundleAssetName ?: "index.android.bundle" def entryFile = config.entryFile ?: "index.android.js" +// because elvis operator def elvisFile(thing) { return thing ? file(thing) : null; } def reactRoot = elvisFile(config.root) ?: file("../../") -def jsBundleDirDebug = elvisFile(config.jsBundleDirDebug) ?: - file("$buildDir/intermediates/assets/debug") -def jsBundleDirRelease = elvisFile(config.jsBundleDirRelease) ?: - file("$buildDir/intermediates/assets/release") -def resourcesDirDebug = elvisFile(config.resourcesDirDebug) ?: - file("$buildDir/intermediates/res/merged/debug") -def resourcesDirRelease = elvisFile(config.resourcesDirRelease) ?: - file("$buildDir/intermediates/res/merged/release") def inputExcludes = config.inputExcludes ?: ["android/**", "ios/**"] -def jsBundleFileDebug = file("$jsBundleDirDebug/$bundleAssetName") -def jsBundleFileRelease = file("$jsBundleDirRelease/$bundleAssetName") - -task bundleDebugJsAndAssets(type: Exec) { - // create dirs if they are not there (e.g. the "clean" task just ran) - doFirst { - jsBundleDirDebug.mkdirs() - resourcesDirDebug.mkdirs() +void runBefore(String dependentTaskName, Task task) { + Task dependentTask = tasks.findByPath(dependentTaskName); + if (dependentTask != null) { + dependentTask.dependsOn task } - - // set up inputs and outputs so gradle can cache the result - inputs.files fileTree(dir: reactRoot, excludes: inputExcludes) - outputs.dir jsBundleDirDebug - outputs.dir resourcesDirDebug - - // set up the call to the react-native cli - workingDir reactRoot - commandLine "react-native", "bundle", "--platform", "android", "--dev", "true", "--entry-file", - entryFile, "--bundle-output", jsBundleFileDebug, "--assets-dest", resourcesDirDebug - - enabled config.bundleInDebug ?: true -} - -task bundleReleaseJsAndAssets(type: Exec) { - // create dirs if they are not there (e.g. the "clean" task just ran) - doFirst { - jsBundleDirRelease.mkdirs() - resourcesDirRelease.mkdirs() - } - - // set up inputs and outputs so gradle can cache the result - inputs.files fileTree(dir: reactRoot, excludes: inputExcludes) - outputs.dir jsBundleDirRelease - outputs.dir resourcesDirRelease - - // set up the call to the react-native cli - workingDir reactRoot - commandLine "react-native", "bundle", "--platform", "android", "--dev", "false", "--entry-file", - entryFile, "--bundle-output", jsBundleFileRelease, "--assets-dest", resourcesDirRelease - - enabled config.bundleInRelease ?: true } gradle.projectsEvaluated { - // hook bundleDebugJsAndAssets into the android build process - bundleDebugJsAndAssets.dependsOn mergeDebugResources - bundleDebugJsAndAssets.dependsOn mergeDebugAssets - processDebugResources.dependsOn bundleDebugJsAndAssets + // Grab all build types and product flavors + def buildTypes = android.buildTypes.collect { type -> type.name } + def productFlavors = android.productFlavors.collect { flavor -> flavor.name } - // hook bundleReleaseJsAndAssets into the android build process - bundleReleaseJsAndAssets.dependsOn mergeReleaseResources - bundleReleaseJsAndAssets.dependsOn mergeReleaseAssets - processReleaseResources.dependsOn bundleReleaseJsAndAssets + // When no product flavors defined, use empty + if (!productFlavors) productFlavors.add('') + + productFlavors.each { productFlavorName -> + buildTypes.each { buildTypeName -> + // Create variant and source names + def sourceName = "${buildTypeName}" + def targetName = "${sourceName.capitalize()}" + if (productFlavorName) { + sourceName = "${productFlavorName}${targetName}" + } + + // React js bundle directories + def jsBundleDirConfigName = "jsBundleDir${targetName}" + def jsBundleDir = elvisFile(config."$jsBundleDirConfigName") ?: + file("$buildDir/intermediates/assets/${sourceName}") + + def resourcesDirConfigName = "jsBundleDir${targetName}" + def resourcesDir = elvisFile(config."${resourcesDirConfigName}") ?: + file("$buildDir/intermediates/res/merged/${sourceName}") + def jsBundleFile = file("$jsBundleDir/$bundleAssetName") + + // Bundle task name for variant + def bundleJsAndAssetsTaskName = "bundle${targetName}JsAndAssets" + + def currentBundleTask = tasks.create( + name: bundleJsAndAssetsTaskName, + type: Exec) { + group = "react" + description = "bundle JS and assets for ${targetName}." + + // Create dirs if they are not there (e.g. the "clean" task just ran) + doFirst { + jsBundleDir.mkdirs() + resourcesDir.mkdirs() + } + + // Set up inputs and outputs so gradle can cache the result + inputs.files fileTree(dir: reactRoot, excludes: inputExcludes) + outputs.dir jsBundleDir + outputs.dir resourcesDir + + // Set up the call to the react-native cli + workingDir reactRoot + + // Set up dev mode + def devEnabled = !targetName.toLowerCase().contains("release") + if (Os.isFamily(Os.FAMILY_WINDOWS)) { + commandLine "cmd", "/c", "react-native", "bundle", "--platform", "android", "--dev", "${devEnabled}", + "--entry-file", entryFile, "--bundle-output", jsBundleFile, "--assets-dest", resourcesDir + } else { + commandLine "react-native", "bundle", "--platform", "android", "--dev", "${devEnabled}", + "--entry-file", entryFile, "--bundle-output", jsBundleFile, "--assets-dest", resourcesDir + } + + enabled config."bundleIn${targetName}" ?: true + } + + // Hook bundle${productFlavor}${buildType}JsAndAssets into the android build process + currentBundleTask.dependsOn("merge${targetName}Resources") + currentBundleTask.dependsOn("merge${targetName}Assets") + + runBefore("processArmeabi-v7a${targetName}Resources", currentBundleTask) + runBefore("processX86${targetName}Resources", currentBundleTask) + runBefore("processUniversal${targetName}Resources", currentBundleTask) + runBefore("process${targetName}Resources", currentBundleTask) + } + } } diff --git a/Examples/CodePushDemoApp/android/app/src/main/java/com/microsoft/codepushdemoapp/MainActivity.java b/Examples/CodePushDemoApp/android/app/src/main/java/com/microsoft/codepushdemoapp/MainActivity.java index be2b9ce..f331cd9 100644 --- a/Examples/CodePushDemoApp/android/app/src/main/java/com/microsoft/codepushdemoapp/MainActivity.java +++ b/Examples/CodePushDemoApp/android/app/src/main/java/com/microsoft/codepushdemoapp/MainActivity.java @@ -1,78 +1,57 @@ package com.microsoft.codepushdemoapp; import android.os.Bundle; -import android.support.v4.app.FragmentActivity; -import android.view.KeyEvent; -import com.facebook.react.LifecycleState; -import com.facebook.react.ReactInstanceManager; -import com.facebook.react.ReactRootView; -import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler; +import com.facebook.react.ReactActivity; +import com.facebook.react.ReactPackage; import com.facebook.react.shell.MainReactPackage; -import com.facebook.soloader.SoLoader; import com.microsoft.codepush.react.CodePush; -public class MainActivity extends FragmentActivity implements DefaultHardwareBackBtnHandler { +import java.util.Arrays; +import java.util.List; - private ReactInstanceManager mReactInstanceManager; - private ReactRootView mReactRootView; +public class MainActivity extends ReactActivity { private CodePush codePush; @Override protected void onCreate(Bundle savedInstanceState) { + codePush = new CodePush("deployment-key-here", this, BuildConfig.DEBUG); super.onCreate(savedInstanceState); - mReactRootView = new ReactRootView(this); - - codePush = new CodePush("DEPLOYMENT_KEY_HERE", this, BuildConfig.DEBUG); - - ReactInstanceManager.Builder builder = ReactInstanceManager.builder() - .setApplication(getApplication()) - .setJSBundleFile(codePush.getBundleUrl("index.android.bundle")) - .setJSMainModuleName("index.android"); - - String mainComponentName = "CodePushDemoApp"; - - mReactInstanceManager = builder.addPackage(new MainReactPackage()) - .addPackage(codePush.getReactPackage()) - .setUseDeveloperSupport(true) - .setInitialLifecycleState(LifecycleState.RESUMED) - .build(); - - mReactRootView.startReactApplication(mReactInstanceManager, mainComponentName, null); - - setContentView(mReactRootView); } @Override - public boolean onKeyUp(int keyCode, KeyEvent event) { - if (keyCode == KeyEvent.KEYCODE_MENU && mReactInstanceManager != null) { - mReactInstanceManager.showDevOptionsDialog(); - return true; - } - return super.onKeyUp(keyCode, event); + protected String getJSBundleFile() { + return this.codePush.getBundleUrl("index.android.bundle"); } + /** + * Returns the name of the main component registered from JavaScript. + * This is used to schedule rendering of the component. + */ @Override - public void invokeDefaultOnBackPressed() { - super.onBackPressed(); + protected String getMainComponentName() { + return "CodePushDemoApp"; } + /** + * Returns whether dev mode should be enabled. + * This enables e.g. the dev menu. + */ @Override - protected void onPause() { - super.onPause(); - - if (mReactInstanceManager != null) { - mReactInstanceManager.onPause(); - } + protected boolean getUseDeveloperSupport() { + return BuildConfig.DEBUG; } + /** + * A list of packages used by the app. If the app uses additional views + * or modules besides the default ones, add more packages here. + */ @Override - protected void onResume() { - super.onResume(); - - if (mReactInstanceManager != null) { - mReactInstanceManager.onResume(this, this); - } + protected List getPackages() { + return Arrays.asList( + new MainReactPackage(), + this.codePush.getReactPackage() + ); } } diff --git a/Examples/CodePushDemoApp/android/build.gradle b/Examples/CodePushDemoApp/android/build.gradle index 32bc6f5..ccdfc4e 100644 --- a/Examples/CodePushDemoApp/android/build.gradle +++ b/Examples/CodePushDemoApp/android/build.gradle @@ -5,7 +5,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:1.3.0' + classpath 'com.android.tools.build:gradle:1.3.1' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/Examples/CodePushDemoApp/package.json b/Examples/CodePushDemoApp/package.json index 0ea0780..0a00f4d 100644 --- a/Examples/CodePushDemoApp/package.json +++ b/Examples/CodePushDemoApp/package.json @@ -6,7 +6,7 @@ "start": "node_modules/react-native/packager/packager.sh --root ../../" }, "dependencies": { - "react-native": "0.15.0", + "react-native": "0.19.0", "react-native-button": "^1.2.0", "react-native-code-push": "file:../../" }, diff --git a/android/app/src/main/java/com/microsoft/codepush/react/CodePushPackage.java b/android/app/src/main/java/com/microsoft/codepush/react/CodePushPackage.java index 80645cb..faa20b9 100644 --- a/android/app/src/main/java/com/microsoft/codepush/react/CodePushPackage.java +++ b/android/app/src/main/java/com/microsoft/codepush/react/CodePushPackage.java @@ -2,7 +2,6 @@ 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; @@ -157,7 +156,6 @@ public class CodePushPackage { String newPackageFolderPath = getPackageFolderPath(CodePushUtils.tryGetString(updatePackage, PACKAGE_HASH_KEY)); String downloadUrlString = CodePushUtils.tryGetString(updatePackage, DOWNLOAD_URL_KEY); - URL downloadUrl = null; HttpURLConnection connection = null; BufferedInputStream bin = null; @@ -220,7 +218,7 @@ public class CodePushPackage { // Unzip the downloaded file and then delete the zip String unzippedFolderPath = getUnzippedFolderPath(); FileUtils.unzipFile(downloadFile, unzippedFolderPath); - FileUtils.deleteFileSilently(downloadFile); + FileUtils.deleteFileOrFolderSilently(downloadFile); // Merge contents with current update based on the manifest String diffManifestFilePath = CodePushUtils.appendPathComponent(unzippedFolderPath, @@ -248,13 +246,12 @@ public class CodePushPackage { 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); + // File is a jsbundle, move it to a folder with the packageHash as its name + FileUtils.moveFile(downloadFile, newPackageFolderPath, UPDATE_BUNDLE_FILE_NAME); } // Save metadata to the folder. diff --git a/android/app/src/main/java/com/microsoft/codepush/react/CodePushUpdateUtils.java b/android/app/src/main/java/com/microsoft/codepush/react/CodePushUpdateUtils.java index a8cd21c..8e43847 100644 --- a/android/app/src/main/java/com/microsoft/codepush/react/CodePushUpdateUtils.java +++ b/android/app/src/main/java/com/microsoft/codepush/react/CodePushUpdateUtils.java @@ -15,7 +15,7 @@ public class CodePushUpdateUtils { for (int i = 0; i < deletedFiles.size(); i++) { String fileNameToDelete = deletedFiles.getString(i); File fileToDelete = new File(newPackageFolderPath, fileNameToDelete); - FileUtils.deleteFileSilently(fileToDelete); + FileUtils.deleteFileOrFolderSilently(fileToDelete); } } diff --git a/android/app/src/main/java/com/microsoft/codepush/react/FileUtils.java b/android/app/src/main/java/com/microsoft/codepush/react/FileUtils.java index 113df51..3275363 100644 --- a/android/app/src/main/java/com/microsoft/codepush/react/FileUtils.java +++ b/android/app/src/main/java/com/microsoft/codepush/react/FileUtils.java @@ -9,6 +9,7 @@ import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; import java.util.zip.ZipInputStream; public class FileUtils { @@ -81,10 +82,23 @@ public class FileUtils { } public static void deleteFileAtPathSilently(String path) { - deleteFileSilently(new File(path)); + deleteFileOrFolderSilently(new File(path)); } - public static void deleteFileSilently(File file) { + public static void deleteFileOrFolderSilently(File file) { + if (file.isDirectory()) { + File[] files = file.listFiles(); + for (int i = 0; i < files.length; i++) { + if (files[i].isDirectory()) { + deleteFileOrFolderSilently(files[i]); + } else { + if (!file.delete()) { + files[i].delete(); + } + } + } + } + if (!file.delete()) { CodePushUtils.log("Error deleting file " + file.getName()); } @@ -94,6 +108,19 @@ public class FileUtils { return new File(filePath).exists(); } + public static void moveFile(File fileToMove, String newFolderPath, String newFileName) { + File newFolder = new File(newFolderPath); + if (!newFolder.exists()) { + newFolder.mkdirs(); + } + + File newFilePath = new File(newFolderPath, newFileName); + if (!fileToMove.renameTo(newFilePath)) { + throw new CodePushUnknownException("Unable to move file from " + + fileToMove.getAbsolutePath() + " to " + newFilePath.getAbsolutePath() + "."); + } + } + public static String readFileToString(String filePath) throws IOException { FileInputStream fin = null; BufferedReader reader = null;