/*
* Copyright 2014 OpenMarket Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.androidsdk.rest.client;
import android.text.TextUtils;
import org.matrix.androidsdk.HomeserverConnectionConfig;
import org.matrix.androidsdk.RestClient;
import org.matrix.androidsdk.rest.api.EventsApi;
import org.matrix.androidsdk.rest.callback.ApiCallback;
import org.matrix.androidsdk.rest.callback.RestAdapterCallback;
import org.matrix.androidsdk.rest.model.Event;
import org.matrix.androidsdk.rest.model.MatrixError;
import org.matrix.androidsdk.rest.model.PublicRoomsFilter;
import org.matrix.androidsdk.rest.model.PublicRoomsParams;
import org.matrix.androidsdk.rest.model.PublicRoomsResponse;
import org.matrix.androidsdk.rest.model.Search.SearchParams;
import org.matrix.androidsdk.rest.model.Search.SearchResponse;
import org.matrix.androidsdk.rest.model.Search.SearchRoomEventCategoryParams;
import org.matrix.androidsdk.rest.model.Sync.SyncResponse;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import retrofit.client.Response;
/**
* Class used to make requests to the events API.
*/
public class EventsRestClient extends RestClient<EventsApi> {
private static final int EVENT_STREAM_TIMEOUT_MS = 30000;
private String mSearchPatternIdentifier = null;
private String mSearchMediaNameIdentifier = null;
/**
* {@inheritDoc}
*/
public EventsRestClient(HomeserverConnectionConfig hsConfig) {
super(hsConfig, EventsApi.class, RestClient.URI_API_PREFIX_PATH_R0, false);
}
protected EventsRestClient(EventsApi api) {
mApi = api;
}
/**
* Get the public rooms count.
* The count can be null.
* @param callback the public rooms count callbacks
*/
public void getPublicRoomsCount(final ApiCallback<Integer> callback) {
final String description = "getPublicRoomsCount";
PublicRoomsParams publicRoomsParams = new PublicRoomsParams();
publicRoomsParams.server = null;
publicRoomsParams.limit = 0;
publicRoomsParams.since = null;
mApi.publicRooms(publicRoomsParams, new RestAdapterCallback<PublicRoomsResponse>(description, mUnsentEventsManager, callback, new RestAdapterCallback.RequestRetryCallBack() {
@Override
public void onRetry() {
getPublicRoomsCount(callback);
}
}) {
@Override
public void success(PublicRoomsResponse publicRoomsResponse, Response response) {
onEventSent();
callback.onSuccess(publicRoomsResponse.total_room_count_estimate);
}
});
}
/**
* Get the list of the public rooms.
* @param server search on this home server only (null for any one)
* @param pattern the pattern to search
* @param since the pagination token
* @param limit the maximum number of public rooms
* @param callback the public rooms callbacks
*/
public void loadPublicRooms(final String server, final String pattern, final String since, final int limit, final ApiCallback<PublicRoomsResponse> callback) {
final String description = "loadPublicRooms";
PublicRoomsParams publicRoomsParams = new PublicRoomsParams();
publicRoomsParams.server = server;
publicRoomsParams.limit = Math.max(1, limit);
publicRoomsParams.since = since;
if (!TextUtils.isEmpty(pattern)) {
publicRoomsParams.filter = new PublicRoomsFilter();
publicRoomsParams.filter.generic_search_term = pattern;
}
mApi.publicRooms(publicRoomsParams, new RestAdapterCallback<PublicRoomsResponse>(description, mUnsentEventsManager, callback, new RestAdapterCallback.RequestRetryCallBack() {
@Override
public void onRetry() {
loadPublicRooms(server, pattern, since, limit, callback);
}
}));
}
/**
* Synchronise the client's state and receive new messages. Based on server sync C-S v2 API.
* Synchronise the client's state with the latest state on the server.
* Client's use this API when they first log in to get an initial snapshot
* of the state on the server, and then continue to call this API to get
* incremental deltas to the state, and to receive new messages.
* @param token the token to stream from (nil in case of initial sync).
* @param serverTimeout the maximum time in ms to wait for an event.
* @param clientTimeout the maximum time in ms the SDK must wait for the server response.
* @param setPresence the optional parameter which controls whether the client is automatically
* marked as online by polling this API. If this parameter is omitted then the client is
* automatically marked as online when it uses this API. Otherwise if
* the parameter is set to "offline" then the client is not marked as
* being online when it uses this API.
* @param filterId the ID of a filter created using the filter API (optional).
* @param callback The request callback
*/
public void syncFromToken(final String token, final int serverTimeout, final int clientTimeout, final String setPresence, final String filterId, final ApiCallback<SyncResponse> callback) {
HashMap<String, Object> params = new HashMap<>();
int timeout = (EVENT_STREAM_TIMEOUT_MS / 1000);
if (!TextUtils.isEmpty(token)) {
params.put("since", token);
}
if (-1 != serverTimeout) {
timeout = serverTimeout;
}
if (!TextUtils.isEmpty(setPresence)) {
params.put("set_presence", setPresence);
}
if (!TextUtils.isEmpty(filterId)) {
params.put("filter", filterId);
}
params.put("timeout", timeout);
final String description = "syncFromToken";
// Disable retry because it interferes with clientTimeout
// Let the client manage retries on events streams
mApi.sync(params, new RestAdapterCallback<SyncResponse>(description, null, false, callback, new RestAdapterCallback.RequestRetryCallBack() {
@Override
public void onRetry() {
syncFromToken(token, serverTimeout, clientTimeout, setPresence, filterId, callback);
}
}));
}
/**
* Search a text in room messages.
*
* @param text the text to search for.
* @param rooms a list of rooms to search in. nil means all rooms the user is in.
* @param beforeLimit the number of events to get before the matching results.
* @param afterLimit the number of events to get after the matching results.
* @param nextBatch the token to pass for doing pagination from a previous response.
* @param callback the request callback
*/
public void searchMessagesByText(final String text, final List<String> rooms, final int beforeLimit, final int afterLimit, final String nextBatch, final ApiCallback<SearchResponse> callback) {
SearchParams searchParams = new SearchParams();
SearchRoomEventCategoryParams searchEventParams = new SearchRoomEventCategoryParams();
searchEventParams.search_term = text;
searchEventParams.order_by = "recent";
searchEventParams.event_context = new HashMap<>();
searchEventParams.event_context.put("before_limit", beforeLimit);
searchEventParams.event_context.put("after_limit", afterLimit);
searchEventParams.event_context.put("include_profile", true);
if (null != rooms) {
searchEventParams.filter = new HashMap<>();
searchEventParams.filter.put("rooms", rooms);
}
searchParams.search_categories = new HashMap<>();
searchParams.search_categories.put("room_events", searchEventParams);
final String description = "searchMessageText";
final String uid = System.currentTimeMillis() + "";
mSearchPatternIdentifier = uid + text;
// don't retry to send the request
// if the search fails, stop it
mApi.search(searchParams, nextBatch, new RestAdapterCallback<SearchResponse>(description, null, new ApiCallback<SearchResponse>() {
/**
* Tells if the current response for the latest request.
* @return true if it is the response of the latest request.
*/
private boolean isActiveRequest() {
return TextUtils.equals(mSearchPatternIdentifier, uid + text);
}
@Override
public void onSuccess(SearchResponse response) {
if (isActiveRequest()) {
if (null != callback) {
callback.onSuccess(response);
}
mSearchPatternIdentifier = null;
}
}
@Override
public void onNetworkError(Exception e) {
if (isActiveRequest()) {
if (null != callback) {
callback.onNetworkError(e);
}
mSearchPatternIdentifier = null;
}
}
@Override
public void onMatrixError(MatrixError e) {
if (isActiveRequest()) {
if (null != callback) {
callback.onMatrixError(e);
}
mSearchPatternIdentifier = null;
}
}
@Override
public void onUnexpectedError(Exception e) {
if (isActiveRequest()) {
if (null != callback) {
callback.onUnexpectedError(e);
}
mSearchPatternIdentifier = null;
}
}
}, new RestAdapterCallback.RequestRetryCallBack() {
@Override
public void onRetry() {
searchMessagesByText(text, rooms, beforeLimit, afterLimit, nextBatch, callback);
}
}));
}
/**
* Search a media from its name.
*
* @param name the text to search for.
* @param rooms a list of rooms to search in. nil means all rooms the user is in.
* @param beforeLimit the number of events to get before the matching results.
* @param afterLimit the number of events to get after the matching results.
* @param nextBatch the token to pass for doing pagination from a previous response.
* @param callback the request callback
*/
public void searchMediasByText(final String name, final List<String> rooms, final int beforeLimit, final int afterLimit, final String nextBatch, final ApiCallback<SearchResponse> callback) {
SearchParams searchParams = new SearchParams();
SearchRoomEventCategoryParams searchEventParams = new SearchRoomEventCategoryParams();
searchEventParams.search_term = name;
searchEventParams.order_by = "recent";
searchEventParams.event_context = new HashMap<>();
searchEventParams.event_context.put("before_limit", beforeLimit);
searchEventParams.event_context.put("after_limit", afterLimit);
searchEventParams.event_context.put("include_profile", true);
searchEventParams.filter = new HashMap<>();
if (null != rooms) {
searchEventParams.filter.put("rooms", rooms);
}
ArrayList<String> types = new ArrayList<>();
types.add(Event.EVENT_TYPE_MESSAGE);
searchEventParams.filter.put("types", types);
searchEventParams.filter.put("contains_url", true);
searchParams.search_categories = new HashMap<>();
searchParams.search_categories.put("room_events", searchEventParams);
// other unused filter items
// not_types
// not_rooms
// senders
// not_senders
final String uid = System.currentTimeMillis() + "";
mSearchMediaNameIdentifier = uid + name;
final String description = "searchMediasByText";
// don't retry to send the request
// if the search fails, stop it
mApi.search(searchParams, nextBatch, new RestAdapterCallback<SearchResponse>(description, null, new ApiCallback<SearchResponse>() {
/**
* Tells if the current response for the latest request.
* @return true if it is the response of the latest request.
*/
private boolean isActiveRequest() {
return TextUtils.equals(mSearchMediaNameIdentifier, uid + name);
}
@Override
public void onSuccess(SearchResponse newSearchResponse) {
if (isActiveRequest()) {
callback.onSuccess(newSearchResponse);
mSearchMediaNameIdentifier = null;
}
}
@Override
public void onNetworkError(Exception e) {
if (isActiveRequest()) {
callback.onNetworkError(e);
mSearchMediaNameIdentifier = null;
}
}
@Override
public void onMatrixError(MatrixError e) {
if (isActiveRequest()) {
callback.onMatrixError(e);
mSearchMediaNameIdentifier = null;
}
}
@Override
public void onUnexpectedError(Exception e) {
if (isActiveRequest()) {
callback.onUnexpectedError(e);
mSearchMediaNameIdentifier = null;
}
}
}, new RestAdapterCallback.RequestRetryCallBack() {
@Override
public void onRetry() {
searchMediasByText(name, rooms, beforeLimit, afterLimit, nextBatch, callback);
}
}));
}
/**
* Cancel any pending file search request
*/
public void cancelSearchMediasByText() {
mSearchMediaNameIdentifier = null;
}
/**
* Cancel any pending search request
*/
public void cancelSearchMessagesByText() {
mSearchPatternIdentifier = null;
}
}