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 # Local files
/captures /captures
/reports
# JRE error dumps # JRE error dumps
hs_err_*.log hs_err_*.log

View File

@ -1,3 +1,3 @@
org.gradle.daemon=true org.gradle.daemon=true
org.gradle.jvmargs=-Xms2048m -Xmx3072m 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; 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 @Override
public int describeContents() { public int describeContents() {
return 0; return 0;

View File

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

View File

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

View File

@ -19,11 +19,17 @@
package org.mariotaku.twidere.model.util 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.Relationship
import org.mariotaku.microblog.library.twitter.model.User 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.ParcelableRelationship
import org.mariotaku.twidere.model.ParcelableUser import org.mariotaku.twidere.model.ParcelableUser
import org.mariotaku.twidere.model.UserKey import org.mariotaku.twidere.model.UserKey
import org.mariotaku.twidere.provider.TwidereDataStore.*
import org.mariotaku.twidere.util.content.ContentResolverUtils
object ParcelableRelationshipUtils { object ParcelableRelationshipUtils {
@ -63,7 +69,7 @@ object ParcelableRelationshipUtils {
} }
fun create(accountKey: UserKey, userKey: UserKey, user: User, fun create(accountKey: UserKey, userKey: UserKey, user: User,
filtering: Boolean): ParcelableRelationship { filtering: Boolean = false): ParcelableRelationship {
val obj = ParcelableRelationship() val obj = ParcelableRelationship()
obj.account_key = accountKey obj.account_key = accountKey
obj.user_key = userKey obj.user_key = userKey
@ -76,4 +82,25 @@ object ParcelableRelationshipUtils {
obj.notifications_enabled = user.isNotificationsEnabled == true obj.notifications_enabled = user.isNotificationsEnabled == true
return obj 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), val where = Expression.and(Expression.equalsArgs(Statuses.ACCOUNT_KEY),
Expression.or(Expression.equalsArgs(Statuses.USER_KEY), Expression.or(Expression.equalsArgs(Statuses.USER_KEY),
Expression.equalsArgs(Statuses.RETWEETED_BY_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 val resolver = context.contentResolver
resolver.delete(Statuses.CONTENT_URI, where.sql, whereArgs) 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/>. * 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.ContentValues
import android.content.Context import android.content.Context
import com.twitter.Extractor import com.twitter.Extractor
import org.mariotaku.abstask.library.AbstractTask 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.Status
import org.mariotaku.microblog.library.twitter.model.User
import org.mariotaku.twidere.R import org.mariotaku.twidere.R
import org.mariotaku.twidere.model.UserKey
import org.mariotaku.twidere.provider.TwidereDataStore.* import org.mariotaku.twidere.provider.TwidereDataStore.*
import org.mariotaku.twidere.util.ContentValuesCreator import org.mariotaku.twidere.util.ContentValuesCreator
import org.mariotaku.twidere.util.InternalTwitterContentUtils import org.mariotaku.twidere.util.InternalTwitterContentUtils
@ -33,28 +36,28 @@ import org.mariotaku.twidere.util.content.ContentResolverUtils
import java.util.* import java.util.*
class CacheUsersStatusesTask( class CacheUsersStatusesTask(
private val context: Context private val context: Context,
) : AbstractTask<TwitterListResponse<Status>, Unit?, Unit?>() { private val accountKey: UserKey,
private val statuses: List<Status>
) : AbstractTask<Any?, Unit?, Unit?>() {
private val profileImageSize = context.getString(R.string.profile_image_size) 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 resolver = context.contentResolver
val extractor = Extractor() val extractor = Extractor()
val list = params.data ?: return
var bulkIdx = 0 var bulkIdx = 0
val totalSize = list.size val totalSize = statuses.size
while (bulkIdx < totalSize) { while (bulkIdx < totalSize) {
var idx = bulkIdx var idx = bulkIdx
val end = Math.min(totalSize, bulkIdx + ContentResolverUtils.MAX_BULK_COUNT) val end = Math.min(totalSize, bulkIdx + ContentResolverUtils.MAX_BULK_COUNT)
while (idx < end) { while (idx < end) {
val status = list[idx] val status = statuses[idx]
val usersValues = HashSet<ContentValues>() val users = HashSet<User>()
val statusesValues = HashSet<ContentValues>() val statusesValues = HashSet<ContentValues>()
val hashTagValues = HashSet<ContentValues>() val hashTagValues = HashSet<ContentValues>()
val accountKey = params.accountKey
statusesValues.add(ContentValuesCreator.createStatus(status, accountKey, profileImageSize)) statusesValues.add(ContentValuesCreator.createStatus(status, accountKey, profileImageSize))
val text = InternalTwitterContentUtils.unescapeTwitterStatusText(status.extendedText) val text = InternalTwitterContentUtils.unescapeTwitterStatusText(status.extendedText)
for (hashtag in extractor.extractHashtags(text)) { for (hashtag in extractor.extractHashtags(text)) {
@ -62,19 +65,14 @@ class CacheUsersStatusesTask(
values.put(CachedHashtags.NAME, hashtag) values.put(CachedHashtags.NAME, hashtag)
hashTagValues.add(values) hashTagValues.add(values)
} }
val cachedUser = ContentValuesCreator.createCachedUser(status.user)
cachedUser.put(CachedUsers.LAST_SEEN, System.currentTimeMillis()) status.user?.addTo(users)
usersValues.add(cachedUser) status.retweetedStatus?.user?.addTo(users)
if (status.isRetweet) { status.quotedStatus?.user?.addTo(users)
val cachedRetweetedUser = ContentValuesCreator.createCachedUser(status.retweetedStatus.user,
profileImageSize)
cachedRetweetedUser.put(CachedUsers.LAST_SEEN, System.currentTimeMillis())
usersValues.add(cachedRetweetedUser)
}
ContentResolverUtils.bulkInsert(resolver, CachedStatuses.CONTENT_URI, statusesValues) ContentResolverUtils.bulkInsert(resolver, CachedStatuses.CONTENT_URI, statusesValues)
ContentResolverUtils.bulkInsert(resolver, CachedHashtags.CONTENT_URI, hashTagValues) ContentResolverUtils.bulkInsert(resolver, CachedHashtags.CONTENT_URI, hashTagValues)
ContentResolverUtils.bulkInsert(resolver, CachedUsers.CONTENT_URI, usersValues) CacheUserRelationshipTask.cacheUserRelationships(resolver, accountKey, users)
idx++ idx++
} }
bulkIdx += 100 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.AccountSupportColumns
import org.mariotaku.twidere.provider.TwidereDataStore.Statuses import org.mariotaku.twidere.provider.TwidereDataStore.Statuses
import org.mariotaku.twidere.task.BaseAbstractTask 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.*
import org.mariotaku.twidere.util.content.ContentResolverUtils import org.mariotaku.twidere.util.content.ContentResolverUtils
import java.util.* import java.util.*
@ -109,12 +109,10 @@ abstract class GetStatusesTask(
val storeResult = storeStatus(accountKey, details, statuses, sinceId, maxId, sinceSortId, val storeResult = storeStatus(accountKey, details, statuses, sinceId, maxId, sinceSortId,
maxSortId, loadItemLimit, false) maxSortId, loadItemLimit, false)
// TODO cache related data and preload // TODO cache related data and preload
val cacheTask = CacheUsersStatusesTask(context) val cacheTask = CacheUsersStatusesTask(context, accountKey, statuses)
val response = TwitterWrapper.StatusListResponse(accountKey, statuses)
cacheTask.params = response
TaskStarter.execute(cacheTask) TaskStarter.execute(cacheTask)
errorInfoStore.remove(errorInfoKey, accountKey.id) errorInfoStore.remove(errorInfoKey, accountKey.id)
result.add(response) result.add(TwitterWrapper.StatusListResponse(accountKey, statuses))
if (storeResult != 0) { if (storeResult != 0) {
throw GetTimelineException(storeResult) throw GetTimelineException(storeResult)
} }

View File

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