MethodCallHelper.java 10.4 KB
Newer Older
Yusuke Iwaki's avatar
Yusuke Iwaki committed
1
package chat.rocket.android.api;
2

3
import android.content.Context;
4
import android.util.Patterns;
5
import bolts.Continuation;
6
import bolts.Task;
Yusuke Iwaki's avatar
Yusuke Iwaki committed
7 8
import chat.rocket.android.helper.CheckSum;
import chat.rocket.android.helper.TextUtils;
Yusuke Iwaki's avatar
Yusuke Iwaki committed
9
import chat.rocket.android.model.SyncState;
10 11
import chat.rocket.android.model.ddp.Message;
import chat.rocket.android.model.ddp.RoomSubscription;
12 13 14 15
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;
16
import chat.rocket.android_ddp.DDPClientCallback;
17
import hugo.weaving.DebugLog;
18
import java.util.UUID;
19
import org.json.JSONArray;
20 21 22 23
import org.json.JSONException;
import org.json.JSONObject;

/**
24
 * Utility class for creating/handling MethodCall or RPC.
25
 * TODO: separate method into several manager classes (SubscriptionManager, MessageManager, ...).
26 27 28
 */
public class MethodCallHelper {

29
  private final Context context;
30
  private final RealmHelper realmHelper;
31
  private final DDPClientWraper ddpClient;
32
  private static final long TIMEOUT_MS = 4000;
33

34 35 36 37
  @Deprecated
  /**
   * Deprecated. use MethodCall(Context, String) instead.
   */
38
  public MethodCallHelper(String serverConfigId) {
39 40 41 42 43 44 45 46
    this(null, serverConfigId);
  }

  /**
   * initialize with Context and ServerConfigId.
   */
  public MethodCallHelper(Context context, String serverConfigId) {
    this.context = context;
47
    this.realmHelper = RealmStore.get(serverConfigId);
48
    ddpClient = null;
49 50
  }

51 52 53
  /**
   * initialize with RealmHelper and DDPClient.
   */
54
  public MethodCallHelper(RealmHelper realmHelper, DDPClientWraper ddpClient) {
55
    this.context = null;
56
    this.realmHelper = realmHelper;
57
    this.ddpClient = ddpClient;
58 59
  }

60
  @DebugLog
61
  private Task<String> executeMethodCall(String methodName, String param, long timeout) {
62 63
    if (ddpClient != null) {
      return ddpClient.rpc(UUID.randomUUID().toString(), methodName, param, timeout)
64 65
          .onSuccessTask(task -> Task.forResult(task.getResult().result));
    } else {
66
      return MethodCall.execute(context, realmHelper, methodName, param, timeout);
67
    }
68 69
  }

70
  private Task<String> injectErrorHandler(Task<String> task) {
71 72 73 74
    return task.continueWithTask(_task -> {
      if (_task.isFaulted()) {
        Exception exception = _task.getError();
        if (exception instanceof MethodCall.Error) {
75 76 77 78 79
          String errMessageJson = exception.getMessage();
          if (TextUtils.isEmpty(errMessageJson)) {
            return Task.forError(exception);
          }
          String errMessage = new JSONObject(errMessageJson).getString("message");
80
          return Task.forError(new Exception(errMessage));
Yusuke Iwaki's avatar
Yusuke Iwaki committed
81 82 83
        } else if (exception instanceof DDPClientCallback.RPC.Error) {
          String errMessage = ((DDPClientCallback.RPC.Error) exception).error.getString("message");
          return Task.forError(new Exception(errMessage));
84 85
        } else if (exception instanceof DDPClientCallback.RPC.Timeout) {
          return Task.forError(new MethodCall.Timeout());
86 87 88 89 90 91 92 93 94
        } else {
          return Task.forError(exception);
        }
      } else {
        return _task;
      }
    });
  }

95
  private interface ParamBuilder {
96
    JSONArray buildParam() throws JSONException;
97 98
  }

99 100
  private Task<String> call(String methodName, long timeout) {
    return injectErrorHandler(executeMethodCall(methodName, null, timeout));
101 102
  }

103
  private Task<String> call(String methodName, long timeout, ParamBuilder paramBuilder) {
104
    try {
105 106 107
      final JSONArray params = paramBuilder.buildParam();
      return injectErrorHandler(executeMethodCall(methodName,
          params != null ? params.toString() : null, timeout));
108 109 110
    } catch (JSONException exception) {
      return Task.forError(exception);
    }
111 112
  }

113 114 115 116 117 118
  private static final Continuation<String, Task<JSONObject>> CONVERT_TO_JSON_OBJECT =
      task -> Task.forResult(new JSONObject(task.getResult()));

