210 lines
9.7 KiB
Kotlin
210 lines
9.7 KiB
Kotlin
package org.mariotaku.twidere.task.twitter
|
|
|
|
import android.accounts.AccountManager
|
|
import android.content.ContentResolver
|
|
import android.content.ContentValues
|
|
import android.content.Context
|
|
import android.net.Uri
|
|
import android.support.annotation.UiThread
|
|
import org.mariotaku.kpreferences.get
|
|
import org.mariotaku.microblog.library.MicroBlog
|
|
import org.mariotaku.microblog.library.MicroBlogException
|
|
import org.mariotaku.microblog.library.twitter.model.Activity
|
|
import org.mariotaku.microblog.library.twitter.model.Paging
|
|
import org.mariotaku.microblog.library.twitter.model.ResponseList
|
|
import org.mariotaku.sqliteqb.library.Expression
|
|
import org.mariotaku.twidere.R
|
|
import org.mariotaku.twidere.TwidereConstants.LOGTAG
|
|
import org.mariotaku.twidere.TwidereConstants.QUERY_PARAM_NOTIFY_CHANGE
|
|
import org.mariotaku.twidere.constant.loadItemLimitKey
|
|
import org.mariotaku.twidere.extension.model.newMicroBlogInstance
|
|
import org.mariotaku.twidere.model.AccountDetails
|
|
import org.mariotaku.twidere.model.RefreshTaskParam
|
|
import org.mariotaku.twidere.model.UserKey
|
|
import org.mariotaku.twidere.model.event.GetActivitiesTaskEvent
|
|
import org.mariotaku.twidere.model.util.AccountUtils
|
|
import org.mariotaku.twidere.model.util.ParcelableActivityUtils
|
|
import org.mariotaku.twidere.provider.TwidereDataStore.Activities
|
|
import org.mariotaku.twidere.task.BaseAbstractTask
|
|
import org.mariotaku.twidere.util.*
|
|
import org.mariotaku.twidere.util.TwitterWrapper.TwitterListResponse
|
|
import org.mariotaku.twidere.util.content.ContentResolverUtils
|
|
import java.util.*
|
|
|
|
/**
|
|
* Created by mariotaku on 16/1/4.
|
|
*/
|
|
abstract class GetActivitiesTask(
|
|
context: Context
|
|
) : BaseAbstractTask<RefreshTaskParam, List<TwitterListResponse<Activity>>, (Boolean) -> Unit>(context) {
|
|
|
|
private val profileImageSize = context.getString(R.string.profile_image_size)
|
|
|
|
protected abstract val errorInfoKey: String
|
|
|
|
protected abstract val contentUri: Uri
|
|
|
|
override fun doLongOperation(param: RefreshTaskParam): List<TwitterListResponse<Activity>> {
|
|
if (!initialized || param.shouldAbort) return emptyList()
|
|
val accountIds = param.accountKeys
|
|
val maxIds = param.maxIds
|
|
val maxSortIds = param.maxSortIds
|
|
val sinceIds = param.sinceIds
|
|
val cr = context.contentResolver
|
|
val result = ArrayList<TwitterListResponse<Activity>>()
|
|
val loadItemLimit = preferences[loadItemLimitKey]
|
|
var saveReadPosition = false
|
|
for (i in accountIds.indices) {
|
|
val accountKey = accountIds[i]
|
|
val noItemsBefore = DataStoreUtils.getActivitiesCount(context, contentUri, accountKey) <= 0
|
|
val credentials = AccountUtils.getAccountDetails(AccountManager.get(context), accountKey, true) ?: continue
|
|
val microBlog = credentials.newMicroBlogInstance(context = context, cls = MicroBlog::class.java)
|
|
val paging = Paging()
|
|
paging.count(loadItemLimit)
|
|
var maxId: String? = null
|
|
var maxSortId: Long = -1
|
|
if (maxIds != null) {
|
|
maxId = maxIds[i]
|
|
if (maxSortIds != null) {
|
|
maxSortId = maxSortIds[i]
|
|
}
|
|
if (maxId != null) {
|
|
paging.maxId(maxId)
|
|
}
|
|
}
|
|
var sinceId: String? = null
|
|
if (sinceIds != null) {
|
|
sinceId = sinceIds[i]
|
|
if (sinceId != null) {
|
|
paging.sinceId(sinceId)
|
|
if (maxIds == null || maxId == null) {
|
|
paging.setLatestResults(true)
|
|
saveReadPosition = true
|
|
}
|
|
}
|
|
}
|
|
// We should delete old activities has intersection with new items
|
|
try {
|
|
val activities = getActivities(microBlog, credentials, paging)
|
|
val storeResult = storeActivities(cr, loadItemLimit, credentials, noItemsBefore, activities, sinceId,
|
|
maxId, false)
|
|
if (saveReadPosition) {
|
|
saveReadPosition(accountKey, credentials, microBlog)
|
|
}
|
|
errorInfoStore.remove(errorInfoKey, accountKey)
|
|
if (storeResult != 0) {
|
|
throw GetStatusesTask.GetTimelineException(storeResult)
|
|
}
|
|
} catch (e: MicroBlogException) {
|
|
DebugLog.w(LOGTAG, tr = e)
|
|
if (e.errorCode == 220) {
|
|
errorInfoStore[errorInfoKey, accountKey] = ErrorInfoStore.CODE_NO_ACCESS_FOR_CREDENTIALS
|
|
} else if (e.isCausedByNetworkIssue) {
|
|
errorInfoStore[errorInfoKey, accountKey] = ErrorInfoStore.CODE_NETWORK_ERROR
|
|
}
|
|
} catch (e: GetStatusesTask.GetTimelineException) {
|
|
result.add(TwitterListResponse(accountKey, e))
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
override fun afterExecute(handler: ((Boolean) -> Unit)?, result: List<TwitterListResponse<Activity>>) {
|
|
if (!initialized) return
|
|
context.contentResolver.notifyChange(contentUri, null)
|
|
val exception = AsyncTwitterWrapper.getException(result)
|
|
bus.post(GetActivitiesTaskEvent(contentUri, false, exception))
|
|
handler?.invoke(true)
|
|
}
|
|
|
|
private fun storeActivities(cr: ContentResolver, loadItemLimit: Int, details: AccountDetails,
|
|
noItemsBefore: Boolean, activities: ResponseList<Activity>,
|
|
sinceId: String?, maxId: String?, notify: Boolean): Int {
|
|
val deleteBound = LongArray(2) { -1 }
|
|
val valuesList = ArrayList<ContentValues>()
|
|
var minIdx = -1
|
|
var minPositionKey: Long = -1
|
|
if (!activities.isEmpty()) {
|
|
val firstSortId = activities.first().createdAt.time
|
|
val lastSortId = activities.last().createdAt.time
|
|
// Get id diff of first and last item
|
|
val sortDiff = firstSortId - lastSortId
|
|
for (i in activities.indices) {
|
|
val item = activities[i]
|
|
val activity = ParcelableActivityUtils.fromActivity(item, details.key, false,
|
|
profileImageSize)
|
|
mediaPreloader.preloadActivity(activity)
|
|
activity.position_key = GetStatusesTask.getPositionKey(activity.timestamp,
|
|
activity.timestamp, lastSortId, sortDiff, i, activities.size)
|
|
if (deleteBound[0] < 0) {
|
|
deleteBound[0] = activity.min_sort_position
|
|
} else {
|
|
deleteBound[0] = Math.min(deleteBound[0], activity.min_sort_position)
|
|
}
|
|
if (deleteBound[1] < 0) {
|
|
deleteBound[1] = activity.max_sort_position
|
|
} else {
|
|
deleteBound[1] = Math.max(deleteBound[1], activity.max_sort_position)
|
|
}
|
|
if (minIdx == -1 || item < activities[minIdx]) {
|
|
minIdx = i
|
|
minPositionKey = activity.position_key
|
|
}
|
|
|
|
activity.inserted_date = System.currentTimeMillis()
|
|
val values = ContentValuesCreator.createActivity(activity, details)
|
|
valuesList.add(values)
|
|
}
|
|
}
|
|
var olderCount = -1
|
|
if (minPositionKey > 0) {
|
|
olderCount = DataStoreUtils.getActivitiesCount(context, contentUri, minPositionKey,
|
|
Activities.POSITION_KEY, false, arrayOf(details.key))
|
|
}
|
|
val writeUri = UriUtils.appendQueryParameters(contentUri, QUERY_PARAM_NOTIFY_CHANGE, notify)
|
|
if (deleteBound[0] > 0 && deleteBound[1] > 0) {
|
|
val where = Expression.and(
|
|
Expression.equalsArgs(Activities.ACCOUNT_KEY),
|
|
Expression.greaterEqualsArgs(Activities.MIN_SORT_POSITION),
|
|
Expression.lesserEqualsArgs(Activities.MAX_SORT_POSITION))
|
|
val whereArgs = arrayOf(details.key.toString(), deleteBound[0].toString(), deleteBound[1].toString())
|
|
val rowsDeleted = cr.delete(writeUri, where.sql, whereArgs)
|
|
// Why loadItemLimit / 2? because it will not acting strange in most cases
|
|
val insertGap = !noItemsBefore && olderCount > 0 && rowsDeleted <= 0 && activities.size > loadItemLimit / 2
|
|
if (insertGap && !valuesList.isEmpty()) {
|
|
valuesList[valuesList.size - 1].put(Activities.IS_GAP, true)
|
|
}
|
|
}
|
|
// Insert previously fetched items.
|
|
ContentResolverUtils.bulkInsert(cr, writeUri, valuesList)
|
|
|
|
// Remove gap flag
|
|
if (maxId != null && sinceId == null) {
|
|
if (activities.isNotEmpty()) {
|
|
// Only remove when actual result returned, otherwise it seems that gap is too old to load
|
|
val noGapValues = ContentValues()
|
|
noGapValues.put(Activities.IS_GAP, false)
|
|
val noGapWhere = Expression.and(Expression.equalsArgs(Activities.ACCOUNT_KEY),
|
|
Expression.equalsArgs(Activities.MIN_REQUEST_POSITION),
|
|
Expression.equalsArgs(Activities.MAX_REQUEST_POSITION)).sql
|
|
val noGapWhereArgs = arrayOf(details.key.toString(), maxId, maxId)
|
|
cr.update(writeUri, noGapValues, noGapWhere, noGapWhereArgs)
|
|
} else {
|
|
return GetStatusesTask.ERROR_LOAD_GAP
|
|
}
|
|
}
|
|
return 0
|
|
}
|
|
|
|
@UiThread
|
|
override fun beforeExecute() {
|
|
if (!initialized) return
|
|
bus.post(GetActivitiesTaskEvent(contentUri, true, null))
|
|
}
|
|
|
|
protected abstract fun saveReadPosition(accountKey: UserKey, details: AccountDetails, twitter: MicroBlog)
|
|
|
|
@Throws(MicroBlogException::class)
|
|
protected abstract fun getActivities(twitter: MicroBlog, details: AccountDetails, paging: Paging): ResponseList<Activity>
|
|
}
|