Commit 32d380d4 authored by Yusuke Iwaki's avatar Yusuke Iwaki

!RESTRUCT Realm DB archtecture! for the capability of connecting to multiple servers.

parent 54b3029a
......@@ -4,13 +4,33 @@ apply plugin: 'realm-android'
apply plugin: 'com.jakewharton.hugo'
apply from: '../config/quality/quality.gradle'
buildscript {
repositories {
jcenter()
mavenCentral()
}
dependencies {
classpath rootProject.ext.androidPlugin
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
classpath 'me.tatarka:gradle-retrolambda:3.3.1'
classpath 'me.tatarka.retrolambda.projectlombok:lombok.ast:0.2.3.a2'
classpath rootProject.ext.realmPlugin
classpath 'com.jakewharton.hugo:hugo-plugin:1.2.1'
}
// Exclude the version that the android plugin depends on.
configurations.classpath.exclude group: 'com.android.tools.external.lombok'
}
android {
compileSdkVersion 25
buildToolsVersion "25.0.0"
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
defaultConfig {
applicationId "chat.rocket.android"
minSdkVersion 17
targetSdkVersion 25
targetSdkVersion rootProject.ext.compileSdkVersion
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
......@@ -56,10 +76,9 @@ repositories {
dependencies {
compile project(':rocket-chat-android-widgets')
compile 'com.android.support:appcompat-v7:25.0.0'
compile 'com.android.support:design:25.0.0'
compile 'jp.co.crowdworks:realm-java-helpers:0.0.8'
compile 'jp.co.crowdworks:realm-java-helpers-bolts:0.0.8'
compile project(':realm-helpers')
compile rootProject.ext.supportAppCompat
compile rootProject.ext.supportDesign
compile 'com.squareup.okhttp3:okhttp:3.4.1'
compile 'com.squareup.picasso:picasso:2.5.2'
......@@ -70,7 +89,7 @@ dependencies {
compile 'chat.rocket:android-ddp:0.0.8'
compile 'com.jakewharton.timber:timber:4.3.1'
compile rootProject.ext.timber
compile 'com.jakewharton.rxbinding:rxbinding:0.4.0'
compile 'com.trello:rxlifecycle:1.0'
......
......@@ -23,6 +23,9 @@
</intent-filter>
</activity>
<activity
android:name=".activity.AddServerActivity"
android:windowSoftInputMode="adjustResize"/>
<activity
android:name=".activity.ServerConfigActivity"
android:windowSoftInputMode="adjustResize"/>
......
......@@ -2,12 +2,20 @@ package chat.rocket.android;
import android.content.Context;
import android.content.Intent;
import chat.rocket.android.activity.AddServerActivity;
import chat.rocket.android.activity.ServerConfigActivity;
/**
* utility class for launching Activity.
*/
public class LaunchUtil {
public static void showAddServerActivity(Context context) {
Intent intent = new Intent(context, AddServerActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT | Intent.FLAG_ACTIVITY_CLEAR_TOP);
context.startActivity(intent);
}
/**
* launch ServerConfigActivity with proper flags.
*/
......
package chat.rocket.android;
import android.app.Application;
import chat.rocket.android.model.ServerConfig;
import chat.rocket.android.realm_helper.RealmStore;
import com.facebook.stetho.Stetho;
import com.uphyca.stetho_realm.RealmInspectorModulesProvider;
import io.realm.Realm;
import io.realm.RealmConfiguration;
import java.util.List;
import timber.log.Timber;
/**
......@@ -20,6 +23,12 @@ public class RocketChatApplication extends Application {
Realm.setDefaultConfiguration(
new RealmConfiguration.Builder().deleteRealmIfMigrationNeeded().build());
List<ServerConfig> configs = RealmStore.getDefault().executeTransactionForReadResults(realm ->
realm.where(ServerConfig.class).isNotNull("session").findAll());
for (ServerConfig config : configs) {
RealmStore.put(config.getServerConfigId());
}
Stetho.initialize(Stetho.newInitializerBuilder(this)
.enableDumpapp(Stetho.defaultDumperPluginsProvider(this))
.enableWebKitInspector(RealmInspectorModulesProvider.builder(this).build())
......
package chat.rocket.android;
import android.content.Context;
import android.content.SharedPreferences;
/**
* sharedpreference-based cache.
*/
public class RocketChatCache {
public static final String KEY_SELECTED_SERVER_CONFIG_ID = "selectedServerConfigId";
public static SharedPreferences get(Context context) {
return context.getSharedPreferences("cache", Context.MODE_PRIVATE);
}
}
package chat.rocket.android.activity;
import chat.rocket.android.helper.LogcatIfError;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.support.annotation.Nullable;
import chat.rocket.android.LaunchUtil;
import chat.rocket.android.RocketChatCache;
import chat.rocket.android.model.ServerConfig;
import chat.rocket.android.realm_helper.RealmListObserver;
import chat.rocket.android.realm_helper.RealmStore;
import chat.rocket.android.service.RocketChatService;
import io.realm.Realm;
import io.realm.RealmResults;
import java.util.List;
import java.util.UUID;
import jp.co.crowdworks.realm_java_helpers.RealmListObserver;
import jp.co.crowdworks.realm_java_helpers_bolts.RealmHelperBolts;
abstract class AbstractAuthedActivity extends AbstractFragmentActivity {
private RealmListObserver<ServerConfig> serverConfigEmptinessObserver =
new RealmListObserver<ServerConfig>() {
@Override protected RealmResults<ServerConfig> queryItems(Realm realm) {
return realm.where(ServerConfig.class).findAll();
private RealmListObserver<ServerConfig> unconfiguredServersObserver =
RealmStore.getDefault()
.createListObserver(realm ->
realm.where(ServerConfig.class).isNotNull("session").findAll())
.setOnUpdateListener(results -> {
if (results.isEmpty()) {
LaunchUtil.showAddServerActivity(this);
}
});
@Override protected void onCollectionChanged(List<ServerConfig> list) {
if (list.isEmpty()) {
final String newId = UUID.randomUUID().toString();
RealmHelperBolts.executeTransaction(
realm -> realm.createObject(ServerConfig.class, newId))
.continueWith(new LogcatIfError());
}
protected String serverConfigId;
SharedPreferences.OnSharedPreferenceChangeListener preferenceChangeListener =
(sharedPreferences, s) -> {
if (RocketChatCache.KEY_SELECTED_SERVER_CONFIG_ID.equals(s)) {
updateServerConfigIdIfNeeded(sharedPreferences);
}
};
private RealmListObserver<ServerConfig> loginRequiredServerConfigObserver =
new RealmListObserver<ServerConfig>() {
@Override protected RealmResults<ServerConfig> queryItems(Realm realm) {
return ServerConfig.queryLoginRequiredConnections(realm).findAll();
private void updateServerConfigIdIfNeeded(SharedPreferences prefs) {
String newServerConfigId = prefs.getString(RocketChatCache.KEY_SELECTED_SERVER_CONFIG_ID, null);
if (serverConfigId == null) {
if (newServerConfigId != null) {
serverConfigId = newServerConfigId;
onServerConfigIdUpdated();
}
} else {
if (!serverConfigId.equals(newServerConfigId)) {
serverConfigId = newServerConfigId;
onServerConfigIdUpdated();
}
}
}
@Override protected void onCollectionChanged(List<ServerConfig> list) {
ServerConfigActivity.launchFor(AbstractAuthedActivity.this, list);
protected void onServerConfigIdUpdated() {}
@Override protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
SharedPreferences prefs = RocketChatCache.get(this);
updateServerConfigIdIfNeeded(prefs);
}
};
@Override protected void onResume() {
super.onResume();
RocketChatService.keepalive(this);
serverConfigEmptinessObserver.sub();
loginRequiredServerConfigObserver.sub();
unconfiguredServersObserver.sub();
SharedPreferences prefs = RocketChatCache.get(this);
updateServerConfigIdIfNeeded(prefs);
prefs.registerOnSharedPreferenceChangeListener(preferenceChangeListener);
}
@Override protected void onPause() {
loginRequiredServerConfigObserver.unsub();
serverConfigEmptinessObserver.unsub();
SharedPreferences prefs = RocketChatCache.get(this);
prefs.unregisterOnSharedPreferenceChangeListener(preferenceChangeListener);
unconfiguredServersObserver.unsub();
super.onPause();
}
}
package chat.rocket.android.activity;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import chat.rocket.android.R;
import chat.rocket.android.RocketChatCache;
import chat.rocket.android.fragment.server_config.InputHostnameFragment;
import chat.rocket.android.fragment.server_config.WaitingFragment;
import chat.rocket.android.model.ServerConfig;
import chat.rocket.android.realm_helper.RealmListObserver;
import chat.rocket.android.realm_helper.RealmObjectObserver;
import chat.rocket.android.realm_helper.RealmStore;
import java.util.UUID;
public class AddServerActivity extends AbstractFragmentActivity {
private String serverConfigId;
private RealmListObserver<ServerConfig> configuredServersObserver = RealmStore.getDefault()
.createListObserver(realm -> realm.where(ServerConfig.class).isNotNull("session").findAll())
.setOnUpdateListener(results -> {
if (!results.isEmpty()) {
RocketChatCache.get(this).edit()
.putString(RocketChatCache.KEY_SELECTED_SERVER_CONFIG_ID, serverConfigId)
.apply();
finish();
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
}
});
private RealmObjectObserver<ServerConfig> targetServerConfigObserver = RealmStore.getDefault()
.createObjectObserver(realm ->
realm.where(ServerConfig.class).equalTo("serverConfigId", serverConfigId))
.setOnUpdateListener(config -> {
if (config == null || config.getState() == ServerConfig.STATE_CONNECTION_ERROR) {
showFragment(new InputHostnameFragment());
} else {
showFragment(WaitingFragment.create("Connecting to server..."));
}
});
@Override protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.simple_screen);
setupServerConfigId();
}
private void setupServerConfigId() {
ServerConfig config = RealmStore.getDefault().executeTransactionForRead(realm ->
realm.where(ServerConfig.class).isNull("hostname").findFirst());
if (config != null) {
serverConfigId = config.getServerConfigId();
return;
}
config = RealmStore.getDefault().executeTransactionForRead(realm ->
realm.where(ServerConfig.class)
.equalTo("state", ServerConfig.STATE_CONNECTION_ERROR).findFirst());
if (config != null) {
serverConfigId = config.getServerConfigId();
return;
}
serverConfigId = UUID.randomUUID().toString();
}
@Override protected int getLayoutContainerForFragment() {
return R.id.content;
}
@Override protected void onResume() {
super.onResume();
configuredServersObserver.sub();
targetServerConfigObserver.sub();
}
@Override protected void onPause() {
configuredServersObserver.unsub();
targetServerConfigObserver.unsub();
super.onPause();
}
@Override protected void showFragment(Fragment fragment) {
injectServerConfigIdArgTo(fragment);
super.showFragment(fragment);
}
@Override protected void showFragmentWithBackStack(Fragment fragment) {
injectServerConfigIdArgTo(fragment);
super.showFragmentWithBackStack(fragment);
}
private void injectServerConfigIdArgTo(Fragment fragment) {
Bundle args = fragment.getArguments();
if (args == null) {
args = new Bundle();
}
args.putString("serverConfigId", serverConfigId);
fragment.setArguments(args);
}
@Override protected void onBackPresseNotHandled() {
moveTaskToBack(true);
}
}
......@@ -5,20 +5,19 @@ import android.support.annotation.Nullable;
import android.support.v4.widget.SlidingPaneLayout;
import android.view.View;
import android.widget.CompoundButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import chat.rocket.android.LaunchUtil;
import chat.rocket.android.R;
import chat.rocket.android.fragment.chatroom.HomeFragment;
import chat.rocket.android.fragment.chatroom.RoomFragment;
import chat.rocket.android.helper.Avatar;
import chat.rocket.android.helper.TextUtils;
import chat.rocket.android.layouthelper.chatroom.RoomListManager;
import chat.rocket.android.model.ddp.RoomSubscription;
import chat.rocket.android.model.internal.Session;
import chat.rocket.android.realm_helper.RealmHelper;
import chat.rocket.android.realm_helper.RealmStore;
import com.jakewharton.rxbinding.view.RxView;
import com.jakewharton.rxbinding.widget.RxCompoundButton;
import io.realm.Realm;
import io.realm.RealmResults;
import java.util.List;
import jp.co.crowdworks.realm_java_helpers.RealmListObserver;
import hugo.weaving.DebugLog;
/**
* Entry-point for Rocket.Chat.Android application.
......@@ -30,6 +29,7 @@ public class MainActivity extends AbstractAuthedActivity {
return R.id.activity_main_container;
}
@Override protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
......@@ -64,8 +64,6 @@ public class MainActivity extends AbstractAuthedActivity {
}
});
}
ImageView myAvatar = (ImageView) findViewById(R.id.img_my_avatar);
new Avatar("demo.rocket.chat", "John Doe").into(myAvatar);
}
private void closeSidebarIfNeeded() {
......@@ -88,28 +86,33 @@ public class MainActivity extends AbstractAuthedActivity {
.subscribe(RxView.visibility(findViewById(R.id.user_action_outer_container)));
}
private RealmListObserver<RoomSubscription> roomsObserver =
new RealmListObserver<RoomSubscription>() {
@Override protected RealmResults<RoomSubscription> queryItems(Realm realm) {
return realm.where(RoomSubscription.class).findAll();
private void showRoomFragment(String roomId) {
showFragment(RoomFragment.create(serverConfigId, roomId));
}
@Override protected void onCollectionChanged(List<RoomSubscription> list) {
roomListManager.setRooms(list);
}
};
@DebugLog
@Override protected void onServerConfigIdUpdated() {
super.onServerConfigIdUpdated();
private void showRoomFragment(String roomId) {
showFragment(RoomFragment.create(roomId));
if (serverConfigId == null) {
return;
}
@Override protected void onResume() {
super.onResume();
roomsObserver.sub();
RealmHelper realmHelper = RealmStore.get(serverConfigId);
if (realmHelper == null) {
return;
}
@Override protected void onPause() {
roomsObserver.unsub();
super.onPause();
Session session = realmHelper.executeTransactionForRead(realm ->
realm.where(Session.class).equalTo("sessionId", Session.DEFAULT_ID).findFirst());
if (session != null
&& !TextUtils.isEmpty(session.getToken())
&& session.isTokenVerified()
&& TextUtils.isEmpty(session.getError())) {
// session is OK.
} else {
LaunchUtil.showServerConfigActivity(this, serverConfigId);
}
}
}
package chat.rocket.android.activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import chat.rocket.android.LaunchUtil;
import chat.rocket.android.R;
import chat.rocket.android.fragment.server_config.InputHostnameFragment;
import chat.rocket.android.fragment.server_config.LoginFragment;
import chat.rocket.android.fragment.server_config.RetryLoginFragment;
import chat.rocket.android.fragment.server_config.WaitingFragment;
import chat.rocket.android.helper.TextUtils;
import chat.rocket.android.model.ServerConfig;
import chat.rocket.android.model.internal.Session;
import chat.rocket.android.realm_helper.RealmObjectObserver;
import chat.rocket.android.realm_helper.RealmStore;
import chat.rocket.android.service.RocketChatService;
import io.realm.Realm;
import io.realm.RealmQuery;
import java.util.List;
import jp.co.crowdworks.realm_java_helpers.RealmObjectObserver;
/**
* Activity for Login, Sign-up, and Connecting...
......@@ -24,54 +20,7 @@ import jp.co.crowdworks.realm_java_helpers.RealmObjectObserver;
public class ServerConfigActivity extends AbstractFragmentActivity {
private String serverConfigId;
private RealmObjectObserver<ServerConfig> serverConfigObserver =
new RealmObjectObserver<ServerConfig>() {
@Override protected RealmQuery<ServerConfig> query(Realm realm) {
return realm.where(ServerConfig.class).equalTo("serverConfigId", serverConfigId);
}
@Override protected void onChange(ServerConfig config) {
onRenderServerConfig(config);
}
};
/**
* Start the ServerConfigActivity with considering the priority of ServerConfig in the list.
*/
public static boolean launchFor(Context context, List<ServerConfig> configList) {
for (ServerConfig config : configList) {
if (TextUtils.isEmpty(config.getHostname())) {
return launchFor(context, config);
} else if (!TextUtils.isEmpty(config.getConnectionError())) {
return launchFor(context, config);
}
}
for (ServerConfig config : configList) {
if (TextUtils.isEmpty(config.getSession())) {
return launchFor(context, config);
}
}
for (ServerConfig config : configList) {
if (TextUtils.isEmpty(config.getToken())) {
return launchFor(context, config);
}
}
for (ServerConfig config : configList) {
if (!config.isTokenVerified()) {
return launchFor(context, config);
}
}
return false;
}
private static boolean launchFor(Context context, ServerConfig config) {
LaunchUtil.showServerConfigActivity(context, config.getServerConfigId());
return true;
}
private RealmObjectObserver<Session> sessionObserver;
@Override protected int getLayoutContainerForFragment() {
return R.id.content;
......@@ -92,51 +41,48 @@ public class ServerConfigActivity extends AbstractFragmentActivity {
return;
}
sessionObserver = RealmStore.get(serverConfigId)
.createObjectObserver(realm ->
realm.where(Session.class).equalTo("sessionId", Session.DEFAULT_ID))
.setOnUpdateListener(this::onRenderServerConfigSession);
setContentView(R.layout.simple_screen);
showFragment(new WaitingFragment());
}
@Override protected void onResume() {
super.onResume();
RocketChatService.keepalive(this);
serverConfigObserver.sub();
sessionObserver.sub();
}
@Override protected void onPause() {
serverConfigObserver.unsub();
sessionObserver.unsub();
super.onPause();
}
private void onRenderServerConfig(ServerConfig config) {
if (config == null) {
finish();
private void onRenderServerConfigSession(Session session) {
if (session == null) {
return;
}
if (config.isTokenVerified()) {
if (session.isTokenVerified() && TextUtils.isEmpty(session.getError())) {
finish();
overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
return;
}
final String token = config.getToken();
final String token = session.getToken();
if (!TextUtils.isEmpty(token)) {
if (TextUtils.isEmpty(session.getError())) {
showFragment(WaitingFragment.create("Authenticating..."));
return;
}
if (!TextUtils.isEmpty(config.getSession())) {
showFragment(new LoginFragment());
return;
} else {
showFragment(new RetryLoginFragment());
}
final String error = config.getConnectionError();
String hostname = config.getHostname();
if (!TextUtils.isEmpty(hostname) && TextUtils.isEmpty(error)) {
showFragment(WaitingFragment.create("Connecting to server..."));
return;
}
showFragment(new InputHostnameFragment());
showFragment(new LoginFragment());
}
@Override protected void showFragment(Fragment fragment) {
......@@ -159,10 +105,6 @@ public class ServerConfigActivity extends AbstractFragmentActivity {
}
@Override protected void onBackPresseNotHandled() {
if (ServerConfig.hasLoginRequiredConnection()) {
moveTaskToBack(true);
} else {
super.onBackPresseNotHandled();
}
}
}
......@@ -4,13 +4,12 @@ import android.os.Bundle;
import android.support.annotation.Nullable;
import chat.rocket.android.R;
import chat.rocket.android.helper.LogcatIfError;
import chat.rocket.android.model.LoadMessageProcedure;
import chat.rocket.android.model.SyncState;
import chat.rocket.android.model.ddp.RoomSubscription;
import io.realm.Realm;
import io.realm.RealmQuery;
import jp.co.crowdworks.realm_java_helpers.RealmObjectObserver;
import jp.co.crowdworks.realm_java_helpers_bolts.RealmHelperBolts;
import chat.rocket.android.model.internal.LoadMessageProcedure;
import chat.rocket.android.realm_helper.RealmHelper;
import chat.rocket.android.realm_helper.RealmObjectObserver;
import chat.rocket.android.realm_helper.RealmStore;
import org.json.JSONObject;
/**
......@@ -18,13 +17,15 @@ import org.json.JSONObject;
*/
public class RoomFragment extends AbstractChatRoomFragment {
private RealmHelper realmHelper;
private String roomId;
/**
* create fragment with roomId.
*/
public static RoomFragment create(String roomId) {
public static RoomFragment create(String serverConfigId, String roomId) {
Bundle args = new Bundle();
args.putString("serverConfigId", serverConfigId);
args.putString("roomId", roomId);
RoomFragment fragment = new RoomFragment();
fragment.setArguments(args);
......@@ -38,6 +39,7 @@ public class RoomFragment extends AbstractChatRoomFragment {
super.onCreate(savedInstanceState);
Bundle args = getArguments();
realmHelper = RealmStore.get(args.getString("serverConfigId"));
roomId = args.getString("roomId");
}
......@@ -48,11 +50,8 @@ public class RoomFragment extends AbstractChatRoomFragment {
@Override protected void onSetupView() {
// TODO: just a sample!!
RealmHelperBolts.executeTransaction(realm -> {
final String serverConfigId = realm.where(RoomSubscription.class)
.equalTo("rid", roomId).findFirst().getServerConfigId();
realmHelper.executeTransaction(realm -> {
realm.createOrUpdateObjectFromJson(LoadMessageProcedure.class, new JSONObject()
.put("serverConfigId", serverConfigId)
.put("roomId", roomId)
.put("syncstate", SyncState.NOT_SYNCED)
.put("count", 50)
......@@ -62,15 +61,9 @@ public class RoomFragment extends AbstractChatRoomFragment {
}
private RealmObjectObserver<RoomSubscription> roomObserver =
new RealmObjectObserver<RoomSubscription>() {
@Override protected RealmQuery<RoomSubscription> query(Realm realm) {
return realm.where(RoomSubscription.class).equalTo("rid", roomId);
}
@Override protected void onChange(RoomSubscription roomSubscription) {
onRenderRoom(roomSubscription);
}
};
realmHelper
.createObjectObserver(realm -> realm.where(RoomSubscription.class).equalTo("rid", roomId))
.setOnUpdateListener(this::onRenderRoom);
private void onRenderRoom(RoomSubscription roomSubscription) {
activityToolbar.setTitle(roomSubscription.getName());
......
......@@ -9,10 +9,10 @@ import android.webkit.WebView;
import chat.rocket.android.fragment.AbstractWebViewFragment;
import chat.rocket.android.helper.LogcatIfError;
import chat.rocket.android.helper.MethodCallHelper;
import chat.rocket.android.model.ddp.MeteorLoginServiceConfiguration;
import chat.rocket.android.model.ServerConfig;
import chat.rocket.android.model.ddp.MeteorLoginServiceConfiguration;
import chat.rocket.android.realm_helper.RealmStore;
import java.nio.charset.Charset;
import jp.co.crowdworks.realm_java_helpers.RealmHelper;
import okhttp3.HttpUrl;
import org.json.JSONException;
import org.json.JSONObject;
......@@ -50,12 +50,12 @@ public class GitHubOAuthFragment extends AbstractWebViewFragment {
}
serverConfigId = args.getString("serverConfigId");
ServerConfig serverConfig = RealmHelper.executeTransactionForRead(realm ->
ServerConfig serverConfig = RealmStore.getDefault().executeTransactionForRead(realm ->
realm.where(ServerConfig.class).equalTo("serverConfigId", serverConfigId).findFirst());
MeteorLoginServiceConfiguration oauthConfig = RealmHelper.executeTransactionForRead(realm ->
MeteorLoginServiceConfiguration oauthConfig =
RealmStore.get(serverConfigId).executeTransactionForRead(realm ->
realm.where(MeteorLoginServiceConfiguration.class)
.equalTo("service", "github")
.equalTo("serverConfigId", serverConfigId)
.findFirst());
if (serverConfig == null || oauthConfig == null) {
throw new IllegalArgumentException(
......
......@@ -5,11 +5,9 @@ import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import chat.rocket.android.R;
import chat.rocket.android.fragment.AbstractFragment;
import chat.rocket.android.helper.OnBackPressListener;
import chat.rocket.android.helper.TextUtils;
abstract class AbstractServerConfigFragment extends AbstractFragment
implements OnBackPressListener {
abstract class AbstractServerConfigFragment extends AbstractFragment {
protected String serverConfigId;
@Override public void onCreate(@Nullable Bundle savedInstanceState) {
......@@ -41,13 +39,4 @@ abstract class AbstractServerConfigFragment extends AbstractFragment
.addToBackStack(null)
.commit();
}
@Override public boolean onBackPressed() {
if (getFragmentManager().getBackStackEntryCount() > 0) {
getFragmentManager().popBackStack();
return true;
}
return false;
}
}
package chat.rocket.android.fragment.server_config;
import android.os.Handler;
import android.os.Message;
import android.support.design.widget.Snackbar;
import android.widget.TextView;
import android.widget.Toast;
import chat.rocket.android.R;
import chat.rocket.android.helper.LogcatIfError;
import chat.rocket.android.helper.TextUtils;
import chat.rocket.android.model.ServerConfig;
import io.realm.Realm;
import io.realm.RealmQuery;
import jp.co.crowdworks.realm_java_helpers.RealmObjectObserver;
import jp.co.crowdworks.realm_java_helpers_bolts.RealmHelperBolts;
import chat.rocket.android.realm_helper.RealmObjectObserver;
import chat.rocket.android.realm_helper.RealmStore;
import org.json.JSONObject;
/**
* Input server host.
*/
public class InputHostnameFragment extends AbstractServerConfigFragment {
private Handler errorShowingHandler = new Handler() {
@Override public void handleMessage(Message msg) {
Toast.makeText(rootView.getContext(), (String) msg.obj, Toast.LENGTH_SHORT).show();
}
};
RealmObjectObserver<ServerConfig> serverConfigObserver = new RealmObjectObserver<ServerConfig>() {
@Override protected RealmQuery<ServerConfig> query(Realm realm) {
return realm.where(ServerConfig.class).equalTo("serverConfigId", serverConfigId);
}
@Override protected void onChange(ServerConfig config) {
onRenderServerConfig(config);
}
};
RealmObjectObserver<ServerConfig> serverConfigObserver = RealmStore.getDefault()
.createObjectObserver(realm ->
realm.where(ServerConfig.class).equalTo("serverConfigId", serverConfigId))
.setOnUpdateListener(this::onRenderServerConfig);
public InputHostnameFragment() {
}
......@@ -52,12 +38,13 @@ public class InputHostnameFragment extends AbstractServerConfigFragment {
final String hostname =
TextUtils.or(TextUtils.or(editor.getText(), editor.getHint()), "").toString();
RealmHelperBolts.executeTransaction(
RealmStore.getDefault().executeTransaction(
realm -> realm.createOrUpdateObjectFromJson(ServerConfig.class,
new JSONObject().put("serverConfigId", serverConfigId)
.put("hostname", hostname)
.put("connectionError", JSONObject.NULL)
.put("session", JSONObject.NULL))).continueWith(new LogcatIfError());
.put("error", JSONObject.NULL)
.put("session", JSONObject.NULL)
.put("state", ServerConfig.STATE_READY))).continueWith(new LogcatIfError());
}
@Override public void onResume() {
......@@ -71,28 +58,21 @@ public class InputHostnameFragment extends AbstractServerConfigFragment {
}
private void showError(String errString) {
errorShowingHandler.removeMessages(0);
Message msg = Message.obtain(errorShowingHandler, 0, errString);
errorShowingHandler.sendMessageDelayed(msg, 160);
Snackbar.make(rootView, errString, Snackbar.LENGTH_LONG).show();
}
private void onRenderServerConfig(ServerConfig config) {
if (config == null) {
return;
}
final TextView editor = (TextView) rootView.findViewById(R.id.editor_hostname);
if (!TextUtils.isEmpty(config.getHostname())) {
editor.setText(config.getHostname());
}
if (!TextUtils.isEmpty(config.getConnectionError())) {
clearConnectionErrorAndHostname();
showError(config.getConnectionError());
}
if (!TextUtils.isEmpty(config.getError())) {
showError(config.getError());
}
private void clearConnectionErrorAndHostname() {
RealmHelperBolts.executeTransaction(
realm -> realm.createOrUpdateObjectFromJson(ServerConfig.class,
new JSONObject().put("serverConfigId", serverConfigId)
.put("hostname", JSONObject.NULL)
.put("connectionError", JSONObject.NULL))).continueWith(new LogcatIfError());
}
}
package chat.rocket.android.fragment.server_config;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.design.widget.Snackbar;
import android.view.View;
import android.widget.TextView;
......@@ -8,10 +10,9 @@ import chat.rocket.android.fragment.oauth.GitHubOAuthFragment;
import chat.rocket.android.helper.MethodCallHelper;
import chat.rocket.android.helper.TextUtils;
import chat.rocket.android.model.ddp.MeteorLoginServiceConfiguration;
import io.realm.Realm;
import io.realm.RealmResults;
import chat.rocket.android.realm_helper.RealmListObserver;
import chat.rocket.android.realm_helper.RealmStore;
import java.util.List;
import jp.co.crowdworks.realm_java_helpers.RealmListObserver;
/**
* Login screen.
......@@ -21,18 +22,14 @@ public class LoginFragment extends AbstractServerConfigFragment {
return R.layout.fragment_login;
}
private RealmListObserver<MeteorLoginServiceConfiguration> authProvidersObserver =
new RealmListObserver<MeteorLoginServiceConfiguration>() {
@Override protected RealmResults<MeteorLoginServiceConfiguration> queryItems(Realm realm) {
return realm.where(MeteorLoginServiceConfiguration.class)
.equalTo("serverConfigId", serverConfigId)
.findAll();
}
private RealmListObserver<MeteorLoginServiceConfiguration> authProvidersObserver;
@Override protected void onCollectionChanged(List<MeteorLoginServiceConfiguration> list) {
onRenderAuthProviders(list);
@Override public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
authProvidersObserver = RealmStore.get(serverConfigId)
.createListObserver(realm -> realm.where(MeteorLoginServiceConfiguration.class).findAll())
.setOnUpdateListener(this::onRenderAuthProviders);
}
};
@Override protected void onSetupView() {
final View btnEmail = rootView.findViewById(R.id.btn_login_with_email);
......
package chat.rocket.android.fragment.server_config;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.view.View;
import android.widget.TextView;
import chat.rocket.android.R;
import chat.rocket.android.helper.MethodCallHelper;
import chat.rocket.android.helper.TextUtils;
import chat.rocket.android.model.internal.Session;
import chat.rocket.android.realm_helper.RealmObjectObserver;
import chat.rocket.android.realm_helper.RealmStore;
/**
* Login screen.
*/
public class RetryLoginFragment extends AbstractServerConfigFragment {
@Override protected int getLayout() {
return R.layout.fragment_retry_login;
}
private RealmObjectObserver<Session> sessionObserver;
@Override public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
sessionObserver = RealmStore.get(serverConfigId)
.createObjectObserver(realm ->
realm.where(Session.class).equalTo("sessionId", Session.DEFAULT_ID))
.setOnUpdateListener(this::onRenderServerConfigSession);
}
@Override protected void onSetupView() {
}
private void onRenderServerConfigSession(Session session) {
if (session == null) {
return;
}
final String token = session.getToken();
if (!TextUtils.isEmpty(token)) {
final View btnRetry = rootView.findViewById(R.id.btn_retry_login);
final View waitingView = rootView.findViewById(R.id.waiting);
waitingView.setVisibility(View.GONE);
btnRetry.setOnClickListener(view -> {
view.setEnabled(false);
waitingView.setVisibility(View.VISIBLE);
new MethodCallHelper(serverConfigId).loginWithToken(token)
.continueWith(task -> {
if (task.isFaulted()) {
view.setEnabled(true);
waitingView.setVisibility(View.GONE);
Session.logError(RealmStore.get(serverConfigId), task.getError());
}
return null;
});
});
}
final String error = session.getError();
final TextView txtError = (TextView) rootView.findViewById(R.id.txt_error_description);
if (!TextUtils.isEmpty(error)) {
txtError.setText(error);
}
}
@Override public void onResume() {
super.onResume();
sessionObserver.sub();
}
@Override public void onPause() {
sessionObserver.unsub();
super.onPause();
}
}
......@@ -3,14 +3,16 @@ package chat.rocket.android.helper;
import android.util.Patterns;
import bolts.Continuation;
import bolts.Task;
import chat.rocket.android.model.MethodCall;
import chat.rocket.android.model.ServerConfig;
import chat.rocket.android.model.ddp.Message;
import chat.rocket.android.model.ddp.RoomSubscription;
import chat.rocket.android.model.internal.MethodCall;
import chat.rocket.android.model.internal.Session;
import chat.rocket.android.realm_helper.RealmHelper;
import chat.rocket.android.realm_helper.RealmStore;
import chat.rocket.android.ws.RocketChatWebSocketAPI;
import chat.rocket.android_ddp.DDPClientCallback;
import hugo.weaving.DebugLog;
import java.util.UUID;
import jp.co.crowdworks.realm_java_helpers_bolts.RealmHelperBolts;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
......@@ -20,26 +22,27 @@ import org.json.JSONObject;
*/
public class MethodCallHelper {
private final String serverConfigId;
private final RealmHelper realmHelper;
private final RocketChatWebSocketAPI api;
private static final long TIMEOUT_MS = 4000;
public MethodCallHelper(String serverConfigId) {
this.serverConfigId = serverConfigId;
this.realmHelper = RealmStore.get(serverConfigId);
api = null;
}
public MethodCallHelper(String serverConfigId, RocketChatWebSocketAPI api) {
this.serverConfigId = serverConfigId;
public MethodCallHelper(RealmHelper realmHelper, RocketChatWebSocketAPI api) {
this.realmHelper = realmHelper;
this.api = api;
}
@DebugLog
private Task<String> executeMethodCall(String methodName, String param, long timeout) {
if (api != null) {
return api.rpc(UUID.randomUUID().toString(), methodName, param, timeout)
.onSuccessTask(task -> Task.forResult(task.getResult().result));
} else {
return MethodCall.execute(serverConfigId, methodName, param, timeout);
return MethodCall.execute(realmHelper, methodName, param, timeout);
}
}
......@@ -48,7 +51,11 @@ public class MethodCallHelper {
if (_task.isFaulted()) {
Exception exception = _task.getError();
if (exception instanceof MethodCall.Error) {
String errMessage = new JSONObject(exception.getMessage()).getString("message");
String errMessageJson = exception.getMessage();
if (TextUtils.isEmpty(errMessageJson)) {
return Task.forError(exception);
}
String errMessage = new JSONObject(errMessageJson).getString("message");
return Task.forError(new Exception(errMessage));
} else if (exception instanceof DDPClientCallback.RPC.Timeout) {
return Task.forError(new MethodCall.Timeout());
......@@ -98,11 +105,12 @@ public class MethodCallHelper {
}
private Task<Void> saveToken(Task<String> task) {
return RealmHelperBolts.executeTransaction(realm ->
realm.createOrUpdateObjectFromJson(ServerConfig.class, new JSONObject()
.put("serverConfigId", serverConfigId)
return realmHelper.executeTransaction(realm ->
realm.createOrUpdateObjectFromJson(Session.class, new JSONObject()
.put("sessionId", Session.DEFAULT_ID)
.put("token", task.getResult())
.put("tokenVerified", true)));
.put("tokenVerified", true)
));
}
/**
......@@ -166,10 +174,10 @@ public class MethodCallHelper {
final JSONArray result = task.getResult();
try {
for (int i = 0; i < result.length(); i++) {
result.getJSONObject(i).put("serverConfigId", serverConfigId);
RoomSubscription.customizeJson(result.getJSONObject(i));
}
return RealmHelperBolts.executeTransaction(realm -> {
return realmHelper.executeTransaction(realm -> {
realm.createOrUpdateAllFromJson(
RoomSubscription.class, result);
return null;
......@@ -198,7 +206,7 @@ public class MethodCallHelper {
Message.customizeJson(messages.getJSONObject(i));
}
return RealmHelperBolts.executeTransaction(realm -> {
return realmHelper.executeTransaction(realm -> {
if (timestamp == 0) {
realm.where(Message.class).equalTo("rid", roomId).findAll().deleteAllFromRealm();
}
......
......@@ -2,69 +2,26 @@ package chat.rocket.android.model;
import bolts.Task;
import chat.rocket.android.helper.LogcatIfError;
import chat.rocket.android.realm_helper.RealmStore;
import hugo.weaving.DebugLog;
import io.realm.Realm;
import io.realm.RealmObject;
import io.realm.RealmQuery;
import io.realm.RealmResults;
import io.realm.annotations.PrimaryKey;
import jp.co.crowdworks.realm_java_helpers.RealmHelper;
import jp.co.crowdworks.realm_java_helpers_bolts.RealmHelperBolts;
import org.json.JSONObject;
/**
* Server configuration.
*/
public class ServerConfig extends RealmObject {
public static final int STATE_READY = 0;
public static final int STATE_CONNECTING = 1;
public static final int STATE_CONNECTED = 2;
public static final int STATE_CONNECTION_ERROR = 3;
@PrimaryKey private String serverConfigId;
private String hostname;
private String connectionError;
private int state;
private String session;
private String token;
private boolean tokenVerified;
public static RealmQuery<ServerConfig> queryLoginRequiredConnections(Realm realm) {
return realm.where(ServerConfig.class).equalTo("tokenVerified", false);
}
/**
* Check if connection login required exists.
*/
public static boolean hasLoginRequiredConnection() {
ServerConfig config =
RealmHelper.executeTransactionForRead(realm ->
queryLoginRequiredConnections(realm).findFirst());
return config != null;
}
/**
* Request token refresh.
*/
public static Task<Void> forceInvalidateToken() {
return RealmHelperBolts.executeTransaction(realm -> {
RealmResults<ServerConfig> targetConfigs = realm.where(ServerConfig.class)
.isNotNull("token")
.equalTo("tokenVerified", true)
.findAll();
for (ServerConfig config : targetConfigs) {
config.setTokenVerified(false);
}
return null;
});
}
/**
* Log the server connection is lost due to soem exception.
*/
@DebugLog public static void logConnectionError(String serverConfigId, Exception exception) {
RealmHelperBolts.executeTransaction(
realm -> realm.createOrUpdateObjectFromJson(ServerConfig.class, new JSONObject()
.put("serverConfigId", serverConfigId)
.put("connectionError", exception.getMessage())
.put("session", JSONObject.NULL)))
.continueWith(new LogcatIfError());
}
private String error;
public String getServerConfigId() {
return serverConfigId;
......@@ -82,12 +39,12 @@ public class ServerConfig extends RealmObject {
this.hostname = hostname;
}
public String getConnectionError() {
return connectionError;
public int getState() {
return state;
}
public void setConnectionError(String connectionError) {
this.connectionError = connectionError;
public void setState(int state) {
this.state = state;
}
public String getSession() {
......@@ -98,19 +55,36 @@ public class ServerConfig extends RealmObject {
this.session = session;
}
public String getToken() {
return token;
public String getError() {
return error;
}
public void setToken(String token) {
this.token = token;
public void setError(String error) {
this.error = error;
}
public boolean isTokenVerified() {
return tokenVerified;
/**
* Log the server connection is lost due to soem exception.
*/
@DebugLog public static void logConnectionError(String serverConfigId, Exception exception) {
RealmStore.getDefault().executeTransaction(
realm -> realm.createOrUpdateObjectFromJson(ServerConfig.class, new JSONObject()
.put("serverConfigId", serverConfigId)
.put("state", STATE_CONNECTION_ERROR)
.put("error", exception.getMessage())))
.continueWith(new LogcatIfError());
}
public void setTokenVerified(boolean tokenVerified) {
this.tokenVerified = tokenVerified;
public static Task<Void> setState(final String serverConfigId, int state) {
return RealmStore.getDefault().executeTransaction(realm -> {
ServerConfig config =
realm.where(ServerConfig.class).equalTo("serverConfigId", serverConfigId).findFirst();
if (config == null || config.getState() != state) {
realm.createOrUpdateObjectFromJson(ServerConfig.class, new JSONObject()
.put("serverConfigId", serverConfigId)
.put("state", state));
}
return null;
});
}
}
......@@ -11,7 +11,6 @@ import io.realm.annotations.PrimaryKey;
public class MeteorLoginServiceConfiguration
extends RealmObject {
@PrimaryKey private String _id;
private String serverConfigId;
private String service;
private String consumerKey; //for Twitter
private String appId; //for Facebook
......@@ -25,14 +24,6 @@ public class MeteorLoginServiceConfiguration
this._id = _id;
}
public String getServerConfigId() {
return serverConfigId;
}
public void setServerConfigId(String serverConfigId) {
this.serverConfigId = serverConfigId;
}
public String getService() {
return service;
}
......
......@@ -2,6 +2,8 @@ package chat.rocket.android.model.ddp;
import io.realm.RealmObject;
import io.realm.annotations.PrimaryKey;
import org.json.JSONException;
import org.json.JSONObject;
/**
* Chat Room(Subscription).
......@@ -14,7 +16,6 @@ public class RoomSubscription extends RealmObject {
public static final String TYPE_DIRECT_MESSAGE = "d";
private String _id; //subscriptionId
private String serverConfigId;
@PrimaryKey private String rid; //roomId
private String name;
//private User u; // REMARK: do not save u, because it is just me.
......@@ -31,14 +32,6 @@ public class RoomSubscription extends RealmObject {
this._id = _id;
}
public String getServerConfigId() {
return serverConfigId;
}
public void setServerConfigId(String serverConfigId) {
this.serverConfigId = serverConfigId;
}
public String getRid() {
return rid;
}
......@@ -86,4 +79,8 @@ public class RoomSubscription extends RealmObject {
public void setUnread(int unread) {
this.unread = unread;
}
public static JSONObject customizeJson(JSONObject roomSubscriptionJson) throws JSONException {
return roomSubscriptionJson;
}
}
package chat.rocket.android.model;
package chat.rocket.android.model.internal;
import io.realm.RealmObject;
import io.realm.annotations.PrimaryKey;
......@@ -8,7 +8,6 @@ import io.realm.annotations.PrimaryKey;
*/
public class LoadMessageProcedure extends RealmObject {
@PrimaryKey private String roomId;
private String serverConfigId;
private int syncstate;
private boolean reset;
......@@ -25,14 +24,6 @@ public class LoadMessageProcedure extends RealmObject {
this.roomId = roomId;
}
public String getServerConfigId() {
return serverConfigId;
}
public void setServerConfigId(String serverConfigId) {
this.serverConfigId = serverConfigId;
}
public int getSyncstate() {
return syncstate;
}
......
package chat.rocket.android.model;
package chat.rocket.android.model.internal;
import bolts.Task;
import bolts.TaskCompletionSource;
import chat.rocket.android.helper.LogcatIfError;
import chat.rocket.android.helper.TextUtils;
import io.realm.Realm;
import chat.rocket.android.model.SyncState;
import chat.rocket.android.realm_helper.RealmHelper;
import chat.rocket.android.realm_helper.RealmObjectObserver;
import io.realm.RealmObject;
import io.realm.RealmQuery;
import io.realm.annotations.PrimaryKey;
import java.util.UUID;
import jp.co.crowdworks.realm_java_helpers.RealmObjectObserver;
import jp.co.crowdworks.realm_java_helpers_bolts.RealmHelperBolts;
import org.json.JSONObject;
import timber.log.Timber;
public class MethodCall extends RealmObject {
@PrimaryKey private String methodCallId;
private String serverConfigId; //not ServerConfig!(not to be notified the change of ServerConfig)
private int syncstate;
private String name;
private String paramsJson;
......@@ -31,14 +30,6 @@ public class MethodCall extends RealmObject {
this.methodCallId = methodCallId;
}
public String getServerConfigId() {
return serverConfigId;
}
public void setServerConfigId(String serverConfigId) {
this.serverConfigId = serverConfigId;
}
public int getSyncstate() {
return syncstate;
}
......@@ -98,14 +89,13 @@ public class MethodCall extends RealmObject {
/**
* insert a new record to request a method call.
*/
public static Task<String> execute(String serverConfigId, String name, String paramsJson,
public static Task<String> execute(RealmHelper realmHelper, String name, String paramsJson,
long timeout) {
final String newId = UUID.randomUUID().toString();
TaskCompletionSource<String> task = new TaskCompletionSource<>();
RealmHelperBolts.executeTransaction(realm -> {
realmHelper.executeTransaction(realm -> {
MethodCall call = realm.createObjectFromJson(MethodCall.class, new JSONObject()
.put("methodCallId", newId)
.put("serverConfigId", serverConfigId)
.put("syncstate", SyncState.NOT_SYNCED)
.put("timeout", timeout)
.put("name", name));
......@@ -115,31 +105,27 @@ public class MethodCall extends RealmObject {
if (_task.isFaulted()) {
task.setError(_task.getError());
} else {
new RealmObjectObserver<MethodCall>() {
@Override protected RealmQuery<MethodCall> query(Realm realm) {
return realm.where(MethodCall.class).equalTo("methodCallId", newId);
}
@Override protected void onChange(MethodCall methodCall) {
final RealmObjectObserver<MethodCall> observer =
realmHelper.createObjectObserver(realm ->
realm.where(MethodCall.class).equalTo("methodCallId", newId));
observer.setOnUpdateListener(methodCall -> {
int syncstate = methodCall.getSyncstate();
Timber.d("MethodCall[%s] syncstate=%d", methodCall.getMethodCallId(), syncstate);
if (syncstate == SyncState.SYNCED) {
String resultJson = methodCall.getResultJson();
if (TextUtils.isEmpty(resultJson)) {
task.setResult(null);
}
task.setResult(resultJson);
exit(methodCall.getMethodCallId());
observer.unsub();
remove(realmHelper, methodCall.getMethodCallId()).continueWith(new LogcatIfError());
} else if (syncstate == SyncState.FAILED) {
task.setError(new Error(methodCall.getResultJson()));
exit(methodCall.getMethodCallId());
}
observer.unsub();
remove(realmHelper, methodCall.getMethodCallId()).continueWith(new LogcatIfError());
}
private void exit(String newId) {
unsub();
remove(newId).continueWith(new LogcatIfError());
}
}.sub();
});
observer.sub();
}
return null;
});
......@@ -149,8 +135,8 @@ public class MethodCall extends RealmObject {
/**
* remove the request.
*/
public static final Task<Void> remove(String methodCallId) {
return RealmHelperBolts.executeTransaction(realm ->
public static final Task<Void> remove(RealmHelper realmHelper, String methodCallId) {
return realmHelper.executeTransaction(realm ->
realm.where(MethodCall.class)
.equalTo("methodCallId", methodCallId)
.findAll()
......
package chat.rocket.android.model.internal;
import chat.rocket.android.helper.LogcatIfError;
import chat.rocket.android.realm_helper.RealmHelper;
import hugo.weaving.DebugLog;
import io.realm.RealmObject;
import io.realm.annotations.PrimaryKey;
import org.json.JSONObject;
/**
* Login session info.
*/
public class Session extends RealmObject {
public static final int DEFAULT_ID = 0;
@PrimaryKey private int sessionId; //only 0 is used!
private String token;
private boolean tokenVerified;
private String error;
public int getSessionId() {
return sessionId;
}
public void setSessionId(int sessionId) {
this.sessionId = sessionId;
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public boolean isTokenVerified() {
return tokenVerified;
}
public void setTokenVerified(boolean tokenVerified) {
this.tokenVerified = tokenVerified;
}
public String getError() {
return error;
}
public void setError(String error) {
this.error = error;
}
/**
* Log the server connection is lost due to soem exception.
*/
@DebugLog public static void logError(RealmHelper realmHelper, Exception exception) {
// TODO: should remove token if 403.
realmHelper.executeTransaction(
realm -> realm.createOrUpdateObjectFromJson(Session.class, new JSONObject()
.put("sessionId", Session.DEFAULT_ID)
.put("tokenVerified", false)
.put("error", exception.getMessage())))
.continueWith(new LogcatIfError());
}
}
......@@ -8,34 +8,23 @@ import android.support.annotation.Nullable;
import bolts.Task;
import chat.rocket.android.helper.LogcatIfError;
import chat.rocket.android.model.ServerConfig;
import io.realm.Realm;
import chat.rocket.android.realm_helper.RealmHelper;
import chat.rocket.android.realm_helper.RealmListObserver;
import chat.rocket.android.realm_helper.RealmStore;
import io.realm.RealmResults;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import jp.co.crowdworks.realm_java_helpers.RealmListObserver;
import jp.co.crowdworks.realm_java_helpers_bolts.RealmHelperBolts;
/**
* Background service for Rocket.Chat.Application class.
*/
public class RocketChatService extends Service {
private RealmHelper realmHelper;
private HashMap<String, RocketChatWebSocketThread> webSocketThreads;
private RealmListObserver<ServerConfig> connectionRequiredServerConfigObserver =
new RealmListObserver<ServerConfig>() {
@Override protected RealmResults<ServerConfig> queryItems(Realm realm) {
return realm.where(ServerConfig.class)
.isNotNull("hostname")
.isNull("connectionError")
.findAll();
}
@Override protected void onCollectionChanged(List<ServerConfig> list) {
syncWebSocketThreadsWith(list);
}
};
private RealmListObserver<ServerConfig> connectionRequiredServerConfigObserver;
/**
* ensure RocketChatService alive.
......@@ -44,32 +33,40 @@ public class RocketChatService extends Service {
context.startService(new Intent(context, RocketChatService.class));
}
/**
* force stop RocketChatService.
*/
public static void kill(Context context) {
context.stopService(new Intent(context, RocketChatService.class));
}
@Override public void onCreate() {
super.onCreate();
webSocketThreads = new HashMap<>();
realmHelper = RealmStore.getDefault();
connectionRequiredServerConfigObserver = realmHelper
.createListObserver(realm -> realm.where(ServerConfig.class)
.isNotNull("hostname")
.equalTo("state", ServerConfig.STATE_READY)
.findAll())
.setOnUpdateListener(this::syncWebSocketThreadsWith);
refreshServerConfigState();
}
ServerConfig.forceInvalidateToken()
.continueWith(new LogcatIfError());
private void refreshServerConfigState() {
realmHelper.executeTransaction(realm -> {
RealmResults<ServerConfig> configs = realm.where(ServerConfig.class).findAll();
for (ServerConfig config: configs) {
config.setState(ServerConfig.STATE_READY);
}
return null;
}).continueWith(new LogcatIfError());;
}
@Override public int onStartCommand(Intent intent, int flags, int startId) {
RealmHelperBolts.executeTransaction(realm -> {
RealmResults<ServerConfig> targetConfigs = realm.where(ServerConfig.class)
.isNotNull("token")
.isNotNull("connectionError")
realmHelper.executeTransaction(realm -> {
RealmResults<ServerConfig> targetConfigs = realm
.where(ServerConfig.class)
.equalTo("state", ServerConfig.STATE_CONNECTION_ERROR)
.isNotNull("session")
.findAll();
for (ServerConfig config : targetConfigs) {
config.setConnectionError(null);
if (config.isTokenVerified()) {
config.setTokenVerified(false);
}
config.setState(ServerConfig.STATE_READY);
config.setError(null);
}
return null;
}).onSuccessTask(task -> {
......@@ -94,7 +91,10 @@ public class RocketChatService extends Service {
}
}
if (!found) {
RocketChatWebSocketThread.terminate(entry.getValue());
RocketChatWebSocketThread thread = entry.getValue();
if (thread != null) {
RocketChatWebSocketThread.destroy(thread);
}
iterator.remove();
}
}
......@@ -103,7 +103,7 @@ public class RocketChatService extends Service {
findOrCreateWebSocketThread(config).onSuccess(task -> {
RocketChatWebSocketThread thread = task.getResult();
if (thread != null) {
thread.syncStateWith(config);
thread.keepalive();
}
return null;
});
......@@ -113,10 +113,17 @@ public class RocketChatService extends Service {
private Task<RocketChatWebSocketThread> findOrCreateWebSocketThread(final ServerConfig config) {
final String serverConfigId = config.getServerConfigId();
if (webSocketThreads.containsKey(serverConfigId)) {
return Task.forResult(webSocketThreads.get(serverConfigId));
return ServerConfig.setState(serverConfigId, ServerConfig.STATE_CONNECTED)
.onSuccessTask(_task -> Task.forResult(webSocketThreads.get(serverConfigId)));
} else {
return ServerConfig.setState(serverConfigId, ServerConfig.STATE_CONNECTING)
.onSuccessTask(_task -> {
webSocketThreads.put(serverConfigId, null);
return RocketChatWebSocketThread.getStarted(getApplicationContext(), config)
return RocketChatWebSocketThread.getStarted(getApplicationContext(), config);
})
.onSuccessTask(task ->
ServerConfig.setState(serverConfigId, ServerConfig.STATE_CONNECTED)
.onSuccessTask(_task -> task))
.onSuccessTask(task -> {
webSocketThreads.put(serverConfigId, task.getResult());
return task;
......
......@@ -9,6 +9,9 @@ import bolts.TaskCompletionSource;
import chat.rocket.android.helper.LogcatIfError;
import chat.rocket.android.helper.TextUtils;
import chat.rocket.android.model.ServerConfig;
import chat.rocket.android.model.internal.Session;
import chat.rocket.android.realm_helper.RealmHelper;
import chat.rocket.android.realm_helper.RealmStore;
import chat.rocket.android.service.ddp.ActiveUsersSubscriber;
import chat.rocket.android.service.ddp.LoginServiceConfigurationSubscriber;
import chat.rocket.android.service.observer.LoadMessageProcedureObserver;
......@@ -21,8 +24,6 @@ import hugo.weaving.DebugLog;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Iterator;
import jp.co.crowdworks.realm_java_helpers.RealmHelper;
import jp.co.crowdworks.realm_java_helpers_bolts.RealmHelperBolts;
import org.json.JSONObject;
import timber.log.Timber;
......@@ -40,6 +41,8 @@ public class RocketChatWebSocketThread extends HandlerThread {
};
private final Context appContext;
private final String serverConfigId;
private final RealmHelper defaultRealm;
private final RealmHelper serverConfigRealm;
private final ArrayList<Registerable> listeners = new ArrayList<>();
private RocketChatWebSocketAPI webSocketAPI;
private boolean socketExists;
......@@ -47,8 +50,10 @@ public class RocketChatWebSocketThread extends HandlerThread {
private RocketChatWebSocketThread(Context appContext, String serverConfigId) {
super("RC_thread_" + serverConfigId);
this.serverConfigId = serverConfigId;
this.appContext = appContext;
this.serverConfigId = serverConfigId;
defaultRealm = RealmStore.getDefault();
serverConfigRealm = RealmStore.getOrCreate(serverConfigId);
}
/**
......@@ -67,19 +72,52 @@ public class RocketChatWebSocketThread extends HandlerThread {
}
}
}.start();
return task.getTask();
return task.getTask()
.onSuccessTask(_task ->
_task.getResult().connect().onSuccessTask(__task -> _task));
}
@Override protected void onLooperPrepared() {
super.onLooperPrepared();
forceInvalidateTokens();
}
private void forceInvalidateTokens() {
serverConfigRealm.executeTransaction(realm -> {
Session session = realm.where(Session.class).equalTo("sessionId", Session.DEFAULT_ID).findFirst();
if (session != null
&& !TextUtils.isEmpty(session.getToken())
&& (session.isTokenVerified() || !TextUtils.isEmpty(session.getError()))) {
session.setTokenVerified(false);
session.setError(null);
}
return null;
}).continueWith(new LogcatIfError());
}
/**
* terminate the thread.
* destroy the thread.
*/
@DebugLog public static void terminate(RocketChatWebSocketThread thread) {
@DebugLog public static void destroy(RocketChatWebSocketThread thread) {
thread.quit();
}
@Override public boolean quit() {
if (isAlive()) {
new Handler(getLooper()).post(() -> {
Timber.d("thread %s: quit()", Thread.currentThread().getId());
unregisterListeners();
RocketChatWebSocketThread.super.quit();
});
return true;
} else {
return super.quit();
}
}
private Task<Void> ensureConnection() {
if (webSocketAPI == null || !webSocketAPI.isConnected()) {
return registerListeners();
return connect();
} else {
return Task.forResult(null);
}
......@@ -88,70 +126,52 @@ public class RocketChatWebSocketThread extends HandlerThread {
/**
* synchronize the state of the thread with ServerConfig.
*/
@DebugLog public void syncStateWith(ServerConfig config) {
if (config == null || TextUtils.isEmpty(config.getHostname()) || !TextUtils.isEmpty(
config.getConnectionError())) {
quit();
} else {
@DebugLog public void keepalive() {
ensureConnection().continueWith(task -> {
new Handler(getLooper()).post(this::keepaliveListeners);
return null;
});
}
}
@Override protected void onLooperPrepared() {
super.onLooperPrepared();
registerListeners();
}
@Override public boolean quit() {
if (isAlive()) {
scheduleUnregisterListenersAndQuit();
return true;
} else {
return super.quit();
}
}
private void scheduleUnregisterListenersAndQuit() {
new Handler(getLooper()).post(() -> {
Timber.d("thread %s: quit()", Thread.currentThread().getId());
unregisterListeners();
RocketChatWebSocketThread.super.quit();
});
}
private void prepareWebSocket(ServerConfig config) {
private void prepareWebSocket(String hostname) {
if (webSocketAPI == null || !webSocketAPI.isConnected()) {
webSocketAPI = RocketChatWebSocketAPI.create(config.getHostname());
webSocketAPI = RocketChatWebSocketAPI.create(hostname);
}
}
@DebugLog private Task<Void> registerListeners() {
@DebugLog private Task<Void> connect() {
if (socketExists) {
return Task.forResult(null);
}
socketExists = true;
final ServerConfig config = RealmHelper.executeTransactionForRead(realm ->
final ServerConfig config = defaultRealm.executeTransactionForRead(realm ->
realm.where(ServerConfig.class).equalTo("serverConfigId", serverConfigId).findFirst());
prepareWebSocket(config);
prepareWebSocket(config.getHostname());
return webSocketAPI.connect(config.getSession()).onSuccessTask(task -> {
final String session = task.getResult().session;
RealmHelperBolts.executeTransaction(realm ->
defaultRealm.executeTransaction(realm ->
realm.createOrUpdateObjectFromJson(ServerConfig.class, new JSONObject()
.put("serverConfigId", serverConfigId)
.put("session", session))
).continueWith(new LogcatIfError());
).onSuccess(_task -> serverConfigRealm.executeTransaction(realm -> {
Session sessionObj = realm.where(Session.class)
.equalTo("sessionId", Session.DEFAULT_ID)
.findFirst();
if (sessionObj == null) {
realm.createOrUpdateObjectFromJson(Session.class,
new JSONObject().put("sessionId", Session.DEFAULT_ID));
}
return null;
})).continueWith(new LogcatIfError());
return task;
}).onSuccess(new Continuation<DDPClientCallback.Connect, Object>() {
// TODO type detection doesn't work due to retrolambda's bug...
@Override public Object then(Task<DDPClientCallback.Connect> task)
throws Exception {
registerListenersActually();
registerListeners();
// handling WebSocket#onClose() callback.
task.getResult().client.getOnCloseCallback().onSuccess(_task -> {
......@@ -175,11 +195,11 @@ public class RocketChatWebSocketThread extends HandlerThread {
}
//@DebugLog
private void registerListenersActually() {
private void registerListeners() {
if (!Thread.currentThread().getName().equals("RC_thread_" + serverConfigId)) {
// execute in Looper.
new Handler(getLooper()).post(() -> {
registerListenersActually();
registerListeners();
});
return;
}
......@@ -191,9 +211,9 @@ public class RocketChatWebSocketThread extends HandlerThread {
for (Class clazz : REGISTERABLE_CLASSES) {
try {
Constructor ctor = clazz.getConstructor(Context.class, String.class,
Constructor ctor = clazz.getConstructor(Context.class, RealmHelper.class,
RocketChatWebSocketAPI.class);
Object obj = ctor.newInstance(appContext, serverConfigId, webSocketAPI);
Object obj = ctor.newInstance(appContext, serverConfigRealm, webSocketAPI);
if (obj instanceof Registerable) {
Registerable registerable = (Registerable) obj;
......
......@@ -3,13 +3,13 @@ package chat.rocket.android.service.ddp;
import android.content.Context;
import android.text.TextUtils;
import chat.rocket.android.helper.LogcatIfError;
import chat.rocket.android.realm_helper.RealmHelper;
import chat.rocket.android.service.Registerable;
import chat.rocket.android.ws.RocketChatWebSocketAPI;
import chat.rocket.android_ddp.DDPSubscription;
import io.realm.Realm;
import io.realm.RealmObject;
import java.util.Iterator;
import jp.co.crowdworks.realm_java_helpers_bolts.RealmHelperBolts;
import org.json.JSONException;
import org.json.JSONObject;
import rx.Subscription;
......@@ -17,15 +17,15 @@ import timber.log.Timber;
abstract class AbstractDDPDocEventSubscriber implements Registerable {
protected final Context context;
protected final String serverConfigId;
protected final RealmHelper realmHelper;
protected final RocketChatWebSocketAPI webSocketAPI;
private String subscriptionId;
private Subscription rxSubscription;
protected AbstractDDPDocEventSubscriber(Context context, String serverConfigId,
protected AbstractDDPDocEventSubscriber(Context context, RealmHelper realmHelper,
RocketChatWebSocketAPI api) {
this.context = context;
this.serverConfigId = serverConfigId;
this.realmHelper = realmHelper;
this.webSocketAPI = api;
}
......@@ -50,7 +50,7 @@ abstract class AbstractDDPDocEventSubscriber implements Registerable {
return null;
});
RealmHelperBolts.executeTransaction(realm -> {
realmHelper.executeTransaction(realm -> {
realm.delete(getModelClass());
return null;
}).onSuccess(task -> {
......@@ -84,7 +84,7 @@ abstract class AbstractDDPDocEventSubscriber implements Registerable {
}
protected void onDocumentAdded(DDPSubscription.Added docEvent) {
RealmHelperBolts.executeTransaction(realm -> {
realmHelper.executeTransaction(realm -> {
onDocumentAdded(realm, docEvent);
return null;
}).continueWith(new LogcatIfError());
......@@ -93,13 +93,12 @@ abstract class AbstractDDPDocEventSubscriber implements Registerable {
private void onDocumentAdded(Realm realm, DDPSubscription.Added docEvent) throws JSONException {
//executed in RealmTransaction
JSONObject json = new JSONObject().put("_id", docEvent.docID);
json.put("serverConfigId", serverConfigId);
mergeJson(json, docEvent.fields);
realm.createOrUpdateObjectFromJson(getModelClass(), customizeFieldJson(json));
}
protected void onDocumentChanged(DDPSubscription.Changed docEvent) {
RealmHelperBolts.executeTransaction(realm -> {
realmHelper.executeTransaction(realm -> {
onDocumentChanged(realm, docEvent);
return null;
}).continueWith(new LogcatIfError());
......@@ -109,7 +108,6 @@ abstract class AbstractDDPDocEventSubscriber implements Registerable {
throws JSONException {
//executed in RealmTransaction
JSONObject json = new JSONObject().put("_id", docEvent.docID);
json.put("serverConfigId", serverConfigId);
for (int i = 0; i < docEvent.cleared.length(); i++) {
String fieldToDelete = docEvent.cleared.getString(i);
json.put(fieldToDelete, JSONObject.NULL);
......@@ -119,7 +117,7 @@ abstract class AbstractDDPDocEventSubscriber implements Registerable {
}
protected void onDocumentRemoved(DDPSubscription.Removed docEvent) {
RealmHelperBolts.executeTransaction(realm -> {
realmHelper.executeTransaction(realm -> {
onDocumentRemoved(realm, docEvent);
return null;
}).continueWith(new LogcatIfError());
......
......@@ -2,6 +2,7 @@ package chat.rocket.android.service.ddp;
import android.content.Context;
import chat.rocket.android.model.ddp.User;
import chat.rocket.android.realm_helper.RealmHelper;
import chat.rocket.android.ws.RocketChatWebSocketAPI;
import io.realm.RealmObject;
......@@ -9,9 +10,9 @@ import io.realm.RealmObject;
* "activeUsers" subscriber.
*/
public class ActiveUsersSubscriber extends AbstractDDPDocEventSubscriber {
public ActiveUsersSubscriber(Context context, String serverConfigId,
public ActiveUsersSubscriber(Context context, RealmHelper realmHelper,
RocketChatWebSocketAPI api) {
super(context, serverConfigId, api);
super(context, realmHelper, api);
}
@Override protected String getSubscriptionName() {
......
......@@ -2,6 +2,7 @@ package chat.rocket.android.service.ddp;
import android.content.Context;
import chat.rocket.android.model.ddp.MeteorLoginServiceConfiguration;
import chat.rocket.android.realm_helper.RealmHelper;
import chat.rocket.android.ws.RocketChatWebSocketAPI;
import io.realm.RealmObject;
......@@ -9,9 +10,9 @@ import io.realm.RealmObject;
* meteor.loginServiceConfiguration subscriber
*/
public class LoginServiceConfigurationSubscriber extends AbstractDDPDocEventSubscriber {
public LoginServiceConfigurationSubscriber(Context context, String serverConfigId,
public LoginServiceConfigurationSubscriber(Context context, RealmHelper realmHelper,
RocketChatWebSocketAPI api) {
super(context, serverConfigId, api);
super(context, realmHelper, api);
}
@Override protected String getSubscriptionName() {
......
package chat.rocket.android.service.observer;
import android.content.Context;
import chat.rocket.android.realm_helper.RealmHelper;
import chat.rocket.android.realm_helper.RealmListObserver;
import chat.rocket.android.service.Registerable;
import chat.rocket.android.ws.RocketChatWebSocketAPI;
import io.realm.RealmObject;
import jp.co.crowdworks.realm_java_helpers.RealmListObserver;
abstract class AbstractModelObserver<T extends RealmObject> extends RealmListObserver<T>
implements Registerable {
abstract class AbstractModelObserver<T extends RealmObject>
implements Registerable, RealmListObserver.Query<T>, RealmListObserver.OnUpdateListener<T> {
protected final Context context;
protected final String serverConfigId;
protected final RealmHelper realmHelper;
protected final RocketChatWebSocketAPI webSocketAPI;
private final RealmListObserver observer;
protected AbstractModelObserver(Context context, String serverConfigId,
protected AbstractModelObserver(Context context, RealmHelper realmHelper,
RocketChatWebSocketAPI api) {
this.context = context;
this.serverConfigId = serverConfigId;
this.realmHelper = realmHelper;
webSocketAPI = api;
observer = realmHelper.createListObserver(this).setOnUpdateListener(this);
}
@Override public void register() {
sub();
observer.sub();
}
@Override public void keepalive() {
observer.keepalive();
}
@Override public void unregister() {
unsub();
observer.unsub();
}
}
......@@ -3,16 +3,15 @@ package chat.rocket.android.service.observer;
import android.content.Context;
import bolts.Task;
import chat.rocket.android.helper.MethodCallHelper;
import chat.rocket.android.model.LoadMessageProcedure;
import chat.rocket.android.model.internal.LoadMessageProcedure;
import chat.rocket.android.model.SyncState;
import chat.rocket.android.model.ddp.Message;
import chat.rocket.android.realm_helper.RealmHelper;
import chat.rocket.android.ws.RocketChatWebSocketAPI;
import io.realm.Realm;
import io.realm.RealmResults;
import io.realm.Sort;
import java.util.List;
import jp.co.crowdworks.realm_java_helpers.RealmHelper;
import jp.co.crowdworks.realm_java_helpers_bolts.RealmHelperBolts;
import org.json.JSONObject;
import timber.log.Timber;
......@@ -23,45 +22,44 @@ public class LoadMessageProcedureObserver extends AbstractModelObserver<LoadMess
private final MethodCallHelper methodCall;
public LoadMessageProcedureObserver(Context context, String serverConfigId,
public LoadMessageProcedureObserver(Context context, RealmHelper realmHelper,
RocketChatWebSocketAPI api) {
super(context, serverConfigId, api);
methodCall = new MethodCallHelper(serverConfigId, api);
super(context, realmHelper, api);
methodCall = new MethodCallHelper(realmHelper, api);
}
@Override protected RealmResults<LoadMessageProcedure> queryItems(Realm realm) {
@Override public RealmResults<LoadMessageProcedure> queryItems(Realm realm) {
return realm.where(LoadMessageProcedure.class)
.equalTo("serverConfigId", serverConfigId)
.equalTo("syncstate", SyncState.NOT_SYNCED)
.findAll();
}
@Override protected void onCollectionChanged(List<LoadMessageProcedure> list) {
if (list == null || list.isEmpty()) {
@Override public void onUpdateResults(List<LoadMessageProcedure> results) {
if (results == null || results.isEmpty()) {
return;
}
LoadMessageProcedure procedure = list.get(0);
LoadMessageProcedure procedure = results.get(0);
final String roomId = procedure.getRoomId();
final boolean isReset = procedure.isReset();
final long timestamp = procedure.getTimestamp();
final int count = procedure.getCount();
final long lastSeen = 0; // TODO: Not implemented yet.
RealmHelperBolts.executeTransaction(realm ->
realmHelper.executeTransaction(realm ->
realm.createOrUpdateObjectFromJson(LoadMessageProcedure.class, new JSONObject()
.put("roomId", roomId)
.put("syncstate", SyncState.SYNCING))
).onSuccessTask(task ->
methodCall.loadHistory(roomId, isReset ? 0 : timestamp, count, lastSeen)
.onSuccessTask(_task -> {
Message lastMessage = RealmHelper.executeTransactionForRead(realm ->
Message lastMessage = realmHelper.executeTransactionForRead(realm ->
realm.where(Message.class)
.equalTo("rid", roomId)
.equalTo("syncstate", SyncState.SYNCED)
.findAllSorted("ts", Sort.ASCENDING).last(null));
long lastTs = lastMessage != null ? lastMessage.getTs() : 0;
return RealmHelperBolts.executeTransaction(realm ->
return realmHelper.executeTransaction(realm ->
realm.createOrUpdateObjectFromJson(LoadMessageProcedure.class, new JSONObject()
.put("roomId", roomId)
.put("syncstate", SyncState.SYNCED)
......@@ -72,7 +70,7 @@ public class LoadMessageProcedureObserver extends AbstractModelObserver<LoadMess
).continueWithTask(task -> {
if (task.isFaulted()) {
Timber.w(task.getError());
return RealmHelperBolts.executeTransaction(realm ->
return realmHelper.executeTransaction(realm ->
realm.createOrUpdateObjectFromJson(LoadMessageProcedure.class, new JSONObject()
.put("roomId", roomId)
.put("syncstate", SyncState.FAILED)));
......
package chat.rocket.android.service.observer;
import android.content.Context;
import chat.rocket.android.helper.CheckSum;
import chat.rocket.android.helper.LogcatIfError;
import chat.rocket.android.model.MethodCall;
import chat.rocket.android.model.SyncState;
import chat.rocket.android.model.internal.MethodCall;
import chat.rocket.android.realm_helper.RealmHelper;
import chat.rocket.android.ws.RocketChatWebSocketAPI;
import chat.rocket.android_ddp.DDPClientCallback;
import io.realm.Realm;
import io.realm.RealmResults;
import java.util.List;
import jp.co.crowdworks.realm_java_helpers_bolts.RealmHelperBolts;
import org.json.JSONObject;
/**
......@@ -17,14 +18,15 @@ import org.json.JSONObject;
*/
public class MethodCallObserver extends AbstractModelObserver<MethodCall> {
private String prevHash;
/**
* constructor.
*/
public MethodCallObserver(Context context, String serverConfigId, RocketChatWebSocketAPI api) {
super(context, serverConfigId, api);
RealmHelperBolts.executeTransaction(realm -> {
public MethodCallObserver(Context context, RealmHelper realmHelper, RocketChatWebSocketAPI api) {
super(context, realmHelper, api);
realmHelper.executeTransaction(realm -> {
// resume pending operations.
RealmResults<MethodCall> pendingMethodCalls = realm.where(MethodCall.class)
.equalTo("serverConfigId", serverConfigId)
.equalTo("syncstate", SyncState.SYNCING)
.findAll();
for (MethodCall call : pendingMethodCalls) {
......@@ -33,7 +35,6 @@ public class MethodCallObserver extends AbstractModelObserver<MethodCall> {
// clean up records.
realm.where(MethodCall.class)
.equalTo("serverConfigId", serverConfigId)
.beginGroup()
.equalTo("syncstate", SyncState.SYNCED)
.or()
......@@ -44,31 +45,55 @@ public class MethodCallObserver extends AbstractModelObserver<MethodCall> {
}).continueWith(new LogcatIfError());
}
@Override protected RealmResults<MethodCall> queryItems(Realm realm) {
@Override public RealmResults<MethodCall> queryItems(Realm realm) {
return realm.where(MethodCall.class)
.isNotNull("name")
.equalTo("serverConfigId", serverConfigId)
.equalTo("syncstate", SyncState.NOT_SYNCED)
.findAll();
}
@Override protected void onCollectionChanged(List<MethodCall> list) {
if (list == null || list.isEmpty()) {
private String getHashFor(List<MethodCall> results) {
if (results == null) {
return "-";
}
if (results.isEmpty()) {
return "[]";
}
StringBuilder stringBuilder = new StringBuilder();
for (MethodCall result : results) {
stringBuilder.append(result.getMethodCallId());
}
return CheckSum.sha256(stringBuilder.toString());
}
@Override public void onUpdateResults(List<MethodCall> results) {
String hash = getHashFor(results);
if (prevHash == null) {
if (hash == null) {
return;
}
} else {
if (prevHash.equals(hash)) {
return;
}
}
prevHash = hash;
if (results == null || results.isEmpty()) {
return;
}
MethodCall call = list.get(0);
MethodCall call = results.get(0);
final String methodCallId = call.getMethodCallId();
final String methodName = call.getName();
final String params = call.getParamsJson();
final long timeout = call.getTimeout();
RealmHelperBolts.executeTransaction(realm ->
realmHelper.executeTransaction(realm ->
realm.createOrUpdateObjectFromJson(MethodCall.class, new JSONObject()
.put("methodCallId", methodCallId)
.put("syncstate", SyncState.SYNCING))
).onSuccessTask(task ->
webSocketAPI.rpc(methodCallId, methodName, params, timeout)
.onSuccessTask(_task -> RealmHelperBolts.executeTransaction(realm -> {
.onSuccessTask(_task -> realmHelper.executeTransaction(realm -> {
String json = _task.getResult().result;
return realm.createOrUpdateObjectFromJson(MethodCall.class, new JSONObject()
.put("methodCallId", methodCallId)
......@@ -78,7 +103,7 @@ public class MethodCallObserver extends AbstractModelObserver<MethodCall> {
)
).continueWithTask(task -> {
if (task.isFaulted()) {
return RealmHelperBolts.executeTransaction(realm -> {
return realmHelper.executeTransaction(realm -> {
Exception exception = task.getError();
final String errMessage = (exception instanceof DDPClientCallback.RPC.Error)
? ((DDPClientCallback.RPC.Error) exception).error.toString()
......
......@@ -4,44 +4,41 @@ import android.content.Context;
import chat.rocket.android.helper.LogcatIfError;
import chat.rocket.android.helper.MethodCallHelper;
import chat.rocket.android.model.ddp.RoomSubscription;
import chat.rocket.android.model.ServerConfig;
import chat.rocket.android.model.internal.Session;
import chat.rocket.android.realm_helper.RealmHelper;
import chat.rocket.android.ws.RocketChatWebSocketAPI;
import hugo.weaving.DebugLog;
import io.realm.Realm;
import io.realm.RealmResults;
import java.util.List;
import jp.co.crowdworks.realm_java_helpers_bolts.RealmHelperBolts;
/**
* Observes user is logged into server.
*/
public class SessionObserver extends AbstractModelObserver<ServerConfig> {
public class SessionObserver extends AbstractModelObserver<Session> {
private final MethodCallHelper methodCall;
private int count;
/**
* constructor.
*/
public SessionObserver(Context context, String serverConfigId, RocketChatWebSocketAPI api) {
super(context, serverConfigId, api);
methodCall = new MethodCallHelper(serverConfigId, api);
public SessionObserver(Context context, RealmHelper realmHelper, RocketChatWebSocketAPI api) {
super(context, realmHelper, api);
methodCall = new MethodCallHelper(realmHelper, api);
count = 0;
}
@Override protected RealmResults<ServerConfig> queryItems(Realm realm) {
return realm.where(ServerConfig.class)
.equalTo("serverConfigId", serverConfigId)
.isNotNull("hostname")
.isNull("connectionError")
.isNotNull("session")
@Override public RealmResults<Session> queryItems(Realm realm) {
return realm.where(Session.class)
.isNotNull("token")
.equalTo("tokenVerified", true)
.isNull("error")
.findAll();
}
@Override protected void onCollectionChanged(List<ServerConfig> list) {
@Override public void onUpdateResults(List<Session> results) {
int origCount = count;
count = list.size();
count = results.size();
if (origCount > 0 && count > 0) {
return;
}
......@@ -59,7 +56,7 @@ public class SessionObserver extends AbstractModelObserver<ServerConfig> {
}
@DebugLog private void onLogin() {
RealmHelperBolts.executeTransaction(realm -> {
realmHelper.executeTransaction(realm -> {
realm.delete(RoomSubscription.class);
return null;
}).onSuccessTask(_task -> methodCall.getRooms())
......
......@@ -2,39 +2,40 @@ package chat.rocket.android.service.observer;
import android.content.Context;
import chat.rocket.android.helper.MethodCallHelper;
import chat.rocket.android.model.ServerConfig;
import chat.rocket.android.model.internal.Session;
import chat.rocket.android.realm_helper.RealmHelper;
import chat.rocket.android.ws.RocketChatWebSocketAPI;
import io.realm.Realm;
import io.realm.RealmResults;
import java.util.List;
public class TokenLoginObserver extends AbstractModelObserver<ServerConfig> {
public class TokenLoginObserver extends AbstractModelObserver<Session> {
private final MethodCallHelper methodCall;
public TokenLoginObserver(Context context, String serverConfigId, RocketChatWebSocketAPI api) {
super(context, serverConfigId, api);
methodCall = new MethodCallHelper(serverConfigId, api);
public TokenLoginObserver(Context context, RealmHelper realmHelper, RocketChatWebSocketAPI api) {
super(context, realmHelper, api);
methodCall = new MethodCallHelper(realmHelper, api);
}
@Override protected RealmResults<ServerConfig> queryItems(Realm realm) {
return realm.where(ServerConfig.class)
.isNotNull("session")
@Override public RealmResults<Session> queryItems(Realm realm) {
return realm.where(Session.class)
.isNotNull("token")
.equalTo("tokenVerified", false)
.isNull("error")
.findAll();
}
@Override protected void onCollectionChanged(List<ServerConfig> list) {
if (list.isEmpty()) {
@Override public void onUpdateResults(List<Session> results) {
if (results.isEmpty()) {
return;
}
ServerConfig config = list.get(0);
methodCall.loginWithToken(config.getToken())
Session session = results.get(0);
methodCall.loginWithToken(session.getToken())
.continueWith(task -> {
if (task.isFaulted()) {
ServerConfig.logConnectionError(serverConfigId, task.getError());
Session.logError(realmHelper, task.getError());
}
return null;
});
......
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/colorPrimaryDark"
>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="@color/white"
android:minWidth="288dp"
android:orientation="vertical"
android:padding="@dimen/margin_24"
>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.AppCompat.Title"
android:text="Oops..."
/>
<TextView
android:id="@+id/txt_error_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@style/Base.TextAppearance.AppCompat.Body1"
android:layout_marginTop="@dimen/margin_8"
android:layout_marginBottom="@dimen/margin_8"
/>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:orientation="vertical"
>
<android.support.design.widget.FloatingActionButton
android:id="@+id/btn_retry_login"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/margin_8"
app:elevation="2dp"
app:fabSize="normal"
app:srcCompat="@drawable/ic_arrow_forward_white_24dp"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.AppCompat.Body1"
android:text="RETRY"
/>
</LinearLayout>
<chat.rocket.android.widget.WaitingView
android:id="@+id/waiting"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin_16"
app:dotCount="5"
app:dotSize="12dp"
android:layout_gravity="center"
/>
</LinearLayout>
</FrameLayout>
\ No newline at end of file
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
jcenter()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.2.2'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
classpath 'me.tatarka:gradle-retrolambda:3.3.1'
classpath 'me.tatarka.retrolambda.projectlombok:lombok.ast:0.2.3.a2'
classpath "io.realm:realm-gradle-plugin:2.1.1"
classpath 'com.jakewharton.hugo:hugo-plugin:1.2.1'
}
// Exclude the version that the android plugin depends on.
configurations.classpath.exclude group: 'com.android.tools.external.lombok'
}
apply from: rootProject.file('dependencies.gradle')
allprojects {
repositories {
......
ext {
androidPlugin = 'com.android.tools.build:gradle:2.2.2'
realmPlugin = 'io.realm:realm-gradle-plugin:2.2.0'
compileSdkVersion = 25
buildToolsVersion = '25.0.0'
supportVersion = '25.0.0'
supportAnnotations = "com.android.support:support-annotations:$supportVersion"
supportRecyclerView = "com.android.support:recyclerview-v7:$supportVersion"
supportAppCompat = "com.android.support:appcompat-v7:$supportVersion"
supportDesign = "com.android.support:design:$supportVersion"
rxJava = 'io.reactivex:rxjava:1.2.2'
boltsTask = 'com.parse.bolts:bolts-tasks:1.4.0'
timber = 'com.jakewharton.timber:timber:4.3.1'
}
subprojects { project ->
project.configurations.all {
resolutionStrategy {
eachDependency { details ->
if (details.requested.group == 'com.android.support') {
details.useVersion(rootProject.ext.supportVersion)
}
}
}
}
}
\ No newline at end of file
apply plugin: 'com.android.library'
apply plugin: 'realm-android'
buildscript {
repositories {
jcenter()
}
dependencies {
classpath rootProject.ext.androidPlugin
classpath rootProject.ext.realmPlugin
}
}
android {
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
defaultConfig {
minSdkVersion 16
targetSdkVersion rootProject.ext.compileSdkVersion
versionCode 1
versionName "1"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
testCompile 'junit:junit:4.12'
compile rootProject.ext.boltsTask
compile rootProject.ext.timber
compile rootProject.ext.supportAnnotations
compile rootProject.ext.supportAppCompat
compile rootProject.ext.supportDesign
}
<manifest package="chat.rocket.android.realm_helper"/>
\ No newline at end of file
package chat.rocket.android.realm_helper;
import io.realm.Realm;
import io.realm.RealmChangeListener;
import io.realm.RealmObject;
import io.realm.RealmResults;
import timber.log.Timber;
abstract class AbstractRealmResultsObserver<T extends RealmObject> {
protected Realm realm;
private RealmChangeListener<RealmResults<T>> listener;
protected abstract RealmResults<T> queryItems(Realm realm);
protected abstract RealmChangeListener<RealmResults<T>> getListener();
private RealmResults<T> results;
private final RealmHelper helper;
protected AbstractRealmResultsObserver(RealmHelper helper) {
this.helper = helper;
}
public void sub() {
unsub();
realm = helper.instance();
listener = getListener();
results = queryItems(realm);
listener.onChange(results);
results.addChangeListener(listener);
}
public void keepalive() {
if (realm == null || realm.isClosed()) {
unsub();
sub();
}
}
public void unsub() {
try {
if (results != null) {
results.removeChangeListener(listener);
results = null;
}
if (realm != null && !realm.isClosed()) {
realm.close();
}
} catch (IllegalStateException exception) {
Timber.w(exception);
}
}
}
package chat.rocket.android.realm_helper;
import android.os.Looper;
import bolts.Task;
import bolts.TaskCompletionSource;
import io.realm.Realm;
import io.realm.RealmConfiguration;
import io.realm.RealmObject;
import io.realm.RealmResults;
import java.util.Collections;
import java.util.List;
import timber.log.Timber;
public class RealmHelper {
private final RealmConfiguration realmConfiguration;
/*package*/ RealmHelper() {
this.realmConfiguration = null;
}
/*package*/ RealmHelper(RealmConfiguration realmConfiguration) {
this.realmConfiguration = realmConfiguration;
}
/*package*/ Realm instance() {
return realmConfiguration == null
? Realm.getDefaultInstance() : Realm.getInstance(realmConfiguration);
}
public <E extends RealmObject> List<E> copyFromRealm(Iterable<E> objects) {
if (objects == null) {
return Collections.emptyList();
}
Realm realm = instance();
List<E> list = realm.copyFromRealm(objects);
if (!realm.isClosed()) {
realm.close();
}
return list;
}
public <E extends RealmObject> E copyFromRealm(E object) {
if (object == null) {
return null;
}
Realm realm = instance();
E element = realm.copyFromRealm(object);
if (!realm.isClosed()) {
realm.close();
}
return element;
}
public interface Transaction<T> {
T execute(Realm realm) throws Exception;
}
public <T extends RealmObject> T executeTransactionForRead(Transaction<T> transaction) {
Realm realm = instance();
T object;
try {
T source = transaction.execute(realm);
object = source != null ? realm.copyFromRealm(source) : null;
} catch (Exception exception) {
Timber.w(exception);
object = null;
} finally {
if (!realm.isClosed()) {
realm.close();
}
}
return object;
}
public <T extends RealmObject> List<T> executeTransactionForReadResults(
Transaction<RealmResults<T>> transaction) {
Realm realm = instance();
List<T> object;
try {
object = realm.copyFromRealm(transaction.execute(realm));
} catch (Exception exception) {
Timber.w(exception);
object = null;
} finally {
if (!realm.isClosed()) {
realm.close();
}
}
return object;
}
public Task<Void> executeTransaction(final RealmHelper.Transaction transaction) {
return Looper.myLooper() == null ? executeTransactionSync(transaction)
: executeTransactionAsync(transaction);
}
private Task<Void> executeTransactionSync(final RealmHelper.Transaction transaction) {
final TaskCompletionSource<Void> task = new TaskCompletionSource<>();
final Realm realm = instance();
realm.executeTransaction(new Realm.Transaction() {
@Override public void execute(Realm realm) {
try {
transaction.execute(realm);
task.setResult(null);
} catch (Exception exception) {
task.setError(exception);
}
}
});
if (!realm.isClosed()) {
realm.close();
}
return task.getTask();
}
private Task<Void> executeTransactionAsync(final RealmHelper.Transaction transaction) {
final TaskCompletionSource<Void> task = new TaskCompletionSource<>();
final Realm realm = instance();
realm.executeTransactionAsync(new Realm.Transaction() {
@Override public void execute(Realm realm) {
try {
transaction.execute(realm);
} catch (Exception exception) {
task.setError(exception);
if (!realm.isClosed()) {
realm.close();
}
}
}
}, new Realm.Transaction.OnSuccess() {
@Override public void onSuccess() {
if (task.trySetResult(null)) {
if (realm != null && !realm.isClosed()) {
realm.close();
}
}
}
}, new Realm.Transaction.OnError() {
@Override public void onError(Throwable error) {
if (task.trySetError(new Exception(error))) {
if (!realm.isClosed()) {
realm.close();
}
}
}
});
return task.getTask();
}
public <T extends RealmObject> RealmListObserver<T> createListObserver(
RealmListObserver.Query<T> query) {
return new RealmListObserver<T>(this, query);
}
public <T extends RealmObject> RealmObjectObserver<T> createObjectObserver(
RealmObjectObserver.Query<T> query) {
return new RealmObjectObserver<T>(this, query);
}
public void delete() {
Realm.deleteRealm(realmConfiguration);
}
}
package chat.rocket.android.realm_helper;
import io.realm.Realm;
import io.realm.RealmChangeListener;
import io.realm.RealmObject;
import io.realm.RealmResults;
import java.util.List;
public class RealmListObserver<T extends RealmObject> extends AbstractRealmResultsObserver<T> {
public interface Query<T extends RealmObject> {
RealmResults<T> queryItems(Realm realm);
}
public interface OnUpdateListener<T extends RealmObject> {
void onUpdateResults(List<T> results);
}
private final Query<T> query;
private OnUpdateListener<T> onUpdateListener;
/*package*/ RealmListObserver(RealmHelper helper, Query<T> query) {
super(helper);
this.query = query;
}
public RealmListObserver<T> setOnUpdateListener(OnUpdateListener<T> onUpdateListener) {
this.onUpdateListener = onUpdateListener;
return this;
}
@Override protected final RealmResults<T> queryItems(Realm realm) {
return query.queryItems(realm);
}
@Override public final RealmChangeListener<RealmResults<T>> getListener() {
return new RealmChangeListener<RealmResults<T>>() {
@Override public void onChange(RealmResults<T> element) {
if (onUpdateListener != null) {
onUpdateListener.onUpdateResults(element);
}
}
};
}
}
\ No newline at end of file
package chat.rocket.android.realm_helper;
import io.realm.Realm;
import io.realm.RealmChangeListener;
import io.realm.RealmObject;
import io.realm.RealmQuery;
import io.realm.RealmResults;
public class RealmObjectObserver<T extends RealmObject> extends AbstractRealmResultsObserver<T> {
public interface Query<T extends RealmObject> {
RealmQuery<T> query(Realm realm);
}
public interface OnUpdateListener<T extends RealmObject> {
void onUpdateObject(T element);
}
public static class Impl<T extends RealmObject> {
protected T extractObjectFromResults(RealmResults<T> results) {
return results.isEmpty() ? null : results.last();
}
protected boolean isSame(T element1, T element2) {
return false;
}
}
private final Query<T> query;
private OnUpdateListener<T> onUpdateListener;
private Impl<T> impl;
/*package*/ RealmObjectObserver(RealmHelper helper, Query<T> query) {
super(helper);
this.query = query;
setImpl(new Impl<T>());
}
public void setImpl(Impl<T> impl) {
this.impl = impl;
}
public RealmObjectObserver<T> setOnUpdateListener(OnUpdateListener<T> onUpdateListener) {
this.onUpdateListener = onUpdateListener;
return this;
}
private T previousResult;
@Override protected final RealmResults<T> queryItems(Realm realm) {
return query.query(realm).findAll();
}
@Override protected final RealmChangeListener<RealmResults<T>> getListener() {
return new RealmChangeListener<RealmResults<T>>() {
@Override public void onChange(RealmResults<T> element) {
T source = impl.extractObjectFromResults(element);
T target = source != null ? realm.copyFromRealm(source) : null;
if (previousResult != null && impl.isSame(previousResult, target)) {
return;
}
previousResult = target;
if (onUpdateListener != null) {
onUpdateListener.onUpdateObject(target);
}
}
};
}
public void sub() {
previousResult = null;
super.sub();
}
}
\ No newline at end of file
package chat.rocket.android.realm_helper;
import io.realm.RealmConfiguration;
import java.util.HashMap;
public class RealmStore {
public static HashMap<String, RealmConfiguration> sStore = new HashMap<>();
private static RealmConfiguration createConfigFor(String name) {
return new RealmConfiguration.Builder()
.name(name)
.deleteRealmIfMigrationNeeded().build();
}
public static void put(String name) {
sStore.put(name, createConfigFor(name));
}
public static RealmHelper getDefault() {
return new RealmHelper();
}
public static RealmHelper get(String name) {
if (!sStore.containsKey(name)) {
return null;
}
return new RealmHelper(sStore.get(name));
}
public static RealmHelper getOrCreate(String name) {
if (!sStore.containsKey(name)) {
put(name);
}
return new RealmHelper(sStore.get(name));
}
}
......@@ -5,17 +5,17 @@ buildscript {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:+'
classpath rootProject.ext.androidPlugin
}
}
android {
compileSdkVersion 25
buildToolsVersion "25.0.0"
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
defaultConfig {
minSdkVersion 17
targetSdkVersion 25
targetSdkVersion rootProject.ext.compileSdkVersion
versionCode 1
versionName "1"
}
......@@ -29,7 +29,7 @@ android {
dependencies {
testCompile 'junit:junit:4.12'
compile 'com.android.support:support-annotations:+'
compile 'com.android.support:appcompat-v7:25.0.0'
compile 'com.android.support:design:25.0.0'
compile rootProject.ext.supportAnnotations
compile rootProject.ext.supportAppCompat
compile rootProject.ext.supportDesign
}
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="chat.rocket.android.widget">
<application
android:label="@string/app_name">
</application>
</manifest>
<manifest package="chat.rocket.android.widget"/>
\ No newline at end of file
<resources>
<string name="app_name">rocket-chat-android-widgets</string>
</resources>
include ':app', ':rocket-chat-android-widgets'
include ':app', ':rocket-chat-android-widgets', ':realm-helpers'
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment