All Downloads are FREE. Search and download functionalities are using the official Maven repository.

cn.leancloud.im.v2.AVIMClient Maven / Gradle / Ivy

package cn.leancloud.im.v2;

import cn.leancloud.AVException;
import cn.leancloud.AVLogger;
import cn.leancloud.AVQuery;
import cn.leancloud.AVUser;
import cn.leancloud.callback.GenericObjectCallback;
import cn.leancloud.im.AVIMOptions;
import cn.leancloud.im.InternalConfiguration;
import cn.leancloud.im.OperationTube;
import cn.leancloud.im.v2.callback.*;
import cn.leancloud.im.v2.conversation.AVIMConversationMemberInfo;
import cn.leancloud.query.QueryConditions;
import cn.leancloud.service.RealtimeClient;
import cn.leancloud.session.AVSession;
import cn.leancloud.session.AVSessionManager;
import cn.leancloud.utils.LogUtil;
import cn.leancloud.utils.StringUtil;
import com.alibaba.fastjson.JSONObject;
import io.reactivex.Observer;
import io.reactivex.disposables.Disposable;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

public class AVIMClient {
  private static final AVLogger LOGGER = LogUtil.getLogger(AVIMClient.class);

  private static ConcurrentMap clients =
          new ConcurrentHashMap();
  private static AVIMClientEventHandler clientEventHandler;

  /**
   * 当前client的状态
   */
  public enum AVIMClientStatus {
    /**
     * 当前client尚未open,或者已经close
     */
    AVIMClientStatusNone(110),
    /**
     * 当前client已经打开,连接正常
     */
    AVIMClientStatusOpened(111),
    /**
     * 当前client由于网络因素导致的连接中断
     */
    AVIMClientStatusPaused(120);

    int code;

    AVIMClientStatus(int code) {
      this.code = code;
    }

    public int getCode() {
      return code;
    }

    public static AVIMClientStatus getClientStatus(int code) {
      switch (code) {
        case 110:
          return AVIMClientStatusNone;
        case 111:
          return AVIMClientStatusOpened;
        case 120:
          return AVIMClientStatusPaused;
        default:
          return null;
      }
    };
  }

  // client id
  private String clientId = null;
  // client tag
  private String tag = null;
  // AVUser authentication session token
  private String userSessionToken = null;
  // realtime session token
  private String realtimeSessionToken = null;
  // realtime session token expired timestamp.
  private long realtimeSessionTokenExpired = 0l;

  private AVIMMessageStorage storage;
  private ConcurrentMap conversationCache =
          new ConcurrentHashMap();

  private AVIMClient(String clientId) {
    this.clientId = clientId;
    this.storage = AVIMMessageStorage.getInstance(clientId);
  }

  /**
   * 设置AVIMClient的事件处理单元,
   *
   * 包括Client断开链接和重连成功事件
   *
   * @param handler event handler.
   */
  public static void setClientEventHandler(AVIMClientEventHandler handler) {
    AVIMClient.clientEventHandler = handler;
  }

  public static AVIMClientEventHandler getClientEventHandler() {
    return AVIMClient.clientEventHandler;
  }

  /**
   * get AVIMClient instance by clientId.
   * @param clientId client id.
   * @return imclient instance.
   */
  public static AVIMClient getInstance(String clientId) {
    if (StringUtil.isEmpty(clientId)) {
      return null;
    }
    AVIMClient client = clients.get(clientId);
    if (null == client) {
      client = new AVIMClient(clientId);
      AVIMClient elderClient = clients.putIfAbsent(clientId, client);
      if (null != elderClient) {
        client = elderClient;
      }
    }
    return client;
  }

  /**
   * count used clients.
   * @return current client count.
   */
  public static int getClientsCount() {
    return clients.size();
  }

  /**
   * get default clientId.
   * @return the default client id.
   */
  public static String getDefaultClient() {
    if (getClientsCount() == 1) {
      return clients.keySet().iterator().next();
    }
    return "";
  }

  /**
   * get AVIMClient instance by clientId and tag.
   * @param clientId client id.
   * @param tag optional tag.
   * @return  imclient instance.
   */
  public static AVIMClient getInstance(String clientId, String tag) {
    AVIMClient client = getInstance(clientId);
    client.tag = tag;
    return client;
  }

