From 063c14ab45b782c4b99c118c71b363477aeb30f4 Mon Sep 17 00:00:00 2001
From: tdzl2003 <tdzl2003@gmail.com>
Date: Mon, 4 Apr 2016 23:02:28 +0800
Subject: [PATCH] bugfixes

---
 .../modules/update/DownloadTask.java          | 294 +++++++++---------
 .../modules/update/DownloadTaskParams.java    |   2 +
 .../modules/update/UpdateContext.java         | 123 +++++++-
 .../modules/update/UpdateModule.java          |  46 ++-
 index.js                                      |  28 +-
 lib/hooks.js                                  |   8 -
 lib/index.js                                  |  84 +++++
 lib/resolveAssetSource.js                     | 144 ---------
 local-cli/src/api.js                          |   9 +-
 local-cli/src/package.js                      |   2 +-
 10 files changed, 394 insertions(+), 346 deletions(-)
 delete mode 100644 lib/hooks.js
 create mode 100644 lib/index.js
 delete mode 100644 lib/resolveAssetSource.js

diff --git a/android/src/main/java/cn/reactnative/modules/update/DownloadTask.java b/android/src/main/java/cn/reactnative/modules/update/DownloadTask.java
index 5febe47..91974e2 100644
--- a/android/src/main/java/cn/reactnative/modules/update/DownloadTask.java
+++ b/android/src/main/java/cn/reactnative/modules/update/DownloadTask.java
@@ -9,6 +9,7 @@ import com.squareup.okhttp.Request;
 import com.squareup.okhttp.Response;
 import com.squareup.okhttp.ResponseBody;
 
+import org.json.JSONException;
 import org.json.JSONObject;
 import org.json.JSONTokener;
 
@@ -212,45 +213,37 @@ class DownloadTask extends AsyncTask<DownloadTaskParams, Void, Void> {
         copyFilesWithBlacklist("", from, to, blackList);
     }
 
-    private void doDownload(DownloadTaskParams param) {
-        try {
-            downloadFile(param.url, param.zipFilePath);
+    private void doDownload(DownloadTaskParams param) throws IOException {
+        downloadFile(param.url, param.zipFilePath);
 
-            ZipInputStream zis = new ZipInputStream(new BufferedInputStream(new FileInputStream(param.zipFilePath)));
-            ZipEntry ze;
-            String filename;
+        ZipInputStream zis = new ZipInputStream(new BufferedInputStream(new FileInputStream(param.zipFilePath)));
+        ZipEntry ze;
+        String filename;
 
-            removeDirectory(param.unzipDirectory);
-            param.unzipDirectory.mkdirs();
+        removeDirectory(param.unzipDirectory);
+        param.unzipDirectory.mkdirs();
 
-            while ((ze = zis.getNextEntry()) != null)
-            {
-                String fn = ze.getName();
-                File fmd = new File(param.unzipDirectory, fn);
-
-                if (UpdateContext.DEBUG) {
-                    Log.d("RNUpdate", "Unzipping " + fn);
-                }
-
-                if (ze.isDirectory()) {
-                    fmd.mkdirs();
-                    continue;
-                }
-
-                unzipToFile(zis, fmd);
-            }
-
-            zis.close();
+        while ((ze = zis.getNextEntry()) != null)
+        {
+            String fn = ze.getName();
+            File fmd = new File(param.unzipDirectory, fn);
 
             if (UpdateContext.DEBUG) {
-                Log.d("RNUpdate", "Unzip finished");
+                Log.d("RNUpdate", "Unzipping " + fn);
             }
 
-        } catch (Throwable e) {
-            if (UpdateContext.DEBUG) {
-                e.printStackTrace();
+            if (ze.isDirectory()) {
+                fmd.mkdirs();
+                continue;
             }
-            param.listener.onDownloadFailed(e);
+
+            unzipToFile(zis, fmd);
+        }
+
+        zis.close();
+
+        if (UpdateContext.DEBUG) {
+            Log.d("RNUpdate", "Unzip finished");
         }
     }
 
@@ -268,162 +261,161 @@ class DownloadTask extends AsyncTask<DownloadTaskParams, Void, Void> {
         }
     }
 
-    private void doPatchFromApk(DownloadTaskParams param) {
-        try {
-            downloadFile(param.url, param.zipFilePath);
+    private void doPatchFromApk(DownloadTaskParams param) throws IOException, JSONException {
+        downloadFile(param.url, param.zipFilePath);
 
-            ZipInputStream zis = new ZipInputStream(new BufferedInputStream(new FileInputStream(param.zipFilePath)));
-            ZipEntry ze;
-            int count;
-            String filename;
+        ZipInputStream zis = new ZipInputStream(new BufferedInputStream(new FileInputStream(param.zipFilePath)));
+        ZipEntry ze;
+        int count;
+        String filename;
 
-            removeDirectory(param.unzipDirectory);
-            param.unzipDirectory.mkdirs();
+        removeDirectory(param.unzipDirectory);
+        param.unzipDirectory.mkdirs();
 
-            while ((ze = zis.getNextEntry()) != null)
-            {
-                String fn = ze.getName();
+        while ((ze = zis.getNextEntry()) != null)
+        {
+            String fn = ze.getName();
 
-                if (fn.equals("__diff.json")) {
-                    // copy files from assets
-                    byte[] bytes = readBytes(zis);
-                    String json = new String(bytes, "UTF-8");
-                    JSONObject obj = (JSONObject)new JSONTokener(json).nextValue();
+            if (fn.equals("__diff.json")) {
+                // copy files from assets
+                byte[] bytes = readBytes(zis);
+                String json = new String(bytes, "UTF-8");
+                JSONObject obj = (JSONObject)new JSONTokener(json).nextValue();
 
-                    JSONObject copies = obj.getJSONObject("copies");
-                    Iterator<?> keys = copies.keys();
-                    while( keys.hasNext() ) {
-                        String to = (String)keys.next();
-                        String from = copies.getString(to);
-                        if (from.isEmpty()) {
-                            from = to;
-                        }
-                        copyFromResource(from, new File(param.unzipDirectory, to));
+                JSONObject copies = obj.getJSONObject("copies");
+                Iterator<?> keys = copies.keys();
+                while( keys.hasNext() ) {
+                    String to = (String)keys.next();
+                    String from = copies.getString(to);
+                    if (from.isEmpty()) {
+                        from = to;
                     }
-                    continue;
+                    copyFromResource(from, new File(param.unzipDirectory, to));
                 }
-                if (fn.equals("index.bundlejs.patch")) {
-                    // do bsdiff patch
-                    byte[] patched = bsdiffPatch(readOriginBundle(), readBytes(zis));
-
-                    FileOutputStream fout = new FileOutputStream(new File(param.unzipDirectory, "index.bundlejs"));
-                    fout.write(patched);
-                    fout.close();
-                    continue;
-                }
-                File fmd = new File(param.unzipDirectory, fn);
-
-                if (UpdateContext.DEBUG) {
-                    Log.d("RNUpdate", "Unzipping " + fn);
-                }
-
-                if (ze.isDirectory()) {
-                    fmd.mkdirs();
-                    continue;
-                }
-
-                unzipToFile(zis, fmd);
+                continue;
             }
+            if (fn.equals("index.bundlejs.patch")) {
+                // do bsdiff patch
+                byte[] patched = bsdiffPatch(readOriginBundle(), readBytes(zis));
 
-            zis.close();
+                FileOutputStream fout = new FileOutputStream(new File(param.unzipDirectory, "index.bundlejs"));
+                fout.write(patched);
+                fout.close();
+                continue;
+            }
+            File fmd = new File(param.unzipDirectory, fn);
 
             if (UpdateContext.DEBUG) {
-                Log.d("RNUpdate", "Unzip finished");
+                Log.d("RNUpdate", "Unzipping " + fn);
             }
 
-        } catch (Throwable e) {
-            if (UpdateContext.DEBUG) {
-                e.printStackTrace();
+            if (ze.isDirectory()) {
+                fmd.mkdirs();
+                continue;
             }
-            param.listener.onDownloadFailed(e);
+
+            unzipToFile(zis, fmd);
         }
+
+        zis.close();
+
+        if (UpdateContext.DEBUG) {
+            Log.d("RNUpdate", "Unzip finished");
+        }
+
     }
 
-    private void doPatchFromPpk(DownloadTaskParams param) {
-        try {
-            downloadFile(param.url, param.zipFilePath);
+    private void doPatchFromPpk(DownloadTaskParams param) throws IOException, JSONException {
+        downloadFile(param.url, param.zipFilePath);
 
-            ZipInputStream zis = new ZipInputStream(new BufferedInputStream(new FileInputStream(param.zipFilePath)));
-            ZipEntry ze;
-            int count;
-            String filename;
+        ZipInputStream zis = new ZipInputStream(new BufferedInputStream(new FileInputStream(param.zipFilePath)));
+        ZipEntry ze;
+        int count;
+        String filename;
 
-            removeDirectory(param.unzipDirectory);
-            param.unzipDirectory.mkdirs();
+        removeDirectory(param.unzipDirectory);
+        param.unzipDirectory.mkdirs();
 
-            while ((ze = zis.getNextEntry()) != null)
-            {
-                String fn = ze.getName();
+        while ((ze = zis.getNextEntry()) != null)
+        {
+            String fn = ze.getName();
 
-                if (fn.equals("__diff.json")) {
-                    // copy files from assets
-                    byte[] bytes = readBytes(zis);
-                    String json = new String(bytes, "UTF-8");
-                    JSONObject obj = (JSONObject)new JSONTokener(json).nextValue();
+            if (fn.equals("__diff.json")) {
+                // copy files from assets
+                byte[] bytes = readBytes(zis);
+                String json = new String(bytes, "UTF-8");
+                JSONObject obj = (JSONObject)new JSONTokener(json).nextValue();
 
-                    JSONObject copies = obj.getJSONObject("copies");
-                    Iterator<?> keys = copies.keys();
-                    while( keys.hasNext() ) {
-                        String to = (String)keys.next();
-                        String from = copies.getString(to);
-                        if (from.isEmpty()) {
-                            from = to;
-                        }
-                        copyFile(new File(param.originDirectory, from), new File(param.unzipDirectory, to));
+                JSONObject copies = obj.getJSONObject("copies");
+                Iterator<?> keys = copies.keys();
+                while( keys.hasNext() ) {
+                    String to = (String)keys.next();
+                    String from = copies.getString(to);
+                    if (from.isEmpty()) {
+                        from = to;
                     }
-                    JSONObject blackList = obj.getJSONObject("deletes");
-                    copyFilesWithBlacklist(param.originDirectory, param.unzipDirectory, blackList);
-                    continue;
+                    copyFile(new File(param.originDirectory, from), new File(param.unzipDirectory, to));
                 }
-                if (fn.equals("index.bundlejs.patch")) {
-                    // do bsdiff patch
-                    byte[] patched = bsdiffPatch(readFile(new File(param.originDirectory, "index.bundlejs")), readBytes(zis));
-
-                    FileOutputStream fout = new FileOutputStream(new File(param.unzipDirectory, "index.bundlejs"));
-                    fout.write(patched);
-                    fout.close();
-                    continue;
-                }
-                File fmd = new File(param.unzipDirectory, fn);
-
-                if (UpdateContext.DEBUG) {
-                    Log.d("RNUpdate", "Unzipping " + fn);
-                }
-
-                if (ze.isDirectory()) {
-                    fmd.mkdirs();
-                    continue;
-                }
-
-                unzipToFile(zis, fmd);
+                JSONObject blackList = obj.getJSONObject("deletes");
+                copyFilesWithBlacklist(param.originDirectory, param.unzipDirectory, blackList);
+                continue;
             }
+            if (fn.equals("index.bundlejs.patch")) {
+                // do bsdiff patch
+                byte[] patched = bsdiffPatch(readFile(new File(param.originDirectory, "index.bundlejs")), readBytes(zis));
 
-            zis.close();
+                FileOutputStream fout = new FileOutputStream(new File(param.unzipDirectory, "index.bundlejs"));
+                fout.write(patched);
+                fout.close();
+                continue;
+            }
+            File fmd = new File(param.unzipDirectory, fn);
 
             if (UpdateContext.DEBUG) {
-                Log.d("RNUpdate", "Unzip finished");
+                Log.d("RNUpdate", "Unzipping " + fn);
             }
 
-        } catch (Throwable e) {
-            if (UpdateContext.DEBUG) {
-                e.printStackTrace();
+            if (ze.isDirectory()) {
+                fmd.mkdirs();
+                continue;
             }
-            param.listener.onDownloadFailed(e);
+
+            unzipToFile(zis, fmd);
         }
+
+        zis.close();
+
+        if (UpdateContext.DEBUG) {
+            Log.d("RNUpdate", "Unzip finished");
+        }
+    }
+    private void doCleanUp(DownloadTaskParams param) {
+
     }
 
     @Override
     protected Void doInBackground(DownloadTaskParams... params) {
-        switch (params[0].type) {
-            case DownloadTaskParams.TASK_TYPE_FULL_DOWNLOAD:
-                doDownload(params[0]);
-                break;
-            case DownloadTaskParams.TASK_TYPE_PATCH_FROM_APK:
-                doPatchFromApk(params[0]);
-                break;
-            case DownloadTaskParams.TASK_TYPE_PATCH_FROM_PPK:
-                doPatchFromPpk(params[0]);
-                break;
+        try {
+            switch (params[0].type) {
+                case DownloadTaskParams.TASK_TYPE_FULL_DOWNLOAD:
+                    doDownload(params[0]);
+                    break;
+                case DownloadTaskParams.TASK_TYPE_PATCH_FROM_APK:
+                    doPatchFromApk(params[0]);
+                    break;
+                case DownloadTaskParams.TASK_TYPE_PATCH_FROM_PPK:
+                    doPatchFromPpk(params[0]);
+                    break;
+                case DownloadTaskParams.TASK_TYPE_CLEARUP:
+                    doCleanUp(params[0]);
+                    break;
+            }
+            params[0].listener.onDownloadCompleted();
+        } catch (Throwable e) {
+            if (UpdateContext.DEBUG) {
+                e.printStackTrace();
+            }
+            params[0].listener.onDownloadFailed(e);
         }
         return null;
     }
diff --git a/android/src/main/java/cn/reactnative/modules/update/DownloadTaskParams.java b/android/src/main/java/cn/reactnative/modules/update/DownloadTaskParams.java
index a7d29a4..042cb69 100644
--- a/android/src/main/java/cn/reactnative/modules/update/DownloadTaskParams.java
+++ b/android/src/main/java/cn/reactnative/modules/update/DownloadTaskParams.java
@@ -12,6 +12,8 @@ class DownloadTaskParams {
     static final int TASK_TYPE_PATCH_FROM_APK = 2;
     static final int TASK_TYPE_PATCH_FROM_PPK = 3;
 
+    static final int TASK_TYPE_CLEARUP = 0; //Keep hash & originHash
+
     int         type;
     String      url;
     String      hash;
diff --git a/android/src/main/java/cn/reactnative/modules/update/UpdateContext.java b/android/src/main/java/cn/reactnative/modules/update/UpdateContext.java
index c7db479..44790a8 100644
--- a/android/src/main/java/cn/reactnative/modules/update/UpdateContext.java
+++ b/android/src/main/java/cn/reactnative/modules/update/UpdateContext.java
@@ -1,8 +1,12 @@
 package cn.reactnative.modules.update;
 
 import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
 
 import java.io.File;
+import java.net.URL;
 
 /**
  * Created by tdzl2003 on 3/31/16.
@@ -13,13 +17,6 @@ public class UpdateContext {
 
     public static boolean DEBUG = false;
 
-
-    private static UpdateContext currentInstance = null;
-
-    static UpdateContext instance() {
-        return currentInstance;
-    }
-
     public UpdateContext(Context context) {
         this.context = context;
 
@@ -28,12 +25,26 @@ public class UpdateContext {
         if (!rootDir.exists()) {
             rootDir.mkdir();
         }
+
+        this.sp = context.getSharedPreferences("update", Context.MODE_PRIVATE);
     }
 
     public String getRootDir() {
         return rootDir.toString();
     }
 
+    public String getPackageVersion() {
+        PackageManager pm = context.getPackageManager();
+        PackageInfo pi = null;
+        try {
+            pi = pm.getPackageInfo(context.getPackageName(), 0);
+            return pi.versionName;
+        } catch( PackageManager.NameNotFoundException e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+
     public interface DownloadFileListener {
         void onDownloadCompleted();
         void onDownloadFailed(Throwable error);
@@ -73,4 +84,102 @@ public class UpdateContext {
         params.originDirectory = new File(rootDir, originHashName);
         new DownloadTask(context).execute(params);
     }
+
+    private SharedPreferences sp;
+
+    public void switchVersion(String hashName) {
+        if (!new File(rootDir, hashName).exists()) {
+            throw new Error("Hash name not found, must download first.");
+        }
+        String lastVersion = getCurrentVersion();
+        SharedPreferences.Editor editor = sp.edit();
+        editor.putString("currentVersion", hashName);
+        if (lastVersion != null) {
+            editor.putString("lastVersion", hashName);
+        }
+        editor.putBoolean("firstTime", true);
+        editor.putBoolean("shouldRollback", false);
+        editor.putBoolean("rolledBack", false);
+        editor.apply();
+    }
+
+    public String getCurrentVersion() {
+        return sp.getString("currentVersion", null);
+    }
+
+    public boolean isFirstTime() {
+        return sp.getBoolean("firstTime", false);
+    }
+
+    public boolean isRolledBack() {
+        return sp.getBoolean("rolledBack", false);
+    }
+
+    public void clearFirstTimeMark() {
+        SharedPreferences.Editor editor = sp.edit();
+        editor.putBoolean("firstTime", false);
+        editor.putBoolean("shouldRollback", false);
+        editor.apply();
+
+        this.clearUp();
+    }
+
+    public static String getBundleUrl(Context context) {
+        return new UpdateContext(context.getApplicationContext()).getBundleUrl();
+    }
+
+    public static String getBundleUrl(Context context, String defaultAssetsUrl) {
+        return new UpdateContext(context.getApplicationContext()).getBundleUrl(defaultAssetsUrl);
+    }
+
+    public String getBundleUrl() {
+        return getBundleUrl("assets://index.android.bundle");
+    }
+
+    public String getBundleUrl(String defaultAssetsUrl) {
+        // Test should rollback.
+        if (sp.getBoolean("shouldRollback", false)) {
+            this.rollBack();
+        }
+        String currentVersion = getCurrentVersion();
+        if (currentVersion == null) {
+            return defaultAssetsUrl;
+        }
+        if (sp.getBoolean("firstTime", false)) {
+            SharedPreferences.Editor editor = sp.edit();
+            editor.putBoolean("shouldRollback", true);
+            editor.apply();
+        }
+        return new File(rootDir, currentVersion+"/index.bundlejs").toURI().toString();
+    }
+
+    private void rollBack() {
+        String lastVersion = sp.getString("lastVersion", null);
+        if (lastVersion == null) {
+            throw new Error("This should never happen");
+        }
+        SharedPreferences.Editor editor = sp.edit();
+        editor.putString("currentVersion", lastVersion);
+        editor.putBoolean("shouldRollback", false);
+        editor.putBoolean("firstTime", false);
+        editor.putBoolean("rolledBack", true);
+        editor.apply();
+    }
+
+    private void clearUp() {
+        DownloadTaskParams params = new DownloadTaskParams();
+        params.type = DownloadTaskParams.TASK_TYPE_CLEARUP;
+        params.hash = sp.getString("currentVersion", null);
+        params.originHash = sp.getString("lastVersion", null);;
+        params.listener = new DownloadFileListener() {
+            @Override
+            public void onDownloadCompleted() {
+            }
+
+            @Override
+            public void onDownloadFailed(Throwable error) {
+            }
+        };
+        new DownloadTask(context).execute(params);
+    }
 }
diff --git a/android/src/main/java/cn/reactnative/modules/update/UpdateModule.java b/android/src/main/java/cn/reactnative/modules/update/UpdateModule.java
index ac9cf3f..447b4fc 100644
--- a/android/src/main/java/cn/reactnative/modules/update/UpdateModule.java
+++ b/android/src/main/java/cn/reactnative/modules/update/UpdateModule.java
@@ -1,10 +1,14 @@
 package cn.reactnative.modules.update;
 
+import android.app.Activity;
+import android.content.Intent;
+
 import com.facebook.react.bridge.Promise;
 import com.facebook.react.bridge.ReactApplicationContext;
 import com.facebook.react.bridge.ReactContextBaseJavaModule;
 import com.facebook.react.bridge.ReactMethod;
 import com.facebook.react.bridge.ReadableMap;
+import com.facebook.react.bridge.UiThreadUtil;
 
 import java.util.HashMap;
 import java.util.Map;
@@ -15,15 +19,23 @@ import java.util.Map;
 public class UpdateModule extends ReactContextBaseJavaModule{
     UpdateContext updateContext;
 
-    public UpdateModule(ReactApplicationContext reactContext) {
+    public UpdateModule(ReactApplicationContext reactContext, UpdateContext updateContext) {
         super(reactContext);
-        this.updateContext = new UpdateContext(reactContext.getApplicationContext());
+        this.updateContext = updateContext;
+    }
+
+    public UpdateModule(ReactApplicationContext reactContext) {
+        this(reactContext, new UpdateContext(reactContext.getApplicationContext()));
     }
 
     @Override
     public Map<String, Object> getConstants() {
         final Map<String, Object> constants = new HashMap<>();
         constants.put("downloadRootDir", updateContext.getRootDir());
+        constants.put("packageVersion", updateContext.getPackageVersion());
+        constants.put("currentVersion", updateContext.getCurrentVersion());
+        constants.put("firstTime", updateContext.isFirstTime());
+        constants.put("rolledBack", updateContext.isRolledBack());
         return constants;
     }
 
@@ -50,7 +62,7 @@ public class UpdateModule extends ReactContextBaseJavaModule{
     }
 
     @ReactMethod
-    public void downloadPatchFromApk(ReadableMap options, final Promise promise){
+    public void downloadPatchFromPackage(ReadableMap options, final Promise promise){
         String url = options.getString("updateUrl");
         String hash = options.getString("hashName");
         updateContext.downloadPatchFromApk(url, hash, new UpdateContext.DownloadFileListener() {
@@ -83,4 +95,32 @@ public class UpdateModule extends ReactContextBaseJavaModule{
             }
         });
     }
+
+    @ReactMethod
+    public void reloadUpdate(ReadableMap options) {
+        final String hash = options.getString("hashName");
+
+        UiThreadUtil.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                updateContext.switchVersion(hash);
+                Activity activity = getCurrentActivity();
+                Intent intent = activity.getIntent();
+                activity.finish();
+                activity.startActivity(intent);
+            }
+        });
+    }
+
+    @ReactMethod
+    public void setNeedUpdate(ReadableMap options) {
+        final String hash = options.getString("hashName");
+
+        UiThreadUtil.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                updateContext.switchVersion(hash);
+            }
+        });
+    }
 }
diff --git a/index.js b/index.js
index ef094c3..0250df9 100644
--- a/index.js
+++ b/index.js
@@ -3,30 +3,4 @@
  * @providesModule react-native-update
  */
 
-//if (__DEV__){
-//  if (global.__fbBatchedBridge) {
-//    require('fbjs/lib/warning')('Should require pushy before react-native to do hook stuff!');
-//  }
-//}
-//
-//require('./lib/hooks');
-const HotUpdate = require('react-native').NativeModules.HotUpdate;
-const NativeAppEventEmitter = require('react-native').NativeAppEventEmitter;
-const downloadRootDir = HotUpdate.downloadRootDir;
-
-export function downloadFile(options) {
-  HotUpdate.downloadUpdate(options, r=>{
-    //console.log(r);
-  })
-}
-
-export function reloadUpdate(options) {
-  HotUpdate.reloadUpdate(options);
-}
-
-export function setNeedUpdate(options) {
-  HotUpdate.setNeedUpdate(options);
-}
-
-
-
+require('./lib/index');
diff --git a/lib/hooks.js b/lib/hooks.js
deleted file mode 100644
index 269cec0..0000000
--- a/lib/hooks.js
+++ /dev/null
@@ -1,8 +0,0 @@
-/**
- * Created by tdzl2003 on 2/6/16.
- */
-
-// Hook resolvedAssetSource
-__d('resolveAssetSource', function(_1, _2, module, _3) {
-  module.exports = require('./resolveAssetSource');
-});
diff --git a/lib/index.js b/lib/index.js
new file mode 100644
index 0000000..27b8a5b
--- /dev/null
+++ b/lib/index.js
@@ -0,0 +1,84 @@
+/**
+ * Created by tdzl2003 on 4/4/16.
+ */
+
+const HotUpdate = require('react-native').NativeModules.HotUpdate;
+
+let host = 'http://update.reactnative.cn/api';
+
+export const downloadRootDir = HotUpdate.downloadRootDir;
+export const packageVersion = HotUpdate.packageVersion;
+export const currentVersion = HotUpdate.currentVersion;
+export const isFirstTime = HotUpdate.isFirstTime;
+export const isRolledBack = HotUpdate.isRolledBack;
+
+/*
+Return json:
+Package was expired:
+{
+  expired: true,
+  downloadUrl: 'http://appstore/downloadUrl',
+}
+Package is up to date:
+{
+  upToDate: true,
+}
+There is available update:
+{
+  update: true,
+  name: '1.0.3-rc',
+  hash: 'hash',
+  description: '添加聊天功能\n修复商城页面BUG',
+  metaInfo: '{"silent":true}',
+  updateUrl: 'http://update-packages.reactnative.cn/hash',
+  pdiffUrl: 'http://update-packages.reactnative.cn/hash',
+  diffUrl: 'http://update-packages.reactnative.cn/hash',
+}
+ */
+export async function checkUpdate(APPKEY) {
+  const resp = await fetch(`${host}/checkUpdate/${APPKEY}`, {
+    method: 'POST',
+    body: JSON.stringify({
+      packageVersion: packageVersion,
+      hash: currentVersion,
+    }),
+  });
+
+  if (resp.status !== 200) {
+    throw new Error(resp.message);
+  }
+
+  return await resp.json();
+}
+
+export async function downloadUpdate(options) {
+  if (!options.update) {
+    return;
+  }
+
+  if (options.diff) {
+    await HotUpdate.downloadPatchFromPpk({
+      updateUrl: options.diffUrl,
+      hashName: options.hash,
+      originHash: currentVersion,
+    });
+  } else if (options.diff) {
+    await HotUpdate.downloadPatchFromPackage({
+      updateUrl: options.pdiffUrl,
+      hashName: options.hash,
+    });
+  } else {
+    await HotUpdate.downloadPatchFromPackage({
+      updateUrl: options.updateUrl,
+      hashName: options.hash,
+    });
+  }
+}
+
+export async function switchVersion({hash}) {
+  HotUpdate.reloadUpdate({hashName:hash});
+}
+
+export async function switchVersionLater({hash}) {
+  HotUpdate.setNeedUpdate({hashName:hash});
+}
diff --git a/lib/resolveAssetSource.js b/lib/resolveAssetSource.js
deleted file mode 100644
index 5cb2052..0000000
--- a/lib/resolveAssetSource.js
+++ /dev/null
@@ -1,144 +0,0 @@
-/**
- * Copyright (c) 2015-present, Facebook, Inc.
- * All rights reserved.
- *
- * This source code is licensed under the BSD-style license found in the
- * LICENSE file in the root directory of this source tree. An additional grant
- * of patent rights can be found in the PATENTS file in the same directory.
- *
- * @providesModule resolveAssetSource
- * @flow
- *
- * Resolves an asset into a `source` for `Image`.
- *
- * Hooked by @tdzl2003 for react-native-pushy
- */
-'use strict';
-
-var AssetRegistry = require('react-native/Libraries/Image/AssetRegistry');
-var PixelRatio = require('react-native/Libraries/Utilities/PixelRatio');
-var Platform = require('react-native/Libraries/Utilities/Platform');
-var NativeModules = require('react-native/Libraries/BatchedBridge/BatchedBridgedModules/NativeModules.js');
-var SourceCode = NativeModules.SourceCode;
-
-var _serverURL, _offlinePath;
-
-function getDevServerURL() {
-  if (_serverURL === undefined) {
-    var scriptURL = SourceCode.scriptURL;
-    var match = scriptURL && scriptURL.match(/^https?:\/\/.*?\//);
-    if (match) {
-      // Bundle was loaded from network
-      _serverURL = match[0];
-    } else {
-      // Bundle was loaded from file
-      _serverURL = null;
-    }
-  }
-
-  return _serverURL;
-}
-
-function getOfflinePath() {
-  if (_offlinePath === undefined) {
-    var scriptURL = SourceCode.scriptURL;
-    var match = scriptURL && scriptURL.match(/^file:\/\/(\/.*\/)/);
-    if (match) {
-      _offlinePath = match[1];
-    } else {
-      _offlinePath = '';
-    }
-  }
-
-  return _offlinePath;
-}
-
-/**
- * Returns the path at which the asset can be found in the archive
- */
-function getPathInArchive(asset) {
-  if (Platform.OS === 'android') {
-    var assetDir = getBasePath(asset);
-    // E.g. 'assets_awesomemodule_icon'
-    // The Android resource system picks the correct scale.
-    return (assetDir + '/' + asset.name)
-      .toLowerCase()
-      .replace(/\//g, '_')           // Encode folder structure in file name
-      .replace(/([^a-z0-9_])/g, '')  // Remove illegal chars
-      .replace(/^assets_/, '');      // Remove "assets_" prefix
-  } else {
-    // E.g. 'assets/AwesomeModule/icon@2x.png'
-    return getOfflinePath() + getScaledAssetPath(asset);
-  }
-}
-
-/**
- * Returns an absolute URL which can be used to fetch the asset
- * from the devserver
- */
-function getPathOnDevserver(devServerUrl, asset) {
-  return devServerUrl + getScaledAssetPath(asset) + '?platform=' + Platform.OS +
-    '&hash=' + asset.hash;
-}
-
-/**
- * Returns a path like 'assets/AwesomeModule'
- */
-function getBasePath(asset) {
-  var path = asset.httpServerLocation;
-  if (path[0] === '/') {
-    path = path.substr(1);
-  }
-  return path;
-}
-
-/**
- * Returns a path like 'assets/AwesomeModule/icon@2x.png'
- */
-function getScaledAssetPath(asset) {
-  var scale = pickScale(asset.scales, PixelRatio.get());
-  var scaleSuffix = scale === 1 ? '' : '@' + scale + 'x';
-  var assetDir = getBasePath(asset);
-  return assetDir + '/' + asset.name + scaleSuffix + '.' + asset.type;
-}
-
-function pickScale(scales, deviceScale) {
-  // Packager guarantees that `scales` array is sorted
-  for (var i = 0; i < scales.length; i++) {
-    if (scales[i] >= deviceScale) {
-      return scales[i];
-    }
-  }
-
-  // If nothing matches, device scale is larger than any available
-  // scales, so we return the biggest one. Unless the array is empty,
-  // in which case we default to 1
-  return scales[scales.length - 1] || 1;
-}
-
-function resolveAssetSource(source){
-  if (typeof source === 'object') {
-    return source;
-  }
-
-  var asset = AssetRegistry.getAssetByID(source);
-  if (asset) {
-    return assetToImageSource(asset);
-  }
-
-  return null;
-}
-
-function assetToImageSource(asset){
-  var devServerURL = getDevServerURL();
-  return {
-    __packager_asset: true,
-    width: asset.width,
-    height: asset.height,
-    uri: devServerURL ? getPathOnDevserver(devServerURL, asset) : getPathInArchive(asset),
-    scale: pickScale(asset.scales, PixelRatio.get()),
-  };
-}
-
-module.exports = resolveAssetSource;
-module.exports.pickScale = pickScale;
diff --git a/local-cli/src/api.js b/local-cli/src/api.js
index d1204e9..34ba63c 100644
--- a/local-cli/src/api.js
+++ b/local-cli/src/api.js
@@ -3,7 +3,7 @@
  */
 
 const fetch = require('isomorphic-fetch');
-let host = process.env.PUSHY_REGISTRY || 'http://update.reactnative.cn';
+let host = process.env.PUSHY_REGISTRY || 'http://update.reactnative.cn/api';
 const fs = require('fs-promise');
 import * as fsOrigin from 'fs';
 import request from 'request';
@@ -18,7 +18,7 @@ exports.loadSession = async function() {
       exports.replaceSession(JSON.parse(await fs.readFile('.update', 'utf8')));
       savedSession = session;
     } catch (e) {
-      console.error('Failed to parse file `.pushy`. Try to remove it manually.');
+      console.error('Failed to parse file `.update`. Try to remove it manually.');
       throw e;
     }
   }
@@ -94,7 +94,7 @@ async function uploadFile(fn) {
   let realUrl = url;
 
   if (!/^https?\:\/\//.test(url)) {
-    url = host + url;
+    realUrl = host + url;
   }
 
   const fileSize = (await fs.stat(fn)).size;
@@ -111,8 +111,7 @@ async function uploadFile(fn) {
     formData.file.on('data', function(data) {
       bar.tick(data.length);
     })
-    request.post({
-      realUrl,
+    request.post(realUrl, {
       formData,
     }, (err, resp, body) => {
       if (err) {
diff --git a/local-cli/src/package.js b/local-cli/src/package.js
index a3465fc..b8d3ae0 100644
--- a/local-cli/src/package.js
+++ b/local-cli/src/package.js
@@ -61,7 +61,7 @@ export const commands = {
   uploadApk: async function({args}) {
     const fn = args[0];
     if (!fn) {
-      throw new Error('Usage: pushy uploadApk <ipaFile>');
+      throw new Error('Usage: pushy uploadApk <apkFile>');
     }
     const name = await getApkVersion(fn);
     const {appId} = await getSelectedApp('android');