From 0f86fc75a466f77bc52ef98782276e4dda9fd3a6 Mon Sep 17 00:00:00 2001 From: Mariotaku Lee Date: Sat, 18 Mar 2017 14:20:57 +0800 Subject: [PATCH] implementing relationship caching --- .gitignore | 1 + gradle.properties | 2 +- .../twidere/model/ParcelableRelationship.java | 19 ++++++ twidere/build.gradle | 2 +- .../ktextension/CollectionExtensions.kt | 4 ++ .../model/util/ParcelableRelationshipUtils.kt | 31 ++++++++- .../twidere/task/DestroyFriendshipTask.kt | 2 +- .../task/cache/CacheUserRelationshipTask.kt | 68 +++++++++++++++++++ .../{ => cache}/CacheUsersStatusesTask.kt | 36 +++++----- .../twidere/task/twitter/GetStatusesTask.kt | 8 +-- .../twidere/util/ContentValuesCreator.kt | 6 +- 11 files changed, 146 insertions(+), 33 deletions(-) create mode 100644 twidere/src/main/kotlin/org/mariotaku/twidere/task/cache/CacheUserRelationshipTask.kt rename twidere/src/main/kotlin/org/mariotaku/twidere/task/{ => cache}/CacheUsersStatusesTask.kt (70%) diff --git a/.gitignore b/.gitignore index ef0e7689d..c18fbb919 100644 --- a/.gitignore +++ b/.gitignore @@ -29,6 +29,7 @@ Thumbs.db # Local files /captures +/reports # JRE error dumps hs_err_*.log \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index a85456d5a..c412038b2 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,3 +1,3 @@ org.gradle.daemon=true org.gradle.jvmargs=-Xms2048m -Xmx3072m -systemProp.kotlin.daemon.verbose=true \ No newline at end of file +kotlin.incremental=true \ No newline at end of file diff --git a/twidere.component.common/src/main/java/org/mariotaku/twidere/model/ParcelableRelationship.java b/twidere.component.common/src/main/java/org/mariotaku/twidere/model/ParcelableRelationship.java index ea61c50d6..cd07fad46 100644 --- a/twidere.component.common/src/main/java/org/mariotaku/twidere/model/ParcelableRelationship.java +++ b/twidere.component.common/src/main/java/org/mariotaku/twidere/model/ParcelableRelationship.java @@ -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; diff --git a/twidere/build.gradle b/twidere/build.gradle index a62c91e94..1477ab9f5 100644 --- a/twidere/build.gradle +++ b/twidere/build.gradle @@ -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' diff --git a/twidere/src/main/kotlin/org/mariotaku/ktextension/CollectionExtensions.kt b/twidere/src/main/kotlin/org/mariotaku/ktextension/CollectionExtensions.kt index d7e54a925..090d668dd 100644 --- a/twidere/src/main/kotlin/org/mariotaku/ktextension/CollectionExtensions.kt +++ b/twidere/src/main/kotlin/org/mariotaku/ktextension/CollectionExtensions.kt @@ -39,4 +39,8 @@ inline fun List.subArray(range: IntRange): Array { return Array(range.count()) { this[range.start + it] } +} + +fun T.addTo(collection: MutableCollection): Boolean { + return collection.add(this) } \ No newline at end of file diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/model/util/ParcelableRelationshipUtils.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/model/util/ParcelableRelationshipUtils.kt index 912e20cd9..35bc0bbfc 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/model/util/ParcelableRelationshipUtils.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/model/util/ParcelableRelationshipUtils.kt @@ -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) { + val insertItems = ArraySet() + 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)) + } } diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/task/DestroyFriendshipTask.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/task/DestroyFriendshipTask.kt index 6ff2de329..4cb75cfbf 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/task/DestroyFriendshipTask.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/task/DestroyFriendshipTask.kt @@ -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) } diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/task/cache/CacheUserRelationshipTask.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/task/cache/CacheUserRelationshipTask.kt new file mode 100644 index 000000000..0daae6d7b --- /dev/null +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/task/cache/CacheUserRelationshipTask.kt @@ -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) : BaseAbstractTask(context) { + override fun doLongOperation(param: Any?) { + cacheUserRelationships(context.contentResolver, accountKey, users) + } + + companion object { + fun cacheUserRelationships(cr: ContentResolver, accountKey: UserKey, users: Collection) { + + 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()) { 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) + } + } + +} diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/task/CacheUsersStatusesTask.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/task/cache/CacheUsersStatusesTask.kt similarity index 70% rename from twidere/src/main/kotlin/org/mariotaku/twidere/task/CacheUsersStatusesTask.kt rename to twidere/src/main/kotlin/org/mariotaku/twidere/task/cache/CacheUsersStatusesTask.kt index bcc31b1e0..6e55e1456 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/task/CacheUsersStatusesTask.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/task/cache/CacheUsersStatusesTask.kt @@ -17,14 +17,17 @@ * along with this program. If not, see . */ -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, Unit?, Unit?>() { + private val context: Context, + private val accountKey: UserKey, + private val statuses: List +) : AbstractTask() { private val profileImageSize = context.getString(R.string.profile_image_size) - override fun doLongOperation(params: TwitterListResponse) { + 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() + val users = HashSet() val statusesValues = HashSet() val hashTagValues = HashSet() - 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 diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/GetStatusesTask.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/GetStatusesTask.kt index 93638d300..7c5ad9ec9 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/GetStatusesTask.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/GetStatusesTask.kt @@ -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) } diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/util/ContentValuesCreator.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/util/ContentValuesCreator.kt index 70642def6..c907e7e5d 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/util/ContentValuesCreator.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/util/ContentValuesCreator.kt @@ -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 {