  /**
   * get AVIMClient instance by AVUser
   * @param user user instance.
   * @return imclient instance.
   */
  public static AVIMClient getInstance(AVUser user) {
    if (null == user) {
      return null;
    }
    String clientId = user.getObjectId();
    String sessionToken = user.getSessionToken();
    if (StringUtil.isEmpty(clientId) || StringUtil.isEmpty(sessionToken)) {
      return null;
    }
    AVIMClient client = getInstance(clientId);
    client.userSessionToken = sessionToken;
    return client;
  }
  public static AVIMClient getInstance(AVUser user, String tag) {
    AVIMClient client = getInstance(user);
    client.tag = tag;
    return client;
  }

  public void getClientStatus(final AVIMClientStatusCallback callback) {
    OperationTube operationTube = InternalConfiguration.getOperationTube();
    operationTube.queryClientStatus(this.clientId, callback);
  }

  /**
   * 获取当前用户的 clientId
   * @return 返回clientId
   */
  public String getClientId() {
    return this.clientId;
  }

  /**
   * Open client.
   * @param callback callback function.
   */
  public void open(final AVIMClientCallback callback) {
    this.open(null, callback);
  }

  /**
   * Open Client with options.
   *
   * @param option open option.
   * @param callback callback function.
   */
  public void open(AVIMClientOpenOption option, final AVIMClientCallback callback) {
    boolean reConnect = null == option? false : option.isReconnect();
    OperationTube operationTube = InternalConfiguration.getOperationTube();
    operationTube.openClient(clientId, tag, userSessionToken, reConnect, callback);
  }

  /**
   * Query online clients.
   *
   * @param clients client list.
   * @param callback callback function.
   */
  public void getOnlineClients(List clients, final AVIMOnlineClientsCallback callback) {
    InternalConfiguration.getOperationTube().queryOnlineClients(this.clientId, clients, callback);
  }

  /**
   * Create a new Conversation
   *
   * @param conversationMembers member list.
   * @param attributes attribute map.
   * @param callback callback function.
   */
  public void createConversation(final List conversationMembers,
                                 final Map attributes, final AVIMConversationCreatedCallback callback) {
    this.createConversation(conversationMembers, null, attributes, false, callback);
  }

  /**
   * Create a new Conversation
   *
   * @param conversationMembers member list.
   * @param name conversation name.
   * @param attributes attribute map.
   * @param callback callback function.
   */
  public void createConversation(final List conversationMembers, String name,
                                 final Map attributes, final AVIMConversationCreatedCallback callback) {
    this.createConversation(conversationMembers, name, attributes, false, callback);
  }

  /**
   * Create a new Conversation
   *
   * @param members member list.
   * @param name conversation name.
   * @param attributes attribute map.
   * @param isTransient flag of transient.
   * @param callback callback function.
   */
  public void createConversation(final List members, final String name, final Map attributes,
                                 final boolean isTransient, final AVIMConversationCreatedCallback callback) {
    this.createConversation(members, name, attributes, isTransient, !isTransient, callback);
  }

  /**
   * Create a new Conversation
   *
   * @param members member list.
   * @param name conversation name.
   * @param attributes attribute map.
   * @param isTransient flag of transient.
   * @param isUnique flag of unique.
   * @param callback callback function.
   */
  public void createConversation(final List members, final String name, final Map attributes,
                                 final boolean isTransient, final boolean isUnique, final AVIMConversationCreatedCallback callback) {
    this.createConversation(members, name, attributes, isTransient, isUnique, false, 0, callback);
  }

  /**
   * Create a new temporary Conversation
   *
   * @param conversationMembers member list.
   * @param callback callback function.
   */
  public void createTemporaryConversation(final List conversationMembers, final AVIMConversationCreatedCallback callback) {
    this.createTemporaryConversation(conversationMembers, 86400*3, callback);
  }

  /**
   * Create a new temporary Conversation
   *
   * @param conversationMembers member list.
   * @param ttl ttl value in seconds.
   * @param callback callback function.
   */
  public void createTemporaryConversation(final List conversationMembers, int ttl, final AVIMConversationCreatedCallback callback) {
    this.createConversation(conversationMembers, null, null, false, true, true, ttl, callback);
  }

