Twidere-App-Android-Twitter.../twidere/src/main/java/org/mariotaku/twidere/task/twitter/GetStatusesTask.java

215 lines
8.6 KiB
Java

package org.mariotaku.twidere.task.twitter;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.UiThread;
import android.util.Log;
import com.desmond.asyncmanager.AsyncManager;
import com.desmond.asyncmanager.TaskRunnable;
import com.squareup.otto.Bus;
import org.apache.commons.lang3.ArrayUtils;
import org.mariotaku.sqliteqb.library.Columns;
import org.mariotaku.sqliteqb.library.Expression;
import org.mariotaku.sqliteqb.library.RawItemArray;
import org.mariotaku.sqliteqb.library.SQLFunctions;
import org.mariotaku.twidere.BuildConfig;
import org.mariotaku.twidere.Constants;
import org.mariotaku.twidere.api.twitter.Twitter;
import org.mariotaku.twidere.api.twitter.TwitterException;
import org.mariotaku.twidere.api.twitter.model.Paging;
import org.mariotaku.twidere.api.twitter.model.ResponseList;
import org.mariotaku.twidere.api.twitter.model.Status;
import org.mariotaku.twidere.model.RefreshTaskParam;
import org.mariotaku.twidere.provider.TwidereDataStore.Statuses;
import org.mariotaku.twidere.task.CacheUsersStatusesTask;
import org.mariotaku.twidere.util.AsyncTwitterWrapper;
import org.mariotaku.twidere.util.ContentValuesCreator;
import org.mariotaku.twidere.util.DataStoreUtils;
import org.mariotaku.twidere.util.ErrorInfoStore;
import org.mariotaku.twidere.util.SharedPreferencesWrapper;
import org.mariotaku.twidere.util.TwitterAPIFactory;
import org.mariotaku.twidere.util.TwitterContentUtils;
import org.mariotaku.twidere.util.TwitterWrapper;
import org.mariotaku.twidere.util.UriUtils;
import org.mariotaku.twidere.util.Utils;
import org.mariotaku.twidere.util.content.ContentResolverUtils;
import org.mariotaku.twidere.util.dagger.GeneralComponentHelper;
import org.mariotaku.twidere.util.message.GetStatusesTaskEvent;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import edu.tsinghua.hotmobi.HotMobiLogger;
import edu.tsinghua.hotmobi.model.RefreshEvent;
import edu.tsinghua.hotmobi.model.TimelineType;
/**
* Created by mariotaku on 16/1/2.
*/
public abstract class GetStatusesTask extends TaskRunnable<RefreshTaskParam,
List<TwitterWrapper.StatusListResponse>, Object> implements Constants {
protected final Context context;
@Inject
protected SharedPreferencesWrapper preferences;
@Inject
protected Bus bus;
@Inject
protected ErrorInfoStore errorInfoStore;
public GetStatusesTask(Context context) {
this.context = context;
GeneralComponentHelper.build(context).inject(this);
}
@NonNull
public abstract ResponseList<Status> getStatuses(Twitter twitter, Paging paging)
throws TwitterException;
@NonNull
protected abstract Uri getContentUri();
private void storeStatus(long accountId, List<Status> statuses,
long sinceId, long maxId, boolean notify) {
if (statuses == null || statuses.isEmpty() || accountId <= 0) {
return;
}
final Uri uri = getContentUri();
final ContentResolver resolver = context.getContentResolver();
final boolean noItemsBefore = DataStoreUtils.getStatusCount(context, uri, accountId) <= 0;
final ContentValues[] values = new ContentValues[statuses.size()];
final long[] statusIds = new long[statuses.size()];
long minId = -1;
int minIdx = -1;
boolean hasIntersection = false;
for (int i = 0, j = statuses.size(); i < j; i++) {
final Status status = statuses.get(i);
values[i] = ContentValuesCreator.createStatus(status, accountId);
values[i].put(Statuses.INSERTED_DATE, System.currentTimeMillis());
final long id = status.getId();
if (sinceId > 0 && id <= sinceId) {
hasIntersection = true;
}
if (minId == -1 || id < minId) {
minId = id;
minIdx = i;
}
statusIds[i] = id;
}
// Delete all rows conflicting before new data inserted.
final Expression accountWhere = Expression.equals(Statuses.ACCOUNT_ID, accountId);
final Expression statusWhere = Expression.in(new Columns.Column(Statuses.STATUS_ID),
new RawItemArray(statusIds));
final String countWhere = Expression.and(accountWhere, statusWhere).getSQL();
final String[] projection = {SQLFunctions.COUNT()};
final int rowsDeleted;
final Cursor countCur = resolver.query(uri, projection, countWhere, null, null);
try {
if (countCur != null && countCur.moveToFirst()) {
rowsDeleted = countCur.getInt(0);
} else {
rowsDeleted = 0;
}
} finally {
Utils.closeSilently(countCur);
}
// BEGIN HotMobi
final RefreshEvent event = RefreshEvent.create(context, statusIds, getTimelineType());
HotMobiLogger.getInstance(context).log(accountId, event);
// END HotMobi
// Insert a gap.
final boolean deletedOldGap = rowsDeleted > 0 && ArrayUtils.contains(statusIds, maxId);
final boolean noRowsDeleted = rowsDeleted == 0;
final boolean insertGap = minId > 0 && (noRowsDeleted || deletedOldGap) && !noItemsBefore
&& !hasIntersection;
if (insertGap && minIdx != -1) {
values[minIdx].put(Statuses.IS_GAP, true);
}
// Insert previously fetched items.
final Uri insertUri = UriUtils.appendQueryParameters(uri, QUERY_PARAM_NOTIFY, notify);
ContentResolverUtils.bulkInsert(resolver, insertUri, values);
}
@TimelineType
protected abstract String getTimelineType();
@Override
public void callback(List<TwitterWrapper.StatusListResponse> result) {
bus.post(new GetStatusesTaskEvent(getContentUri(), false, AsyncTwitterWrapper.getException(result)));
}
@UiThread
public void notifyStart() {
bus.post(new GetStatusesTaskEvent(getContentUri(), true, null));
}
@Override
public List<TwitterWrapper.StatusListResponse> doLongOperation(final RefreshTaskParam param) {
final long[] accountIds = param.getAccountIds(), maxIds = param.getMaxIds(), sinceIds = param.getSinceIds();
final List<TwitterWrapper.StatusListResponse> result = new ArrayList<>();
if (accountIds == null) return result;
int idx = 0;
final int loadItemLimit = preferences.getInt(KEY_LOAD_ITEM_LIMIT, DEFAULT_LOAD_ITEM_LIMIT);
for (final long accountId : accountIds) {
final Twitter twitter = TwitterAPIFactory.getTwitterInstance(context, accountId, true);
if (twitter == null) continue;
try {
final Paging paging = new Paging();
paging.count(loadItemLimit);
final long maxId, sinceId;
if (maxIds != null && maxIds[idx] > 0) {
maxId = maxIds[idx];
paging.maxId(maxId);
} else {
maxId = -1;
}
if (sinceIds != null && sinceIds[idx] > 0) {
sinceId = sinceIds[idx];
paging.sinceId(sinceId - 1);
if (maxIds == null || sinceIds[idx] <= 0) {
paging.setLatestResults(true);
}
} else {
sinceId = -1;
}
final List<Status> statuses = getStatuses(twitter, paging);
TwitterContentUtils.getStatusesWithQuoteData(twitter, statuses);
storeStatus(accountId, statuses, sinceId, maxId, true);
// TODO cache related data and preload
final CacheUsersStatusesTask cacheTask = new CacheUsersStatusesTask(context);
cacheTask.setParams(new TwitterWrapper.StatusListResponse(accountId, statuses));
AsyncManager.runBackgroundTask(cacheTask);
errorInfoStore.remove(getErrorInfoKey(), accountId);
} catch (final TwitterException e) {
if (BuildConfig.DEBUG) {
Log.w(LOGTAG, e);
}
if (e.isCausedByNetworkIssue()) {
errorInfoStore.put(getErrorInfoKey(), accountId,
ErrorInfoStore.CODE_NETWORK_ERROR);
}
result.add(new TwitterWrapper.StatusListResponse(accountId, e));
}
idx++;
}
return result;
}
@NonNull
protected abstract String getErrorInfoKey();
}