implementing relationship caching

This commit is contained in:
Mariotaku Lee 2017-03-18 14:20:57 +08:00
parent e9205dfb23
commit 0f86fc75a4
No known key found for this signature in database
GPG Key ID: 99505AEA531814F1
11 changed files with 146 additions and 33 deletions

1
.gitignore vendored
View File

@ -29,6 +29,7 @@ Thumbs.db
# Local files
/captures
/reports
# JRE error dumps
hs_err_*.log

View File

@ -1,3 +1,3 @@
org.gradle.daemon=true
org.gradle.jvmargs=-Xms2048m -Xmx3072m
systemProp.kotlin.daemon.verbose=true
kotlin.incremental=true

View File

@ -73,6 +73,25 @@ public class ParcelableRelationship implements Parcelable {
public boolean filtering;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ParcelableRelationship that = (ParcelableRelationship) o;
if (!account_key.equals(that.account_key)) return false;
return user_key.equals(that.user_key);
}
@Override
public int hashCode() {
int result = account_key.hashCode();
result = 31 * result + user_key.hashCode();
return result;
}
@Override
public int describeContents() {
return 0;

View File

@ -34,7 +34,7 @@ android {
defaultConfig {
applicationId "org.mariotaku.twidere"
minSdkVersion 14
minSdkVersion project.properties['overrideMinSdkVersion'] ?: 14
targetSdkVersion 25
versionCode 301
versionName '3.4.39'

View File

@ -39,4 +39,8 @@ inline fun <reified T> List<T>.subArray(range: IntRange): Array<T> {
return Array(range.count()) {
this[range.start + it]
}
}
fun <T> T.addTo(collection: MutableCollection<T>): Boolean {
return collection.add(this)
}

View File

@ -19,16 +19,22 @@
package org.mariotaku.twidere.model.util
import android.content.ContentResolver
import android.support.v4.util.ArraySet
import org.mariotaku.library.objectcursor.ObjectCursor
import org.mariotaku.microblog.library.twitter.model.Relationship
import org.mariotaku.microblog.library.twitter.model.User
import org.mariotaku.sqliteqb.library.Expression
import org.mariotaku.twidere.model.ParcelableRelationship
import org.mariotaku.twidere.model.ParcelableUser
import org.mariotaku.twidere.model.UserKey
import org.mariotaku.twidere.provider.TwidereDataStore.*
import org.mariotaku.twidere.util.content.ContentResolverUtils
object ParcelableRelationshipUtils {
fun create(accountKey: UserKey, userKey: UserKey, relationship: Relationship?,
filtering: Boolean): ParcelableRelationship {
filtering: Boolean): ParcelableRelationship {
val obj = ParcelableRelationship()
obj.account_key = accountKey
obj.user_key = userKey
@ -63,7 +69,7 @@ object ParcelableRelationshipUtils {
}
fun create(accountKey: UserKey, userKey: UserKey, user: User,
filtering: Boolean): ParcelableRelationship {
filtering: Boolean = false): ParcelableRelationship {
val obj = ParcelableRelationship()
obj.account_key = accountKey
obj.user_key = userKey
@ -76,4 +82,25 @@ object ParcelableRelationshipUtils {
obj.notifications_enabled = user.isNotificationsEnabled == true
return obj
}
/**
* @param relationships Relationships to update, if an item has _id, then we will call
* `ContentResolver.update`, `ContentResolver.bulkInsert` otherwise.
*/
fun insert(cr: ContentResolver, relationships: Collection<ParcelableRelationship>) {
val insertItems = ArraySet<ParcelableRelationship>()
val valuesCreator = ObjectCursor.valuesCreatorFrom(ParcelableRelationship::class.java)
relationships.forEach {
if (it._id > 0) {
val values = valuesCreator.create(it)
val where = Expression.equalsArgs(CachedRelationships._ID).sql
val whereArgs = arrayOf(it._id.toString())
cr.update(CachedRelationships.CONTENT_URI, values, where, whereArgs)
} else {
insertItems.add(it)
}
}
ContentResolverUtils.bulkInsert(cr, CachedRelationships.CONTENT_URI,
insertItems.map(valuesCreator::create))
}
}

View File

@ -35,7 +35,7 @@ class DestroyFriendshipTask(context: Context) : AbsFriendshipOperationTask(conte
val where = Expression.and(Expression.equalsArgs(Statuses.ACCOUNT_KEY),
Expression.or(Expression.equalsArgs(Statuses.USER_KEY),
Expression.equalsArgs(Statuses.RETWEETED_BY_USER_KEY)))
val whereArgs = arrayOf(args.userKey.toString(), args.userKey.toString(), args.userKey.toString())
val whereArgs = arrayOf(args.accountKey.toString(), args.userKey.toString(), args.userKey.toString())
val resolver = context.contentResolver
resolver.delete(Statuses.CONTENT_URI, where.sql, whereArgs)
}

View File

@ -0,0 +1,68 @@
package org.mariotaku.twidere.task.cache
import android.annotation.SuppressLint
import android.content.ContentResolver
import android.content.Context
import android.support.v4.util.ArraySet
import org.mariotaku.abstask.library.AbstractTask
import org.mariotaku.ktextension.map
import org.mariotaku.ktextension.useCursor
import org.mariotaku.library.objectcursor.ObjectCursor
import org.mariotaku.microblog.library.twitter.model.User
import org.mariotaku.sqliteqb.library.Expression
import org.mariotaku.twidere.model.ParcelableRelationship
import org.mariotaku.twidere.model.ParcelableUser
import org.mariotaku.twidere.model.UserKey
import org.mariotaku.twidere.model.util.ParcelableRelationshipUtils
import org.mariotaku.twidere.model.util.ParcelableUserUtils
import org.mariotaku.twidere.model.util.UserKeyUtils
import org.mariotaku.twidere.provider.TwidereDataStore.CachedUsers
import org.mariotaku.twidere.provider.TwidereDataStore.CachedRelationships
import org.mariotaku.twidere.task.BaseAbstractTask
import org.mariotaku.twidere.util.content.ContentResolverUtils
/**
* Created by Mariotaku on 2017/3/17.
*/
class CacheUserRelationshipTask(context: Context, val accountKey: UserKey, val users: Collection<User>) : BaseAbstractTask<Any?, Unit, Any?>(context) {
override fun doLongOperation(param: Any?) {
cacheUserRelationships(context.contentResolver, accountKey, users)
}
companion object {
fun cacheUserRelationships(cr: ContentResolver, accountKey: UserKey, users: Collection<User>) {
val parcelableUsers = users.map { ParcelableUserUtils.fromUser(it, accountKey) }
val userValuesCreator = ObjectCursor.valuesCreatorFrom(ParcelableUser::class.java)
ContentResolverUtils.bulkInsert(cr, CachedUsers.CONTENT_URI, parcelableUsers.map(userValuesCreator::create))
val selectionArgsList = parcelableUsers.mapTo(mutableListOf(accountKey.toString())) {
it.key.toString()
}
@SuppressLint("Recycle")
val localRelationships = cr.query(CachedRelationships.CONTENT_URI, CachedRelationships.COLUMNS,
Expression.and(Expression.equalsArgs(CachedRelationships.ACCOUNT_KEY),
Expression.inArgs(CachedRelationships.USER_KEY, users.size)).sql,
selectionArgsList.toTypedArray(), null).useCursor { cur ->
return@useCursor cur.map(ObjectCursor.indicesFrom(cur, ParcelableRelationship::class.java))
}
val relationships = users.mapTo(ArraySet<ParcelableRelationship>()) { user ->
val userKey = UserKeyUtils.fromUser(user)
return@mapTo localRelationships.find {
it.user_key == userKey
}?.apply {
user.isFollowing?.let { this.following = it }
user.isFollowedBy?.let { this.followed_by = it }
user.isBlocking?.let { this.blocking = it }
user.isBlockedBy?.let { this.blocked_by = it }
user.isMuting?.let { this.muting = it }
user.isNotificationsEnabled?.let { this.notifications_enabled = it }
} ?: ParcelableRelationshipUtils.create(accountKey, userKey, user)
}
ParcelableRelationshipUtils.insert(cr, relationships)
}
}
}

View File

@ -17,14 +17,17 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.mariotaku.twidere.task
package org.mariotaku.twidere.task.cache
import android.content.ContentValues
import android.content.Context
import com.twitter.Extractor
import org.mariotaku.abstask.library.AbstractTask
import org.mariotaku.ktextension.addTo
import org.mariotaku.microblog.library.twitter.model.Status
import org.mariotaku.microblog.library.twitter.model.User
import org.mariotaku.twidere.R
import org.mariotaku.twidere.model.UserKey
import org.mariotaku.twidere.provider.TwidereDataStore.*
import org.mariotaku.twidere.util.ContentValuesCreator
import org.mariotaku.twidere.util.InternalTwitterContentUtils
@ -33,28 +36,28 @@ import org.mariotaku.twidere.util.content.ContentResolverUtils
import java.util.*
class CacheUsersStatusesTask(
private val context: Context
) : AbstractTask<TwitterListResponse<Status>, Unit?, Unit?>() {
private val context: Context,
private val accountKey: UserKey,
private val statuses: List<Status>
) : AbstractTask<Any?, Unit?, Unit?>() {
private val profileImageSize = context.getString(R.string.profile_image_size)
override fun doLongOperation(params: TwitterListResponse<Status>) {
override fun doLongOperation(params: Any?) {
val resolver = context.contentResolver
val extractor = Extractor()
val list = params.data ?: return
var bulkIdx = 0
val totalSize = list.size
val totalSize = statuses.size
while (bulkIdx < totalSize) {
var idx = bulkIdx
val end = Math.min(totalSize, bulkIdx + ContentResolverUtils.MAX_BULK_COUNT)
while (idx < end) {
val status = list[idx]
val status = statuses[idx]
val usersValues = HashSet<ContentValues>()
val users = HashSet<User>()
val statusesValues = HashSet<ContentValues>()
val hashTagValues = HashSet<ContentValues>()
val accountKey = params.accountKey
statusesValues.add(ContentValuesCreator.createStatus(status, accountKey, profileImageSize))
val text = InternalTwitterContentUtils.unescapeTwitterStatusText(status.extendedText)
for (hashtag in extractor.extractHashtags(text)) {
@ -62,19 +65,14 @@ class CacheUsersStatusesTask(
values.put(CachedHashtags.NAME, hashtag)
hashTagValues.add(values)
}
val cachedUser = ContentValuesCreator.createCachedUser(status.user)
cachedUser.put(CachedUsers.LAST_SEEN, System.currentTimeMillis())
usersValues.add(cachedUser)
if (status.isRetweet) {
val cachedRetweetedUser = ContentValuesCreator.createCachedUser(status.retweetedStatus.user,
profileImageSize)
cachedRetweetedUser.put(CachedUsers.LAST_SEEN, System.currentTimeMillis())
usersValues.add(cachedRetweetedUser)
}
status.user?.addTo(users)
status.retweetedStatus?.user?.addTo(users)
status.quotedStatus?.user?.addTo(users)
ContentResolverUtils.bulkInsert(resolver, CachedStatuses.CONTENT_URI, statusesValues)
ContentResolverUtils.bulkInsert(resolver, CachedHashtags.CONTENT_URI, hashTagValues)
ContentResolverUtils.bulkInsert(resolver, CachedUsers.CONTENT_URI, usersValues)
CacheUserRelationshipTask.cacheUserRelationships(resolver, accountKey, users)
idx++
}
bulkIdx += 100

View File

@ -33,7 +33,7 @@ import org.mariotaku.twidere.model.util.ParcelableStatusUtils
import org.mariotaku.twidere.provider.TwidereDataStore.AccountSupportColumns
import org.mariotaku.twidere.provider.TwidereDataStore.Statuses
import org.mariotaku.twidere.task.BaseAbstractTask
import org.mariotaku.twidere.task.CacheUsersStatusesTask
import org.mariotaku.twidere.task.cache.CacheUsersStatusesTask
import org.mariotaku.twidere.util.*
import org.mariotaku.twidere.util.content.ContentResolverUtils
import java.util.*
@ -109,12 +109,10 @@ abstract class GetStatusesTask(
val storeResult = storeStatus(accountKey, details, statuses, sinceId, maxId, sinceSortId,
maxSortId, loadItemLimit, false)
// TODO cache related data and preload
val cacheTask = CacheUsersStatusesTask(context)
val response = TwitterWrapper.StatusListResponse(accountKey, statuses)
cacheTask.params = response
val cacheTask = CacheUsersStatusesTask(context, accountKey, statuses)
TaskStarter.execute(cacheTask)
errorInfoStore.remove(errorInfoKey, accountKey.id)
result.add(response)
result.add(TwitterWrapper.StatusListResponse(accountKey, statuses))
if (storeResult != 0) {
throw GetTimelineException(storeResult)
}

View File

@ -34,10 +34,8 @@ import org.mariotaku.twidere.provider.TwidereDataStore.SavedSearches
object ContentValuesCreator {
fun createCachedUser(user: User, profileImageSize: String = "normal"): ContentValues {
val values = ContentValues()
ObjectCursor.valuesCreatorFrom(ParcelableUser::class.java).writeTo(ParcelableUserUtils.fromUser(user, null,
profileImageSize = profileImageSize), values)
return values
return ObjectCursor.valuesCreatorFrom(ParcelableUser::class.java)
.create(ParcelableUserUtils.fromUser(user, null, profileImageSize = profileImageSize))
}
fun createFilteredUser(status: ParcelableStatus): ContentValues {