  /**
   * Create a new Chatroom
   *
   * @param conversationMembers member list.
   * @param name conversation name
   * @param attributes conversation attribute map.
   * @param isUnique deprecated chatroom is always not unique.
   * @param callback callback function.
   */
  public void createChatRoom(final List conversationMembers, String name, final Map attributes,
                             final boolean isUnique, final AVIMConversationCreatedCallback callback) {
    this.createConversation(conversationMembers, name, attributes, true, false, callback);
  }

  /**
   * Create a new Chatroom
   * @param name conversation name
   * @param attributes conversation attribute map.
   * @param callback callback function.
   */
  public void createChatRoom(String name, final Map attributes, final AVIMConversationCreatedCallback callback) {
    this.createConversation(null, name, attributes, true, false, callback);
  }

  private void createServiceConversation(String name, final Map attributes,
                                         final AVIMConversationCreatedCallback callback) {
    throw new UnsupportedOperationException("can't invoke createServiceConversation within SDK.");
  }

  private void createConversation(final List members, final String name,
                                  final Map attributes, final boolean isTransient, final boolean isUnique,
                                  final boolean isTemp, final int tempTTL, final AVIMConversationCreatedCallback callback) {
    final HashMap conversationAttributes = new HashMap();
    if (attributes != null) {
      conversationAttributes.putAll(attributes);
    }
    if (!StringUtil.isEmpty(name)) {
      conversationAttributes.put(Conversation.NAME, name);
    }
    Map assembledAttributes = null;
    if (conversationAttributes.size() > 0) {
      assembledAttributes = AVIMConversation.processAttributes(conversationAttributes, true);
    }
    final List conversationMembers = new ArrayList();
    if (null != members && members.size() > 0) {
      conversationMembers.addAll(members);
    }
    if (!conversationMembers.contains(clientId)) {
      conversationMembers.add(clientId);
    }
    final AVIMCommonJsonCallback middleCallback = new AVIMCommonJsonCallback() {
      @Override
      public void done(Map result, AVIMException e) {
        AVIMConversation conversation = null;
        if (null != result) {
          String conversationId =
                  (String) result.get(Conversation.callbackConversationKey);
          conversation = getConversation(conversationId, isTransient, isTemp);

          String createdAt = (String) result.get(Conversation.callbackCreatedAt);
          int tempTTLFromServer = 0;
          if (result.containsKey(Conversation.callbackTemporaryTTL)) {
            tempTTLFromServer = (int) result.get(Conversation.callbackTemporaryTTL);
          }
          if (result.containsKey(Conversation.callbackUniqueId)) {
            conversation.setUniqueId((String) result.get(Conversation.callbackUniqueId));
          }
          conversation.setMembers(conversationMembers);
          conversation.setAttributesForInit(attributes);
          conversation.setNameForInit(name);
          conversation.setTransientForInit(isTransient);
          conversation.setConversationId(conversationId);
          conversation.setCreator(clientId);
          conversation.setCreatedAt(createdAt);
          conversation.setUpdatedAt(createdAt);
          conversation.setTemporary(isTemp);
          conversation.setTemporaryExpiredat(tempTTL);
          conversation.updateFetchTimestamp(System.currentTimeMillis());
          conversation.setTemporaryExpiredat(System.currentTimeMillis()/1000 + tempTTLFromServer);
          if (AVIMOptions.getGlobalOptions().isMessageQueryCacheEnabled()) {
            storage.insertConversations(Arrays.asList(conversation));
          }
        }
        if (null != callback) {
          callback.internalDone(conversation, AVIMException.wrapperAVException(e));
        }
      }
    };
    InternalConfiguration.getOperationTube().createConversation(getClientId(), conversationMembers, assembledAttributes,
            isTransient, isUnique, isTemp, tempTTL, middleCallback);
  }