  private static final Continuation<String, Task<JSONArray>> CONVERT_TO_JSON_ARRAY =
      task -> Task.forResult(new JSONArray(task.getResult()));

119 120 121
  /**
   * Register User.
   */
122
  public Task<String> registerUser(final String name, final String email,
123
      final String password, final String confirmPassword) {
124
    return call("registerUser", TIMEOUT_MS, () -> new JSONArray().put(new JSONObject()
125 126 127
        .put("name", name)
        .put("email", email)
        .put("pass", password)
128
        .put("confirm-pass", confirmPassword))); // nothing to do.
129 130
  }

131
  private Task<Void> saveToken(Task<String> task) {
132 133 134
    return realmHelper.executeTransaction(realm ->
        realm.createOrUpdateObjectFromJson(Session.class, new JSONObject()
            .put("sessionId", Session.DEFAULT_ID)
135
            .put("token", task.getResult())
136
            .put("tokenVerified", true)
Yusuke Iwaki's avatar
Yusuke Iwaki committed
137
            .put("error", JSONObject.NULL)
138
        ));
139 140
  }

141 142 143 144 145 146 147 148 149 150 151 152
  /**
   * set current user's name.
   */
  public Task<String> setUsername(final String username) {
    return call("setUsername", TIMEOUT_MS, () -> new JSONArray().put(username));
  }

  public Task<Void> joinDefaultChannels() {
    return call("joinDefaultChannels", TIMEOUT_MS)
        .onSuccessTask(task -> Task.forResult(null));
  }

153 154 155 156
  /**
   * Login with username/email and password.
   */
  public Task<Void> loginWithEmail(final String usernameOrEmail, final String password) {
157
    return call("login", TIMEOUT_MS, () -> {
158
      JSONObject param = new JSONObject();
159 160 161 162 163 164 165 166
      if (Patterns.EMAIL_ADDRESS.matcher(usernameOrEmail).matches()) {
        param.put("user", new JSONObject().put("email", usernameOrEmail));
      } else {
        param.put("user", new JSONObject().put("username", usernameOrEmail));
      }
      param.put("password", new JSONObject()
          .put("digest", CheckSum.sha256(password))
          .put("algorithm", "sha-256"));
167 168 169 170
      return new JSONArray().put(param);
    }).onSuccessTask(CONVERT_TO_JSON_OBJECT)
        .onSuccessTask(task -> Task.forResult(task.getResult().getString("token")))
        .onSuccessTask(this::saveToken);
171 172 173 174 175 176 177
  }

  /**
   * Login with GitHub OAuth.
   */
  public Task<Void> loginWithGitHub(final String credentialToken,
      final String credentialSecret) {
178 179 180 181 182 183 184
    return call("login", TIMEOUT_MS, () -> new JSONArray().put(new JSONObject()
        .put("oauth", new JSONObject()
            .put("credentialToken", credentialToken)
            .put("credentialSecret", credentialSecret))
    )).onSuccessTask(CONVERT_TO_JSON_OBJECT)
        .onSuccessTask(task -> Task.forResult(task.getResult().getString("token")))
        .onSuccessTask(this::saveToken);
185 186 187 188 189 190
  }

  /**
   * Login with token.
   */
  public Task<Void> loginWithToken(final String token) {
191 192 193 194
    return call("login", TIMEOUT_MS, () -> new JSONArray().put(new JSONObject()
        .put("resume", token)
    )).onSuccessTask(CONVERT_TO_JSON_OBJECT)
        .onSuccessTask(task -> Task.forResult(task.getResult().getString("token")))
Yusuke Iwaki's avatar
Yusuke Iwaki committed
195 196 197 198 199 200 201
        .onSuccessTask(this::saveToken)
        .continueWithTask(task -> {
          if (task.isFaulted()) {
            Session.logError(realmHelper, task.getError());
          }
          return task;
        });
202 203 204 205 206
  }

