983 lines
46 KiB
Kotlin
983 lines
46 KiB
Kotlin
/*
|
|
* Twidere - Twitter client for Android
|
|
*
|
|
* Copyright (C) 2012-2017 Mariotaku Lee <mariotaku.lee@gmail.com>
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
package org.mariotaku.twidere.util
|
|
|
|
import android.accounts.AccountManager
|
|
import android.annotation.SuppressLint
|
|
import android.content.*
|
|
import android.database.Cursor
|
|
import android.net.Uri
|
|
import android.os.Bundle
|
|
import android.os.Parcelable
|
|
import android.provider.BaseColumns
|
|
import android.support.annotation.WorkerThread
|
|
import android.text.TextUtils
|
|
import org.mariotaku.kpreferences.get
|
|
import org.mariotaku.ktextension.useCursor
|
|
import org.mariotaku.library.objectcursor.ObjectCursor
|
|
import org.mariotaku.microblog.library.MicroBlog
|
|
import org.mariotaku.microblog.library.MicroBlogException
|
|
import org.mariotaku.microblog.library.twitter.model.Activity
|
|
import org.mariotaku.sqliteqb.library.*
|
|
import org.mariotaku.sqliteqb.library.Columns.Column
|
|
import org.mariotaku.sqliteqb.library.query.SQLSelectQuery
|
|
import org.mariotaku.twidere.TwidereConstants.*
|
|
import org.mariotaku.twidere.constant.IntentConstants
|
|
import org.mariotaku.twidere.constant.databaseItemLimitKey
|
|
import org.mariotaku.twidere.extension.model.*
|
|
import org.mariotaku.twidere.extension.rawQuery
|
|
import org.mariotaku.twidere.model.*
|
|
import org.mariotaku.twidere.model.tab.extra.HomeTabExtras
|
|
import org.mariotaku.twidere.model.tab.extra.InteractionsTabExtras
|
|
import org.mariotaku.twidere.model.tab.extra.TabExtras
|
|
import org.mariotaku.twidere.model.util.AccountUtils
|
|
import org.mariotaku.twidere.model.util.ParcelableStatusUtils
|
|
import org.mariotaku.twidere.provider.TwidereDataStore
|
|
import org.mariotaku.twidere.provider.TwidereDataStore.*
|
|
import org.mariotaku.twidere.provider.TwidereDataStore.Messages.Conversations
|
|
import org.mariotaku.twidere.util.content.ContentResolverUtils
|
|
import java.io.IOException
|
|
import java.util.*
|
|
|
|
/**
|
|
* Created by mariotaku on 15/11/28.
|
|
*/
|
|
object DataStoreUtils {
|
|
|
|
val STATUSES_URIS = arrayOf(Statuses.CONTENT_URI, CachedStatuses.CONTENT_URI)
|
|
val CACHE_URIS = arrayOf(CachedUsers.CONTENT_URI, CachedStatuses.CONTENT_URI, CachedHashtags.CONTENT_URI, CachedTrends.Local.CONTENT_URI)
|
|
val MESSAGES_URIS = arrayOf(Messages.CONTENT_URI, Conversations.CONTENT_URI)
|
|
val ACTIVITIES_URIS = arrayOf(Activities.AboutMe.CONTENT_URI)
|
|
|
|
private val CONTENT_PROVIDER_URI_MATCHER = UriMatcher(UriMatcher.NO_MATCH)
|
|
|
|
init {
|
|
CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, Statuses.CONTENT_PATH,
|
|
TABLE_ID_STATUSES)
|
|
CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, Activities.AboutMe.CONTENT_PATH,
|
|
TABLE_ID_ACTIVITIES_ABOUT_ME)
|
|
CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, Drafts.CONTENT_PATH,
|
|
TABLE_ID_DRAFTS)
|
|
CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, CachedUsers.CONTENT_PATH,
|
|
TABLE_ID_CACHED_USERS)
|
|
CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, Filters.Users.CONTENT_PATH,
|
|
TABLE_ID_FILTERED_USERS)
|
|
CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, Filters.Keywords.CONTENT_PATH,
|
|
TABLE_ID_FILTERED_KEYWORDS)
|
|
CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, Filters.Sources.CONTENT_PATH,
|
|
TABLE_ID_FILTERED_SOURCES)
|
|
CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, Filters.Links.CONTENT_PATH,
|
|
TABLE_ID_FILTERED_LINKS)
|
|
CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, Filters.Subscriptions.CONTENT_PATH,
|
|
TABLE_ID_FILTERS_SUBSCRIPTIONS)
|
|
CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, Messages.CONTENT_PATH,
|
|
TABLE_ID_MESSAGES)
|
|
CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, Conversations.CONTENT_PATH,
|
|
TABLE_ID_MESSAGES_CONVERSATIONS)
|
|
CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, CachedTrends.Local.CONTENT_PATH,
|
|
TABLE_ID_TRENDS_LOCAL)
|
|
CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, Tabs.CONTENT_PATH,
|
|
TABLE_ID_TABS)
|
|
CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, CachedStatuses.CONTENT_PATH,
|
|
TABLE_ID_CACHED_STATUSES)
|
|
CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, CachedHashtags.CONTENT_PATH,
|
|
TABLE_ID_CACHED_HASHTAGS)
|
|
CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, CachedRelationships.CONTENT_PATH,
|
|
TABLE_ID_CACHED_RELATIONSHIPS)
|
|
CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, SavedSearches.CONTENT_PATH,
|
|
TABLE_ID_SAVED_SEARCHES)
|
|
CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, SearchHistory.CONTENT_PATH,
|
|
TABLE_ID_SEARCH_HISTORY)
|
|
|
|
CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, Permissions.CONTENT_PATH,
|
|
VIRTUAL_TABLE_ID_PERMISSIONS)
|
|
CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, CachedUsers.CONTENT_PATH_WITH_RELATIONSHIP + "/*",
|
|
VIRTUAL_TABLE_ID_CACHED_USERS_WITH_RELATIONSHIP)
|
|
CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, CachedUsers.CONTENT_PATH_WITH_SCORE + "/*",
|
|
VIRTUAL_TABLE_ID_CACHED_USERS_WITH_SCORE)
|
|
CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, Drafts.CONTENT_PATH_UNSENT,
|
|
VIRTUAL_TABLE_ID_DRAFTS_UNSENT)
|
|
CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, Drafts.CONTENT_PATH_NOTIFICATIONS,
|
|
VIRTUAL_TABLE_ID_DRAFTS_NOTIFICATIONS)
|
|
CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, Drafts.CONTENT_PATH_NOTIFICATIONS,
|
|
VIRTUAL_TABLE_ID_DRAFTS_NOTIFICATIONS)
|
|
CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, Suggestions.AutoComplete.CONTENT_PATH,
|
|
VIRTUAL_TABLE_ID_SUGGESTIONS_AUTO_COMPLETE)
|
|
CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, Suggestions.Search.CONTENT_PATH,
|
|
VIRTUAL_TABLE_ID_SUGGESTIONS_SEARCH)
|
|
CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, TwidereDataStore.CONTENT_PATH_DATABASE_PREPARE,
|
|
VIRTUAL_TABLE_ID_DATABASE_PREPARE)
|
|
CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, TwidereDataStore.CONTENT_PATH_NULL,
|
|
VIRTUAL_TABLE_ID_NULL)
|
|
CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, TwidereDataStore.CONTENT_PATH_EMPTY,
|
|
VIRTUAL_TABLE_ID_EMPTY)
|
|
CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, TwidereDataStore.CONTENT_PATH_RAW_QUERY + "/*",
|
|
VIRTUAL_TABLE_ID_RAW_QUERY)
|
|
}
|
|
|
|
fun getNewestStatusIds(context: Context, uri: Uri, accountKeys: Array<UserKey?>): Array<String?> {
|
|
return getStringFieldArray(context, uri, accountKeys, Statuses.ACCOUNT_KEY, Statuses.STATUS_ID,
|
|
OrderBy(SQLFunctions.MAX(Statuses.STATUS_TIMESTAMP)), null, null)
|
|
}
|
|
|
|
fun getNewestMessageIds(context: Context, uri: Uri, accountKeys: Array<UserKey?>, outgoing: Boolean): Array<String?> {
|
|
val having: Expression = Expression.equals(Messages.IS_OUTGOING, if (outgoing) 1 else 0)
|
|
return getStringFieldArray(context, uri, accountKeys, Messages.ACCOUNT_KEY, Messages.MESSAGE_ID,
|
|
OrderBy(SQLFunctions.MAX(Messages.LOCAL_TIMESTAMP)), having, null)
|
|
}
|
|
|
|
fun getOldestMessageIds(context: Context, uri: Uri, accountKeys: Array<UserKey?>, outgoing: Boolean): Array<String?> {
|
|
if (accountKeys.all { it == null }) return arrayOfNulls(accountKeys.size)
|
|
val having: Expression = Expression.equals(Messages.IS_OUTGOING, if (outgoing) 1 else 0)
|
|
return getStringFieldArray(context, uri, accountKeys, Messages.ACCOUNT_KEY, Messages.MESSAGE_ID,
|
|
OrderBy(SQLFunctions.MIN(Messages.LOCAL_TIMESTAMP)), having, null)
|
|
}
|
|
|
|
fun getOldestConversations(context: Context, uri: Uri, accountKeys: Array<UserKey?>): Array<ParcelableMessageConversation?> {
|
|
if (accountKeys.all { it == null }) return arrayOfNulls(accountKeys.size)
|
|
return getObjectFieldArray(context, uri, accountKeys, Conversations.ACCOUNT_KEY, Conversations.COLUMNS,
|
|
OrderBy(SQLFunctions.MIN(Messages.LOCAL_TIMESTAMP)), null, null,
|
|
{ ObjectCursor.indicesFrom(it, ParcelableMessageConversation::class.java) },
|
|
{ arrayOfNulls<ParcelableMessageConversation>(it) })
|
|
}
|
|
|
|
fun getNewestConversations(context: Context, uri: Uri, accountKeys: Array<UserKey?>,
|
|
extraWhere: Expression? = null, extraWhereArgs: Array<String>? = null): Array<ParcelableMessageConversation?> {
|
|
if (accountKeys.all { it == null }) return arrayOfNulls(accountKeys.size)
|
|
return getObjectFieldArray(context, uri, accountKeys, Conversations.ACCOUNT_KEY, Conversations.COLUMNS,
|
|
OrderBy(SQLFunctions.MAX(Messages.LOCAL_TIMESTAMP)), extraWhere, extraWhereArgs,
|
|
{ ObjectCursor.indicesFrom(it, ParcelableMessageConversation::class.java) },
|
|
{ arrayOfNulls<ParcelableMessageConversation>(it) })
|
|
}
|
|
|
|
fun getNewestStatusSortIds(context: Context, uri: Uri, accountKeys: Array<UserKey?>): LongArray {
|
|
return getLongFieldArray(context, uri, accountKeys, Statuses.ACCOUNT_KEY, Statuses.SORT_ID,
|
|
OrderBy(SQLFunctions.MAX(Statuses.STATUS_TIMESTAMP)), null, null)
|
|
}
|
|
|
|
|
|
fun getOldestStatusIds(context: Context, uri: Uri, accountKeys: Array<UserKey?>): Array<String?> {
|
|
return getStringFieldArray(context, uri, accountKeys, Statuses.ACCOUNT_KEY, Statuses.STATUS_ID,
|
|
OrderBy(SQLFunctions.MIN(Statuses.STATUS_TIMESTAMP)), null, null)
|
|
}
|
|
|
|
|
|
fun getOldestStatusSortIds(context: Context, uri: Uri, accountKeys: Array<UserKey?>): LongArray {
|
|
return getLongFieldArray(context, uri, accountKeys, Statuses.ACCOUNT_KEY, Statuses.SORT_ID,
|
|
OrderBy(SQLFunctions.MIN(Statuses.STATUS_TIMESTAMP)), null, null)
|
|
}
|
|
|
|
fun getNewestActivityMaxPositions(context: Context, uri: Uri, accountKeys: Array<UserKey?>,
|
|
extraWhere: Expression?, extraWhereArgs: Array<String>?): Array<String?> {
|
|
return getStringFieldArray(context, uri, accountKeys, Activities.ACCOUNT_KEY,
|
|
Activities.MAX_REQUEST_POSITION, OrderBy(SQLFunctions.MAX(Activities.TIMESTAMP)),
|
|
extraWhere, extraWhereArgs)
|
|
}
|
|
|
|
fun getRefreshNewestActivityMaxPositions(context: Context, uri: Uri, accountKeys: Array<UserKey?>):
|
|
Array<String?> {
|
|
return getOfficialSeparatedIds(context, { accountKeys, isOfficial ->
|
|
val (where, whereArgs) = getIdsWhere(isOfficial)
|
|
DataStoreUtils.getNewestActivityMaxPositions(context, uri, accountKeys,
|
|
where, whereArgs)
|
|
}, { arr1, arr2 ->
|
|
Array(accountKeys.size) { arr1[it] ?: arr2[it] }
|
|
}, accountKeys)
|
|
}
|
|
|
|
fun getOldestActivityMaxPositions(context: Context, uri: Uri, accountKeys: Array<UserKey?>,
|
|
extraWhere: Expression?, extraWhereArgs: Array<String>?): Array<String?> {
|
|
return getStringFieldArray(context, uri, accountKeys, Activities.ACCOUNT_KEY,
|
|
Activities.MAX_REQUEST_POSITION, OrderBy(SQLFunctions.MIN(Activities.TIMESTAMP)),
|
|
extraWhere, extraWhereArgs)
|
|
}
|
|
|
|
fun getRefreshOldestActivityMaxPositions(context: Context, uri: Uri, accountKeys: Array<UserKey?>):
|
|
Array<String?> {
|
|
return getOfficialSeparatedIds(context, { accountKeys, isOfficial ->
|
|
val (where, whereArgs) = getIdsWhere(isOfficial)
|
|
DataStoreUtils.getOldestActivityMaxPositions(context, uri, accountKeys,
|
|
where, whereArgs)
|
|
}, { arr1, arr2 ->
|
|
Array(accountKeys.size) { arr1[it] ?: arr2[it] }
|
|
}, accountKeys)
|
|
}
|
|
|
|
fun getNewestActivityMaxSortPositions(context: Context, uri: Uri, accountKeys: Array<UserKey?>,
|
|
extraWhere: Expression?, extraWhereArgs: Array<String>?): LongArray {
|
|
return getLongFieldArray(context, uri, accountKeys, Activities.ACCOUNT_KEY,
|
|
Activities.MAX_SORT_POSITION, OrderBy(SQLFunctions.MAX(Activities.TIMESTAMP)),
|
|
extraWhere, extraWhereArgs)
|
|
}
|
|
|
|
fun getRefreshNewestActivityMaxSortPositions(context: Context, uri: Uri, accountKeys: Array<UserKey?>):
|
|
LongArray {
|
|
return getOfficialSeparatedIds(context, { accountKeys, isOfficial ->
|
|
val (where, whereArgs) = getIdsWhere(isOfficial)
|
|
DataStoreUtils.getNewestActivityMaxSortPositions(context, uri, accountKeys,
|
|
where, whereArgs)
|
|
}, { arr1, arr2 ->
|
|
LongArray(accountKeys.size) { arr1[it].takeIf { it > 0 } ?: arr2[it] }
|
|
}, accountKeys)
|
|
}
|
|
|
|
fun getOldestActivityMaxSortPositions(context: Context, uri: Uri, accountKeys: Array<UserKey?>,
|
|
extraWhere: Expression?, extraWhereArgs: Array<String>?): LongArray {
|
|
return getLongFieldArray(context, uri, accountKeys, Activities.ACCOUNT_KEY,
|
|
Activities.MAX_SORT_POSITION, OrderBy(SQLFunctions.MIN(Activities.TIMESTAMP)),
|
|
extraWhere, extraWhereArgs)
|
|
}
|
|
|
|
fun getRefreshOldestActivityMaxSortPositions(context: Context, uri: Uri, accountKeys: Array<UserKey?>):
|
|
LongArray {
|
|
return getOfficialSeparatedIds(context, { accountKeys, isOfficial ->
|
|
val (where, whereArgs) = getIdsWhere(isOfficial)
|
|
DataStoreUtils.getOldestActivityMaxSortPositions(context, uri, accountKeys,
|
|
where, whereArgs)
|
|
}, { arr1, arr2 ->
|
|
LongArray(accountKeys.size) { arr1[it].takeIf { it > 0 } ?: arr2[it] }
|
|
}, accountKeys)
|
|
}
|
|
|
|
fun getStatusCount(context: Context, uri: Uri, accountId: UserKey): Int {
|
|
val where = Expression.equalsArgs(AccountSupportColumns.ACCOUNT_KEY).sql
|
|
val whereArgs = arrayOf(accountId.toString())
|
|
return queryCount(context.contentResolver, uri, where, whereArgs)
|
|
}
|
|
|
|
fun getActivitiesCount(context: Context, uri: Uri,
|
|
accountKey: UserKey): Int {
|
|
val where = Expression.equalsArgs(AccountSupportColumns.ACCOUNT_KEY).sql
|
|
return queryCount(context.contentResolver, uri, where, arrayOf(accountKey.toString()))
|
|
}
|
|
|
|
|
|
@SuppressLint("Recycle")
|
|
fun getFilteredUserIds(context: Context?): Array<UserKey> {
|
|
if (context == null) return emptyArray()
|
|
val resolver = context.contentResolver
|
|
val projection = arrayOf(Filters.Users.USER_KEY)
|
|
return resolver.query(Filters.Users.CONTENT_URI, projection, null, null, null)?.useCursor { cur ->
|
|
return@useCursor Array(cur.count) { i ->
|
|
cur.moveToPosition(i)
|
|
UserKey.valueOf(cur.getString(0))
|
|
}
|
|
} ?: emptyArray()
|
|
}
|
|
|
|
fun getAccountDisplayName(context: Context, accountKey: UserKey, nameFirst: Boolean): String? {
|
|
val name: String?
|
|
if (nameFirst) {
|
|
name = getAccountName(context, accountKey)
|
|
} else {
|
|
name = "@${getAccountScreenName(context, accountKey)}"
|
|
}
|
|
return name
|
|
}
|
|
|
|
fun getAccountName(context: Context, accountKey: UserKey): String? {
|
|
val am = AccountManager.get(context)
|
|
val account = AccountUtils.findByAccountKey(am, accountKey) ?: return null
|
|
|
|
return account.getAccountUser(am).name
|
|
}
|
|
|
|
fun getAccountScreenName(context: Context, accountKey: UserKey): String? {
|
|
val am = AccountManager.get(context)
|
|
val account = AccountUtils.findByAccountKey(am, accountKey) ?: return null
|
|
return account.getAccountUser(am).screen_name
|
|
}
|
|
|
|
fun getActivatedAccountKeys(context: Context): Array<UserKey> {
|
|
val am = AccountManager.get(context)
|
|
val keys = ArrayList<UserKey>()
|
|
for (account in AccountUtils.getAccounts(am)) {
|
|
if (account.isActivated(am)) {
|
|
keys.add(account.getAccountKey(am))
|
|
}
|
|
}
|
|
return keys.toTypedArray()
|
|
}
|
|
|
|
fun getStatusesCount(context: Context, preferences: SharedPreferences, uri: Uri,
|
|
extraArgs: Bundle?, compare: Long, compareColumn: String, greaterThan: Boolean,
|
|
accountKeys: Array<UserKey>?): Int {
|
|
val keys = accountKeys ?: getActivatedAccountKeys(context)
|
|
|
|
val expressions = ArrayList<Expression>()
|
|
val expressionArgs = ArrayList<String>()
|
|
|
|
expressions.add(Expression.inArgs(Column(Statuses.ACCOUNT_KEY), keys.size))
|
|
for (accountKey in keys) {
|
|
expressionArgs.add(accountKey.toString())
|
|
}
|
|
|
|
if (greaterThan) {
|
|
expressions.add(Expression.greaterThanArgs(compareColumn))
|
|
} else {
|
|
expressions.add(Expression.lesserThanArgs(compareColumn))
|
|
}
|
|
expressionArgs.add(compare.toString())
|
|
|
|
expressions.add(buildStatusFilterWhereClause(preferences, getTableNameByUri(uri)!!, null))
|
|
|
|
if (extraArgs != null) {
|
|
val extras = extraArgs.getParcelable<Parcelable>(EXTRA_EXTRAS)
|
|
if (extras is HomeTabExtras) {
|
|
processTabExtras(expressions, expressionArgs, extras)
|
|
}
|
|
}
|
|
|
|
val selection = Expression.and(*expressions.toTypedArray())
|
|
return queryCount(context.contentResolver, uri, selection.sql, expressionArgs.toTypedArray())
|
|
}
|
|
|
|
fun getActivitiesCount(context: Context, uri: Uri, compare: Long,
|
|
compareColumn: String, greaterThan: Boolean, accountKeys: Array<UserKey>?): Int {
|
|
val keys = accountKeys ?: getActivatedAccountKeys(context)
|
|
val selection = Expression.and(
|
|
Expression.inArgs(Column(Activities.ACCOUNT_KEY), keys.size),
|
|
if (greaterThan) Expression.greaterThanArgs(compareColumn) else Expression.lesserThanArgs(compareColumn),
|
|
buildActivityFilterWhereClause(getTableNameByUri(uri)!!, null)
|
|
)
|
|
val whereArgs = arrayListOf<String>()
|
|
keys.mapTo(whereArgs) { it.toString() }
|
|
whereArgs.add(compare.toString())
|
|
return queryCount(context.contentResolver, uri, selection.sql, whereArgs.toTypedArray())
|
|
}
|
|
|
|
fun getActivitiesCount(context: Context, uri: Uri,
|
|
extraWhere: Expression?, extraWhereArgs: Array<String>?,
|
|
since: Long, sinceColumn: String, followingOnly: Boolean,
|
|
accountKeys: Array<UserKey>?): Int {
|
|
val keys = (accountKeys ?: getActivatedAccountKeys(context)).map { it.toString() }.toTypedArray()
|
|
val expressions = ArrayList<Expression>()
|
|
expressions.add(Expression.inArgs(Column(Activities.ACCOUNT_KEY), keys.size))
|
|
expressions.add(Expression.greaterThanArgs(sinceColumn))
|
|
expressions.add(buildActivityFilterWhereClause(getTableNameByUri(uri)!!, null))
|
|
if (extraWhere != null) {
|
|
expressions.add(extraWhere)
|
|
}
|
|
val selection = Expression.and(*expressions.toTypedArray())
|
|
val selectionArgs: Array<String>
|
|
if (extraWhereArgs != null) {
|
|
selectionArgs = keys + since.toString() + extraWhereArgs
|
|
} else {
|
|
selectionArgs = keys + since.toString()
|
|
}
|
|
// If followingOnly option is on, we have to iterate over items
|
|
val resolver = context.contentResolver
|
|
if (followingOnly) {
|
|
val projection = arrayOf(Activities.SOURCES)
|
|
val cur = resolver.query(uri, projection, selection.sql, selectionArgs, null) ?: return -1
|
|
cur.useCursor { cur ->
|
|
var total = 0
|
|
cur.moveToFirst()
|
|
while (!cur.isAfterLast) {
|
|
val string = cur.getString(0)
|
|
if (TextUtils.isEmpty(string)) continue
|
|
var hasFollowing = false
|
|
try {
|
|
for (state in JsonSerializer.parseList(string, UserFollowState::class.java)) {
|
|
if (state.is_following) {
|
|
hasFollowing = true
|
|
break
|
|
}
|
|
}
|
|
} catch (e: IOException) {
|
|
continue
|
|
}
|
|
|
|
if (hasFollowing) {
|
|
total++
|
|
}
|
|
cur.moveToNext()
|
|
}
|
|
return total
|
|
}
|
|
}
|
|
return queryCount(resolver, uri, selection.sql, selectionArgs)
|
|
}
|
|
|
|
fun getTableId(uri: Uri?): Int {
|
|
if (uri == null) return -1
|
|
return CONTENT_PROVIDER_URI_MATCHER.match(uri)
|
|
}
|
|
|
|
fun getTableNameById(id: Int): String? {
|
|
when (id) {
|
|
TABLE_ID_STATUSES -> return Statuses.TABLE_NAME
|
|
TABLE_ID_ACTIVITIES_ABOUT_ME -> return Activities.AboutMe.TABLE_NAME
|
|
TABLE_ID_DRAFTS -> return Drafts.TABLE_NAME
|
|
TABLE_ID_FILTERED_USERS -> return Filters.Users.TABLE_NAME
|
|
TABLE_ID_FILTERED_KEYWORDS -> return Filters.Keywords.TABLE_NAME
|
|
TABLE_ID_FILTERED_SOURCES -> return Filters.Sources.TABLE_NAME
|
|
TABLE_ID_FILTERED_LINKS -> return Filters.Links.TABLE_NAME
|
|
TABLE_ID_FILTERS_SUBSCRIPTIONS -> return Filters.Subscriptions.TABLE_NAME
|
|
TABLE_ID_MESSAGES -> return Messages.TABLE_NAME
|
|
TABLE_ID_MESSAGES_CONVERSATIONS -> return Conversations.TABLE_NAME
|
|
TABLE_ID_TRENDS_LOCAL -> return CachedTrends.Local.TABLE_NAME
|
|
TABLE_ID_TABS -> return Tabs.TABLE_NAME
|
|
TABLE_ID_CACHED_STATUSES -> return CachedStatuses.TABLE_NAME
|
|
TABLE_ID_CACHED_USERS -> return CachedUsers.TABLE_NAME
|
|
TABLE_ID_CACHED_HASHTAGS -> return CachedHashtags.TABLE_NAME
|
|
TABLE_ID_CACHED_RELATIONSHIPS -> return CachedRelationships.TABLE_NAME
|
|
TABLE_ID_SAVED_SEARCHES -> return SavedSearches.TABLE_NAME
|
|
TABLE_ID_SEARCH_HISTORY -> return SearchHistory.TABLE_NAME
|
|
else -> return null
|
|
}
|
|
}
|
|
|
|
fun getTableNameByUri(uri: Uri?): String? {
|
|
if (uri == null) return null
|
|
return getTableNameById(getTableId(uri))
|
|
}
|
|
|
|
fun buildActivityFilterWhereClause(table: String, extraSelection: Expression?): Expression {
|
|
val filteredUsersQuery = SQLQueryBuilder
|
|
.select(Column(Table(Filters.Users.TABLE_NAME), Filters.Users.USER_KEY))
|
|
.from(Tables(Filters.Users.TABLE_NAME))
|
|
.build()
|
|
val filteredUsersWhere = Expression.or(
|
|
Expression.`in`(Column(Table(table), Activities.STATUS_USER_KEY), filteredUsersQuery),
|
|
Expression.`in`(Column(Table(table), Activities.STATUS_RETWEETED_BY_USER_KEY), filteredUsersQuery),
|
|
Expression.`in`(Column(Table(table), Activities.STATUS_QUOTED_USER_KEY), filteredUsersQuery)
|
|
)
|
|
val filteredIdsQueryBuilder = SQLQueryBuilder
|
|
.select(Column(Table(table), Activities._ID))
|
|
.from(Tables(table))
|
|
.where(filteredUsersWhere)
|
|
.union()
|
|
.select(Columns(Column(Table(table), Activities._ID)))
|
|
.from(Tables(table, Filters.Sources.TABLE_NAME))
|
|
.where(Expression.or(
|
|
Expression.likeRaw(Column(Table(table), Activities.STATUS_SOURCE),
|
|
"'%>'||${Filters.Sources.TABLE_NAME}.${Filters.Sources.VALUE}||'</a>%'"),
|
|
Expression.likeRaw(Column(Table(table), Activities.STATUS_QUOTE_SOURCE),
|
|
"'%>'||${Filters.Sources.TABLE_NAME}.${Filters.Sources.VALUE}||'</a>%'")
|
|
))
|
|
.union()
|
|
.select(Columns(Column(Table(table), Activities._ID)))
|
|
.from(Tables(table, Filters.Keywords.TABLE_NAME))
|
|
.where(Expression.or(
|
|
Expression.likeRaw(Column(Table(table), Activities.STATUS_TEXT_PLAIN),
|
|
"'%'||${Filters.Keywords.TABLE_NAME}.${Filters.Keywords.VALUE}||'%'"),
|
|
Expression.likeRaw(Column(Table(table), Activities.STATUS_QUOTE_TEXT_PLAIN),
|
|
"'%'||${Filters.Keywords.TABLE_NAME}.${Filters.Keywords.VALUE}||'%'")
|
|
))
|
|
.union()
|
|
.select(Columns(Column(Table(table), Activities._ID)))
|
|
.from(Tables(table, Filters.Links.TABLE_NAME))
|
|
.where(Expression.or(
|
|
Expression.likeRaw(Column(Table(table), Activities.STATUS_SPANS),
|
|
"'%'||${Filters.Links.TABLE_NAME}.${Filters.Links.VALUE}||'%'"),
|
|
Expression.likeRaw(Column(Table(table), Activities.STATUS_QUOTE_SPANS),
|
|
"'%'||${Filters.Links.TABLE_NAME}.${Filters.Links.VALUE}||'%'")
|
|
))
|
|
val filterExpression = Expression.or(
|
|
Expression.notIn(Column(Table(table), Activities._ID), filteredIdsQueryBuilder.build()),
|
|
Expression.equals(Column(Table(table), Activities.IS_GAP), 1)
|
|
)
|
|
if (extraSelection != null) {
|
|
return Expression.and(filterExpression, extraSelection)
|
|
}
|
|
return filterExpression
|
|
}
|
|
|
|
fun getAccountColors(context: Context, accountKeys: Array<UserKey>): IntArray {
|
|
val am = AccountManager.get(context)
|
|
val colors = IntArray(accountKeys.size)
|
|
for (i in accountKeys.indices) {
|
|
val account = AccountUtils.findByAccountKey(am, accountKeys[i])
|
|
if (account != null) {
|
|
colors[i] = account.getColor(am)
|
|
}
|
|
}
|
|
return colors
|
|
}
|
|
|
|
fun findAccountKeyByScreenName(context: Context, screenName: String): UserKey? {
|
|
val am = AccountManager.get(context)
|
|
for (account in AccountUtils.getAccounts(am)) {
|
|
val user = account.getAccountUser(am)
|
|
if (screenName.equals(user.screen_name, ignoreCase = true)) {
|
|
return user.key
|
|
}
|
|
}
|
|
return null
|
|
}
|
|
|
|
fun getAccountKeys(context: Context): Array<UserKey> {
|
|
val am = AccountManager.get(context)
|
|
val accounts = AccountUtils.getAccounts(am)
|
|
val keys = ArrayList<UserKey>(accounts.size)
|
|
for (account in accounts) {
|
|
val keyString = am.getUserData(account, ACCOUNT_USER_DATA_KEY) ?: continue
|
|
keys.add(UserKey.valueOf(keyString))
|
|
}
|
|
return keys.toTypedArray()
|
|
}
|
|
|
|
fun findAccountKey(context: Context, accountId: String): UserKey? {
|
|
val am = AccountManager.get(context)
|
|
for (account in AccountUtils.getAccounts(am)) {
|
|
val key = account.getAccountKey(am)
|
|
if (accountId == key.id) {
|
|
return key
|
|
}
|
|
}
|
|
return null
|
|
}
|
|
|
|
fun hasAccount(context: Context): Boolean {
|
|
return AccountUtils.getAccounts(AccountManager.get(context)).isNotEmpty()
|
|
}
|
|
|
|
@Synchronized fun cleanDatabasesByItemLimit(context: Context) {
|
|
val resolver = context.contentResolver
|
|
val preferences = context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE)
|
|
val itemLimit = preferences[databaseItemLimitKey]
|
|
|
|
for (accountKey in getAccountKeys(context)) {
|
|
// Clean statuses.
|
|
for (uri in STATUSES_URIS) {
|
|
if (CachedStatuses.CONTENT_URI == uri) {
|
|
continue
|
|
}
|
|
val table = getTableNameByUri(uri)
|
|
val qb = SQLSelectQuery.Builder()
|
|
qb.select(Column(Statuses._ID))
|
|
.from(Tables(table))
|
|
.where(Expression.equalsArgs(Statuses.ACCOUNT_KEY))
|
|
.orderBy(OrderBy(Statuses.POSITION_KEY, false))
|
|
.limit(itemLimit)
|
|
val where = Expression.and(
|
|
Expression.notIn(Column(Statuses._ID), qb.build()),
|
|
Expression.equalsArgs(Statuses.ACCOUNT_KEY)
|
|
)
|
|
val whereArgs = arrayOf(accountKey.toString(), accountKey.toString())
|
|
resolver.delete(uri, where.sql, whereArgs)
|
|
}
|
|
for (uri in ACTIVITIES_URIS) {
|
|
val table = getTableNameByUri(uri)
|
|
val qb = SQLSelectQuery.Builder()
|
|
qb.select(Column(Activities._ID))
|
|
.from(Tables(table))
|
|
.where(Expression.equalsArgs(Activities.ACCOUNT_KEY))
|
|
.orderBy(OrderBy(Activities.TIMESTAMP, false))
|
|
.limit(itemLimit)
|
|
val where = Expression.and(
|
|
Expression.notIn(Column(Activities._ID), qb.build()),
|
|
Expression.equalsArgs(Activities.ACCOUNT_KEY)
|
|
)
|
|
val whereArgs = arrayOf(accountKey.toString(), accountKey.toString())
|
|
resolver.delete(uri, where.sql, whereArgs)
|
|
}
|
|
}
|
|
// Clean cached values.
|
|
for (uri in CACHE_URIS) {
|
|
val table = getTableNameByUri(uri) ?: continue
|
|
val qb = SQLSelectQuery.Builder()
|
|
qb.select(Column(BaseColumns._ID))
|
|
.from(Tables(table))
|
|
.orderBy(OrderBy(BaseColumns._ID, false))
|
|
.limit(itemLimit * 20)
|
|
val where = Expression.notIn(Column(BaseColumns._ID), qb.build())
|
|
resolver.delete(uri, where.sql, null)
|
|
}
|
|
}
|
|
|
|
fun isFilteringUser(context: Context, userKey: UserKey): Boolean {
|
|
return isFilteringUser(context, userKey.toString())
|
|
}
|
|
|
|
fun isFilteringUser(context: Context, userKey: String): Boolean {
|
|
val cr = context.contentResolver
|
|
val where = Expression.equalsArgs(Filters.Users.USER_KEY)
|
|
val c = cr.query(Filters.Users.CONTENT_URI, arrayOf(SQLFunctions.COUNT()),
|
|
where.sql, arrayOf(userKey), null) ?: return false
|
|
try {
|
|
if (c.moveToFirst()) {
|
|
return c.getLong(0) > 0
|
|
}
|
|
} finally {
|
|
c.close()
|
|
}
|
|
return false
|
|
}
|
|
|
|
private fun <T> getObjectFieldArray(context: Context, uri: Uri, keys: Array<UserKey?>,
|
|
keyField: String, valueFields: Array<String>, sortExpression: OrderBy?, extraWhere: Expression?,
|
|
extraWhereArgs: Array<String>?, createIndices: (Cursor) -> ObjectCursor.CursorIndices<T>,
|
|
createArray: (Int) -> Array<T?>): Array<T?> {
|
|
return getFieldsArray(context, uri, keys, keyField, valueFields, sortExpression,
|
|
extraWhere, extraWhereArgs, object : FieldArrayCreator<Array<T?>, ObjectCursor.CursorIndices<T>> {
|
|
override fun newArray(size: Int): Array<T?> {
|
|
return createArray(size)
|
|
}
|
|
|
|
override fun newIndex(cur: Cursor): ObjectCursor.CursorIndices<T> {
|
|
return createIndices(cur)
|
|
}
|
|
|
|
override fun assign(array: Array<T?>, arrayIdx: Int, cur: Cursor, colIdx: ObjectCursor.CursorIndices<T>) {
|
|
array[arrayIdx] = colIdx.newObject(cur)
|
|
}
|
|
})
|
|
}
|
|
|
|
private fun getStringFieldArray(context: Context, uri: Uri, keys: Array<UserKey?>,
|
|
keyField: String, valueField: String, sortExpression: OrderBy?, extraWhere: Expression?,
|
|
extraWhereArgs: Array<String>?): Array<String?> {
|
|
return getFieldsArray(context, uri, keys, keyField, arrayOf(valueField), sortExpression,
|
|
extraWhere, extraWhereArgs, object : FieldArrayCreator<Array<String?>, Int> {
|
|
override fun newArray(size: Int): Array<String?> {
|
|
return arrayOfNulls(size)
|
|
}
|
|
|
|
override fun newIndex(cur: Cursor): Int {
|
|
return cur.getColumnIndex(valueField)
|
|
}
|
|
|
|
override fun assign(array: Array<String?>, arrayIdx: Int, cur: Cursor, colIdx: Int) {
|
|
array[arrayIdx] = cur.getString(colIdx)
|
|
}
|
|
})
|
|
}
|
|
|
|
private fun getLongFieldArray(context: Context, uri: Uri, keys: Array<UserKey?>,
|
|
keyField: String, valueField: String, sortExpression: OrderBy?, extraWhere: Expression?,
|
|
extraWhereArgs: Array<String>?): LongArray {
|
|
return getFieldsArray(context, uri, keys, keyField, arrayOf(valueField), sortExpression,
|
|
extraWhere, extraWhereArgs, object : FieldArrayCreator<LongArray, Int> {
|
|
override fun newArray(size: Int): LongArray {
|
|
return LongArray(size)
|
|
}
|
|
|
|
override fun newIndex(cur: Cursor): Int {
|
|
return cur.getColumnIndex(valueField)
|
|
}
|
|
|
|
override fun assign(array: LongArray, arrayIdx: Int, cur: Cursor, colIdx: Int) {
|
|
array[arrayIdx] = cur.getLong(colIdx)
|
|
}
|
|
})
|
|
}
|
|
|
|
@SuppressLint("Recycle")
|
|
private fun <T, I> getFieldsArray(
|
|
context: Context, uri: Uri,
|
|
keys: Array<UserKey?>, keyField: String,
|
|
valueFields: Array<String>, sortExpression: OrderBy?,
|
|
extraWhere: Expression?, extraWhereArgs: Array<String>?,
|
|
creator: FieldArrayCreator<T, I>
|
|
): T {
|
|
val resolver = context.contentResolver
|
|
val resultArray = creator.newArray(keys.size)
|
|
val nonNullKeys = keys.mapNotNull { it?.toString() }.toTypedArray()
|
|
val tableName = getTableNameByUri(uri) ?: throw NullPointerException()
|
|
val having = Expression.inArgs(keyField, nonNullKeys.size)
|
|
val bindingArgs: Array<String>
|
|
if (extraWhereArgs != null) {
|
|
bindingArgs = extraWhereArgs + nonNullKeys
|
|
} else {
|
|
bindingArgs = nonNullKeys
|
|
}
|
|
val builder = SQLQueryBuilder.select(Columns(keyField, *valueFields))
|
|
builder.from(Table(tableName))
|
|
if (extraWhere != null) {
|
|
builder.where(extraWhere)
|
|
}
|
|
builder.groupBy(Column(keyField))
|
|
builder.having(having)
|
|
if (sortExpression != null) {
|
|
builder.orderBy(sortExpression)
|
|
}
|
|
resolver.rawQuery(builder.buildSQL(), bindingArgs)?.useCursor { cur ->
|
|
cur.moveToFirst()
|
|
val colIdx = creator.newIndex(cur)
|
|
while (!cur.isAfterLast) {
|
|
val keyString = cur.getString(cur.getColumnIndex(keyField))
|
|
if (keyString != null) {
|
|
val accountKey = UserKey.valueOf(keyString)
|
|
val arrayIdx = keys.indexOfFirst {
|
|
accountKey == it
|
|
}
|
|
if (arrayIdx >= 0) {
|
|
creator.assign(resultArray, arrayIdx, cur, colIdx)
|
|
}
|
|
}
|
|
cur.moveToNext()
|
|
}
|
|
}
|
|
return resultArray
|
|
}
|
|
|
|
fun deleteStatus(cr: ContentResolver, accountKey: UserKey,
|
|
statusId: String, status: ParcelableStatus?) {
|
|
|
|
val host = accountKey.host
|
|
val deleteWhere: String
|
|
val updateWhere: String
|
|
val deleteWhereArgs: Array<String>
|
|
val updateWhereArgs: Array<String>
|
|
if (host != null) {
|
|
deleteWhere = Expression.and(
|
|
Expression.likeRaw(Column(Statuses.ACCOUNT_KEY), "'%@'||?"),
|
|
Expression.or(
|
|
Expression.equalsArgs(Statuses.STATUS_ID),
|
|
Expression.equalsArgs(Statuses.RETWEET_ID)
|
|
)).sql
|
|
deleteWhereArgs = arrayOf(host, statusId, statusId)
|
|
updateWhere = Expression.and(
|
|
Expression.likeRaw(Column(Statuses.ACCOUNT_KEY), "'%@'||?"),
|
|
Expression.equalsArgs(Statuses.MY_RETWEET_ID)
|
|
).sql
|
|
updateWhereArgs = arrayOf(host, statusId)
|
|
} else {
|
|
deleteWhere = Expression.or(
|
|
Expression.equalsArgs(Statuses.STATUS_ID),
|
|
Expression.equalsArgs(Statuses.RETWEET_ID)
|
|
).sql
|
|
deleteWhereArgs = arrayOf(statusId, statusId)
|
|
updateWhere = Expression.equalsArgs(Statuses.MY_RETWEET_ID).sql
|
|
updateWhereArgs = arrayOf(statusId)
|
|
}
|
|
for (uri in STATUSES_URIS) {
|
|
cr.delete(uri, deleteWhere, deleteWhereArgs)
|
|
if (status != null) {
|
|
val values = ContentValues()
|
|
values.putNull(Statuses.MY_RETWEET_ID)
|
|
values.put(Statuses.RETWEET_COUNT, status.retweet_count - 1)
|
|
cr.update(uri, values, updateWhere, updateWhereArgs)
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
fun processTabExtras(expressions: MutableList<Expression>, expressionArgs: MutableList<String>, extras: HomeTabExtras) {
|
|
if (extras.isHideRetweets) {
|
|
expressions.add(Expression.equalsArgs(Statuses.IS_RETWEET))
|
|
expressionArgs.add("0")
|
|
}
|
|
if (extras.isHideQuotes) {
|
|
expressions.add(Expression.equalsArgs(Statuses.IS_QUOTE))
|
|
expressionArgs.add("0")
|
|
}
|
|
if (extras.isHideReplies) {
|
|
expressions.add(Expression.isNull(Column(Statuses.IN_REPLY_TO_STATUS_ID)))
|
|
}
|
|
}
|
|
|
|
fun prepareDatabase(context: Context) {
|
|
val cr = context.contentResolver
|
|
val cursor = cr.query(TwidereDataStore.CONTENT_URI_DATABASE_PREPARE, null, null,
|
|
null, null) ?: return
|
|
cursor.close()
|
|
}
|
|
|
|
internal interface FieldArrayCreator<T, I> {
|
|
fun newArray(size: Int): T
|
|
|
|
fun newIndex(cur: Cursor): I
|
|
|
|
fun assign(array: T, arrayIdx: Int, cur: Cursor, colIdx: I)
|
|
}
|
|
|
|
fun queryCount(cr: ContentResolver, uri: Uri, selection: String?, selectionArgs: Array<String>?): Int {
|
|
val projection = arrayOf(SQLFunctions.COUNT())
|
|
val cur = cr.query(uri, projection, selection, selectionArgs, null) ?: return -1
|
|
try {
|
|
if (cur.moveToFirst()) {
|
|
return cur.getInt(0)
|
|
}
|
|
return -1
|
|
} finally {
|
|
cur.close()
|
|
}
|
|
}
|
|
|
|
fun getInteractionsCount(context: Context, extraArgs: Bundle?,
|
|
accountIds: Array<UserKey>, since: Long, sinceColumn: String): Int {
|
|
var extraWhere: Expression? = null
|
|
var extraWhereArgs: Array<String>? = null
|
|
var followingOnly = false
|
|
if (extraArgs != null) {
|
|
val extras = extraArgs.getParcelable<TabExtras>(IntentConstants.EXTRA_EXTRAS)
|
|
if (extras is InteractionsTabExtras) {
|
|
if (extras.isMentionsOnly) {
|
|
extraWhere = Expression.inArgs(Activities.ACTION, 3)
|
|
extraWhereArgs = arrayOf(Activity.Action.MENTION, Activity.Action.REPLY, Activity.Action.QUOTE)
|
|
}
|
|
if (extras.isMyFollowingOnly) {
|
|
followingOnly = true
|
|
}
|
|
}
|
|
}
|
|
return getActivitiesCount(context, Activities.AboutMe.CONTENT_URI, extraWhere, extraWhereArgs,
|
|
since, sinceColumn, followingOnly, accountIds)
|
|
}
|
|
|
|
fun addToFilter(context: Context, users: Collection<ParcelableUser>, filterAnywhere: Boolean) {
|
|
val cr = context.contentResolver
|
|
|
|
try {
|
|
val baseCreator = ObjectCursor.valuesCreatorFrom(FiltersData.BaseItem::class.java)
|
|
val userCreator = ObjectCursor.valuesCreatorFrom(FiltersData.UserItem::class.java)
|
|
val userValues = ArrayList<ContentValues>()
|
|
val keywordValues = ArrayList<ContentValues>()
|
|
val linkValues = ArrayList<ContentValues>()
|
|
for (user in users) {
|
|
val userItem = FiltersData.UserItem()
|
|
userItem.userKey = user.key
|
|
userItem.screenName = user.screen_name
|
|
userItem.name = user.name
|
|
userValues.add(userCreator.create(userItem))
|
|
|
|
val keywordItem = FiltersData.BaseItem()
|
|
keywordItem.value = "@" + user.screen_name
|
|
keywordItem.userKey = user.key
|
|
keywordValues.add(baseCreator.create(keywordItem))
|
|
|
|
// Insert user link (without scheme) to links
|
|
val linkItem = FiltersData.BaseItem()
|
|
val userLink = LinkCreator.getUserWebLink(user)
|
|
val linkWithoutScheme = userLink.toString().substringAfter("://")
|
|
linkItem.value = linkWithoutScheme
|
|
linkItem.userKey = user.key
|
|
linkValues.add(baseCreator.create(linkItem))
|
|
}
|
|
|
|
ContentResolverUtils.bulkInsert(cr, Filters.Users.CONTENT_URI, userValues)
|
|
if (filterAnywhere) {
|
|
// Insert to filtered users
|
|
ContentResolverUtils.bulkInsert(cr, Filters.Keywords.CONTENT_URI, keywordValues)
|
|
// Insert user mention to keywords
|
|
ContentResolverUtils.bulkInsert(cr, Filters.Links.CONTENT_URI, linkValues)
|
|
}
|
|
} catch (e: IOException) {
|
|
throw RuntimeException(e)
|
|
}
|
|
|
|
}
|
|
|
|
fun removeFromFilter(context: Context, users: Collection<ParcelableUser>) {
|
|
val cr = context.contentResolver
|
|
// Delete from filtered users
|
|
val userKeyValues = users.map { it.key.toString() }
|
|
ContentResolverUtils.bulkDelete(cr, Filters.Users.CONTENT_URI, Filters.Users.USER_KEY,
|
|
false, userKeyValues, null, null)
|
|
ContentResolverUtils.bulkDelete(cr, Filters.Keywords.CONTENT_URI, Filters.Keywords.USER_KEY,
|
|
false, userKeyValues, null, null)
|
|
ContentResolverUtils.bulkDelete(cr, Filters.Links.CONTENT_URI, Filters.Links.USER_KEY,
|
|
false, userKeyValues, null, null)
|
|
}
|
|
|
|
@WorkerThread
|
|
fun findStatusInDatabases(context: Context,
|
|
accountKey: UserKey,
|
|
statusId: String): ParcelableStatus? {
|
|
val resolver = context.contentResolver
|
|
var status: ParcelableStatus? = null
|
|
val where = Expression.and(Expression.equalsArgs(Statuses.ACCOUNT_KEY),
|
|
Expression.equalsArgs(Statuses.STATUS_ID)).sql
|
|
val whereArgs = arrayOf(accountKey.toString(), statusId)
|
|
for (uri in DataStoreUtils.STATUSES_URIS) {
|
|
val cur = resolver.query(uri, Statuses.COLUMNS, where, whereArgs, null) ?: continue
|
|
try {
|
|
if (cur.moveToFirst()) {
|
|
val indices = ObjectCursor.indicesFrom(cur, ParcelableStatus::class.java)
|
|
status = indices.newObject(cur)
|
|
}
|
|
} finally {
|
|
cur.close()
|
|
}
|
|
}
|
|
return status
|
|
}
|
|
|
|
|
|
@WorkerThread
|
|
@Throws(MicroBlogException::class)
|
|
fun findStatus(context: Context, accountKey: UserKey, statusId: String): ParcelableStatus {
|
|
val cached = findStatusInDatabases(context, accountKey, statusId)
|
|
if (cached != null) return cached
|
|
val details = AccountUtils.getAccountDetails(AccountManager.get(context), accountKey,
|
|
true) ?: throw MicroBlogException("No account")
|
|
val microBlog = details.newMicroBlogInstance(context, MicroBlog::class.java)
|
|
val result = microBlog.showStatus(statusId)
|
|
val where = Expression.and(Expression.equalsArgs(Statuses.ACCOUNT_KEY),
|
|
Expression.equalsArgs(Statuses.STATUS_ID)).sql
|
|
val whereArgs = arrayOf(accountKey.toString(), statusId)
|
|
val resolver = context.contentResolver
|
|
val status = ParcelableStatusUtils.fromStatus(result, accountKey, details.type, false)
|
|
resolver.delete(CachedStatuses.CONTENT_URI, where, whereArgs)
|
|
resolver.insert(CachedStatuses.CONTENT_URI, ObjectCursor.valuesCreatorFrom(ParcelableStatus::class.java).create(status))
|
|
return status
|
|
}
|
|
|
|
@WorkerThread
|
|
fun findMessageConversation(context: Context, accountKey: UserKey, conversationId: String): ParcelableMessageConversation? {
|
|
val resolver = context.contentResolver
|
|
val where = Expression.and(Expression.equalsArgs(Conversations.ACCOUNT_KEY),
|
|
Expression.equalsArgs(Conversations.CONVERSATION_ID)).sql
|
|
val whereArgs = arrayOf(accountKey.toString(), conversationId)
|
|
val cur = resolver.query(Conversations.CONTENT_URI, Conversations.COLUMNS, where, whereArgs, null) ?: return null
|
|
try {
|
|
if (cur.moveToFirst()) {
|
|
val indices = ObjectCursor.indicesFrom(cur, ParcelableMessageConversation::class.java)
|
|
return indices.newObject(cur)
|
|
}
|
|
} finally {
|
|
cur.close()
|
|
}
|
|
return null
|
|
}
|
|
|
|
|
|
private fun getIdsWhere(official: Boolean): Pair<Expression?, Array<String>?> {
|
|
if (official) return Pair(null, null)
|
|
return Pair(Expression.inArgs(Activities.ACTION, Activity.Action.MENTION_ACTIONS.size)
|
|
, Activity.Action.MENTION_ACTIONS)
|
|
}
|
|
|
|
private fun <T> getOfficialSeparatedIds(context: Context, getFromDatabase: (Array<UserKey?>, Boolean) -> T,
|
|
mergeResult: (T, T) -> T, accountKeys: Array<UserKey?>): T {
|
|
val officialKeys = Array(accountKeys.size) {
|
|
val key = accountKeys[it]
|
|
if (Utils.isOfficialCredentials(context, key)) {
|
|
return@Array key
|
|
}
|
|
return@Array null
|
|
}
|
|
val notOfficialKeys = Array(accountKeys.size) {
|
|
val key = accountKeys[it]
|
|
if (Utils.isOfficialCredentials(context, key)) {
|
|
return@Array null
|
|
}
|
|
return@Array key
|
|
}
|
|
|
|
val officialMaxPositions = getFromDatabase(officialKeys, true)
|
|
val notOfficialMaxPositions = getFromDatabase(notOfficialKeys, false)
|
|
return mergeResult(officialMaxPositions, notOfficialMaxPositions)
|
|
}
|
|
}
|