  /**
   * get conversation by id
   *
   * @param conversationId  conversation id.
   * @return conversation instance.
   */
  public AVIMConversation getConversation(String conversationId) {
    if (StringUtil.isEmpty(conversationId)) {
      return null;
    }
    return this.getConversation(conversationId, false, conversationId.startsWith(Conversation.TEMPCONV_ID_PREFIX));
  }

  /**
   * get conversation by id and type
   *
   * @param conversationId  conversation id.
   * @param convType conversation type.
   * @return conversation instance.
   */
  public AVIMConversation getConversation(String conversationId, int convType) {
    AVIMConversation result = null;
    switch (convType) {
      case Conversation.CONV_TYPE_SYSTEM:
        result = getServiceConversation(conversationId);
        break;
      case Conversation.CONV_TYPE_TEMPORARY:
        result = getTemporaryConversation(conversationId);
        break;
      case Conversation.CONV_TYPE_TRANSIENT:
        result = getChatRoom(conversationId);
        break;
      default:
        result = getConversation(conversationId);
        break;
    }
    return result;
  }

  /**
   * get an existed conversation
   *
   * @param conversationId conversation id.
   * @param isTransient flag of transient.
   * @param isTemporary flag of temporary.
   * @return conversation instance.
   */
  public AVIMConversation getConversation(String conversationId, boolean isTransient, boolean isTemporary) {
    return this.getConversation(conversationId, isTransient, isTemporary,false);
  }

  /**
   * get an existed Chatroom by id
   * @param conversationId conversation id.
   * @return chatroom conversation instance.
   */
  public AVIMConversation getChatRoom(String conversationId) {
    return this.getConversation(conversationId, true, false);
  }

  /**
   * get an existed Service Conversation
   *
   * @param conversationId conversation id.
   * @return service conversation instance.
   */
  public AVIMConversation getServiceConversation(String conversationId) {
    return this.getConversation(conversationId, false, false, true);
  }

  /**
   * get an existed temporary conversation
   *
   * @param conversationId conversation id.
   * @return temporary conversation instance.
   */
  public AVIMConversation getTemporaryConversation(String conversationId) {
    return this.getConversation(conversationId, false, true);
  }

  private AVIMConversation getConversation(String conversationId, boolean isTransient, boolean isTemporary, boolean isSystem) {
    if (StringUtil.isEmpty(conversationId)) {
      return null;
    }
    AVIMConversation conversation = conversationCache.get(conversationId);
    if (null != conversation) {
      return conversation;
    } else {
      if (isSystem) {
        conversation = new AVIMServiceConversation(this, conversationId);
      } else if (isTemporary || conversationId.startsWith(Conversation.TEMPCONV_ID_PREFIX)) {
        conversation = new AVIMTemporaryConversation(this, conversationId);
      } else if (isTransient) {
        conversation = new AVIMChatRoom(this, conversationId);
      } else {
        conversation = new AVIMConversation(this, conversationId);
      }
      AVIMConversation elder = conversationCache.putIfAbsent(conversationId, conversation);
      return null == elder? conversation : elder;
    }
  }

  /**
   * 获取 AVIMConversationsQuery 对象,以此来查询 conversation
   * @return ConversationsQuery instance
   */
  public AVIMConversationsQuery getConversationsQuery() {
    return new AVIMConversationsQuery(this);
  }

  /**
   * 获取服务号的查询对象
   * 开发者拿到这个对象之后,就可以像 AVIMConversationsQuery 以前的接口一样对目标属性(如名字)等进行查询。
   * @return ConversationsQuery instance
   */
  public AVIMConversationsQuery getServiceConversationQuery() {
    AVIMConversationsQuery query = new AVIMConversationsQuery(this);
    query.whereEqualTo("sys", true);
    return query;
  }

  /**
   * 获取临时对话的查询对象
   * 开发者拿到这个对象之后,就可以像 AVIMConversationsQuery 以前的接口一样对目标属性(如名字)等进行查询。
   * @return ConversationsQuery instance
   */
  private AVIMConversationsQuery getTemporaryConversationQuery() {
    throw new UnsupportedOperationException("only conversationId query is allowed, please invoke #getTemporaryConversaton with conversationId.");
  }