  /**
   * Logout.
   */
Yusuke Iwaki's avatar
Yusuke Iwaki committed
207 208 209 210 211 212
  public Task<Void> logout() {
    return call("logout", TIMEOUT_MS).onSuccessTask(task ->
        realmHelper.executeTransaction(realm -> {
          realm.delete(Session.class);
          return null;
        }));
213
  }
214 215

  /**
216
   * request "subscriptions/get".
217 218
   */
  public Task<Void> getRooms() {
219 220 221
    return call("subscriptions/get", TIMEOUT_MS).onSuccessTask(CONVERT_TO_JSON_ARRAY)
        .onSuccessTask(task -> {
          final JSONArray result = task.getResult();
222
          try {
223
            for (int i = 0; i < result.length(); i++) {
224
              RoomSubscription.customizeJson(result.getJSONObject(i));
225
            }
226

227
            return realmHelper.executeTransaction(realm -> {
228
              realm.delete(RoomSubscription.class);
229
              realm.createOrUpdateAllFromJson(
230
                  RoomSubscription.class, result);
231 232 233 234 235
              return null;
            });
          } catch (JSONException exception) {
            return Task.forError(exception);
          }
236
        });
237
  }
238 239 240 241

  /**
   * Load messages for room.
   */
242
  public Task<JSONArray> loadHistory(final String roomId, final long timestamp,
243
      final int count, final long lastSeen) {
244 245 246 247 248 249 250
    return call("loadHistory", TIMEOUT_MS, () -> new JSONArray()
        .put(roomId)
        .put(timestamp > 0 ? new JSONObject().put("$date", timestamp) : JSONObject.NULL)
        .put(count)
        .put(lastSeen > 0 ? new JSONObject().put("$date", lastSeen) : JSONObject.NULL)
    ).onSuccessTask(CONVERT_TO_JSON_OBJECT)
        .onSuccessTask(task -> {
251 252 253 254 255 256
          JSONObject result = task.getResult();
          final JSONArray messages = result.getJSONArray("messages");
          for (int i = 0; i < messages.length(); i++) {
            Message.customizeJson(messages.getJSONObject(i));
          }

257
          return realmHelper.executeTransaction(realm -> {
258
            if (timestamp == 0) {
Yusuke Iwaki's avatar
Yusuke Iwaki committed
259 260 261 262
              realm.where(Message.class)
                  .equalTo("rid", roomId)
                  .equalTo("syncstate", SyncState.SYNCED)
                  .findAll().deleteAllFromRealm();
263
            }
264 265 266
            if (messages.length() > 0) {
              realm.createOrUpdateAllFromJson(Message.class, messages);
            }
267
            return null;
268
          }).onSuccessTask(_task -> Task.forResult(messages));
269 270 271
        });
  }

272 273 274 275 276 277 278 279
  /**
   * update user's status.
   */
  public Task<Void> setUserStatus(final String status) {
    return call("UserPresence:setDefaultStatus", TIMEOUT_MS, () -> new JSONArray().put(status))
        .onSuccessTask(task -> Task.forResult(null));
  }

280 281 282 283 284

  public Task<JSONObject> getUsersOfRoom(final String roomId, final boolean showAll) {
    return call("getUsersOfRoom", TIMEOUT_MS, () -> new JSONArray().put(roomId).put(showAll))
        .onSuccessTask(CONVERT_TO_JSON_OBJECT);
  }
Yusuke Iwaki's avatar
Yusuke Iwaki committed
285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307

  /**
   * send message.
   */
  public Task<JSONObject> sendMessage(String messageId, String roomId, String msg) {
    try {
      return sendMessage(new JSONObject()
          .put("_id", messageId)
          .put("rid", roomId)
          .put("msg", msg));
    } catch (JSONException exception) {
      return Task.forError(exception);
    }
  }

  /**
   * Send message object.
   */
  public Task<JSONObject> sendMessage(final JSONObject messageJson) {
    return call("sendMessage", TIMEOUT_MS, () -> new JSONArray().put(messageJson))
        .onSuccessTask(CONVERT_TO_JSON_OBJECT)
        .onSuccessTask(task -> Task.forResult(Message.customizeJson(task.getResult())));
  }
308
}