From d266b9f55b062a187349f8a29d5e486618bd7ea9 Mon Sep 17 00:00:00 2001 From: Akinwale Ariwodola Date: Fri, 9 Aug 2019 07:42:21 +0100 Subject: [PATCH] Performance (#626) --- app | 2 +- .../java/io/lbry/browser/LbrynetService.java | 84 +------- src/main/java/io/lbry/browser/Utils.java | 116 ++++++++++- .../browser/reactmodules/RequestsModule.java | 74 +++++++ .../reactmodules/StatePersistorModule.java | 193 ++++++++++++++++++ .../reactpackages/LbryReactPackage.java | 6 +- 6 files changed, 390 insertions(+), 85 deletions(-) create mode 100644 src/main/java/io/lbry/browser/reactmodules/RequestsModule.java create mode 100644 src/main/java/io/lbry/browser/reactmodules/StatePersistorModule.java diff --git a/app b/app index 60b4210c..836ff2ae 160000 --- a/app +++ b/app @@ -1 +1 @@ -Subproject commit 60b4210c530e82b6f6d47e0b4e41b35fe24a2617 +Subproject commit 836ff2ae134d3cd3b114b88a64a7cdd0900582c9 diff --git a/src/main/java/io/lbry/browser/LbrynetService.java b/src/main/java/io/lbry/browser/LbrynetService.java index ea33d0a1..d6a9dd1b 100644 --- a/src/main/java/io/lbry/browser/LbrynetService.java +++ b/src/main/java/io/lbry/browser/LbrynetService.java @@ -74,8 +74,6 @@ public class LbrynetService extends PythonService { public static LbrynetService serviceInstance; - private static final String SDK_URL = "http://127.0.0.1:5279"; - private static final int SDK_POLL_INTERVAL = 500; // 500 milliseconds private BroadcastReceiver stopServiceReceiver; @@ -192,86 +190,10 @@ public class LbrynetService extends PythonService { } } - private String sdkCall(String method) throws ConnectException { - return sdkCall(method, null); - } - - private String sdkCall(String method, Map params) throws ConnectException { - BufferedReader reader = null; - DataOutputStream dos = null; - HttpURLConnection conn = null; - - try { - JSONObject request = new JSONObject(); - request.put("method", method); - if (params != null) { - JSONObject requestParams = new JSONObject(); - for (Map.Entry entry : params.entrySet()) { - requestParams.put(entry.getKey(), entry.getValue()); - } - request.put("params", requestParams); - } - - URL url = new URL(SDK_URL); - conn = (HttpURLConnection) url.openConnection(); - conn.setDoInput(true); - conn.setDoOutput(true); - conn.setUseCaches(false); - conn.setRequestMethod("POST"); - conn.setRequestProperty("Content-type", "application/json"); - - dos = new DataOutputStream(conn.getOutputStream()); - dos.writeBytes(request.toString()); - dos.flush(); - dos.close(); - - if (conn.getResponseCode() == 200) { - reader = new BufferedReader(new InputStreamReader(conn.getInputStream(), "utf-8")); - StringBuilder sb = new StringBuilder(); - String input; - while ((input = reader.readLine()) != null) { - sb.append(input); - } - - return sb.toString(); - } else { - reader = new BufferedReader(new InputStreamReader(conn.getErrorStream(), "utf-8")); - StringBuilder sb = new StringBuilder(); - String error; - while ((error = reader.readLine()) != null) { - sb.append(error); - } - return sb.toString(); - } - } catch (ConnectException ex) { - // sdk not started yet. rethrow - throw ex; - } catch (IOException ex) { - Log.e(TAG, ex.getMessage(), ex); - // ignore and continue - } catch (Exception ex) { - Log.e(TAG, ex.getMessage(), ex); - // ignore - } finally { - try { - if (reader != null) { - reader.close(); - } - if (conn != null) { - conn.disconnect(); - } - } catch (IOException ex) { - // pass - } - } - - return null; - } - private void pollFileList() { try { if (!streamManagerReady) { - String statusResponse = sdkCall("status"); + String statusResponse = Utils.sdkCall("status"); if (statusResponse != null) { JSONObject status = new JSONObject(statusResponse); if (status.has("error")) { @@ -288,7 +210,7 @@ public class LbrynetService extends PythonService { } if (streamManagerReady) { - String fileList = sdkCall("file_list"); + String fileList = Utils.sdkCall("file_list"); if (fileList != null) { JSONObject response = new JSONObject(fileList); if (!response.has("error")) { @@ -309,7 +231,7 @@ public class LbrynetService extends PythonService { try { Map params = new HashMap(); params.put("outpoint", outpoint); - return sdkCall("file_list", params); + return Utils.sdkCall("file_list", params); } catch (ConnectException ex) { return null; } diff --git a/src/main/java/io/lbry/browser/Utils.java b/src/main/java/io/lbry/browser/Utils.java index f41c94d0..f7b9f2f4 100644 --- a/src/main/java/io/lbry/browser/Utils.java +++ b/src/main/java/io/lbry/browser/Utils.java @@ -10,7 +10,14 @@ import io.lbry.browser.BuildConfig; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.io.BufferedReader; import java.io.File; +import java.io.InputStreamReader; +import java.io.IOException; +import java.io.DataOutputStream; +import java.net.ConnectException; +import java.net.HttpURLConnection; +import java.net.URL; import java.math.BigInteger; import java.nio.charset.Charset; import java.security.InvalidAlgorithmParameterException; @@ -21,6 +28,7 @@ import java.security.NoSuchProviderException; import java.security.SecureRandom; import java.util.ArrayList; import java.util.Calendar; +import java.util.Map; import javax.crypto.Cipher; import javax.crypto.CipherInputStream; @@ -28,8 +36,11 @@ import javax.crypto.CipherOutputStream; import javax.crypto.spec.SecretKeySpec; import javax.security.auth.x500.X500Principal; -public final class Utils { +import org.json.JSONObject; +import org.json.JSONException; +public final class Utils { + private static final String TAG = Utils.class.getName(); private static final String AES_MODE = "AES/ECB/PKCS7Padding"; @@ -45,7 +56,9 @@ public final class Utils { private static final String SP_ENCRYPTION_KEY = "key"; private static final String SP_API_SECRET_KEY = "api_secret"; - + + public static final String SDK_URL = "http://127.0.0.1:5279"; + public static String getAndroidRelease() { return android.os.Build.VERSION.RELEASE; } @@ -242,6 +255,105 @@ public final class Utils { return ks; } + + public static String performRequest(String url) throws ConnectException { + return performRequest(url, "GET", null); + } + + public static String performRequest(String requestUrl, String requestMethod, String json) throws ConnectException { + BufferedReader reader = null; + DataOutputStream dos = null; + HttpURLConnection conn = null; + + try { + URL url = new URL(requestUrl); + conn = (HttpURLConnection) url.openConnection(); + conn.setDoInput(true); + conn.setUseCaches(false); + conn.setRequestMethod(requestMethod); + if ("POST".equals(requestMethod)) { + conn.setDoOutput(true); + conn.setRequestProperty("Content-type", "application/json"); + } + + if (json != null) { + dos = new DataOutputStream(conn.getOutputStream()); + dos.writeBytes(json); + dos.flush(); + dos.close(); + } + + if (conn.getResponseCode() == 200) { + reader = new BufferedReader(new InputStreamReader(conn.getInputStream(), "utf-8")); + StringBuilder sb = new StringBuilder(); + String input; + while ((input = reader.readLine()) != null) { + sb.append(input); + } + + return sb.toString(); + } else { + reader = new BufferedReader(new InputStreamReader(conn.getErrorStream(), "utf-8")); + StringBuilder sb = new StringBuilder(); + String error; + while ((error = reader.readLine()) != null) { + sb.append(error); + } + return sb.toString(); + } + } catch (ConnectException ex) { + // unable to connect. rethrow + throw ex; + } catch (IOException ex) { + Log.e(TAG, ex.getMessage(), ex); + // pass + } catch (Exception ex) { + Log.e(TAG, ex.getMessage(), ex); + // pass + } finally { + try { + if (reader != null) { + reader.close(); + } + if (conn != null) { + conn.disconnect(); + } + } catch (IOException ex) { + // pass + } + } + + return null; + } + + public static String sdkCall(String method) throws ConnectException { + return sdkCall(method, null); + } + + public static String sdkCall(String method, Map params) throws ConnectException { + try { + JSONObject request = new JSONObject(); + request.put("method", method); + if (params != null) { + JSONObject requestParams = new JSONObject(); + for (Map.Entry entry : params.entrySet()) { + requestParams.put(entry.getKey(), entry.getValue()); + } + request.put("params", requestParams); + } + + return performRequest(SDK_URL, "POST", request.toString()); + } catch (ConnectException ex) { + // sdk not started yet. rethrow + throw ex; + } catch (JSONException ex) { + // normally shouldn't happen + Log.e(TAG, ex.getMessage(), ex); + // pass + } + + return null; + } private static byte[] rsaEncrypt(byte[] secret, KeyStore keyStore) throws Exception { KeyStore.PrivateKeyEntry privateKeyEntry = (KeyStore.PrivateKeyEntry) keyStore.getEntry(KEY_ALIAS, null); diff --git a/src/main/java/io/lbry/browser/reactmodules/RequestsModule.java b/src/main/java/io/lbry/browser/reactmodules/RequestsModule.java new file mode 100644 index 00000000..c137d1eb --- /dev/null +++ b/src/main/java/io/lbry/browser/reactmodules/RequestsModule.java @@ -0,0 +1,74 @@ +package io.lbry.browser.reactmodules; + +import android.content.Context; +import android.os.AsyncTask; +import android.os.Bundle; + +import com.facebook.react.bridge.Arguments; +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.ReadableArray; +import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.bridge.ReadableMapKeySetIterator; +import com.facebook.react.bridge.ReadableType; +import com.facebook.react.bridge.WritableMap; + +import io.lbry.browser.MainActivity; +import io.lbry.browser.Utils; + +import java.util.List; +import java.util.ArrayList; + +import org.json.JSONObject; +import org.json.JSONArray; +import org.json.JSONException; + +public class RequestsModule extends ReactContextBaseJavaModule { + private Context context; + + public RequestsModule(ReactApplicationContext reactContext) { + super(reactContext); + this.context = reactContext; + } + + @Override + public String getName() { + return "Requests"; + } + + @ReactMethod + public void get(final String url, final Promise promise) { + (new AsyncTask() { + @Override + protected String doInBackground(Void... params) { + try { + return Utils.performRequest(url); + } catch (Exception ex) { + return null; + } + } + + protected void onPostExecute(String response) { + if (response == null) { + promise.reject(String.format("Request to %s returned null.", url)); + return; + } + + promise.resolve(response); + } + }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + + } + + @ReactMethod + public void lbryioCall(String authToken, final Promise promise) { + // get the auth token here, or let the app pass it in? + } + + @ReactMethod + public void lbryCall(final Promise promise) { + + } +} \ No newline at end of file diff --git a/src/main/java/io/lbry/browser/reactmodules/StatePersistorModule.java b/src/main/java/io/lbry/browser/reactmodules/StatePersistorModule.java new file mode 100644 index 00000000..9424abcb --- /dev/null +++ b/src/main/java/io/lbry/browser/reactmodules/StatePersistorModule.java @@ -0,0 +1,193 @@ +package io.lbry.browser.reactmodules; + +import android.content.Context; +import android.os.AsyncTask; +import android.os.Bundle; + +import com.facebook.react.bridge.Arguments; +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.ReadableArray; +import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.bridge.ReadableMapKeySetIterator; +import com.facebook.react.bridge.ReadableType; +import com.facebook.react.bridge.WritableMap; + +import io.lbry.browser.MainActivity; + +import java.util.List; +import java.util.ArrayList; + +import org.json.JSONObject; +import org.json.JSONArray; +import org.json.JSONException; + +public class StatePersistorModule extends ReactContextBaseJavaModule { + private Context context; + + private List queue; + + private ReadableMap filter; + + private ReadableMap lastState; + + private AsyncTask persistTask; + + public StatePersistorModule(ReactApplicationContext reactContext) { + super(reactContext); + this.context = reactContext; + queue = new ArrayList(); + } + + @Override + public String getName() { + return "StatePersistor"; + } + + /*private WritableMap filterState(ReadableMap state) { + WritableMap filteredState = Arguments.createMap(); + + return state; + }*/ + + public boolean hasStateChanged(ReadableMap newState) { + return false; + } + + @ReactMethod + public void update(ReadableMap state, ReadableMap filter) { + if (this.filter == null) { + this.filter = filter; + } + // process state updates from the queue using a background task + synchronized (this) { + queue.add(state); + } + persistState(); + } + + private void persistState() { + persistState(false); + } + + private void persistState(final boolean flush) { + if (flush && persistTask != null) { + persistTask.cancel(true); + persistTask = null; + } + + if (persistTask == null) { + persistTask = (new AsyncTask() { + protected Boolean doInBackground(Object... param) { + // get the first item in the queue + ReadableMap queuedState = null; + if (queue.size() > 0) { + synchronized (StatePersistorModule.this) { + queuedState = queue.remove(flush ? queue.size() - 1 : 0); + if (flush) { + // we only want the final state in this scenario + queue.clear(); + } + } + } + + if (queuedState != null) { + ReadableMap state = queuedState; //(ReadableMap) filterState(queuedState); + // convert to JSON object + + try { + JSONObject json = readableMapToJSON(state); + + // save the state file + // TODO: explore this option at a later date + throw new UnsupportedOperationException(); + } catch (JSONException ex) { + // normally shouldn't happen, but if it does, reinsert into the queue + if (queuedState != null) { + synchronized (StatePersistorModule.this) { + queue.add(0, queuedState); + } + } + return false; + } + } + + return false; + } + + public void onPostExecute(Boolean result) { + if (queue.size() > 0) { + persistState(); + } + + persistTask = null; + } + }); + persistTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + } + + @ReactMethod + public void flush() { + persistState(true); + } + + private static JSONObject readableMapToJSON(ReadableMap readableMap) throws JSONException { + JSONObject json = new JSONObject(); + ReadableMapKeySetIterator iterator = readableMap.keySetIterator(); + while (iterator.hasNextKey()) { + String key = iterator.nextKey(); + switch (readableMap.getType(key)) { + case Map: + json.put(key, readableMapToJSON(readableMap.getMap(key))); + break; + case Array: + json.put(key, readableArrayToJSON(readableMap.getArray(key))); + break; + case Boolean: + json.put(key, readableMap.getBoolean(key)); + break; + case Null: + json.put(key, JSONObject.NULL); + break; + case Number: + json.put(key, readableMap.getDouble(key)); + break; + case String: + json.put(key, readableMap.getString(key)); + break; + } + } + + return json; + } + + private static JSONArray readableArrayToJSON(ReadableArray readableArray) throws JSONException { + JSONArray array = new JSONArray(); + for (int i = 0; i < readableArray.size(); i++) { + switch (readableArray.getType(i)) { + case Null: + break; + case Boolean: + array.put(readableArray.getBoolean(i)); + break; + case Number: + array.put(readableArray.getDouble(i)); + break; + case String: + array.put(readableArray.getString(i)); + break; + case Map: + array.put(readableMapToJSON(readableArray.getMap(i))); + break; + case Array: + array.put(readableArrayToJSON(readableArray.getArray(i))); + break; + } + } + + return array; + } +} \ No newline at end of file diff --git a/src/main/java/io/lbry/browser/reactpackages/LbryReactPackage.java b/src/main/java/io/lbry/browser/reactpackages/LbryReactPackage.java index 6b535bae..7b1f53e5 100644 --- a/src/main/java/io/lbry/browser/reactpackages/LbryReactPackage.java +++ b/src/main/java/io/lbry/browser/reactpackages/LbryReactPackage.java @@ -10,9 +10,11 @@ import io.lbry.browser.reactmodules.DaemonServiceControlModule; import io.lbry.browser.reactmodules.FirstRunModule; import io.lbry.browser.reactmodules.FirebaseModule; import io.lbry.browser.reactmodules.GalleryModule; +import io.lbry.browser.reactmodules.RequestsModule; import io.lbry.browser.reactmodules.ScreenOrientationModule; +import io.lbry.browser.reactmodules.StatePersistorModule; import io.lbry.browser.reactmodules.VersionInfoModule; -import io.lbry.browser.reactmodules.UtilityModule;; +import io.lbry.browser.reactmodules.UtilityModule; import java.util.ArrayList; import java.util.Collections; @@ -33,7 +35,9 @@ public class LbryReactPackage implements ReactPackage { modules.add(new FirstRunModule(reactContext)); modules.add(new FirebaseModule(reactContext)); modules.add(new GalleryModule(reactContext)); + modules.add(new RequestsModule(reactContext)); modules.add(new ScreenOrientationModule(reactContext)); + modules.add(new StatePersistorModule(reactContext)); modules.add(new UtilityModule(reactContext)); modules.add(new VersionInfoModule(reactContext));