Twidere-App-Android-Twitter.../twidere/src/main/kotlin/org/mariotaku/twidere/util/DataStoreUtils.kt

1018 lines
48 KiB
Kotlin
Raw Normal View History

2017-02-10 16:38:59 +01:00
/*
* 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.content.*
import android.database.Cursor
import android.net.Uri
import android.os.Bundle
import android.os.Parcelable
import android.provider.BaseColumns
2020-01-26 08:35:15 +01:00
import androidx.annotation.WorkerThread
2017-02-10 16:38:59 +01:00
import android.text.TextUtils
import org.mariotaku.kpreferences.get
2017-04-14 11:34:36 +02:00
import org.mariotaku.ktextension.mapToArray
import org.mariotaku.library.objectcursor.ObjectCursor
2017-03-27 15:56:03 +02:00
import org.mariotaku.microblog.library.MicroBlog
2017-02-10 16:38:59 +01:00
import org.mariotaku.microblog.library.MicroBlogException
2017-04-29 05:19:31 +02:00
import org.mariotaku.microblog.library.mastodon.Mastodon
2017-02-10 16:38:59 +01:00
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
2017-04-29 05:19:31 +02:00
import org.mariotaku.twidere.R
2017-02-10 16:38:59 +01:00
import org.mariotaku.twidere.TwidereConstants.*
2017-04-29 05:19:31 +02:00
import org.mariotaku.twidere.annotation.AccountType
2017-09-10 17:32:12 +02:00
import org.mariotaku.twidere.annotation.FilterScope
2017-02-10 16:38:59 +01:00
import org.mariotaku.twidere.constant.IntentConstants
import org.mariotaku.twidere.constant.databaseItemLimitKey
2017-09-13 11:52:09 +02:00
import org.mariotaku.twidere.constant.filterPossibilitySensitiveStatusesKey
import org.mariotaku.twidere.constant.filterUnavailableQuoteStatusesKey
2017-03-27 15:56:03 +02:00
import org.mariotaku.twidere.extension.model.*
2017-04-29 05:19:31 +02:00
import org.mariotaku.twidere.extension.model.api.mastodon.toParcelable
2017-04-19 08:50:47 +02:00
import org.mariotaku.twidere.extension.model.api.toParcelable
2017-08-27 18:37:40 +02:00
import org.mariotaku.twidere.extension.queryCount
import org.mariotaku.twidere.extension.queryOne
import org.mariotaku.twidere.extension.queryReference
import org.mariotaku.twidere.extension.rawQueryReference
2017-02-10 16:38:59 +01:00
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.provider.TwidereDataStore.*
2017-02-12 15:26:09 +01:00
import org.mariotaku.twidere.provider.TwidereDataStore.Messages.Conversations
2017-02-10 16:38:59 +01:00
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)
2017-04-29 18:34:26 +02:00
val CACHE_URIS = arrayOf(CachedUsers.CONTENT_URI, CachedStatuses.CONTENT_URI,
CachedHashtags.CONTENT_URI, CachedTrends.Local.CONTENT_URI)
2017-02-12 15:26:09 +01:00
val MESSAGES_URIS = arrayOf(Messages.CONTENT_URI, Conversations.CONTENT_URI)
2017-02-10 16:38:59 +01:00
val ACTIVITIES_URIS = arrayOf(Activities.AboutMe.CONTENT_URI)
2017-04-29 18:34:26 +02:00
val STATUSES_ACTIVITIES_URIS = arrayOf(Statuses.CONTENT_URI, CachedStatuses.CONTENT_URI,
Activities.AboutMe.CONTENT_URI)
2017-02-10 16:38:59 +01:00
private val CONTENT_PROVIDER_URI_MATCHER = UriMatcher(UriMatcher.NO_MATCH)
init {
2020-06-08 23:01:17 +02:00
CONTENT_PROVIDER_URI_MATCHER.addURI(AUTHORITY, Statuses.CONTENT_PATH,
2017-02-10 16:38:59 +01:00
TABLE_ID_STATUSES)
2020-06-08 23:01:17 +02:00
CONTENT_PROVIDER_URI_MATCHER.addURI(AUTHORITY, Activities.AboutMe.CONTENT_PATH,
2017-02-10 16:38:59 +01:00
TABLE_ID_ACTIVITIES_ABOUT_ME)
2020-06-08 23:01:17 +02:00
CONTENT_PROVIDER_URI_MATCHER.addURI(AUTHORITY, Drafts.CONTENT_PATH,
2017-02-10 16:38:59 +01:00
TABLE_ID_DRAFTS)
2020-06-08 23:01:17 +02:00
CONTENT_PROVIDER_URI_MATCHER.addURI(AUTHORITY, CachedUsers.CONTENT_PATH,
2017-02-10 16:38:59 +01:00
TABLE_ID_CACHED_USERS)
2020-06-08 23:01:17 +02:00
CONTENT_PROVIDER_URI_MATCHER.addURI(AUTHORITY, Filters.Users.CONTENT_PATH,
2017-02-10 16:38:59 +01:00
TABLE_ID_FILTERED_USERS)
2020-06-08 23:01:17 +02:00
CONTENT_PROVIDER_URI_MATCHER.addURI(AUTHORITY, Filters.Keywords.CONTENT_PATH,
2017-02-10 16:38:59 +01:00
TABLE_ID_FILTERED_KEYWORDS)
2020-06-08 23:01:17 +02:00
CONTENT_PROVIDER_URI_MATCHER.addURI(AUTHORITY, Filters.Sources.CONTENT_PATH,
2017-02-10 16:38:59 +01:00
TABLE_ID_FILTERED_SOURCES)
2020-06-08 23:01:17 +02:00
CONTENT_PROVIDER_URI_MATCHER.addURI(AUTHORITY, Filters.Links.CONTENT_PATH,
2017-02-10 16:38:59 +01:00
TABLE_ID_FILTERED_LINKS)
2020-06-08 23:01:17 +02:00
CONTENT_PROVIDER_URI_MATCHER.addURI(AUTHORITY, Filters.Subscriptions.CONTENT_PATH,
2017-02-10 16:38:59 +01:00
TABLE_ID_FILTERS_SUBSCRIPTIONS)
2020-06-08 23:01:17 +02:00
CONTENT_PROVIDER_URI_MATCHER.addURI(AUTHORITY, Messages.CONTENT_PATH,
2017-02-10 16:38:59 +01:00
TABLE_ID_MESSAGES)
2020-06-08 23:01:17 +02:00
CONTENT_PROVIDER_URI_MATCHER.addURI(AUTHORITY, Conversations.CONTENT_PATH,
2017-02-10 16:38:59 +01:00
TABLE_ID_MESSAGES_CONVERSATIONS)
2020-06-08 23:01:17 +02:00
CONTENT_PROVIDER_URI_MATCHER.addURI(AUTHORITY, CachedTrends.Local.CONTENT_PATH,
2017-02-10 16:38:59 +01:00
TABLE_ID_TRENDS_LOCAL)
2020-06-08 23:01:17 +02:00
CONTENT_PROVIDER_URI_MATCHER.addURI(AUTHORITY, Tabs.CONTENT_PATH,
2017-02-10 16:38:59 +01:00
TABLE_ID_TABS)
2020-06-08 23:01:17 +02:00
CONTENT_PROVIDER_URI_MATCHER.addURI(AUTHORITY, CachedStatuses.CONTENT_PATH,
2017-02-10 16:38:59 +01:00
TABLE_ID_CACHED_STATUSES)
2020-06-08 23:01:17 +02:00
CONTENT_PROVIDER_URI_MATCHER.addURI(AUTHORITY, CachedHashtags.CONTENT_PATH,
2017-02-10 16:38:59 +01:00
TABLE_ID_CACHED_HASHTAGS)
2020-06-08 23:01:17 +02:00
CONTENT_PROVIDER_URI_MATCHER.addURI(AUTHORITY, CachedRelationships.CONTENT_PATH,
2017-02-10 16:38:59 +01:00
TABLE_ID_CACHED_RELATIONSHIPS)
2020-06-08 23:01:17 +02:00
CONTENT_PROVIDER_URI_MATCHER.addURI(AUTHORITY, SavedSearches.CONTENT_PATH,
2017-02-10 16:38:59 +01:00
TABLE_ID_SAVED_SEARCHES)
2020-06-08 23:01:17 +02:00
CONTENT_PROVIDER_URI_MATCHER.addURI(AUTHORITY, SearchHistory.CONTENT_PATH,
2017-02-10 16:38:59 +01:00
TABLE_ID_SEARCH_HISTORY)
2020-06-08 23:01:17 +02:00
CONTENT_PROVIDER_URI_MATCHER.addURI(AUTHORITY, Permissions.CONTENT_PATH,
2017-02-10 16:38:59 +01:00
VIRTUAL_TABLE_ID_PERMISSIONS)
2020-06-08 23:01:17 +02:00
CONTENT_PROVIDER_URI_MATCHER.addURI(AUTHORITY, CachedUsers.CONTENT_PATH_WITH_RELATIONSHIP + "/*",
2017-02-10 16:38:59 +01:00
VIRTUAL_TABLE_ID_CACHED_USERS_WITH_RELATIONSHIP)
2020-06-08 23:01:17 +02:00
CONTENT_PROVIDER_URI_MATCHER.addURI(AUTHORITY, CachedUsers.CONTENT_PATH_WITH_SCORE + "/*",
2017-02-10 16:38:59 +01:00
VIRTUAL_TABLE_ID_CACHED_USERS_WITH_SCORE)
2020-06-08 23:01:17 +02:00
CONTENT_PROVIDER_URI_MATCHER.addURI(AUTHORITY, Drafts.CONTENT_PATH_UNSENT,
2017-02-10 16:38:59 +01:00
VIRTUAL_TABLE_ID_DRAFTS_UNSENT)
2020-06-08 23:01:17 +02:00
CONTENT_PROVIDER_URI_MATCHER.addURI(AUTHORITY, Drafts.CONTENT_PATH_NOTIFICATIONS,
2017-02-10 16:38:59 +01:00
VIRTUAL_TABLE_ID_DRAFTS_NOTIFICATIONS)
2020-06-08 23:01:17 +02:00
CONTENT_PROVIDER_URI_MATCHER.addURI(AUTHORITY, Drafts.CONTENT_PATH_NOTIFICATIONS + "/#",
2017-02-10 16:38:59 +01:00
VIRTUAL_TABLE_ID_DRAFTS_NOTIFICATIONS)
2020-06-08 23:01:17 +02:00
CONTENT_PROVIDER_URI_MATCHER.addURI(AUTHORITY, Suggestions.AutoComplete.CONTENT_PATH,
2017-02-10 16:38:59 +01:00
VIRTUAL_TABLE_ID_SUGGESTIONS_AUTO_COMPLETE)
2020-06-08 23:01:17 +02:00
CONTENT_PROVIDER_URI_MATCHER.addURI(AUTHORITY, Suggestions.Search.CONTENT_PATH,
2017-02-10 16:38:59 +01:00
VIRTUAL_TABLE_ID_SUGGESTIONS_SEARCH)
2020-06-08 23:01:17 +02:00
CONTENT_PROVIDER_URI_MATCHER.addURI(AUTHORITY, CONTENT_PATH_DATABASE_PREPARE,
2017-02-10 16:38:59 +01:00
VIRTUAL_TABLE_ID_DATABASE_PREPARE)
2020-06-08 23:01:17 +02:00
CONTENT_PROVIDER_URI_MATCHER.addURI(AUTHORITY, CONTENT_PATH_NULL,
2017-02-10 16:38:59 +01:00
VIRTUAL_TABLE_ID_NULL)
2020-06-08 23:01:17 +02:00
CONTENT_PROVIDER_URI_MATCHER.addURI(AUTHORITY, CONTENT_PATH_EMPTY,
2017-02-10 16:38:59 +01:00
VIRTUAL_TABLE_ID_EMPTY)
2020-06-08 23:07:20 +02:00
CONTENT_PROVIDER_URI_MATCHER.addURI(AUTHORITY, "$CONTENT_PATH_RAW_QUERY/*",
2017-02-10 16:38:59 +01:00
VIRTUAL_TABLE_ID_RAW_QUERY)
}
fun getNewestStatusIds(context: Context, uri: Uri, accountKeys: Array<UserKey?>): Array<String?> {
2017-04-28 15:44:45 +02:00
return getStringFieldArray(context, uri, accountKeys, Statuses.ACCOUNT_KEY, Statuses.ID,
OrderBy(SQLFunctions.MAX(Statuses.TIMESTAMP)), null, null)
2017-02-10 16:38:59 +01:00
}
2017-02-10 18:03:40 +01:00
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)
2017-02-10 16:38:59 +01:00
return getStringFieldArray(context, uri, accountKeys, Messages.ACCOUNT_KEY, Messages.MESSAGE_ID,
OrderBy(SQLFunctions.MAX(Messages.LOCAL_TIMESTAMP)), having, null)
}
2017-02-10 19:01:31 +01:00
fun getOldestMessageIds(context: Context, uri: Uri, accountKeys: Array<UserKey?>, outgoing: Boolean): Array<String?> {
2017-02-17 11:42:39 +01:00
if (accountKeys.all { it == null }) return arrayOfNulls(accountKeys.size)
2017-02-10 19:01:31 +01:00
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?> {
2017-02-17 11:42:39 +01:00
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,
2017-03-05 09:08:09 +01:00
{ ObjectCursor.indicesFrom(it, ParcelableMessageConversation::class.java) },
{ arrayOfNulls<ParcelableMessageConversation>(it) })
}
2017-02-14 17:26:48 +01:00
fun getNewestConversations(context: Context, uri: Uri, accountKeys: Array<UserKey?>,
extraWhere: Expression? = null, extraWhereArgs: Array<String>? = null): Array<ParcelableMessageConversation?> {
2017-02-17 11:42:39 +01:00
if (accountKeys.all { it == null }) return arrayOfNulls(accountKeys.size)
return getObjectFieldArray(context, uri, accountKeys, Conversations.ACCOUNT_KEY, Conversations.COLUMNS,
2017-02-14 17:26:48 +01:00
OrderBy(SQLFunctions.MAX(Messages.LOCAL_TIMESTAMP)), extraWhere, extraWhereArgs,
2017-03-05 09:08:09 +01:00
{ ObjectCursor.indicesFrom(it, ParcelableMessageConversation::class.java) },
{ arrayOfNulls<ParcelableMessageConversation>(it) })
}
2017-02-10 16:38:59 +01:00
fun getNewestStatusSortIds(context: Context, uri: Uri, accountKeys: Array<UserKey?>): LongArray {
return getLongFieldArray(context, uri, accountKeys, Statuses.ACCOUNT_KEY, Statuses.SORT_ID,
2017-04-28 15:44:45 +02:00
OrderBy(SQLFunctions.MAX(Statuses.TIMESTAMP)), null, null)
2017-02-10 16:38:59 +01:00
}
fun getOldestStatusIds(context: Context, uri: Uri, accountKeys: Array<UserKey?>): Array<String?> {
2017-04-28 15:44:45 +02:00
return getStringFieldArray(context, uri, accountKeys, Statuses.ACCOUNT_KEY, Statuses.ID,
OrderBy(SQLFunctions.MIN(Statuses.TIMESTAMP)), null, null)
2017-02-10 16:38:59 +01:00
}
fun getOldestStatusSortIds(context: Context, uri: Uri, accountKeys: Array<UserKey?>): LongArray {
return getLongFieldArray(context, uri, accountKeys, Statuses.ACCOUNT_KEY, Statuses.SORT_ID,
2017-04-28 15:44:45 +02:00
OrderBy(SQLFunctions.MIN(Statuses.TIMESTAMP)), null, null)
2017-02-10 16:38:59 +01:00
}
fun getNewestActivityMaxPositions(context: Context, uri: Uri, accountKeys: Array<UserKey?>,
extraWhere: Expression?, extraWhereArgs: Array<String>?): Array<String?> {
2017-02-10 16:38:59 +01:00
return getStringFieldArray(context, uri, accountKeys, Activities.ACCOUNT_KEY,
Activities.MAX_REQUEST_POSITION, OrderBy(SQLFunctions.MAX(Activities.TIMESTAMP)),
extraWhere, extraWhereArgs)
2017-02-10 16:38:59 +01:00
}
fun getRefreshNewestActivityMaxPositions(context: Context, uri: Uri, accountKeys: Array<UserKey?>):
Array<String?> {
2017-08-28 06:31:19 +02:00
return getOfficialSeparatedIds(context, { keys, isOfficial ->
val (where, whereArgs) = getIdsWhere(isOfficial)
2020-06-08 23:01:17 +02:00
getNewestActivityMaxPositions(context, uri, keys, where, whereArgs)
}, { arr1, arr2 ->
2020-02-12 06:04:02 +01:00
Array(accountKeys.size) { arr1.elementAtOrNull(it) ?: arr2.elementAtOrNull(it) }
}, accountKeys)
}
fun getOldestActivityMaxPositions(context: Context, uri: Uri, accountKeys: Array<UserKey?>,
extraWhere: Expression?, extraWhereArgs: Array<String>?): Array<String?> {
2017-02-10 16:38:59 +01:00
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?> {
2017-08-28 06:31:19 +02:00
return getOfficialSeparatedIds(context, { keys, isOfficial ->
val (where, whereArgs) = getIdsWhere(isOfficial)
2020-06-08 23:01:17 +02:00
getOldestActivityMaxPositions(context, uri, keys, where, whereArgs)
}, { arr1, arr2 ->
2020-02-12 06:04:02 +01:00
Array(accountKeys.size) { arr1.elementAtOrNull(it) ?: arr2.elementAtOrNull(it) }
}, accountKeys)
2017-02-10 16:38:59 +01:00
}
fun getNewestActivityMaxSortPositions(context: Context, uri: Uri, accountKeys: Array<UserKey?>,
extraWhere: Expression?, extraWhereArgs: Array<String>?): LongArray {
2017-02-10 16:38:59 +01:00
return getLongFieldArray(context, uri, accountKeys, Activities.ACCOUNT_KEY,
Activities.MAX_SORT_POSITION, OrderBy(SQLFunctions.MAX(Activities.TIMESTAMP)),
extraWhere, extraWhereArgs)
2017-02-10 16:38:59 +01:00
}
fun getRefreshNewestActivityMaxSortPositions(context: Context, uri: Uri, accountKeys: Array<UserKey?>):
LongArray {
2017-08-28 06:31:19 +02:00
return getOfficialSeparatedIds(context, { keys, isOfficial ->
val (where, whereArgs) = getIdsWhere(isOfficial)
2020-06-08 23:01:17 +02:00
getNewestActivityMaxSortPositions(context, uri, keys, where, whereArgs)
}, { arr1, arr2 ->
LongArray(accountKeys.size) { array -> arr1.elementAtOrNull(array)?.takeIf { it > 0 } ?: arr2.elementAtOrNull(array) ?: 0 }
}, accountKeys)
}
fun getOldestActivityMaxSortPositions(context: Context, uri: Uri, accountKeys: Array<UserKey?>,
extraWhere: Expression?, extraWhereArgs: Array<String>?): LongArray {
2017-02-10 16:38:59 +01:00
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 {
2017-08-28 06:31:19 +02:00
return getOfficialSeparatedIds(context, { keys, isOfficial ->
val (where, whereArgs) = getIdsWhere(isOfficial)
2020-06-08 23:01:17 +02:00
getOldestActivityMaxSortPositions(context, uri, keys, where, whereArgs)
}, { arr1, arr2 ->
LongArray(accountKeys.size) { array -> arr1.elementAtOrNull(array)?.takeIf { it > 0 } ?: arr2.elementAtOrNull(array) ?: 0 }
}, accountKeys)
2017-02-10 16:38:59 +01:00
}
2017-04-12 14:58:08 +02:00
fun getStatusCount(context: Context, uri: Uri, accountKey: UserKey): Int {
2017-02-10 16:38:59 +01:00
val where = Expression.equalsArgs(AccountSupportColumns.ACCOUNT_KEY).sql
2017-04-12 14:58:08 +02:00
val whereArgs = arrayOf(accountKey.toString())
2017-08-27 18:37:40 +02:00
return context.contentResolver.queryCount(uri, where, whereArgs)
2017-02-10 16:38:59 +01:00
}
fun getActivitiesCount(context: Context, uri: Uri,
2017-02-12 15:26:09 +01:00
accountKey: UserKey): Int {
2017-02-10 16:38:59 +01:00
val where = Expression.equalsArgs(AccountSupportColumns.ACCOUNT_KEY).sql
2017-08-27 18:37:40 +02:00
return context.contentResolver.queryCount(uri, where, arrayOf(accountKey.toString()))
2017-02-10 16:38:59 +01:00
}
2017-09-15 09:18:33 +02:00
fun getFilteredUserKeys(context: Context, @FilterScope scope: Int): Array<UserKey> {
2017-02-10 16:38:59 +01:00
val resolver = context.contentResolver
val projection = arrayOf(Filters.Users.USER_KEY)
2017-09-15 09:18:33 +02:00
val where = Expression.or(
Expression.equals("${Filters.Users.SCOPE} & ${FilterScope.MASK_SCOPE}", 0),
Expression.notEquals("${Filters.Users.SCOPE} & $scope", 0)
)
return resolver.queryReference(Filters.Users.CONTENT_URI, projection, where.sql,
2017-09-16 16:36:21 +02:00
null, null)?.use { (cur) ->
2017-08-27 18:37:40 +02:00
return@use Array(cur.count) { i ->
2017-02-10 16:38:59 +01:00
cur.moveToPosition(i)
UserKey.valueOf(cur.getString(0))
}
2017-09-16 16:36:21 +02:00
} ?: emptyArray()
2017-02-10 16:38:59 +01:00
}
2017-09-15 09:18:33 +02:00
fun getFilteredKeywords(context: Context, @FilterScope scope: Int): Array<String> {
val resolver = context.contentResolver
val projection = arrayOf(Filters.VALUE)
val where = Expression.or(
Expression.equals("${Filters.SCOPE} & ${FilterScope.MASK_SCOPE}", 0),
Expression.notEquals("${Filters.SCOPE} & $scope", 0)
)
return resolver.queryReference(Filters.Keywords.CONTENT_URI, projection, where.sql,
2017-09-16 16:36:21 +02:00
null, null)?.use { (cur) ->
2017-09-15 09:18:33 +02:00
return@use Array(cur.count) { i ->
cur.moveToPosition(i)
cur.getString(0)
}
2017-09-16 16:36:21 +02:00
} ?: emptyArray()
2017-09-15 09:18:33 +02:00
}
2017-02-10 16:38:59 +01:00
fun getAccountDisplayName(context: Context, accountKey: UserKey, nameFirst: Boolean): String? {
2020-06-09 01:42:44 +02:00
return if (nameFirst) {
2020-06-08 23:19:10 +02:00
getAccountName(context, accountKey)
2017-02-10 16:38:59 +01:00
} else {
2020-06-08 23:19:10 +02:00
"@${getAccountScreenName(context, accountKey)}"
2017-02-10 16:38:59 +01:00
}
}
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()
}
2017-03-27 15:56:03 +02:00
fun getStatusesCount(context: Context, preferences: SharedPreferences, uri: Uri,
2017-04-10 11:35:35 +02:00
extraArgs: Bundle?, compareColumn: String, compare: Long, greaterThan: Boolean,
2017-09-13 11:52:09 +02:00
accountKeys: Array<UserKey>?, @FilterScope filterScopes: Int): Int {
2017-02-10 16:38:59 +01:00
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) {
2017-04-10 11:35:35 +02:00
expressions.add(Expression.greaterThan(compareColumn, compare))
2017-02-10 16:38:59 +01:00
} else {
2017-04-10 11:35:35 +02:00
expressions.add(Expression.lesserThan(compareColumn, compare))
2017-02-10 16:38:59 +01:00
}
2017-09-10 17:32:12 +02:00
expressions.add(buildStatusFilterWhereClause(preferences, getTableNameByUri(uri)!!,
null, filterScopes))
2017-02-10 16:38:59 +01:00
if (extraArgs != null) {
val extras = extraArgs.getParcelable<Parcelable>(EXTRA_EXTRAS)
if (extras is HomeTabExtras) {
processTabExtras(expressions, expressionArgs, extras)
}
}
val selection = Expression.and(*expressions.toTypedArray())
2017-08-27 18:37:40 +02:00
return context.contentResolver.queryCount(uri, selection.sql, expressionArgs.toTypedArray())
2017-02-10 16:38:59 +01:00
}
2017-09-13 11:52:09 +02:00
fun getActivitiesCount(context: Context, preferences: SharedPreferences, uri: Uri,
compareColumn: String, compare: Long, greaterThan: Boolean,
accountKeys: Array<UserKey>?, @FilterScope filterScopes: Int): Int {
2017-02-10 16:38:59 +01:00
val keys = accountKeys ?: getActivatedAccountKeys(context)
val selection = Expression.and(
Expression.inArgs(Column(Activities.ACCOUNT_KEY), keys.size),
2017-04-10 11:35:35 +02:00
if (greaterThan) {
Expression.greaterThan(compareColumn, compare)
} else {
Expression.lesserThan(compareColumn, compare)
},
2017-09-13 11:52:09 +02:00
buildStatusFilterWhereClause(preferences, getTableNameByUri(uri)!!, null,
2017-09-10 17:32:12 +02:00
filterScopes)
2017-02-10 16:38:59 +01:00
)
val whereArgs = arrayListOf<String>()
keys.mapTo(whereArgs) { it.toString() }
2017-08-27 18:37:40 +02:00
return context.contentResolver.queryCount(uri, selection.sql, whereArgs.toTypedArray())
2017-02-10 16:38:59 +01:00
}
2017-09-13 11:52:09 +02:00
fun getActivitiesCount(context: Context, preferences: SharedPreferences, uri: Uri,
2017-02-12 15:26:09 +01:00
extraWhere: Expression?, extraWhereArgs: Array<String>?,
2017-09-13 11:52:09 +02:00
sinceColumn: String, since: Long, followingOnly: Boolean, accountKeys: Array<UserKey>?,
@FilterScope filterScopes: Int): Int {
2017-04-14 11:34:36 +02:00
val keys = (accountKeys ?: getActivatedAccountKeys(context)).mapToArray { it.toString() }
2017-02-10 16:38:59 +01:00
val expressions = ArrayList<Expression>()
expressions.add(Expression.inArgs(Column(Activities.ACCOUNT_KEY), keys.size))
2017-04-10 11:35:35 +02:00
expressions.add(Expression.greaterThan(sinceColumn, since))
2017-09-13 11:52:09 +02:00
expressions.add(buildStatusFilterWhereClause(preferences, getTableNameByUri(uri)!!, null,
2017-09-10 17:32:12 +02:00
filterScopes))
2017-02-10 16:38:59 +01:00
if (extraWhere != null) {
expressions.add(extraWhere)
}
val selection = Expression.and(*expressions.toTypedArray())
2017-04-10 11:35:35 +02:00
var selectionArgs = keys
2017-02-10 16:38:59 +01:00
if (extraWhereArgs != null) {
2017-04-10 11:35:35 +02:00
selectionArgs += extraWhereArgs
2017-02-10 16:38:59 +01:00
}
// If followingOnly option is on, we have to iterate over items
val resolver = context.contentResolver
2017-02-10 16:38:59 +01:00
if (followingOnly) {
val projection = arrayOf(Activities.SOURCES)
2017-09-16 16:36:21 +02:00
return resolver.queryReference(uri, projection, selection.sql, selectionArgs, null)?.use { (cur) ->
2017-02-10 16:38:59 +01:00
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)) {
2017-02-10 16:38:59 +01:00
if (state.is_following) {
hasFollowing = true
break
}
}
} catch (e: IOException) {
continue
}
if (hasFollowing) {
total++
}
cur.moveToNext()
}
2017-08-27 18:37:40 +02:00
return@use total
2017-09-16 16:36:21 +02:00
} ?: 0
2017-02-10 16:38:59 +01:00
}
2017-08-27 18:37:40 +02:00
return resolver.queryCount(uri, selection.sql, selectionArgs)
2017-02-10 16:38:59 +01:00
}
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
2017-02-12 15:26:09 +01:00
TABLE_ID_MESSAGES_CONVERSATIONS -> return Conversations.TABLE_NAME
2017-02-10 16:38:59 +01:00
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))
}
2017-09-13 11:52:09 +02:00
fun buildStatusFilterWhereClause(preferences: SharedPreferences, table: String,
extraSelection: Expression?, @FilterScope filterScopes: Int): Expression {
fun ScopeMatchesExpression(scopeTable: String, scopeField: String) = Expression.or(
Expression.equals("$scopeTable.$scopeField & ${FilterScope.MASK_SCOPE}", 0),
Expression.notEquals("$scopeTable.$scopeField & $filterScopes", 0)
)
fun ContainsExpression(dataField: String, filterTable: String, filterField: String) =
Expression.likeRaw(Column(Table(table), dataField), "'%'||$filterTable.$filterField||'%'")
2017-09-16 13:11:27 +02:00
fun LineContainsExpression(dataField: String, filterTable: String, filterField: String) =
Expression.likeRaw(Column(Table(table), dataField), "'\\%'||$filterTable.$filterField||'%\\'")
fun LineMatchExpression(dataField: String, filterTable: String, filterField: String) =
Expression.likeRaw(Column(Table(table), dataField), "'%\\'||$filterTable.$filterField||'\\%'")
2017-09-13 11:52:09 +02:00
val filteredUsersWhere = Expression.and(
ScopeMatchesExpression(Filters.Users.TABLE_NAME, Filters.Users.SCOPE),
2017-09-16 13:11:27 +02:00
LineMatchExpression(Statuses.FILTER_USERS, Filters.Users.TABLE_NAME, Filters.Users.USER_KEY)
2017-02-10 16:38:59 +01:00
)
2017-09-10 17:32:12 +02:00
val filteredSourcesWhere = Expression.and(
2017-09-13 11:52:09 +02:00
ScopeMatchesExpression(Filters.Sources.TABLE_NAME, Filters.Sources.SCOPE),
2017-09-16 13:11:27 +02:00
LineMatchExpression(Statuses.FILTER_SOURCES, Filters.Sources.TABLE_NAME, Filters.Sources.VALUE)
2017-09-10 17:32:12 +02:00
)
2017-09-13 11:52:09 +02:00
val filteredTextKeywordsWhere = Expression.or(
Expression.and(
Expression.or(
2017-09-15 08:11:59 +02:00
Expression.equals("${Filters.Keywords.TABLE_NAME}.${Filters.Keywords.SCOPE} & ${FilterScope.MASK_TARGET}", 0),
Expression.notEquals("${Filters.Keywords.TABLE_NAME}.${Filters.Keywords.SCOPE} & ${FilterScope.TARGET_TEXT}", 0)
2017-09-13 11:52:09 +02:00
),
ScopeMatchesExpression(Filters.Keywords.TABLE_NAME, Filters.Keywords.SCOPE),
ContainsExpression(Statuses.FILTER_TEXTS, Filters.Keywords.TABLE_NAME, Filters.Keywords.VALUE)
2017-09-10 17:32:12 +02:00
),
2017-09-13 11:52:09 +02:00
Expression.and(
2017-09-15 08:11:59 +02:00
Expression.notEquals("${Filters.Keywords.TABLE_NAME}.${Filters.Keywords.SCOPE} & ${FilterScope.TARGET_NAME}", 0),
2017-09-13 11:52:09 +02:00
ScopeMatchesExpression(Filters.Keywords.TABLE_NAME, Filters.Keywords.SCOPE),
2017-09-16 13:11:27 +02:00
LineMatchExpression(Statuses.FILTER_NAMES, Filters.Keywords.TABLE_NAME, Filters.Keywords.VALUE)
2017-09-14 17:38:31 +02:00
),
Expression.and(
2017-09-15 08:11:59 +02:00
Expression.notEquals("${Filters.Keywords.TABLE_NAME}.${Filters.Keywords.SCOPE} & ${FilterScope.TARGET_DESCRIPTION}", 0),
2017-09-14 17:38:31 +02:00
ScopeMatchesExpression(Filters.Keywords.TABLE_NAME, Filters.Keywords.SCOPE),
ContainsExpression(Statuses.FILTER_DESCRIPTIONS, Filters.Keywords.TABLE_NAME, Filters.Keywords.VALUE)
2017-09-10 17:32:12 +02:00
)
)
val filteredLinksWhere = Expression.and(
2017-09-13 11:52:09 +02:00
ScopeMatchesExpression(Filters.Links.TABLE_NAME, Filters.Links.SCOPE),
2017-09-16 13:11:27 +02:00
LineContainsExpression(Statuses.FILTER_LINKS, Filters.Links.TABLE_NAME, Filters.Links.VALUE)
2017-09-10 17:32:12 +02:00
)
2017-02-10 16:38:59 +01:00
val filteredIdsQueryBuilder = SQLQueryBuilder
2017-09-13 11:52:09 +02:00
.select(Column(Table(table), Statuses._ID))
.from(Tables(table, Filters.Users.TABLE_NAME))
2017-02-10 16:38:59 +01:00
.where(filteredUsersWhere)
.union()
2017-09-13 11:52:09 +02:00
.select(Columns(Column(Table(table), Statuses._ID)))
2017-02-10 16:38:59 +01:00
.from(Tables(table, Filters.Sources.TABLE_NAME))
2017-09-10 17:32:12 +02:00
.where(filteredSourcesWhere)
2017-02-10 16:38:59 +01:00
.union()
2017-09-13 11:52:09 +02:00
.select(Columns(Column(Table(table), Statuses._ID)))
2017-02-10 16:38:59 +01:00
.from(Tables(table, Filters.Keywords.TABLE_NAME))
2017-09-13 11:52:09 +02:00
.where(filteredTextKeywordsWhere)
2017-02-10 16:38:59 +01:00
.union()
2017-09-13 11:52:09 +02:00
.select(Columns(Column(Table(table), Statuses._ID)))
2017-02-10 16:38:59 +01:00
.from(Tables(table, Filters.Links.TABLE_NAME))
2017-09-10 17:32:12 +02:00
.where(filteredLinksWhere)
2017-09-13 11:52:09 +02:00
var filterFlags: Long = 0
if (preferences[filterUnavailableQuoteStatusesKey]) {
filterFlags = filterFlags or ParcelableStatus.FilterFlags.QUOTE_NOT_AVAILABLE
}
if (preferences[filterPossibilitySensitiveStatusesKey]) {
filterFlags = filterFlags or ParcelableStatus.FilterFlags.POSSIBLY_SENSITIVE
}
2017-02-10 16:38:59 +01:00
val filterExpression = Expression.or(
2017-09-13 11:52:09 +02:00
Expression.and(
Expression.equals("${Statuses.FILTER_FLAGS} & $filterFlags", 0),
Expression.notIn(Column(Table(table), Statuses._ID), filteredIdsQueryBuilder.build())
),
Expression.equals(Column(Table(table), Statuses.IS_GAP), 1)
2017-02-10 16:38:59 +01:00
)
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)
2017-04-07 06:31:30 +02:00
if (screenName.equals(user.screen_name, ignoreCase = true)) {
2017-02-10 16:38:59 +01:00
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()
}
2017-08-27 18:37:40 +02:00
@Synchronized
fun cleanDatabasesByItemLimit(context: Context) {
2017-02-10 16:38:59 +01:00
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)
2017-08-27 18:37:40 +02:00
return cr.queryCount(Filters.Users.CONTENT_URI, where.sql, arrayOf(userKey)) > 0
2017-02-10 16:38:59 +01:00
}
private fun <T> getObjectFieldArray(context: Context, uri: Uri, keys: Array<UserKey?>,
2017-02-14 17:26:48 +01:00
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,
2017-02-14 17:26:48 +01:00
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?>,
2017-02-14 17:26:48 +01:00
keyField: String, valueField: String, sortExpression: OrderBy?, extraWhere: Expression?,
extraWhereArgs: Array<String>?): Array<String?> {
return getFieldsArray(context, uri, keys, keyField, arrayOf(valueField), sortExpression,
2017-02-14 17:26:48 +01:00
extraWhere, extraWhereArgs, object : FieldArrayCreator<Array<String?>, Int> {
2017-02-10 16:38:59 +01:00
override fun newArray(size: Int): Array<String?> {
return arrayOfNulls(size)
}
override fun newIndex(cur: Cursor): Int {
return cur.getColumnIndex(valueField)
}
2017-02-10 16:38:59 +01:00
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> {
2017-02-10 16:38:59 +01:00
override fun newArray(size: Int): LongArray {
return LongArray(size)
}
override fun newIndex(cur: Cursor): Int {
return cur.getColumnIndex(valueField)
}
2017-02-10 16:38:59 +01:00
override fun assign(array: LongArray, arrayIdx: Int, cur: Cursor, colIdx: Int) {
array[arrayIdx] = cur.getLong(colIdx)
}
})
}
private fun <T, I> getFieldsArray(
2017-02-10 18:03:40 +01:00
context: Context, uri: Uri,
keys: Array<UserKey?>, keyField: String,
valueFields: Array<String>, sortExpression: OrderBy?,
2017-02-10 18:03:40 +01:00
extraWhere: Expression?, extraWhereArgs: Array<String>?,
creator: FieldArrayCreator<T, I>
2017-02-10 18:03:40 +01:00
): T {
2017-02-10 16:38:59 +01:00
val resolver = context.contentResolver
val resultArray = creator.newArray(keys.size)
val nonNullKeys = keys.mapNotNull { it?.toString() }.toTypedArray()
val tableName = getTableNameByUri(uri) ?: throw NullPointerException()
2017-02-10 18:03:40 +01:00
val having = Expression.inArgs(keyField, nonNullKeys.size)
val bindingArgs: Array<String>
2020-06-08 23:19:10 +02:00
bindingArgs = if (extraWhereArgs != null) {
extraWhereArgs + nonNullKeys
2017-02-10 16:38:59 +01:00
} else {
2020-06-08 23:19:10 +02:00
nonNullKeys
2017-02-10 16:38:59 +01:00
}
val builder = SQLQueryBuilder.select(Columns(keyField, *valueFields))
2017-02-10 18:03:40 +01:00
builder.from(Table(tableName))
if (extraWhere != null) {
builder.where(extraWhere)
}
builder.groupBy(Column(keyField))
builder.having(having)
2017-02-10 16:38:59 +01:00
if (sortExpression != null) {
builder.orderBy(sortExpression)
}
resolver.rawQueryReference(builder.buildSQL(), bindingArgs)?.use { (cur) ->
2017-02-10 16:38:59 +01:00
cur.moveToFirst()
val colIdx = creator.newIndex(cur)
2017-02-10 16:38:59 +01:00
while (!cur.isAfterLast) {
val keyString = cur.getString(cur.getColumnIndex(keyField))
if (keyString != null) {
val accountKey = UserKey.valueOf(keyString)
2017-04-07 06:12:34 +02:00
val arrayIdx = keys.indexOfFirst {
accountKey == it
}
if (arrayIdx >= 0) {
creator.assign(resultArray, arrayIdx, cur, colIdx)
2017-02-10 16:38:59 +01:00
}
}
cur.moveToNext()
}
}
return resultArray
}
fun deleteStatus(cr: ContentResolver, accountKey: UserKey,
2017-02-12 15:26:09 +01:00
statusId: String, status: ParcelableStatus?) {
2017-02-10 16:38:59 +01:00
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(
2017-04-28 15:44:45 +02:00
Expression.equalsArgs(Statuses.ID),
2017-02-10 16:38:59 +01:00
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(
2017-04-28 15:44:45 +02:00
Expression.equalsArgs(Statuses.ID),
2017-02-10 16:38:59 +01:00
Expression.equalsArgs(Statuses.RETWEET_ID)
).sql
deleteWhereArgs = arrayOf(statusId, statusId)
updateWhere = Expression.equalsArgs(Statuses.MY_RETWEET_ID).sql
updateWhereArgs = arrayOf(statusId)
}
2017-04-29 18:34:26 +02:00
for (uri in STATUSES_ACTIVITIES_URIS) {
2017-02-10 16:38:59 +01:00
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
2020-06-08 23:01:17 +02:00
cr.queryReference(CONTENT_URI_DATABASE_PREPARE, null, null,
2017-08-27 18:37:40 +02:00
null, null).use {
// Just try to initialize database
}
2017-02-10 16:38:59 +01:00
}
internal interface FieldArrayCreator<T, I> {
2017-02-10 16:38:59 +01:00
fun newArray(size: Int): T
fun newIndex(cur: Cursor): I
fun assign(array: T, arrayIdx: Int, cur: Cursor, colIdx: I)
2017-02-10 16:38:59 +01:00
}
2017-09-13 11:52:09 +02:00
fun getInteractionsCount(context: Context, preferences: SharedPreferences, extraArgs: Bundle?,
accountKeys: Array<UserKey>, since: Long, sinceColumn: String,
@FilterScope filterScopes: Int): Int {
2017-02-10 16:38:59 +01:00
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
}
}
}
2017-09-13 11:52:09 +02:00
return getActivitiesCount(context, preferences, Activities.AboutMe.CONTENT_URI, extraWhere,
extraWhereArgs, sinceColumn, since, followingOnly, accountKeys, filterScopes)
2017-02-10 16:38:59 +01:00
}
fun addToFilter(context: Context, users: Collection<ParcelableUser>, filterAnywhere: Boolean) {
val cr = context.contentResolver
try {
2017-03-05 09:08:09 +01:00
val baseCreator = ObjectCursor.valuesCreatorFrom(FiltersData.BaseItem::class.java)
val userCreator = ObjectCursor.valuesCreatorFrom(FiltersData.UserItem::class.java)
2017-02-10 16:38:59 +01:00
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
2017-03-05 09:08:09 +01:00
userValues.add(userCreator.create(userItem))
2017-02-10 16:38:59 +01:00
val keywordItem = FiltersData.BaseItem()
keywordItem.value = "@" + user.screen_name
keywordItem.userKey = user.key
2017-03-05 09:08:09 +01:00
keywordValues.add(baseCreator.create(keywordItem))
2017-02-10 16:38:59 +01:00
// 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
2017-03-05 09:08:09 +01:00
linkValues.add(baseCreator.create(linkItem))
2017-02-10 16:38:59 +01:00
}
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() }
2017-02-13 14:33:45 +01:00
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)
2017-02-10 16:38:59 +01:00
}
@WorkerThread
fun findStatusInDatabases(context: Context,
2017-02-12 15:26:09 +01:00
accountKey: UserKey,
statusId: String): ParcelableStatus? {
2017-02-10 16:38:59 +01:00
val resolver = context.contentResolver
val where = Expression.and(Expression.equalsArgs(Statuses.ACCOUNT_KEY),
2017-04-28 15:44:45 +02:00
Expression.equalsArgs(Statuses.ID)).sql
2017-02-10 16:38:59 +01:00
val whereArgs = arrayOf(accountKey.toString(), statusId)
2020-06-08 23:01:17 +02:00
for (uri in STATUSES_URIS) {
2017-08-27 18:37:40 +02:00
val status = resolver.queryOne(uri, Statuses.COLUMNS, where, whereArgs, null,
ParcelableStatus::class.java)
if (status != null) return status
2017-02-10 16:38:59 +01:00
}
2017-08-27 18:37:40 +02:00
return null
2017-02-10 16:38:59 +01:00
}
@WorkerThread
@Throws(MicroBlogException::class)
fun findStatus(context: Context, accountKey: UserKey, statusId: String): ParcelableStatus {
val cached = findStatusInDatabases(context, accountKey, statusId)
2017-04-29 05:19:31 +02:00
val profileImageSize = context.getString(R.string.profile_image_size)
2017-02-10 16:38:59 +01:00
if (cached != null) return cached
2017-03-27 15:56:03 +02:00
val details = AccountUtils.getAccountDetails(AccountManager.get(context), accountKey,
true) ?: throw MicroBlogException("No account")
2017-04-29 05:19:31 +02:00
val status = when (details.type) {
AccountType.MASTODON -> {
val mastodon = details.newMicroBlogInstance(context, Mastodon::class.java)
mastodon.fetchStatus(statusId).toParcelable(details)
}
else -> {
val microBlog = details.newMicroBlogInstance(context, MicroBlog::class.java)
microBlog.showStatus(statusId).toParcelable(details, profileImageSize)
}
}
2017-02-10 16:38:59 +01:00
val where = Expression.and(Expression.equalsArgs(Statuses.ACCOUNT_KEY),
2017-04-28 15:44:45 +02:00
Expression.equalsArgs(Statuses.ID)).sql
2017-02-10 16:38:59 +01:00
val whereArgs = arrayOf(accountKey.toString(), statusId)
val resolver = context.contentResolver
resolver.delete(CachedStatuses.CONTENT_URI, where, whereArgs)
2017-03-05 09:08:09 +01:00
resolver.insert(CachedStatuses.CONTENT_URI, ObjectCursor.valuesCreatorFrom(ParcelableStatus::class.java).create(status))
2017-02-12 15:26:09 +01:00
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)
2017-08-27 18:37:40 +02:00
return resolver.queryOne(Conversations.CONTENT_URI, Conversations.COLUMNS, where, whereArgs,
null, ParcelableMessageConversation::class.java)
2017-02-10 16:38:59 +01:00
}
2017-02-12 15:26:09 +01:00
2017-03-13 06:35:11 +01:00
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] ?: return@Array null
if (AccountUtils.isOfficial(context, key)) {
return@Array key
}
return@Array null
}
val notOfficialKeys = Array(accountKeys.size) {
val key = accountKeys[it] ?: return@Array null
if (AccountUtils.isOfficial(context, key)) {
return@Array null
}
return@Array key
}
val officialMaxPositions = getFromDatabase(officialKeys, true)
val notOfficialMaxPositions = getFromDatabase(notOfficialKeys, false)
return mergeResult(officialMaxPositions, notOfficialMaxPositions)
}
2017-02-10 16:38:59 +01:00
}