  /**
   * 获取开放聊天室的查询对象
   * 开发者拿到这个对象之后,就可以像 AVIMConversationsQuery 以前的接口一样对目标属性(如名字)等进行查询。
   * @return ConversationsQuery instance
   */
  public AVIMConversationsQuery getChatRoomQuery() {
    AVIMConversationsQuery query = new AVIMConversationsQuery(this);
    query.whereEqualTo("tr", true);
    return query;
  }

  /**
   * close client.
   *
   * @param callback callback function.
   */
  public void close(final AVIMClientCallback callback) {
    final AVIMClientCallback internalCallback = new AVIMClientCallback() {
      @Override
      public void done(AVIMClient client, AVIMException e) {
        if (null == e) {
          AVIMClient.this.localClose();
        }
        if (null != callback) {
          callback.done(client, e);
        }
      }
    };
    InternalConfiguration.getOperationTube().closeClient(this.clientId, internalCallback);
  }

  /**
   * [internal use only]
   * update realtime session token
   *
   * @param token session token
   * @param expiredInSec expired interval.
   */
  public void updateRealtimeSessionToken(String token, long expiredInSec) {
    this.realtimeSessionToken = token;
    this.realtimeSessionTokenExpired = expiredInSec;
  }

  private boolean realtimeSessionTokenExpired() {
    long now = System.currentTimeMillis()/1000;
    return (now + AVSession.REALTIME_TOKEN_WINDOW_INSECONDS) >= this.realtimeSessionTokenExpired;
  }

  AVIMConversation mergeConversationCache(AVIMConversation allNewConversation, boolean forceReplace, JSONObject deltaObject) {
    if (null == allNewConversation || StringUtil.isEmpty(allNewConversation.getConversationId())) {
      return null;
    }
    String convId = allNewConversation.getConversationId();
    if (forceReplace) {
      this.conversationCache.put(convId, allNewConversation);
      return allNewConversation;
    } else {
      AVIMConversation origin = this.conversationCache.get(convId);
      if (null == origin) {
        this.conversationCache.put(convId, allNewConversation);
        origin = allNewConversation;
      } else {
        // update cache object again.
        origin = AVIMConversation.updateConversation(origin, deltaObject);
      }
      return origin;
    }
  }

  void queryConversationMemberInfo(final QueryConditions queryConditions, final AVIMConversationMemberQueryCallback cb) {
    if (null == queryConditions || null == cb) {
      return;
    }
    if (!realtimeSessionTokenExpired()) {
      queryConvMemberThroughNetwork(queryConditions, cb);
    } else {
      // refresh realtime session token.
      LOGGER.d("realtime session token expired, start to refresh...");
      boolean ret = InternalConfiguration.getOperationTube().renewSessionToken(this.getClientId(), new AVIMClientCallback() {
        @Override
        public void done(AVIMClient client, AVIMException e) {
          if (null != e) {
            cb.internalDone(null, AVIMException.wrapperAVException(e));
          } else {
            queryConvMemberThroughNetwork(queryConditions, cb);
          }
        }
      });
      if (!ret) {
        cb.internalDone(null, new AVException(AVException.OPERATION_FORBIDDEN, "couldn't start service in background."));
      }
    }
  }


  private void queryConvMemberThroughNetwork(final QueryConditions queryConditions, final AVIMConversationMemberQueryCallback callback) {
    if (null == callback || null == queryConditions) {
      return;
    }
    queryConditions.assembleParameters();
    Map queryParams = queryConditions.getParameters();
    queryParams.put("client_id", this.clientId);
    RealtimeClient.getInstance().queryMemberInfo(queryParams, this.realtimeSessionToken)
            .subscribe(new Observer>() {
      @Override
      public void onSubscribe(Disposable disposable) {
      }

      @Override
      public void onNext(List avimConversationMemberInfos) {
        callback.internalDone(avimConversationMemberInfos, null);
      }

      @Override
      public void onError(Throwable throwable) {
        callback.internalDone(null, AVIMException.wrapperAVException(throwable));
      }

      @Override
      public void onComplete() {

      }
    });
  }

  protected void localClose() {
    clients.remove(this.clientId);
    conversationCache.clear();
    storage.deleteClientData();
  }

  AVIMMessageStorage getStorage() {
    return this.storage;
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy