From 90cb5770e918145745b5963f8aa49a670e9fcaf3 Mon Sep 17 00:00:00 2001 From: Mariotaku Lee Date: Fri, 10 Feb 2017 23:38:59 +0800 Subject: [PATCH] kotlin migration --- build.gradle | 2 +- .../tsinghua/hotmobi/model/SessionEvent.java | 2 +- .../fragment/NetworkDiagnosticsFragment.java | 2 +- .../preference/ClearDatabasesPreference.java | 83 -- .../twidere/provider/TwidereDataProvider.java | 102 +- .../twidere/task/RemoveUnreadCountsTask.java | 50 + .../twidere/util/AsyncTwitterWrapper.java | 1133 ----------------- .../twidere/util/DataStoreUtils.java | 1102 ---------------- .../org/mariotaku/twidere/util/Utils.java | 69 +- .../mariotaku/ktextension/ArrayExtensions.kt | 4 + .../twidere/constant/PreferenceKeys.kt | 1 + .../fragment/CursorActivitiesFragment.kt | 27 +- .../fragment/CursorStatusesFragment.kt | 23 +- .../fragment/MessagesEntriesFragment.kt | 36 +- .../twidere/fragment/UserListFragment.kt | 70 +- .../twidere/loader/ParcelableStatusLoader.kt | 6 +- .../twidere/model/SimpleRefreshTaskParam.kt | 9 - .../preference/ClearDatabasesPreference.kt | 58 + .../task/AbsFriendshipOperationTask.kt | 33 +- .../twidere/task/AcceptFriendshipTask.kt | 3 +- .../twidere/task/AddUserListMembersTask.kt | 16 +- .../twidere/task/CacheUsersStatusesTask.kt | 2 +- .../twidere/task/CreateFavoriteTask.kt | 7 +- .../twidere/task/DestroyFavoriteTask.kt | 19 +- .../twidere/task/DestroyStatusTask.kt | 20 +- .../twidere/task/DestroyUserListTask.kt | 66 + .../task/ExceptionHandlingAbstractTask.kt | 62 + .../mariotaku/twidere/task/GetMessagesTask.kt | 21 +- .../twidere/task/GetSavedSearchesTask.kt | 4 +- .../twidere/task/RetweetStatusTask.kt | 12 +- .../mariotaku/twidere/task/SendMessageTask.kt | 4 +- .../task/UpdateProfileBackgroundImageTask.kt | 8 +- .../twidere/task/UpdateUserListDetailsTask.kt | 68 + .../twidere/task/twitter/GetActivitiesTask.kt | 2 +- .../twidere/util/AsyncTwitterWrapper.kt | 780 ++++++++++++ .../twidere/util/DataStoreFunctions.kt | 107 +- .../mariotaku/twidere/util/DataStoreUtils.kt | 886 +++++++++++++ .../twidere/util/TaskServiceRunner.kt | 20 +- 38 files changed, 2340 insertions(+), 2579 deletions(-) delete mode 100644 twidere/src/main/java/org/mariotaku/twidere/preference/ClearDatabasesPreference.java create mode 100644 twidere/src/main/java/org/mariotaku/twidere/task/RemoveUnreadCountsTask.java delete mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/AsyncTwitterWrapper.java delete mode 100644 twidere/src/main/java/org/mariotaku/twidere/util/DataStoreUtils.java create mode 100644 twidere/src/main/kotlin/org/mariotaku/twidere/preference/ClearDatabasesPreference.kt create mode 100644 twidere/src/main/kotlin/org/mariotaku/twidere/task/DestroyUserListTask.kt create mode 100644 twidere/src/main/kotlin/org/mariotaku/twidere/task/ExceptionHandlingAbstractTask.kt create mode 100644 twidere/src/main/kotlin/org/mariotaku/twidere/task/UpdateUserListDetailsTask.kt create mode 100644 twidere/src/main/kotlin/org/mariotaku/twidere/util/AsyncTwitterWrapper.kt create mode 100644 twidere/src/main/kotlin/org/mariotaku/twidere/util/DataStoreUtils.kt diff --git a/build.gradle b/build.gradle index afd144cd0..07b586caf 100644 --- a/build.gradle +++ b/build.gradle @@ -34,7 +34,7 @@ subprojects { ext.kotlin_version = '1.0.6' ext.android_support_lib_version = '25.1.1' ext.mariotaku_commons_library_version = '0.9.11' - ext.mariotaku_restfu_version = '0.9.34' + ext.mariotaku_restfu_version = '0.9.35' ext.play_services_version = '10.0.1' ext.crashlyrics_version = '2.6.6' ext.fabric_plugin_version = '1.22.0' diff --git a/twidere/src/main/java/edu/tsinghua/hotmobi/model/SessionEvent.java b/twidere/src/main/java/edu/tsinghua/hotmobi/model/SessionEvent.java index 3bcebffbc..5f75a1158 100644 --- a/twidere/src/main/java/edu/tsinghua/hotmobi/model/SessionEvent.java +++ b/twidere/src/main/java/edu/tsinghua/hotmobi/model/SessionEvent.java @@ -98,7 +98,7 @@ public class SessionEvent extends BaseEvent implements Parcelable { public void dumpPreferences(Context context) { final HashMap preferences = new HashMap<>(); - for (AccountPreferences pref : AccountPreferences.getAccountPreferences(context, DataStoreUtils.getAccountKeys(context))) { + for (AccountPreferences pref : AccountPreferences.getAccountPreferences(context, DataStoreUtils.INSTANCE.getAccountKeys(context))) { final UserKey accountKey = pref.getAccountKey(); preferences.put("notification_" + accountKey + "_home", String.valueOf(pref.isHomeTimelineNotificationEnabled())); preferences.put("notification_" + accountKey + "_interactions", String.valueOf(pref.isInteractionsNotificationEnabled())); diff --git a/twidere/src/main/java/org/mariotaku/twidere/fragment/NetworkDiagnosticsFragment.java b/twidere/src/main/java/org/mariotaku/twidere/fragment/NetworkDiagnosticsFragment.java index 5dd4582a8..80bcd7a70 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/fragment/NetworkDiagnosticsFragment.java +++ b/twidere/src/main/java/org/mariotaku/twidere/fragment/NetworkDiagnosticsFragment.java @@ -166,7 +166,7 @@ public class NetworkDiagnosticsFragment extends BaseFragment { } publishProgress(LogText.LINEBREAK, LogText.LINEBREAK); - for (UserKey accountKey : DataStoreUtils.getAccountKeys(mContext)) { + for (UserKey accountKey : DataStoreUtils.INSTANCE.getAccountKeys(mContext)) { final AccountDetails details = AccountUtils.getAccountDetails(AccountManager.get(mContext), accountKey, true); final MicroBlog twitter = MicroBlogAPIFactory.getInstance(mContext, accountKey); if (details == null || twitter == null) continue; diff --git a/twidere/src/main/java/org/mariotaku/twidere/preference/ClearDatabasesPreference.java b/twidere/src/main/java/org/mariotaku/twidere/preference/ClearDatabasesPreference.java deleted file mode 100644 index 478f650ba..000000000 --- a/twidere/src/main/java/org/mariotaku/twidere/preference/ClearDatabasesPreference.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Twidere - Twitter client for Android - * - * Copyright (C) 2012-2014 Mariotaku Lee - * - * 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 . - */ - -package org.mariotaku.twidere.preference; - -import android.content.ContentResolver; -import android.content.Context; -import android.content.SharedPreferences; -import android.net.Uri; -import android.util.AttributeSet; - -import org.mariotaku.twidere.Constants; -import org.mariotaku.twidere.R; -import org.mariotaku.twidere.provider.TwidereDataStore.Activities; -import org.mariotaku.twidere.provider.TwidereDataStore.CachedStatuses; -import org.mariotaku.twidere.provider.TwidereDataStore.Notifications; -import org.mariotaku.twidere.provider.TwidereDataStore.SavedSearches; -import org.mariotaku.twidere.provider.TwidereDataStore.UnreadCounts; - -import static org.mariotaku.twidere.util.DataStoreUtils.CACHE_URIS; -import static org.mariotaku.twidere.util.DataStoreUtils.DIRECT_MESSAGES_URIS; -import static org.mariotaku.twidere.util.DataStoreUtils.STATUSES_URIS; - -public class ClearDatabasesPreference extends AsyncTaskPreference implements Constants { - - public ClearDatabasesPreference(final Context context) { - this(context, null); - } - - public ClearDatabasesPreference(final Context context, final AttributeSet attrs) { - this(context, attrs, R.attr.preferenceStyle); - } - - public ClearDatabasesPreference(final Context context, final AttributeSet attrs, final int defStyle) { - super(context, attrs, defStyle); - } - - @Override - protected void doInBackground() { - final Context context = getContext(); - if (context == null) return; - final ContentResolver resolver = context.getContentResolver(); - for (final Uri uri : STATUSES_URIS) { - if (CachedStatuses.CONTENT_URI.equals(uri)) { - continue; - } - resolver.delete(uri, null, null); - } - for (final Uri uri : DIRECT_MESSAGES_URIS) { - resolver.delete(uri, null, null); - } - for (final Uri uri : CACHE_URIS) { - resolver.delete(uri, null, null); - } - resolver.delete(Activities.AboutMe.CONTENT_URI, null, null); - resolver.delete(Activities.ByFriends.CONTENT_URI, null, null); - resolver.delete(Notifications.CONTENT_URI, null, null); - resolver.delete(UnreadCounts.CONTENT_URI, null, null); - resolver.delete(SavedSearches.CONTENT_URI, null, null); - - final SharedPreferences prefs = context.getSharedPreferences(TIMELINE_POSITIONS_PREFERENCES_NAME, Context.MODE_PRIVATE); - final SharedPreferences.Editor editor = prefs.edit(); - editor.clear(); - editor.apply(); - } - -} diff --git a/twidere/src/main/java/org/mariotaku/twidere/provider/TwidereDataProvider.java b/twidere/src/main/java/org/mariotaku/twidere/provider/TwidereDataProvider.java index d5431e080..1dd70ed3d 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/provider/TwidereDataProvider.java +++ b/twidere/src/main/java/org/mariotaku/twidere/provider/TwidereDataProvider.java @@ -68,7 +68,6 @@ import org.mariotaku.sqliteqb.library.SQLConstants; import org.mariotaku.sqliteqb.library.SQLFunctions; import org.mariotaku.sqliteqb.library.query.SQLSelectQuery; import org.mariotaku.twidere.BuildConfig; -import org.mariotaku.twidere.Constants; import org.mariotaku.twidere.R; import org.mariotaku.twidere.activity.HomeActivity; import org.mariotaku.twidere.annotation.CustomTabType; @@ -140,7 +139,72 @@ import java.util.regex.Pattern; import javax.inject.Inject; -public final class TwidereDataProvider extends ContentProvider implements Constants, +import static org.mariotaku.twidere.TwidereConstants.AUTHORITY_DRAFTS; +import static org.mariotaku.twidere.TwidereConstants.AUTHORITY_INTERACTIONS; +import static org.mariotaku.twidere.TwidereConstants.BROADCAST_NOTIFICATION_DELETED; +import static org.mariotaku.twidere.TwidereConstants.INTENT_ACTION_DISCARD_DRAFT; +import static org.mariotaku.twidere.TwidereConstants.INTENT_ACTION_SEND_DRAFT; +import static org.mariotaku.twidere.TwidereConstants.KEY_COMBINED_NOTIFICATIONS; +import static org.mariotaku.twidere.TwidereConstants.KEY_I_WANT_MY_STARS_BACK; +import static org.mariotaku.twidere.TwidereConstants.KEY_NAME_FIRST; +import static org.mariotaku.twidere.TwidereConstants.LOGTAG; +import static org.mariotaku.twidere.TwidereConstants.NOTIFICATION_ID_DIRECT_MESSAGES; +import static org.mariotaku.twidere.TwidereConstants.NOTIFICATION_ID_DRAFTS; +import static org.mariotaku.twidere.TwidereConstants.NOTIFICATION_ID_HOME_TIMELINE; +import static org.mariotaku.twidere.TwidereConstants.NOTIFICATION_ID_INTERACTIONS_TIMELINE; +import static org.mariotaku.twidere.TwidereConstants.PERMISSION_DIRECT_MESSAGES; +import static org.mariotaku.twidere.TwidereConstants.PERMISSION_PREFERENCES; +import static org.mariotaku.twidere.TwidereConstants.PERMISSION_READ; +import static org.mariotaku.twidere.TwidereConstants.PERMISSION_WRITE; +import static org.mariotaku.twidere.TwidereConstants.QUERY_PARAM_ACCOUNT_KEY; +import static org.mariotaku.twidere.TwidereConstants.QUERY_PARAM_FROM_NOTIFICATION; +import static org.mariotaku.twidere.TwidereConstants.QUERY_PARAM_NOTIFICATION_TYPE; +import static org.mariotaku.twidere.TwidereConstants.QUERY_PARAM_NOTIFY; +import static org.mariotaku.twidere.TwidereConstants.QUERY_PARAM_QUERY; +import static org.mariotaku.twidere.TwidereConstants.QUERY_PARAM_READ_POSITION; +import static org.mariotaku.twidere.TwidereConstants.QUERY_PARAM_READ_POSITIONS; +import static org.mariotaku.twidere.TwidereConstants.QUERY_PARAM_TIMESTAMP; +import static org.mariotaku.twidere.TwidereConstants.QUERY_PARAM_TYPE; +import static org.mariotaku.twidere.TwidereConstants.QUERY_PARAM_URL; +import static org.mariotaku.twidere.TwidereConstants.SCHEME_TWIDERE; +import static org.mariotaku.twidere.TwidereConstants.TABLE_ID_ACTIVITIES_ABOUT_ME; +import static org.mariotaku.twidere.TwidereConstants.TABLE_ID_CACHED_HASHTAGS; +import static org.mariotaku.twidere.TwidereConstants.TABLE_ID_CACHED_RELATIONSHIPS; +import static org.mariotaku.twidere.TwidereConstants.TABLE_ID_CACHED_STATUSES; +import static org.mariotaku.twidere.TwidereConstants.TABLE_ID_CACHED_USERS; +import static org.mariotaku.twidere.TwidereConstants.TABLE_ID_DRAFTS; +import static org.mariotaku.twidere.TwidereConstants.TABLE_ID_FILTERED_KEYWORDS; +import static org.mariotaku.twidere.TwidereConstants.TABLE_ID_FILTERED_LINKS; +import static org.mariotaku.twidere.TwidereConstants.TABLE_ID_FILTERED_SOURCES; +import static org.mariotaku.twidere.TwidereConstants.TABLE_ID_FILTERED_USERS; +import static org.mariotaku.twidere.TwidereConstants.TABLE_ID_MENTIONS; +import static org.mariotaku.twidere.TwidereConstants.TABLE_ID_MESSAGES; +import static org.mariotaku.twidere.TwidereConstants.TABLE_ID_MESSAGES_CONVERSATIONS; +import static org.mariotaku.twidere.TwidereConstants.TABLE_ID_SEARCH_HISTORY; +import static org.mariotaku.twidere.TwidereConstants.TABLE_ID_STATUSES; +import static org.mariotaku.twidere.TwidereConstants.TABLE_ID_TABS; +import static org.mariotaku.twidere.TwidereConstants.TABLE_ID_TRENDS_LOCAL; +import static org.mariotaku.twidere.TwidereConstants.VIRTUAL_TABLE_ID_ALL_PREFERENCES; +import static org.mariotaku.twidere.TwidereConstants.VIRTUAL_TABLE_ID_CACHED_IMAGES; +import static org.mariotaku.twidere.TwidereConstants.VIRTUAL_TABLE_ID_CACHED_USERS_WITH_RELATIONSHIP; +import static org.mariotaku.twidere.TwidereConstants.VIRTUAL_TABLE_ID_CACHED_USERS_WITH_SCORE; +import static org.mariotaku.twidere.TwidereConstants.VIRTUAL_TABLE_ID_CACHE_FILES; +import static org.mariotaku.twidere.TwidereConstants.VIRTUAL_TABLE_ID_DATABASE_PREPARE; +import static org.mariotaku.twidere.TwidereConstants.VIRTUAL_TABLE_ID_DNS; +import static org.mariotaku.twidere.TwidereConstants.VIRTUAL_TABLE_ID_DRAFTS_NOTIFICATIONS; +import static org.mariotaku.twidere.TwidereConstants.VIRTUAL_TABLE_ID_DRAFTS_UNSENT; +import static org.mariotaku.twidere.TwidereConstants.VIRTUAL_TABLE_ID_EMPTY; +import static org.mariotaku.twidere.TwidereConstants.VIRTUAL_TABLE_ID_NOTIFICATIONS; +import static org.mariotaku.twidere.TwidereConstants.VIRTUAL_TABLE_ID_NULL; +import static org.mariotaku.twidere.TwidereConstants.VIRTUAL_TABLE_ID_PERMISSIONS; +import static org.mariotaku.twidere.TwidereConstants.VIRTUAL_TABLE_ID_PREFERENCES; +import static org.mariotaku.twidere.TwidereConstants.VIRTUAL_TABLE_ID_RAW_QUERY; +import static org.mariotaku.twidere.TwidereConstants.VIRTUAL_TABLE_ID_SUGGESTIONS_AUTO_COMPLETE; +import static org.mariotaku.twidere.TwidereConstants.VIRTUAL_TABLE_ID_SUGGESTIONS_SEARCH; +import static org.mariotaku.twidere.TwidereConstants.VIRTUAL_TABLE_ID_UNREAD_COUNTS; +import static org.mariotaku.twidere.TwidereConstants.VIRTUAL_TABLE_ID_UNREAD_COUNTS_BY_TYPE; + +public final class TwidereDataProvider extends ContentProvider implements OnSharedPreferenceChangeListener, LazyLoadCallback { public static final String TAG_OLDEST_MESSAGES = "oldest_messages"; @@ -330,8 +394,8 @@ public final class TwidereDataProvider extends ContentProvider implements Consta } private int bulkInsertInternal(@NonNull Uri uri, @NonNull ContentValues[] valuesArray) { - final int tableId = DataStoreUtils.getTableId(uri); - final String table = DataStoreUtils.getTableNameById(tableId); + final int tableId = DataStoreUtils.INSTANCE.getTableId(uri); + final String table = DataStoreUtils.INSTANCE.getTableNameById(tableId); checkWritePermission(tableId, table); int result = 0; final long[] newIds = new long[valuesArray.length]; @@ -394,8 +458,8 @@ public final class TwidereDataProvider extends ContentProvider implements Consta } private int deleteInternal(@NonNull Uri uri, String selection, String[] selectionArgs) { - final int tableId = DataStoreUtils.getTableId(uri); - final String table = DataStoreUtils.getTableNameById(tableId); + final int tableId = DataStoreUtils.INSTANCE.getTableId(uri); + final String table = DataStoreUtils.INSTANCE.getTableNameById(tableId); checkWritePermission(tableId, table); switch (tableId) { case VIRTUAL_TABLE_ID_NOTIFICATIONS: { @@ -446,8 +510,8 @@ public final class TwidereDataProvider extends ContentProvider implements Consta } private Uri insertInternal(@NonNull Uri uri, ContentValues values) { - final int tableId = DataStoreUtils.getTableId(uri); - final String table = DataStoreUtils.getTableNameById(tableId); + final int tableId = DataStoreUtils.INSTANCE.getTableId(uri); + final String table = DataStoreUtils.INSTANCE.getTableNameById(tableId); checkWritePermission(tableId, table); final long rowId; switch (tableId) { @@ -596,8 +660,8 @@ public final class TwidereDataProvider extends ContentProvider implements Consta @Override public ParcelFileDescriptor openFile(@NonNull final Uri uri, @NonNull final String mode) throws FileNotFoundException { - final int table_id = DataStoreUtils.getTableId(uri); - final String table = DataStoreUtils.getTableNameById(table_id); + final int table_id = DataStoreUtils.INSTANCE.getTableId(uri); + final String table = DataStoreUtils.INSTANCE.getTableNameById(table_id); final int modeCode; switch (mode) { case "r": @@ -633,8 +697,8 @@ public final class TwidereDataProvider extends ContentProvider implements Consta public Cursor query(@NonNull final Uri uri, final String[] projection, final String selection, final String[] selectionArgs, final String sortOrder) { try { - final int tableId = DataStoreUtils.getTableId(uri); - final String table = DataStoreUtils.getTableNameById(tableId); + final int tableId = DataStoreUtils.INSTANCE.getTableId(uri); + final String table = DataStoreUtils.INSTANCE.getTableNameById(tableId); checkReadPermission(tableId, table, projection); switch (tableId) { case VIRTUAL_TABLE_ID_DATABASE_PREPARE: { @@ -908,8 +972,8 @@ public final class TwidereDataProvider extends ContentProvider implements Consta } private int updateInternal(@NonNull Uri uri, ContentValues values, String selection, String[] selectionArgs) { - final int tableId = DataStoreUtils.getTableId(uri); - final String table = DataStoreUtils.getTableNameById(tableId); + final int tableId = DataStoreUtils.INSTANCE.getTableId(uri); + final String table = DataStoreUtils.INSTANCE.getTableNameById(tableId); checkWritePermission(tableId, table); int result = 0; if (table != null) { @@ -1120,7 +1184,7 @@ public final class TwidereDataProvider extends ContentProvider implements Consta @Override public void run() { final AccountPreferences[] prefs = AccountPreferences.getNotificationEnabledPreferences(context, - DataStoreUtils.getAccountKeys(context)); + DataStoreUtils.INSTANCE.getAccountKeys(context)); for (final AccountPreferences pref : prefs) { if (!pref.isHomeTimelineNotificationEnabled()) continue; final long positionTag = getPositionTag(CustomTabType.HOME_TIMELINE, pref.getAccountKey()); @@ -1136,7 +1200,7 @@ public final class TwidereDataProvider extends ContentProvider implements Consta @Override public void run() { final AccountPreferences[] prefs = AccountPreferences.getNotificationEnabledPreferences(context, - DataStoreUtils.getAccountKeys(context)); + DataStoreUtils.INSTANCE.getAccountKeys(context)); final boolean combined = preferences.getBoolean(KEY_COMBINED_NOTIFICATIONS); for (final AccountPreferences pref : prefs) { if (!pref.isInteractionsNotificationEnabled()) continue; @@ -1150,7 +1214,7 @@ public final class TwidereDataProvider extends ContentProvider implements Consta } case TABLE_ID_MESSAGES: { final AccountPreferences[] prefs = AccountPreferences.getNotificationEnabledPreferences(context, - DataStoreUtils.getAccountKeys(context)); + DataStoreUtils.INSTANCE.getAccountKeys(context)); for (final AccountPreferences pref : prefs) { if (!pref.isDirectMessagesNotificationEnabled()) continue; final StringLongPair[] pairs = readStateManager.getPositionPairs(CustomTabType.DIRECT_MESSAGES); @@ -1266,7 +1330,7 @@ public final class TwidereDataProvider extends ContentProvider implements Consta applyNotificationPreferences(builder, pref, pref.getMentionsNotificationType()); final Resources resources = context.getResources(); - final String accountName = DataStoreUtils.getAccountDisplayName(context, accountKey, nameFirst); + final String accountName = DataStoreUtils.INSTANCE.getAccountDisplayName(context, accountKey, nameFirst); builder.setContentText(accountName); final InboxStyle style = new InboxStyle(); builder.setStyle(style); @@ -1296,7 +1360,7 @@ public final class TwidereDataProvider extends ContentProvider implements Consta activity.status_quoted_user_key)) { continue; } - final UserKey[] filteredUserIds = DataStoreUtils.getFilteredUserIds(context); + final UserKey[] filteredUserIds = DataStoreUtils.INSTANCE.getFilteredUserIds(context); if (timestamp == -1) { timestamp = activity.timestamp; } diff --git a/twidere/src/main/java/org/mariotaku/twidere/task/RemoveUnreadCountsTask.java b/twidere/src/main/java/org/mariotaku/twidere/task/RemoveUnreadCountsTask.java new file mode 100644 index 000000000..03393f16e --- /dev/null +++ b/twidere/src/main/java/org/mariotaku/twidere/task/RemoveUnreadCountsTask.java @@ -0,0 +1,50 @@ +/* + * Twidere - Twitter client for Android + * + * Copyright (C) 2012-2017 Mariotaku Lee + * + * 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 . + */ + +package org.mariotaku.twidere.task; + +import android.content.Context; +import android.os.AsyncTask; +import android.support.v4.util.SimpleArrayMap; + +import org.mariotaku.twidere.model.UserKey; +import org.mariotaku.twidere.util.TwitterWrapper; + +import java.util.Set; + +/** + * Created by mariotaku on 2017/2/10. + */ +public final class RemoveUnreadCountsTask extends AsyncTask { + private final Context context; + private final int position; + private final SimpleArrayMap> counts; + + public RemoveUnreadCountsTask(Context context, final int position, final SimpleArrayMap> counts) { + this.context = context; + this.position = position; + this.counts = counts; + } + + @Override + protected Integer doInBackground(final Object... params) { + return TwitterWrapper.removeUnreadCounts(context, position, counts); + } + +} diff --git a/twidere/src/main/java/org/mariotaku/twidere/util/AsyncTwitterWrapper.java b/twidere/src/main/java/org/mariotaku/twidere/util/AsyncTwitterWrapper.java deleted file mode 100644 index 978bdf936..000000000 --- a/twidere/src/main/java/org/mariotaku/twidere/util/AsyncTwitterWrapper.java +++ /dev/null @@ -1,1133 +0,0 @@ -/* - * Twidere - Twitter client for Android - * - * Copyright (C) 2012-2014 Mariotaku Lee - * - * 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 . - */ - -package org.mariotaku.twidere.util; - -import android.content.ContentResolver; -import android.content.ContentValues; -import android.content.Context; -import android.content.Intent; -import android.content.res.Resources; -import android.net.Uri; -import android.os.AsyncTask; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.v4.util.SimpleArrayMap; - -import com.squareup.otto.Bus; -import com.squareup.otto.Subscribe; - -import org.apache.commons.collections.primitives.ArrayIntList; -import org.apache.commons.collections.primitives.ArrayLongList; -import org.apache.commons.collections.primitives.IntList; -import org.apache.commons.collections.primitives.LongList; -import org.mariotaku.abstask.library.AbstractTask; -import org.mariotaku.abstask.library.TaskStarter; -import org.mariotaku.microblog.library.MicroBlog; -import org.mariotaku.microblog.library.MicroBlogException; -import org.mariotaku.microblog.library.twitter.http.HttpResponseCode; -import org.mariotaku.microblog.library.twitter.model.DirectMessage; -import org.mariotaku.microblog.library.twitter.model.ErrorInfo; -import org.mariotaku.microblog.library.twitter.model.FriendshipUpdate; -import org.mariotaku.microblog.library.twitter.model.Relationship; -import org.mariotaku.microblog.library.twitter.model.SavedSearch; -import org.mariotaku.microblog.library.twitter.model.User; -import org.mariotaku.microblog.library.twitter.model.UserList; -import org.mariotaku.microblog.library.twitter.model.UserListUpdate; -import org.mariotaku.sqliteqb.library.Expression; -import org.mariotaku.twidere.R; -import org.mariotaku.twidere.model.ListResponse; -import org.mariotaku.twidere.model.ParcelableStatus; -import org.mariotaku.twidere.model.ParcelableUser; -import org.mariotaku.twidere.model.ParcelableUserList; -import org.mariotaku.twidere.model.RefreshTaskParam; -import org.mariotaku.twidere.model.Response; -import org.mariotaku.twidere.model.SimpleRefreshTaskParam; -import org.mariotaku.twidere.model.SingleResponse; -import org.mariotaku.twidere.model.UserKey; -import org.mariotaku.twidere.model.event.FriendshipUpdatedEvent; -import org.mariotaku.twidere.model.event.GetMessagesTaskEvent; -import org.mariotaku.twidere.model.event.GetStatusesTaskEvent; -import org.mariotaku.twidere.model.event.SavedSearchDestroyedEvent; -import org.mariotaku.twidere.model.event.UserListCreatedEvent; -import org.mariotaku.twidere.model.event.UserListDestroyedEvent; -import org.mariotaku.twidere.model.event.UserListMembersChangedEvent; -import org.mariotaku.twidere.model.event.UserListSubscriptionEvent; -import org.mariotaku.twidere.model.event.UserListUpdatedEvent; -import org.mariotaku.twidere.model.event.UsersBlockedEvent; -import org.mariotaku.twidere.model.util.ParcelableUserListUtils; -import org.mariotaku.twidere.provider.TwidereDataStore.AccountSupportColumns; -import org.mariotaku.twidere.provider.TwidereDataStore.Activities; -import org.mariotaku.twidere.provider.TwidereDataStore.CachedRelationships; -import org.mariotaku.twidere.provider.TwidereDataStore.DirectMessages; -import org.mariotaku.twidere.provider.TwidereDataStore.DirectMessages.Inbox; -import org.mariotaku.twidere.provider.TwidereDataStore.DirectMessages.Outbox; -import org.mariotaku.twidere.provider.TwidereDataStore.Drafts; -import org.mariotaku.twidere.provider.TwidereDataStore.Statuses; -import org.mariotaku.twidere.service.LengthyOperationsService; -import org.mariotaku.twidere.task.AcceptFriendshipTask; -import org.mariotaku.twidere.task.AddUserListMembersTask; -import org.mariotaku.twidere.task.CreateFavoriteTask; -import org.mariotaku.twidere.task.CreateFriendshipTask; -import org.mariotaku.twidere.task.CreateUserBlockTask; -import org.mariotaku.twidere.task.CreateUserMuteTask; -import org.mariotaku.twidere.task.DenyFriendshipTask; -import org.mariotaku.twidere.task.DestroyFavoriteTask; -import org.mariotaku.twidere.task.DestroyFriendshipTask; -import org.mariotaku.twidere.task.DestroyStatusTask; -import org.mariotaku.twidere.task.DestroyUserBlockTask; -import org.mariotaku.twidere.task.DestroyUserMuteTask; -import org.mariotaku.twidere.task.GetActivitiesAboutMeTask; -import org.mariotaku.twidere.task.GetHomeTimelineTask; -import org.mariotaku.twidere.task.GetMessagesTask; -import org.mariotaku.twidere.task.GetSavedSearchesTask; -import org.mariotaku.twidere.task.GetTrendsTask; -import org.mariotaku.twidere.task.ManagedAsyncTask; -import org.mariotaku.twidere.task.ReportSpamAndBlockTask; -import org.mariotaku.twidere.task.RetweetStatusTask; -import org.mariotaku.twidere.task.twitter.GetActivitiesTask; -import org.mariotaku.twidere.util.collection.CompactHashSet; - -import java.util.ArrayList; -import java.util.List; -import java.util.Set; - -public class AsyncTwitterWrapper extends TwitterWrapper { - - private final Context context; - private final ContentResolver resolver; - - private final AsyncTaskManager asyncTaskManager; - private final SharedPreferencesWrapper preferences; - private final Bus bus; - - - public IntList destroyingStatusIds = new ArrayIntList(); - private IntList updatingRelationshipIds = new ArrayIntList(); - - private final LongList sendingDraftIds = new ArrayLongList(); - - private final Set getMessageTasks = new CompactHashSet<>(); - private final Set getStatusTasks = new CompactHashSet<>(); - - public AsyncTwitterWrapper(Context context, Bus bus, SharedPreferencesWrapper preferences, - AsyncTaskManager asyncTaskManager) { - this.context = context; - resolver = context.getContentResolver(); - this.bus = bus; - this.preferences = preferences; - this.asyncTaskManager = asyncTaskManager; - bus.register(new Object() { - @Subscribe - public void onGetDirectMessagesTaskEvent(GetMessagesTaskEvent event) { - if (event.running) { - getMessageTasks.add(event.uri); - } else { - getMessageTasks.remove(event.uri); - } - } - - @Subscribe - public void onGetStatusesTaskEvent(GetStatusesTaskEvent event) { - if (event.running) { - getStatusTasks.add(event.uri); - } else { - getStatusTasks.remove(event.uri); - } - } - }); - } - - public void acceptFriendshipAsync(final UserKey accountKey, final UserKey userKey) { - final AcceptFriendshipTask task = new AcceptFriendshipTask(context); - task.setup(accountKey, userKey); - TaskStarter.execute(task); - } - - public void addSendingDraftId(long id) { - synchronized (sendingDraftIds) { - sendingDraftIds.add(id); - resolver.notifyChange(Drafts.CONTENT_URI_UNSENT, null); - } - } - - public int addUserListMembersAsync(final UserKey accountKey, final String listId, @NonNull final ParcelableUser... users) { - final AddUserListMembersTask task = new AddUserListMembersTask(context, accountKey, listId, users); - return asyncTaskManager.add(task, true); - } - - public int cancelRetweetAsync(UserKey accountKey, String statusId, String myRetweetId) { - if (myRetweetId != null) - return destroyStatusAsync(accountKey, myRetweetId); - else if (statusId != null) - return destroyStatusAsync(accountKey, statusId); - return -1; - } - - public void clearNotificationAsync(final int notificationType) { - clearNotificationAsync(notificationType, null); - } - - public void clearNotificationAsync(final int notificationId, @Nullable final UserKey accountKey) { - final ClearNotificationTask task = new ClearNotificationTask(notificationId, accountKey); - AsyncTaskUtils.executeTask(task); - } - - public void clearUnreadCountAsync(final int position) { - final ClearUnreadCountTask task = new ClearUnreadCountTask(position); - AsyncTaskUtils.executeTask(task); - } - - public void createBlockAsync(final UserKey accountKey, final UserKey userKey, boolean filterEverywhere) { - final CreateUserBlockTask task = new CreateUserBlockTask(context, filterEverywhere); - task.setup(accountKey, userKey); - TaskStarter.execute(task); - } - - public void createFavoriteAsync(final UserKey accountKey, final ParcelableStatus status) { - final CreateFavoriteTask task = new CreateFavoriteTask(context, accountKey, status); - TaskStarter.execute(task); - } - - public void createFriendshipAsync(final UserKey accountKey, final UserKey userKey) { - final CreateFriendshipTask task = new CreateFriendshipTask(context); - task.setup(accountKey, userKey); - TaskStarter.execute(task); - } - - public int createMultiBlockAsync(final UserKey accountKey, final String[] userIds) { - final CreateMultiBlockTask task = new CreateMultiBlockTask(accountKey, userIds); - return asyncTaskManager.add(task, true); - } - - public void createMuteAsync(final UserKey accountKey, final UserKey userKey, boolean filterEverywhere) { - final CreateUserMuteTask task = new CreateUserMuteTask(context, filterEverywhere); - task.setup(accountKey, userKey); - TaskStarter.execute(task); - } - - public int createSavedSearchAsync(final UserKey accountKey, final String query) { - final CreateSavedSearchTask task = new CreateSavedSearchTask(accountKey, query); - return asyncTaskManager.add(task, true); - } - - public int createUserListAsync(final UserKey accountKey, final String listName, final boolean isPublic, - final String description) { - final CreateUserListTask task = new CreateUserListTask(context, accountKey, listName, isPublic, - description); - return asyncTaskManager.add(task, true); - } - - public int createUserListSubscriptionAsync(final UserKey accountKey, final String listId) { - final CreateUserListSubscriptionTask task = new CreateUserListSubscriptionTask(accountKey, listId); - return asyncTaskManager.add(task, true); - } - - public int deleteUserListMembersAsync(final UserKey accountKey, final String listId, final ParcelableUser... users) { - final DeleteUserListMembersTask task = new DeleteUserListMembersTask(accountKey, listId, users); - return asyncTaskManager.add(task, true); - } - - public void denyFriendshipAsync(final UserKey accountKey, final UserKey userKey) { - final DenyFriendshipTask task = new DenyFriendshipTask(context); - task.setup(accountKey, userKey); - TaskStarter.execute(task); - } - - public void destroyBlockAsync(final UserKey accountKey, final UserKey userKey) { - final DestroyUserBlockTask task = new DestroyUserBlockTask(context); - task.setup(accountKey, userKey); - TaskStarter.execute(task); - } - - public int destroyDirectMessageAsync(final UserKey accountKey, final String messageId) { - final DestroyDirectMessageTask task = new DestroyDirectMessageTask(accountKey, messageId); - return asyncTaskManager.add(task, true); - } - - public int destroyMessageConversationAsync(final UserKey accountKey, final String userId) { - final DestroyMessageConversationTask task = new DestroyMessageConversationTask(accountKey, userId); - return asyncTaskManager.add(task, true); - } - - public int destroyFavoriteAsync(final UserKey accountKey, final String statusId) { - final DestroyFavoriteTask task = new DestroyFavoriteTask(context, accountKey, statusId); - return asyncTaskManager.add(task, true); - } - - public void destroyFriendshipAsync(final UserKey accountKey, final UserKey userKey) { - final DestroyFriendshipTask task = new DestroyFriendshipTask(context); - task.setup(accountKey, userKey); - TaskStarter.execute(task); - } - - public void destroyMuteAsync(final UserKey accountKey, final UserKey userKey) { - final DestroyUserMuteTask task = new DestroyUserMuteTask(context); - task.setup(accountKey, userKey); - TaskStarter.execute(task); - } - - public int destroySavedSearchAsync(final UserKey accountKey, final long searchId) { - final DestroySavedSearchTask task = new DestroySavedSearchTask(accountKey, searchId); - return asyncTaskManager.add(task, true); - } - - public int destroyStatusAsync(final UserKey accountKey, final String statusId) { - final DestroyStatusTask task = new DestroyStatusTask(context, accountKey, statusId); - return asyncTaskManager.add(task, true); - } - - public int destroyUserListAsync(final UserKey accountKey, final String listId) { - final DestroyUserListTask task = new DestroyUserListTask(context, accountKey, listId); - return asyncTaskManager.add(task, true); - } - - public int destroyUserListSubscriptionAsync(final UserKey accountKey, final String listId) { - final DestroyUserListSubscriptionTask task = new DestroyUserListSubscriptionTask(accountKey, listId); - return asyncTaskManager.add(task, true); - } - - public Context getContext() { - return context; - } - - public boolean getHomeTimelineAsync(RefreshTaskParam param) { - final GetHomeTimelineTask task = new GetHomeTimelineTask(getContext()); - task.setParams(param); - TaskStarter.execute(task); - return true; - } - - public void getLocalTrendsAsync(final UserKey accountId, final int woeId) { - final GetTrendsTask task = new GetTrendsTask(context, accountId, woeId); - TaskStarter.execute(task); - } - - public void getMessagesAsync(RefreshTaskParam param) { - final GetMessagesTask task = new GetMessagesTask(context); - task.setParams(param); - TaskStarter.execute(task); - } - - public void getSavedSearchesAsync(UserKey[] accountKeys) { - final GetSavedSearchesTask task = new GetSavedSearchesTask(context); - task.setParams(accountKeys); - TaskStarter.execute(task); - } - - @NonNull - public long[] getSendingDraftIds() { - return sendingDraftIds.toArray(); - } - - public boolean isDestroyingStatus(@Nullable final UserKey accountId, @Nullable final String statusId) { - return destroyingStatusIds.contains(calculateHashCode(accountId, statusId)); - } - - public static int calculateHashCode(@Nullable final UserKey accountId, @Nullable final String statusId) { - return (accountId == null ? 0 : accountId.hashCode()) ^ (statusId == null ? 0 : statusId.hashCode()); - } - - public boolean isStatusTimelineRefreshing(Uri uri) { - return getStatusTasks.contains(uri); - } - - public void refreshAll() { - refreshAll(new GetAccountKeysClosure() { - @Override - public UserKey[] getAccountKeys() { - return DataStoreUtils.getActivatedAccountKeys(context); - } - }); - } - - public boolean refreshAll(final UserKey[] accountKeys) { - return refreshAll(new GetAccountKeysClosure() { - @Override - public UserKey[] getAccountKeys() { - return accountKeys; - } - }); - } - - public boolean refreshAll(final GetAccountKeysClosure closure) { - getHomeTimelineAsync(new SimpleRefreshTaskParam() { - - @NonNull - @Override - public UserKey[] getAccountKeysWorker() { - return closure.getAccountKeys(); - } - - @Nullable - @Override - public String[] getSinceIds() { - return DataStoreUtils.getNewestStatusIds(context, Statuses.CONTENT_URI, - getAccountKeys()); - } - }); - if (preferences.getBoolean(KEY_HOME_REFRESH_MENTIONS)) { - getActivitiesAboutMeAsync(new SimpleRefreshTaskParam() { - @NonNull - @Override - public UserKey[] getAccountKeysWorker() { - return closure.getAccountKeys(); - } - - @Nullable - @Override - public String[] getSinceIds() { - return DataStoreUtils.getNewestActivityMaxPositions(context, - Activities.AboutMe.CONTENT_URI, getAccountKeys()); - } - }); - } - if (preferences.getBoolean(KEY_HOME_REFRESH_DIRECT_MESSAGES)) { - getMessagesAsync(new SimpleRefreshTaskParam() { - @NonNull - @Override - public UserKey[] getAccountKeysWorker() { - return closure.getAccountKeys(); - } - }); - } - if (preferences.getBoolean(KEY_HOME_REFRESH_SAVED_SEARCHES)) { - getSavedSearchesAsync(closure.getAccountKeys()); - } - return true; - } - - public void removeSendingDraftId(long id) { - synchronized (sendingDraftIds) { - sendingDraftIds.removeElement(id); - resolver.notifyChange(Drafts.CONTENT_URI_UNSENT, null); - } - } - - public void removeUnreadCountsAsync(final int position, final SimpleArrayMap> counts) { - final RemoveUnreadCountsTask task = new RemoveUnreadCountsTask(position, counts); - AsyncTaskUtils.executeTask(task); - } - - public void reportMultiSpam(final UserKey accountKey, final String[] userIds) { - // TODO implementation - } - - public void reportSpamAsync(final UserKey accountKey, final UserKey userKey) { - final ReportSpamAndBlockTask task = new ReportSpamAndBlockTask(context); - task.setup(accountKey, userKey); - TaskStarter.execute(task); - } - - public void retweetStatusAsync(final UserKey accountKey, final ParcelableStatus status) { - final RetweetStatusTask task = new RetweetStatusTask(context, accountKey, status); - TaskStarter.execute(task); - } - - public int sendDirectMessageAsync(final UserKey accountKey, final String recipientId, final String text, - final String imageUri) { - final Intent intent = new Intent(context, LengthyOperationsService.class); - intent.setAction(INTENT_ACTION_SEND_DIRECT_MESSAGE); - intent.putExtra(EXTRA_ACCOUNT_KEY, accountKey); - intent.putExtra(EXTRA_RECIPIENT_ID, recipientId); - intent.putExtra(EXTRA_TEXT, text); - intent.putExtra(EXTRA_IMAGE_URI, imageUri); - context.startService(intent); - return 0; - } - - public int updateUserListDetails(final UserKey accountKey, final String listId, - final UserListUpdate update) { - final UpdateUserListDetailsTask task = new UpdateUserListDetailsTask(context, accountKey, - listId, update); - return asyncTaskManager.add(task, true); - } - - @Nullable - public static > Exception getException(List responses) { - for (T response : responses) { - if (response.hasException()) return response.getException(); - } - return null; - } - - public void updateFriendship(final UserKey accountKey, final UserKey userKey, final FriendshipUpdate update) { - final Bus bus = this.bus; - if (bus == null) return; - TaskStarter.execute(new AbstractTask, Bus>() { - @Override - public SingleResponse doLongOperation(Object param) { - final MicroBlog microBlog = MicroBlogAPIFactory.getInstance(context, accountKey); - try { - if (microBlog == null) { - throw new MicroBlogException("No account"); - } - final Relationship relationship = microBlog.updateFriendship(userKey.getId(), update); - if (!relationship.isSourceWantRetweetsFromTarget()) { - // TODO remove cached retweets - final Expression where = Expression.and( - Expression.equalsArgs(Statuses.ACCOUNT_KEY), - Expression.equalsArgs(Statuses.RETWEETED_BY_USER_KEY) - ); - final String[] selectionArgs = {accountKey.toString(), userKey.toString()}; - context.getContentResolver().delete(Statuses.CONTENT_URI, where.getSQL(), selectionArgs); - } - return SingleResponse.Companion.getInstance(relationship); - } catch (MicroBlogException e) { - return SingleResponse.Companion.getInstance(e); - } - } - - @Override - public void afterExecute(Bus handler, SingleResponse result) { - if (result.hasData()) { - handler.post(new FriendshipUpdatedEvent(accountKey, userKey, result.getData())); - } else if (result.hasException()) { - DebugLog.w(LOGTAG, "Unable to update friendship", result.getException()); - } - } - }.setCallback(bus)); - } - - public void getActivitiesAboutMeAsync(final RefreshTaskParam param) { - final GetActivitiesTask task = new GetActivitiesAboutMeTask(getContext()); - task.setParams(param); - TaskStarter.execute(task); - } - - public void setActivitiesAboutMeUnreadAsync(final UserKey[] accountKeys, final long cursor) { - AbstractTask task = new AbstractTask() { - - @Override - public Object doLongOperation(Object o) { - for (UserKey accountId : accountKeys) { - MicroBlog microBlog = MicroBlogAPIFactory.getInstance(context, accountId); - if (!Utils.isOfficialCredentials(context, accountId)) continue; - try { - if (microBlog == null) { - throw new MicroBlogException("No account"); - } - microBlog.setActivitiesAboutMeUnread(cursor); - } catch (MicroBlogException e) { - DebugLog.w(LOGTAG, null, e); - } - } - return null; - } - }; - TaskStarter.execute(task); - } - - public void addUpdatingRelationshipId(UserKey accountKey, UserKey userId) { - updatingRelationshipIds.add(ParcelableUser.calculateHashCode(accountKey, userId)); - } - - public void removeUpdatingRelationshipId(UserKey accountKey, UserKey userId) { - updatingRelationshipIds.removeElement(ParcelableUser.calculateHashCode(accountKey, userId)); - } - - public boolean isUpdatingRelationship(UserKey accountId, UserKey userId) { - return updatingRelationshipIds.contains(ParcelableUser.calculateHashCode(accountId, userId)); - } - - final class ClearNotificationTask extends AsyncTask { - private final int notificationType; - private final UserKey accountKey; - - ClearNotificationTask(final int notificationType, final UserKey accountKey) { - this.notificationType = notificationType; - this.accountKey = accountKey; - } - - @Override - protected Integer doInBackground(final Object... params) { - return clearNotification(context, notificationType, accountKey); - } - - } - - final class ClearUnreadCountTask extends AsyncTask { - private final int position; - - ClearUnreadCountTask(final int position) { - this.position = position; - } - - @Override - protected Integer doInBackground(final Object... params) { - return clearUnreadCount(context, position); - } - - } - - class CreateMultiBlockTask extends ManagedAsyncTask> { - - private final UserKey mAccountKey; - private final String[] mUserIds; - - public CreateMultiBlockTask(final UserKey accountKey, final String[] userIds) { - super(context); - this.mAccountKey = accountKey; - this.mUserIds = userIds; - } - - private void deleteCaches(final List list) { - // I bet you don't want to see these users in your auto complete list. - //TODO insert to blocked users data - final ContentValues values = new ContentValues(); - values.put(CachedRelationships.BLOCKING, true); - values.put(CachedRelationships.FOLLOWING, false); - values.put(CachedRelationships.FOLLOWED_BY, false); - final String where = Expression.inArgs(CachedRelationships.USER_KEY, list.size()).getSQL(); - final String[] selectionArgs = list.toArray(new String[list.size()]); - resolver.update(CachedRelationships.CONTENT_URI, values, where, selectionArgs); - } - - @Override - protected ListResponse doInBackground(final Object... params) { - final List blockedUsers = new ArrayList<>(); - final MicroBlog microBlog = MicroBlogAPIFactory.getInstance(context, mAccountKey); - if (microBlog != null) { - for (final String userId : mUserIds) { - try { - final User user = microBlog.createBlock(userId); - blockedUsers.add(user.getId()); - } catch (final MicroBlogException e) { - deleteCaches(blockedUsers); - return ListResponse.getListInstance(e); - } - } - } - deleteCaches(blockedUsers); - return ListResponse.getListInstance(blockedUsers); - } - - @Override - protected void onPostExecute(final ListResponse result) { - if (result.hasData()) { - Utils.showInfoMessage(context, R.string.users_blocked, false); - } else { - Utils.showErrorMessage(context, R.string.action_blocking, result.getException(), true); - } - AsyncTwitterWrapper.this.bus.post(new UsersBlockedEvent(mAccountKey, mUserIds)); - super.onPostExecute(result); - } - - - } - - class CreateSavedSearchTask extends ManagedAsyncTask> { - - private final UserKey mAccountKey; - private final String mQuery; - - CreateSavedSearchTask(final UserKey accountKey, final String query) { - super(context); - mAccountKey = accountKey; - mQuery = query; - } - - @Override - protected SingleResponse doInBackground(final Object... params) { - final MicroBlog microBlog = MicroBlogAPIFactory.getInstance(context, mAccountKey); - if (microBlog == null) return null; - try { - return SingleResponse.Companion.getInstance(microBlog.createSavedSearch(mQuery)); - } catch (final MicroBlogException e) { - return SingleResponse.Companion.getInstance(e); - } - } - - @Override - protected void onPostExecute(final SingleResponse result) { - if (result.hasData()) { - final String message = context.getString(R.string.message_toast_search_name_saved, result.getData().getQuery()); - Utils.showOkMessage(context, message, false); - } else if (result.hasException()) { - final Exception exception = result.getException(); - // https://github.com/TwidereProject/Twidere-Android/issues/244 - if (exception instanceof MicroBlogException && ((MicroBlogException) exception).getStatusCode() == 403) { - final String desc = context.getString(R.string.saved_searches_already_saved_hint); - Utils.showErrorMessage(context, R.string.action_saving_search, desc, false); - } else { - Utils.showErrorMessage(context, R.string.action_saving_search, exception, false); - } - } - super.onPostExecute(result); - } - - } - - class CreateUserListSubscriptionTask extends ManagedAsyncTask> { - - private final UserKey mAccountKey; - private final String mListId; - - public CreateUserListSubscriptionTask(final UserKey accountKey, final String listId) { - super(context); - this.mAccountKey = accountKey; - this.mListId = listId; - } - - @Override - protected SingleResponse doInBackground(final Object... params) { - final MicroBlog microBlog = MicroBlogAPIFactory.getInstance(context, mAccountKey); - if (microBlog == null) return SingleResponse.Companion.getInstance(); - try { - final UserList userList = microBlog.createUserListSubscription(mListId); - final ParcelableUserList list = ParcelableUserListUtils.from(userList, mAccountKey); - return SingleResponse.Companion.getInstance(list); - } catch (final MicroBlogException e) { - return SingleResponse.Companion.getInstance(e); - } - } - - @Override - protected void onPostExecute(final SingleResponse result) { - final boolean succeed = result.hasData(); - if (succeed) { - final String message = context.getString(R.string.subscribed_to_list, result.getData().name); - Utils.showOkMessage(context, message, false); - getBus().post(new UserListSubscriptionEvent(UserListSubscriptionEvent.Action.SUBSCRIBE, - result.getData())); - } else { - Utils.showErrorMessage(context, R.string.action_subscribing_to_list, result.getException(), true); - } - super.onPostExecute(result); - } - - } - - static class CreateUserListTask extends ManagedAsyncTask> { - - private final UserKey mAccountKey; - private final String mListName, mDescription; - private final boolean mIsPublic; - - public CreateUserListTask(Context context, final UserKey accountKey, final String listName, - final boolean isPublic, final String description) { - super(context); - this.mAccountKey = accountKey; - this.mListName = listName; - this.mDescription = description; - this.mIsPublic = isPublic; - } - - @Override - protected SingleResponse doInBackground(final Object... params) { - final MicroBlog microBlog = MicroBlogAPIFactory.getInstance(getContext(), mAccountKey - ); - if (microBlog == null || mListName == null) - return SingleResponse.Companion.getInstance(); - try { - final UserListUpdate userListUpdate = new UserListUpdate(); - userListUpdate.setName(mListName); - userListUpdate.setMode(mIsPublic ? UserList.Mode.PUBLIC : UserList.Mode.PRIVATE); - userListUpdate.setDescription(mDescription); - final UserList list = microBlog.createUserList(userListUpdate); - return SingleResponse.Companion.getInstance(ParcelableUserListUtils.from(list, mAccountKey)); - } catch (final MicroBlogException e) { - return SingleResponse.Companion.getInstance(e); - } - } - - @Override - protected void onPostExecute(final SingleResponse result) { - final Context context = getContext(); - if (result.hasData()) { - final ParcelableUserList userList = result.getData(); - final String message = context.getString(R.string.created_list, userList.name); - Utils.showOkMessage(context, message, false); - getBus().post(new UserListCreatedEvent(userList)); - } else { - Utils.showErrorMessage(context, R.string.action_creating_list, result.getException(), true); - } - super.onPostExecute(result); - } - - } - - class DeleteUserListMembersTask extends ManagedAsyncTask> { - - private final UserKey mAccountKey; - private final String mUserListId; - private final ParcelableUser[] users; - - public DeleteUserListMembersTask(final UserKey accountKey, final String userListId, final ParcelableUser[] users) { - super(context); - mAccountKey = accountKey; - mUserListId = userListId; - this.users = users; - } - - @Override - protected SingleResponse doInBackground(final Object... params) { - final MicroBlog microBlog = MicroBlogAPIFactory.getInstance(context, mAccountKey); - if (microBlog == null) return SingleResponse.Companion.getInstance(); - try { - final UserKey[] userIds = new UserKey[users.length]; - for (int i = 0, j = users.length; i < j; i++) { - userIds[i] = users[i].key; - } - final UserList userList = microBlog.deleteUserListMembers(mUserListId, UserKey.getIds(userIds)); - final ParcelableUserList list = ParcelableUserListUtils.from(userList, mAccountKey); - return SingleResponse.Companion.getInstance(list); - } catch (final MicroBlogException e) { - return SingleResponse.Companion.getInstance(e); - } - } - - @Override - protected void onPostExecute(final SingleResponse result) { - final boolean succeed = result.hasData(); - final String message; - if (succeed) { - if (users.length == 1) { - final ParcelableUser user = users[0]; - final boolean nameFirst = getPreferences().getBoolean(KEY_NAME_FIRST); - final String displayName = getUserColorNameManager().getDisplayName(user.key, - user.name, user.screen_name, nameFirst); - message = context.getString(R.string.deleted_user_from_list, displayName, - result.getData().name); - } else { - final Resources res = context.getResources(); - message = res.getQuantityString(R.plurals.deleted_N_users_from_list, users.length, users.length, - result.getData().name); - } - getBus().post(new UserListMembersChangedEvent(UserListMembersChangedEvent.Action.REMOVED, - result.getData(), users)); - Utils.showInfoMessage(context, message, false); - } else { - Utils.showErrorMessage(context, R.string.action_deleting, result.getException(), true); - } - super.onPostExecute(result); - } - - } - - - class DestroyDirectMessageTask extends ManagedAsyncTask> { - - private final UserKey mAccountKey; - private final String mMessageId; - - public DestroyDirectMessageTask(final UserKey accountKey, final String messageId) { - super(context); - mAccountKey = accountKey; - mMessageId = messageId; - } - - private void deleteMessages() { - final String where = Expression.and(Expression.equalsArgs(DirectMessages.ACCOUNT_KEY), - Expression.equalsArgs(DirectMessages.MESSAGE_ID)).getSQL(); - final String[] whereArgs = new String[]{mAccountKey.toString(), mMessageId}; - resolver.delete(DirectMessages.Inbox.CONTENT_URI, where, whereArgs); - resolver.delete(DirectMessages.Outbox.CONTENT_URI, where, whereArgs); - } - - private boolean isMessageNotFound(final Exception e) { - if (!(e instanceof MicroBlogException)) return false; - final MicroBlogException te = (MicroBlogException) e; - return te.getErrorCode() == ErrorInfo.PAGE_NOT_FOUND - || te.getStatusCode() == HttpResponseCode.NOT_FOUND; - } - - @Override - protected SingleResponse doInBackground(final Object... args) { - final MicroBlog microBlog = MicroBlogAPIFactory.getInstance(context, mAccountKey); - if (microBlog == null) return SingleResponse.Companion.getInstance(); - try { - final DirectMessage message = microBlog.destroyDirectMessage(mMessageId); - deleteMessages(); - return SingleResponse.Companion.getInstance(message); - } catch (final MicroBlogException e) { - if (isMessageNotFound(e)) { - deleteMessages(); - } - return SingleResponse.Companion.getInstance(e); - } - } - - - @Override - protected void onPostExecute(final SingleResponse result) { - super.onPostExecute(result); - if (result == null) return; - if (result.hasData() || isMessageNotFound(result.getException())) { - Utils.showInfoMessage(context, R.string.message_direct_message_deleted, false); - } else { - Utils.showErrorMessage(context, R.string.action_deleting, result.getException(), true); - } - } - - - } - - - class DestroyMessageConversationTask extends ManagedAsyncTask> { - - private final String mUserId; - private final UserKey mAccountKey; - - DestroyMessageConversationTask(final UserKey accountKey, final String userId) { - super(context); - mAccountKey = accountKey; - mUserId = userId; - } - - private void deleteMessages(final UserKey accountKey, final String userId) { - final String[] whereArgs = {accountKey.toString(), userId}; - resolver.delete(DirectMessages.Inbox.CONTENT_URI, Expression.and( - Expression.equalsArgs(AccountSupportColumns.ACCOUNT_KEY), - Expression.equalsArgs(Inbox.SENDER_ID) - ).getSQL(), whereArgs); - resolver.delete(DirectMessages.Outbox.CONTENT_URI, Expression.and( - Expression.equalsArgs(AccountSupportColumns.ACCOUNT_KEY), - Expression.equalsArgs(Outbox.RECIPIENT_ID) - ).getSQL(), whereArgs); - } - - private boolean isMessageNotFound(final Exception e) { - if (!(e instanceof MicroBlogException)) return false; - final MicroBlogException te = (MicroBlogException) e; - return te.getErrorCode() == ErrorInfo.PAGE_NOT_FOUND - || te.getStatusCode() == HttpResponseCode.NOT_FOUND; - } - - @Override - protected SingleResponse doInBackground(final Object... args) { - final MicroBlog microBlog = MicroBlogAPIFactory.getInstance(context, mAccountKey); - if (microBlog == null) - return new SingleResponse<>(new MicroBlogException("No account")); - try { - microBlog.destroyDirectMessagesConversation(mAccountKey.getId(), mUserId); - deleteMessages(mAccountKey, mUserId); - return new SingleResponse<>(true); - } catch (final MicroBlogException e) { - if (isMessageNotFound(e)) { - deleteMessages(mAccountKey, mUserId); - } - return new SingleResponse<>(e); - } - } - - - @Override - protected void onPostExecute(final SingleResponse result) { - super.onPostExecute(result); - if (result == null) return; - if (result.hasData() || isMessageNotFound(result.getException())) { - Utils.showInfoMessage(context, R.string.message_direct_message_deleted, false); - } else { - Utils.showErrorMessage(context, R.string.action_deleting, result.getException(), true); - } - } - - - } - - - class DestroySavedSearchTask extends ManagedAsyncTask> { - - private final UserKey mAccountKey; - private final long mSearchId; - - DestroySavedSearchTask(final UserKey accountKey, final long searchId) { - super(context); - mAccountKey = accountKey; - mSearchId = searchId; - } - - @Override - protected SingleResponse doInBackground(final Object... params) { - final MicroBlog microBlog = MicroBlogAPIFactory.getInstance(context, mAccountKey); - if (microBlog == null) return SingleResponse.Companion.getInstance(); - try { - return SingleResponse.Companion.getInstance(microBlog.destroySavedSearch(mSearchId)); - } catch (final MicroBlogException e) { - return SingleResponse.Companion.getInstance(e); - } - } - - @Override - protected void onPostExecute(final SingleResponse result) { - if (result.hasData()) { - final String message = context.getString(R.string.message_toast_search_name_deleted, result.getData().getQuery()); - Utils.showOkMessage(context, message, false); - getBus().post(new SavedSearchDestroyedEvent(mAccountKey, mSearchId)); - } else { - Utils.showErrorMessage(context, R.string.action_deleting_search, result.getException(), false); - } - super.onPostExecute(result); - } - - } - - class DestroyUserListSubscriptionTask extends ManagedAsyncTask> { - - private final UserKey mAccountKey; - private final String mListId; - - public DestroyUserListSubscriptionTask(@NonNull final UserKey accountKey, final String listId) { - super(context); - mAccountKey = accountKey; - mListId = listId; - } - - @Override - protected SingleResponse doInBackground(final Object... params) { - - final MicroBlog microBlog = MicroBlogAPIFactory.getInstance(context, mAccountKey); - if (microBlog == null) return SingleResponse.Companion.getInstance(); - try { - final UserList userList = microBlog.destroyUserListSubscription(mListId); - final ParcelableUserList list = ParcelableUserListUtils.from(userList, mAccountKey); - return SingleResponse.Companion.getInstance(list); - } catch (final MicroBlogException e) { - return SingleResponse.Companion.getInstance(e); - } - } - - @Override - protected void onPostExecute(final SingleResponse result) { - final boolean succeed = result.hasData(); - if (succeed) { - final String message = context.getString(R.string.unsubscribed_from_list, result.getData().name); - Utils.showOkMessage(context, message, false); - getBus().post(new UserListSubscriptionEvent(UserListSubscriptionEvent.Action.UNSUBSCRIBE, - result.getData())); - } else { - Utils.showErrorMessage(context, R.string.action_unsubscribing_from_list, result.getException(), true); - } - super.onPostExecute(result); - } - - } - - static class DestroyUserListTask extends ManagedAsyncTask> { - - private final UserKey mAccountKey; - private final String mListId; - - public DestroyUserListTask(Context context, final UserKey accountKey, final String listId) { - super(context); - mAccountKey = accountKey; - mListId = listId; - } - - @Override - protected SingleResponse doInBackground(final Object... params) { - final MicroBlog microBlog = MicroBlogAPIFactory.getInstance(getContext(), mAccountKey - ); - if (microBlog == null) return SingleResponse.Companion.getInstance(); - try { - final UserList userList = microBlog.destroyUserList(mListId); - final ParcelableUserList list = ParcelableUserListUtils.from(userList, mAccountKey); - return SingleResponse.Companion.getInstance(list); - } catch (final MicroBlogException e) { - return SingleResponse.Companion.getInstance(e); - } - } - - @Override - protected void onPostExecute(final SingleResponse result) { - final boolean succeed = result.hasData(); - final Context context = getContext(); - if (succeed) { - final String message = context.getString(R.string.deleted_list, result.getData().name); - Utils.showInfoMessage(context, message, false); - getBus().post(new UserListDestroyedEvent(result.getData())); - } else { - Utils.showErrorMessage(context, R.string.action_deleting, result.getException(), true); - } - super.onPostExecute(result); - } - - } - - public SharedPreferencesWrapper getPreferences() { - return preferences; - } - - final class RemoveUnreadCountsTask extends AsyncTask { - private final int position; - private final SimpleArrayMap> counts; - - RemoveUnreadCountsTask(final int position, final SimpleArrayMap> counts) { - this.position = position; - this.counts = counts; - } - - @Override - protected Integer doInBackground(final Object... params) { - return removeUnreadCounts(context, position, counts); - } - - } - - - static class UpdateUserListDetailsTask extends ManagedAsyncTask> { - - private final UserKey mAccountKey; - private final String listId; - private final UserListUpdate update; - private Context mContext; - - public UpdateUserListDetailsTask(Context context, final UserKey accountKey, - final String listId, UserListUpdate update) { - super(context); - this.mAccountKey = accountKey; - this.listId = listId; - this.update = update; - this.mContext = context; - } - - @Override - protected SingleResponse doInBackground(final Object... params) { - - final MicroBlog microBlog = MicroBlogAPIFactory.getInstance(mContext, mAccountKey); - if (microBlog != null) { - try { - final UserList list = microBlog.updateUserList(listId, update); - return SingleResponse.Companion.getInstance(ParcelableUserListUtils.from(list, mAccountKey)); - } catch (final MicroBlogException e) { - return SingleResponse.Companion.getInstance(e); - } - } - return SingleResponse.Companion.getInstance(); - } - - @Override - protected void onPostExecute(final SingleResponse result) { - if (result.hasData()) { - final String message = mContext.getString(R.string.updated_list_details, result.getData().name); - Utils.showOkMessage(mContext, message, false); - getBus().post(new UserListUpdatedEvent(result.getData())); - } else { - Utils.showErrorMessage(mContext, R.string.action_updating_details, result.getException(), true); - } - super.onPostExecute(result); - } - - } - - interface GetAccountKeysClosure { - UserKey[] getAccountKeys(); - } -} diff --git a/twidere/src/main/java/org/mariotaku/twidere/util/DataStoreUtils.java b/twidere/src/main/java/org/mariotaku/twidere/util/DataStoreUtils.java deleted file mode 100644 index 04120e46e..000000000 --- a/twidere/src/main/java/org/mariotaku/twidere/util/DataStoreUtils.java +++ /dev/null @@ -1,1102 +0,0 @@ -/* - * Twidere - Twitter client for Android - * - * Copyright (C) 2012-2015 Mariotaku Lee - * - * 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 . - */ - -package org.mariotaku.twidere.util; - -import android.accounts.Account; -import android.accounts.AccountManager; -import android.content.ContentResolver; -import android.content.ContentValues; -import android.content.Context; -import android.content.SharedPreferences; -import android.content.UriMatcher; -import android.database.Cursor; -import android.net.Uri; -import android.os.Bundle; -import android.os.Parcelable; -import android.provider.BaseColumns; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.annotation.WorkerThread; -import android.support.v4.util.LongSparseArray; -import android.text.TextUtils; - -import com.bluelinelabs.logansquare.JsonMapper; -import com.bluelinelabs.logansquare.LoganSquare; - -import org.apache.commons.lang3.ArrayUtils; -import org.apache.commons.lang3.StringUtils; -import org.mariotaku.microblog.library.twitter.model.Activity; -import org.mariotaku.sqliteqb.library.ArgsArray; -import org.mariotaku.sqliteqb.library.Columns; -import org.mariotaku.sqliteqb.library.Columns.Column; -import org.mariotaku.sqliteqb.library.Expression; -import org.mariotaku.sqliteqb.library.OrderBy; -import org.mariotaku.sqliteqb.library.SQLFunctions; -import org.mariotaku.sqliteqb.library.SQLQueryBuilder; -import org.mariotaku.sqliteqb.library.Table; -import org.mariotaku.sqliteqb.library.Tables; -import org.mariotaku.sqliteqb.library.query.SQLSelectQuery; -import org.mariotaku.twidere.extension.model.AccountExtensionsKt; -import org.mariotaku.twidere.model.FiltersData; -import org.mariotaku.twidere.model.FiltersData$BaseItemValuesCreator; -import org.mariotaku.twidere.model.FiltersData$UserItemValuesCreator; -import org.mariotaku.twidere.model.ParcelableActivity; -import org.mariotaku.twidere.model.ParcelableActivityCursorIndices; -import org.mariotaku.twidere.model.ParcelableActivityValuesCreator; -import org.mariotaku.twidere.model.ParcelableStatus; -import org.mariotaku.twidere.model.ParcelableUser; -import org.mariotaku.twidere.model.UserFollowState; -import org.mariotaku.twidere.model.UserKey; -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; -import org.mariotaku.twidere.provider.TwidereDataStore.AccountSupportColumns; -import org.mariotaku.twidere.provider.TwidereDataStore.Activities; -import org.mariotaku.twidere.provider.TwidereDataStore.CacheFiles; -import org.mariotaku.twidere.provider.TwidereDataStore.CachedHashtags; -import org.mariotaku.twidere.provider.TwidereDataStore.CachedImages; -import org.mariotaku.twidere.provider.TwidereDataStore.CachedRelationships; -import org.mariotaku.twidere.provider.TwidereDataStore.CachedStatuses; -import org.mariotaku.twidere.provider.TwidereDataStore.CachedTrends; -import org.mariotaku.twidere.provider.TwidereDataStore.CachedUsers; -import org.mariotaku.twidere.provider.TwidereDataStore.DNS; -import org.mariotaku.twidere.provider.TwidereDataStore.DirectMessages; -import org.mariotaku.twidere.provider.TwidereDataStore.Drafts; -import org.mariotaku.twidere.provider.TwidereDataStore.Filters; -import org.mariotaku.twidere.provider.TwidereDataStore.Messages; -import org.mariotaku.twidere.provider.TwidereDataStore.Notifications; -import org.mariotaku.twidere.provider.TwidereDataStore.Permissions; -import org.mariotaku.twidere.provider.TwidereDataStore.Preferences; -import org.mariotaku.twidere.provider.TwidereDataStore.SavedSearches; -import org.mariotaku.twidere.provider.TwidereDataStore.SearchHistory; -import org.mariotaku.twidere.provider.TwidereDataStore.Statuses; -import org.mariotaku.twidere.provider.TwidereDataStore.Suggestions; -import org.mariotaku.twidere.provider.TwidereDataStore.Tabs; -import org.mariotaku.twidere.provider.TwidereDataStore.UnreadCounts; -import org.mariotaku.twidere.util.content.ContentResolverUtils; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -import static org.mariotaku.twidere.TwidereConstants.ACCOUNT_USER_DATA_KEY; -import static org.mariotaku.twidere.TwidereConstants.DEFAULT_DATABASE_ITEM_LIMIT; -import static org.mariotaku.twidere.TwidereConstants.EXTRA_EXTRAS; -import static org.mariotaku.twidere.TwidereConstants.KEY_DATABASE_ITEM_LIMIT; -import static org.mariotaku.twidere.TwidereConstants.SHARED_PREFERENCES_NAME; -import static org.mariotaku.twidere.TwidereConstants.TABLE_ID_ACTIVITIES_ABOUT_ME; -import static org.mariotaku.twidere.TwidereConstants.TABLE_ID_ACTIVITIES_BY_FRIENDS; -import static org.mariotaku.twidere.TwidereConstants.TABLE_ID_CACHED_HASHTAGS; -import static org.mariotaku.twidere.TwidereConstants.TABLE_ID_CACHED_RELATIONSHIPS; -import static org.mariotaku.twidere.TwidereConstants.TABLE_ID_CACHED_STATUSES; -import static org.mariotaku.twidere.TwidereConstants.TABLE_ID_CACHED_USERS; -import static org.mariotaku.twidere.TwidereConstants.TABLE_ID_DRAFTS; -import static org.mariotaku.twidere.TwidereConstants.TABLE_ID_FILTERED_KEYWORDS; -import static org.mariotaku.twidere.TwidereConstants.TABLE_ID_FILTERED_LINKS; -import static org.mariotaku.twidere.TwidereConstants.TABLE_ID_FILTERED_SOURCES; -import static org.mariotaku.twidere.TwidereConstants.TABLE_ID_FILTERED_USERS; -import static org.mariotaku.twidere.TwidereConstants.TABLE_ID_FILTERS_SUBSCRIPTIONS; -import static org.mariotaku.twidere.TwidereConstants.TABLE_ID_MESSAGES; -import static org.mariotaku.twidere.TwidereConstants.TABLE_ID_MESSAGES_CONVERSATIONS; -import static org.mariotaku.twidere.TwidereConstants.TABLE_ID_SAVED_SEARCHES; -import static org.mariotaku.twidere.TwidereConstants.TABLE_ID_SEARCH_HISTORY; -import static org.mariotaku.twidere.TwidereConstants.TABLE_ID_STATUSES; -import static org.mariotaku.twidere.TwidereConstants.TABLE_ID_TABS; -import static org.mariotaku.twidere.TwidereConstants.TABLE_ID_TRENDS_LOCAL; -import static org.mariotaku.twidere.TwidereConstants.VIRTUAL_TABLE_ID_ALL_PREFERENCES; -import static org.mariotaku.twidere.TwidereConstants.VIRTUAL_TABLE_ID_CACHED_IMAGES; -import static org.mariotaku.twidere.TwidereConstants.VIRTUAL_TABLE_ID_CACHED_USERS_WITH_RELATIONSHIP; -import static org.mariotaku.twidere.TwidereConstants.VIRTUAL_TABLE_ID_CACHED_USERS_WITH_SCORE; -import static org.mariotaku.twidere.TwidereConstants.VIRTUAL_TABLE_ID_CACHE_FILES; -import static org.mariotaku.twidere.TwidereConstants.VIRTUAL_TABLE_ID_DATABASE_PREPARE; -import static org.mariotaku.twidere.TwidereConstants.VIRTUAL_TABLE_ID_DNS; -import static org.mariotaku.twidere.TwidereConstants.VIRTUAL_TABLE_ID_DRAFTS_NOTIFICATIONS; -import static org.mariotaku.twidere.TwidereConstants.VIRTUAL_TABLE_ID_DRAFTS_UNSENT; -import static org.mariotaku.twidere.TwidereConstants.VIRTUAL_TABLE_ID_EMPTY; -import static org.mariotaku.twidere.TwidereConstants.VIRTUAL_TABLE_ID_NOTIFICATIONS; -import static org.mariotaku.twidere.TwidereConstants.VIRTUAL_TABLE_ID_NULL; -import static org.mariotaku.twidere.TwidereConstants.VIRTUAL_TABLE_ID_PERMISSIONS; -import static org.mariotaku.twidere.TwidereConstants.VIRTUAL_TABLE_ID_PREFERENCES; -import static org.mariotaku.twidere.TwidereConstants.VIRTUAL_TABLE_ID_RAW_QUERY; -import static org.mariotaku.twidere.TwidereConstants.VIRTUAL_TABLE_ID_SUGGESTIONS_AUTO_COMPLETE; -import static org.mariotaku.twidere.TwidereConstants.VIRTUAL_TABLE_ID_SUGGESTIONS_SEARCH; -import static org.mariotaku.twidere.TwidereConstants.VIRTUAL_TABLE_ID_UNREAD_COUNTS; -import static org.mariotaku.twidere.TwidereConstants.VIRTUAL_TABLE_ID_UNREAD_COUNTS_BY_TYPE; - -/** - * Created by mariotaku on 15/11/28. - */ -public class DataStoreUtils { - - public static final Uri[] STATUSES_URIS = new Uri[]{Statuses.CONTENT_URI, CachedStatuses.CONTENT_URI}; - public static final Uri[] CACHE_URIS = new Uri[]{CachedUsers.CONTENT_URI, CachedStatuses.CONTENT_URI, CachedHashtags.CONTENT_URI, CachedTrends.Local.CONTENT_URI}; - public static final Uri[] DIRECT_MESSAGES_URIS = new Uri[]{DirectMessages.Inbox.CONTENT_URI, DirectMessages.Outbox.CONTENT_URI}; - public static final Uri[] ACTIVITIES_URIS = new Uri[]{Activities.AboutMe.CONTENT_URI}; - - private static final UriMatcher CONTENT_PROVIDER_URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH); - - static { - 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, Activities.ByFriends.CONTENT_PATH, - TABLE_ID_ACTIVITIES_BY_FRIENDS); - 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, Messages.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, Notifications.CONTENT_PATH, - VIRTUAL_TABLE_ID_NOTIFICATIONS); - CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, Notifications.CONTENT_PATH + "/#", - VIRTUAL_TABLE_ID_NOTIFICATIONS); - CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, Notifications.CONTENT_PATH + "/#/*", - VIRTUAL_TABLE_ID_NOTIFICATIONS); - CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, Permissions.CONTENT_PATH, - VIRTUAL_TABLE_ID_PERMISSIONS); - CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, DNS.CONTENT_PATH + "/*", - VIRTUAL_TABLE_ID_DNS); - CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, CachedImages.CONTENT_PATH, - VIRTUAL_TABLE_ID_CACHED_IMAGES); - CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, CacheFiles.CONTENT_PATH + "/*", - VIRTUAL_TABLE_ID_CACHE_FILES); - CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, Preferences.CONTENT_PATH, - VIRTUAL_TABLE_ID_ALL_PREFERENCES); - CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, Preferences.CONTENT_PATH + "/*", - VIRTUAL_TABLE_ID_PREFERENCES); - CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, UnreadCounts.CONTENT_PATH, - VIRTUAL_TABLE_ID_UNREAD_COUNTS); - CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, UnreadCounts.CONTENT_PATH + "/#", - VIRTUAL_TABLE_ID_UNREAD_COUNTS); - CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, UnreadCounts.CONTENT_PATH + "/#/#/*", - VIRTUAL_TABLE_ID_UNREAD_COUNTS); - CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, UnreadCounts.ByType.CONTENT_PATH + "/*", - VIRTUAL_TABLE_ID_UNREAD_COUNTS_BY_TYPE); - 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); - } - - @NonNull - public static String[] getNewestMessageIds(@NonNull final Context context, @NonNull final Uri uri, - @NonNull final UserKey[] accountKeys) { - return getStringFieldArray(context, uri, accountKeys, DirectMessages.ACCOUNT_KEY, - DirectMessages.MESSAGE_ID, new OrderBy(SQLFunctions.MAX(DirectMessages.MESSAGE_TIMESTAMP))); - } - - @NonNull - public static String[] getNewestStatusIds(@NonNull final Context context, @NonNull final Uri uri, - @NonNull final UserKey[] accountKeys) { - return getStringFieldArray(context, uri, accountKeys, Statuses.ACCOUNT_KEY, - Statuses.STATUS_ID, new OrderBy(SQLFunctions.MAX(Statuses.STATUS_TIMESTAMP))); - } - - - @NonNull - public static long[] getNewestStatusSortIds(@NonNull final Context context, @NonNull final Uri uri, - @NonNull final UserKey[] accountKeys) { - return getLongFieldArray(context, uri, accountKeys, Statuses.ACCOUNT_KEY, - Statuses.SORT_ID, new OrderBy(SQLFunctions.MAX(Statuses.STATUS_TIMESTAMP))); - } - - - @NonNull - public static String[] getOldestMessageIds(@NonNull final Context context, @NonNull final Uri uri, - @NonNull final UserKey[] accountKeys) { - return getStringFieldArray(context, uri, accountKeys, DirectMessages.ACCOUNT_KEY, - DirectMessages.MESSAGE_ID, new OrderBy(SQLFunctions.MIN(DirectMessages.MESSAGE_TIMESTAMP))); - } - - @NonNull - public static String[] getOldestStatusIds(@NonNull final Context context, @NonNull final Uri uri, - @NonNull final UserKey[] accountKeys) { - return getStringFieldArray(context, uri, accountKeys, Statuses.ACCOUNT_KEY, - Statuses.STATUS_ID, new OrderBy(SQLFunctions.MIN(Statuses.STATUS_TIMESTAMP))); - } - - - @NonNull - public static long[] getOldestStatusSortIds(@NonNull final Context context, @NonNull final Uri uri, - @NonNull final UserKey[] accountKeys) { - return getLongFieldArray(context, uri, accountKeys, Statuses.ACCOUNT_KEY, - Statuses.SORT_ID, new OrderBy(SQLFunctions.MIN(Statuses.STATUS_TIMESTAMP))); - } - - @NonNull - public static String[] getNewestActivityMaxPositions(final Context context, final Uri uri, - final UserKey[] accountKeys) { - return getStringFieldArray(context, uri, accountKeys, Activities.ACCOUNT_KEY, - Activities.MAX_REQUEST_POSITION, new OrderBy(SQLFunctions.MAX(Activities.TIMESTAMP))); - } - - @NonNull - public static String[] getOldestActivityMaxPositions(@NonNull final Context context, - @NonNull final Uri uri, - @NonNull final UserKey[] accountKeys) { - return getStringFieldArray(context, uri, accountKeys, Activities.ACCOUNT_KEY, - Activities.MAX_REQUEST_POSITION, new OrderBy(SQLFunctions.MIN(Activities.TIMESTAMP))); - } - - @NonNull - public static long[] getNewestActivityMaxSortPositions(final Context context, final Uri uri, - final UserKey[] accountKeys) { - return getLongFieldArray(context, uri, accountKeys, Activities.ACCOUNT_KEY, - Activities.MAX_SORT_POSITION, new OrderBy(SQLFunctions.MAX(Activities.TIMESTAMP))); - } - - @NonNull - public static long[] getOldestActivityMaxSortPositions(@NonNull final Context context, - @NonNull final Uri uri, - @NonNull final UserKey[] accountKeys) { - return getLongFieldArray(context, uri, accountKeys, Activities.ACCOUNT_KEY, - Activities.MAX_SORT_POSITION, new OrderBy(SQLFunctions.MIN(Activities.TIMESTAMP))); - } - - public static int getStatusCount(final Context context, final Uri uri, final UserKey accountId) { - final String where = Expression.equalsArgs(AccountSupportColumns.ACCOUNT_KEY).getSQL(); - final String[] whereArgs = {accountId.toString()}; - return queryCount(context, uri, where, whereArgs); - } - - public static int getActivitiesCount(@NonNull final Context context, @NonNull final Uri uri, - @NonNull final UserKey accountKey) { - final String where = Expression.equalsArgs(AccountSupportColumns.ACCOUNT_KEY).getSQL(); - return queryCount(context, uri, where, new String[]{accountKey.toString()}); - } - - - @NonNull - public static UserKey[] getFilteredUserIds(Context context) { - if (context == null) return new UserKey[0]; - final ContentResolver resolver = context.getContentResolver(); - final String[] projection = {Filters.Users.USER_KEY}; - final Cursor cur = resolver.query(Filters.Users.CONTENT_URI, projection, null, null, null); - if (cur == null) return new UserKey[0]; - try { - final UserKey[] ids = new UserKey[cur.getCount()]; - cur.moveToFirst(); - int i = 0; - while (!cur.isAfterLast()) { - ids[i] = UserKey.valueOf(cur.getString(0)); - cur.moveToNext(); - i++; - } - cur.close(); - return ids; - } finally { - cur.close(); - } - } - - public static String getAccountDisplayName(final Context context, final UserKey accountKey, final boolean nameFirst) { - final String name; - if (nameFirst) { - name = getAccountName(context, accountKey); - } else { - name = String.format("@%s", getAccountScreenName(context, accountKey)); - } - return name; - } - - public static String getAccountName(@NonNull final Context context, final UserKey accountKey) { - AccountManager am = AccountManager.get(context); - Account account = AccountUtils.findByAccountKey(am, accountKey); - if (account == null) return null; - - return AccountExtensionsKt.getAccountUser(account, am).name; - } - - public static String getAccountScreenName(final Context context, final UserKey accountKey) { - if (context == null) return null; - AccountManager am = AccountManager.get(context); - Account account = AccountUtils.findByAccountKey(am, accountKey); - if (account == null) return null; - return AccountExtensionsKt.getAccountUser(account, am).screen_name; - } - - @NonNull - public static UserKey[] getActivatedAccountKeys(@NonNull final Context context) { - AccountManager am = AccountManager.get(context); - List keys = new ArrayList<>(); - for (Account account : AccountUtils.getAccounts(am)) { - if (AccountExtensionsKt.isActivated(account, am)) { - keys.add(AccountExtensionsKt.getAccountKey(account, am)); - } - } - return keys.toArray(new UserKey[keys.size()]); - } - - public static int getStatusesCount(@NonNull final Context context, - @NonNull final SharedPreferences preferences, - final Uri uri, - @Nullable final Bundle extraArgs, final long compare, - String compareColumn, boolean greaterThan, - @Nullable UserKey[] accountKeys) { - if (accountKeys == null) { - accountKeys = getActivatedAccountKeys(context); - } - - List expressions = new ArrayList<>(); - List expressionArgs = new ArrayList<>(); - - expressions.add(Expression.inArgs(new Column(Statuses.ACCOUNT_KEY), accountKeys.length)); - for (UserKey accountKey : accountKeys) { - expressionArgs.add(accountKey.toString()); - } - - if (greaterThan) { - expressions.add(Expression.greaterThanArgs(compareColumn)); - } else { - expressions.add(Expression.lesserThanArgs(compareColumn)); - } - expressionArgs.add(String.valueOf(compare)); - - expressions.add(DataStoreFunctionsKt.buildStatusFilterWhereClause(preferences, getTableNameByUri(uri), null)); - - if (extraArgs != null) { - Parcelable extras = extraArgs.getParcelable(EXTRA_EXTRAS); - if (extras instanceof HomeTabExtras) { - processTabExtras(expressions, expressionArgs, (HomeTabExtras) extras); - } - } - - Expression selection = Expression.and(expressions.toArray(new Expression[expressions.size()])); - return queryCount(context, uri, selection.getSQL(), expressionArgs.toArray(new String[expressionArgs.size()])); - } - - public static int getActivitiesCount(final Context context, final Uri uri, final long compare, - String compareColumn, boolean greaterThan, UserKey... accountKeys) { - if (context == null) return 0; - if (accountKeys == null) { - accountKeys = getActivatedAccountKeys(context); - } - final Expression selection = Expression.and( - Expression.inArgs(new Column(Activities.ACCOUNT_KEY), accountKeys.length), - greaterThan ? Expression.greaterThanArgs(compareColumn) : Expression.lesserThanArgs(compareColumn), - buildActivityFilterWhereClause(getTableNameByUri(uri), null) - ); - final String[] whereArgs = new String[accountKeys.length + 1]; - for (int i = 0; i < accountKeys.length; i++) { - whereArgs[i] = accountKeys[i].toString(); - } - whereArgs[accountKeys.length] = String.valueOf(compare); - return queryCount(context, uri, selection.getSQL(), whereArgs); - } - - public static int getActivitiesCount(@NonNull final Context context, final Uri uri, - final Expression extraWhere, final String[] extraWhereArgs, - final long since, String sinceColumn, boolean followingOnly, - @Nullable UserKey[] accountKeys) { - if (accountKeys == null) { - accountKeys = getActivatedAccountKeys(context); - } - Expression[] expressions; - if (extraWhere != null) { - expressions = new Expression[4]; - expressions[3] = extraWhere; - } else { - expressions = new Expression[3]; - } - expressions[0] = Expression.inArgs(new Column(Activities.ACCOUNT_KEY), accountKeys.length); - expressions[1] = Expression.greaterThanArgs(sinceColumn); - expressions[2] = buildActivityFilterWhereClause(getTableNameByUri(uri), null); - final Expression selection = Expression.and(expressions); - String[] selectionArgs; - if (extraWhereArgs != null) { - selectionArgs = new String[accountKeys.length + 1 + extraWhereArgs.length]; - System.arraycopy(extraWhereArgs, 0, selectionArgs, accountKeys.length + 1, - extraWhereArgs.length); - } else { - selectionArgs = new String[accountKeys.length + 1]; - } - for (int i = 0; i < accountKeys.length; i++) { - selectionArgs[i] = accountKeys[i].toString(); - } - selectionArgs[accountKeys.length] = String.valueOf(since); - // If followingOnly option is on, we have to iterate over items - if (followingOnly) { - final ContentResolver resolver = context.getContentResolver(); - final String[] projection = new String[]{Activities.SOURCES}; - final Cursor cur = resolver.query(uri, projection, selection.getSQL(), selectionArgs, null); - if (cur == null) return -1; - try { - final JsonMapper mapper = LoganSquare.mapperFor(UserFollowState.class); - int total = 0; - cur.moveToFirst(); - while (!cur.isAfterLast()) { - final String string = cur.getString(0); - if (TextUtils.isEmpty(string)) continue; - boolean hasFollowing = false; - try { - for (UserFollowState state : mapper.parseList(string)) { - if (state.is_following) { - hasFollowing = true; - break; - } - } - } catch (IOException e) { - continue; - } - if (hasFollowing) { - total++; - } - cur.moveToNext(); - } - return total; - } finally { - cur.close(); - } - } - return queryCount(context, uri, selection.getSQL(), selectionArgs); - } - - public static int getTableId(final Uri uri) { - if (uri == null) return -1; - return CONTENT_PROVIDER_URI_MATCHER.match(uri); - } - - public static String getTableNameById(final int id) { - switch (id) { - case TABLE_ID_STATUSES: - return Statuses.TABLE_NAME; - case TABLE_ID_ACTIVITIES_ABOUT_ME: - return Activities.AboutMe.TABLE_NAME; - case TABLE_ID_ACTIVITIES_BY_FRIENDS: - return Activities.ByFriends.TABLE_NAME; - case TABLE_ID_DRAFTS: - return Drafts.TABLE_NAME; - case TABLE_ID_FILTERED_USERS: - return Filters.Users.TABLE_NAME; - case TABLE_ID_FILTERED_KEYWORDS: - return Filters.Keywords.TABLE_NAME; - case TABLE_ID_FILTERED_SOURCES: - return Filters.Sources.TABLE_NAME; - case TABLE_ID_FILTERED_LINKS: - return Filters.Links.TABLE_NAME; - case TABLE_ID_FILTERS_SUBSCRIPTIONS: - return Filters.Subscriptions.TABLE_NAME; - case TABLE_ID_MESSAGES: - return Messages.TABLE_NAME; - case TABLE_ID_MESSAGES_CONVERSATIONS: - return Messages.Conversations.TABLE_NAME; - case TABLE_ID_TRENDS_LOCAL: - return CachedTrends.Local.TABLE_NAME; - case TABLE_ID_TABS: - return Tabs.TABLE_NAME; - case TABLE_ID_CACHED_STATUSES: - return CachedStatuses.TABLE_NAME; - case TABLE_ID_CACHED_USERS: - return CachedUsers.TABLE_NAME; - case TABLE_ID_CACHED_HASHTAGS: - return CachedHashtags.TABLE_NAME; - case TABLE_ID_CACHED_RELATIONSHIPS: - return CachedRelationships.TABLE_NAME; - case TABLE_ID_SAVED_SEARCHES: - return SavedSearches.TABLE_NAME; - case TABLE_ID_SEARCH_HISTORY: - return SearchHistory.TABLE_NAME; - default: - return null; - } - } - - public static String getTableNameByUri(final Uri uri) { - if (uri == null) return null; - return getTableNameById(getTableId(uri)); - } - - @NonNull - public static Expression buildActivityFilterWhereClause(@NonNull final String table, final Expression extraSelection) { - final SQLSelectQuery filteredUsersQuery = SQLQueryBuilder - .select(new Column(new Table(Filters.Users.TABLE_NAME), Filters.Users.USER_KEY)) - .from(new Tables(Filters.Users.TABLE_NAME)) - .build(); - final Expression filteredUsersWhere = Expression.or( - Expression.in(new Column(new Table(table), Activities.STATUS_USER_KEY), filteredUsersQuery), - Expression.in(new Column(new Table(table), Activities.STATUS_RETWEETED_BY_USER_KEY), filteredUsersQuery), - Expression.in(new Column(new Table(table), Activities.STATUS_QUOTED_USER_KEY), filteredUsersQuery) - ); - final SQLSelectQuery.Builder filteredIdsQueryBuilder = SQLQueryBuilder - .select(new Column(new Table(table), Activities._ID)) - .from(new Tables(table)) - .where(filteredUsersWhere) - .union() - .select(new Columns(new Column(new Table(table), Activities._ID))) - .from(new Tables(table, Filters.Sources.TABLE_NAME)) - .where(Expression.or( - Expression.likeRaw(new Column(new Table(table), Activities.STATUS_SOURCE), - "'%>'||" + Filters.Sources.TABLE_NAME + "." + Filters.Sources.VALUE + "||'%'"), - Expression.likeRaw(new Column(new Table(table), Activities.STATUS_QUOTE_SOURCE), - "'%>'||" + Filters.Sources.TABLE_NAME + "." + Filters.Sources.VALUE + "||'%'") - )) - .union() - .select(new Columns(new Column(new Table(table), Activities._ID))) - .from(new Tables(table, Filters.Keywords.TABLE_NAME)) - .where(Expression.or( - Expression.likeRaw(new Column(new Table(table), Activities.STATUS_TEXT_PLAIN), - "'%'||" + Filters.Keywords.TABLE_NAME + "." + Filters.Keywords.VALUE + "||'%'"), - Expression.likeRaw(new Column(new Table(table), Activities.STATUS_QUOTE_TEXT_PLAIN), - "'%'||" + Filters.Keywords.TABLE_NAME + "." + Filters.Keywords.VALUE + "||'%'") - )) - .union() - .select(new Columns(new Column(new Table(table), Activities._ID))) - .from(new Tables(table, Filters.Links.TABLE_NAME)) - .where(Expression.or( - Expression.likeRaw(new Column(new Table(table), Activities.STATUS_SPANS), - "'%'||" + Filters.Links.TABLE_NAME + "." + Filters.Links.VALUE + "||'%'"), - Expression.likeRaw(new Column(new Table(table), Activities.STATUS_QUOTE_SPANS), - "'%'||" + Filters.Links.TABLE_NAME + "." + Filters.Links.VALUE + "||'%'") - )); - final Expression filterExpression = Expression.or( - Expression.notIn(new Column(new Table(table), Activities._ID), filteredIdsQueryBuilder.build()), - Expression.equals(new Column(new Table(table), Activities.IS_GAP), 1) - ); - if (extraSelection != null) { - return Expression.and(filterExpression, extraSelection); - } - return filterExpression; - } - - @NonNull - public static int[] getAccountColors(@NonNull final Context context, @NonNull final UserKey[] accountKeys) { - AccountManager am = AccountManager.get(context); - final int[] colors = new int[accountKeys.length]; - for (int i = 0; i < accountKeys.length; i++) { - Account account = AccountUtils.findByAccountKey(am, accountKeys[i]); - if (account != null) { - colors[i] = AccountExtensionsKt.getColor(account, am); - } - } - return colors; - } - - @Nullable - public static UserKey findAccountKeyByScreenName(@NonNull final Context context, @NonNull final String screenName) { - AccountManager am = AccountManager.get(context); - for (Account account : AccountUtils.getAccounts(am)) { - ParcelableUser user = AccountExtensionsKt.getAccountUser(account, am); - if (StringUtils.equalsIgnoreCase(screenName, user.screen_name)) { - return user.key; - } - } - return null; - } - - @NonNull - public static UserKey[] getAccountKeys(final Context context) { - final AccountManager am = AccountManager.get(context); - final Account[] accounts = AccountUtils.getAccounts(am); - final List keys = new ArrayList<>(accounts.length); - for (Account account : accounts) { - String keyString = am.getUserData(account, ACCOUNT_USER_DATA_KEY); - if (keyString == null) continue; - keys.add(UserKey.valueOf(keyString)); - } - return keys.toArray(new UserKey[keys.size()]); - } - - @Nullable - public static UserKey findAccountKey(@NonNull final Context context, @NonNull final String accountId) { - AccountManager am = AccountManager.get(context); - for (Account account : AccountUtils.getAccounts(am)) { - UserKey key = AccountExtensionsKt.getAccountKey(account, am); - if (accountId.equals(key.getId())) { - return key; - } - } - return null; - } - - public static boolean hasAccount(@NonNull final Context context) { - return AccountUtils.getAccounts(AccountManager.get(context)).length > 0; - } - - public static synchronized void cleanDatabasesByItemLimit(final Context context) { - if (context == null) return; - final ContentResolver resolver = context.getContentResolver(); - final int itemLimit = context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE).getInt( - KEY_DATABASE_ITEM_LIMIT, DEFAULT_DATABASE_ITEM_LIMIT); - - for (final UserKey accountKey : getAccountKeys(context)) { - // Clean statuses. - for (final Uri uri : STATUSES_URIS) { - if (CachedStatuses.CONTENT_URI.equals(uri)) { - continue; - } - final String table = getTableNameByUri(uri); - final SQLSelectQuery.Builder qb = new SQLSelectQuery.Builder(); - qb.select(new Column(Statuses._ID)) - .from(new Tables(table)) - .where(Expression.equalsArgs(Statuses.ACCOUNT_KEY)) - .orderBy(new OrderBy(Statuses.POSITION_KEY, false)) - .limit(itemLimit); - final Expression where = Expression.and( - Expression.notIn(new Column(Statuses._ID), qb.build()), - Expression.equalsArgs(Statuses.ACCOUNT_KEY) - ); - final String[] whereArgs = {String.valueOf(accountKey), String.valueOf(accountKey)}; - resolver.delete(uri, where.getSQL(), whereArgs); - } - for (final Uri uri : ACTIVITIES_URIS) { - final String table = getTableNameByUri(uri); - final SQLSelectQuery.Builder qb = new SQLSelectQuery.Builder(); - qb.select(new Column(Activities._ID)) - .from(new Tables(table)) - .where(Expression.equalsArgs(Activities.ACCOUNT_KEY)) - .orderBy(new OrderBy(Activities.TIMESTAMP, false)) - .limit(itemLimit); - final Expression where = Expression.and( - Expression.notIn(new Column(Activities._ID), qb.build()), - Expression.equalsArgs(Activities.ACCOUNT_KEY) - ); - final String[] whereArgs = {String.valueOf(accountKey), String.valueOf(accountKey)}; - resolver.delete(uri, where.getSQL(), whereArgs); - } - } - // Clean cached values. - for (final Uri uri : CACHE_URIS) { - final String table = getTableNameByUri(uri); - if (table == null) continue; - final SQLSelectQuery.Builder qb = new SQLSelectQuery.Builder(); - qb.select(new Column(BaseColumns._ID)) - .from(new Tables(table)) - .orderBy(new OrderBy(BaseColumns._ID, false)) - .limit(itemLimit * 20); - final Expression where = Expression.notIn(new Column(BaseColumns._ID), qb.build()); - resolver.delete(uri, where.getSQL(), null); - } - } - - public static boolean isFilteringUser(Context context, UserKey userKey) { - return isFilteringUser(context, userKey.toString()); - } - - public static boolean isFilteringUser(Context context, String userKey) { - final ContentResolver cr = context.getContentResolver(); - final Expression where = Expression.equalsArgs(Filters.Users.USER_KEY); - final Cursor c = cr.query(Filters.Users.CONTENT_URI, new String[]{SQLFunctions.COUNT()}, - where.getSQL(), new String[]{userKey}, null); - if (c == null) return false; - try { - if (c.moveToFirst()) { - return c.getLong(0) > 0; - } - } finally { - c.close(); - } - return false; - } - - @NonNull - static String[] getStringFieldArray(@NonNull Context context, @NonNull Uri uri, - @NonNull UserKey[] keys, @NonNull String keyField, - @NonNull String valueField, @Nullable OrderBy sortExpression) { - return getFieldArray(context, uri, keys, keyField, valueField, sortExpression, new FieldArrayCreator() { - @Override - public String[] newArray(int size) { - return new String[size]; - } - - @Override - public void assign(String[] array, int arrayIdx, Cursor cur, int colIdx) { - array[arrayIdx] = cur.getString(colIdx); - } - }); - } - - @NonNull - static long[] getLongFieldArray(@NonNull Context context, @NonNull Uri uri, - @NonNull UserKey[] keys, @NonNull String keyField, - @NonNull String valueField, @Nullable OrderBy sortExpression) { - return getFieldArray(context, uri, keys, keyField, valueField, sortExpression, new FieldArrayCreator() { - @Override - public long[] newArray(int size) { - return new long[size]; - } - - @Override - public void assign(long[] array, int arrayIdx, Cursor cur, int colIdx) { - array[arrayIdx] = cur.getLong(colIdx); - } - }); - } - - @NonNull - static T getFieldArray(@NonNull Context context, @NonNull Uri uri, - @NonNull UserKey[] keys, @NonNull String keyField, - @NonNull String valueField, @Nullable OrderBy sortExpression, - @NonNull FieldArrayCreator creator) { - final ContentResolver resolver = context.getContentResolver(); - final T messageIds = creator.newArray(keys.length); - final String[] selectionArgs = TwidereArrayUtils.toStringArray(keys); - final SQLSelectQuery.Builder builder = SQLQueryBuilder.select(new Columns(keyField, valueField)) - .from(new Table(getTableNameByUri(uri))) - .groupBy(new Column(keyField)) - .having(Expression.in(new Column(keyField), new ArgsArray(keys.length))); - if (sortExpression != null) { - builder.orderBy(sortExpression); - } - final Cursor cur = resolver.query(Uri.withAppendedPath(TwidereDataStore.CONTENT_URI_RAW_QUERY, - builder.buildSQL()), null, null, selectionArgs, null); - if (cur == null) return messageIds; - try { - while (cur.moveToNext()) { - final String string = cur.getString(0); - final UserKey accountKey = string != null ? UserKey.valueOf(string) : null; - int idx = ArrayUtils.indexOf(keys, accountKey); - if (idx < 0) continue; - creator.assign(messageIds, idx, cur, 1); - } - return messageIds; - } finally { - cur.close(); - } - } - - public static void deleteStatus(@NonNull ContentResolver cr, @NonNull UserKey accountKey, - @NonNull String statusId, @Nullable ParcelableStatus status) { - - final String host = accountKey.getHost(); - final String deleteWhere, updateWhere; - final String[] deleteWhereArgs, updateWhereArgs; - if (host != null) { - deleteWhere = Expression.and( - Expression.likeRaw(new Column(Statuses.ACCOUNT_KEY), "'%@'||?"), - Expression.or( - Expression.equalsArgs(Statuses.STATUS_ID), - Expression.equalsArgs(Statuses.RETWEET_ID) - )).getSQL(); - deleteWhereArgs = new String[]{host, statusId, statusId}; - updateWhere = Expression.and( - Expression.likeRaw(new Column(Statuses.ACCOUNT_KEY), "'%@'||?"), - Expression.equalsArgs(Statuses.MY_RETWEET_ID) - ).getSQL(); - updateWhereArgs = new String[]{host, statusId}; - } else { - deleteWhere = Expression.or( - Expression.equalsArgs(Statuses.STATUS_ID), - Expression.equalsArgs(Statuses.RETWEET_ID) - ).getSQL(); - deleteWhereArgs = new String[]{statusId, statusId}; - updateWhere = Expression.equalsArgs(Statuses.MY_RETWEET_ID).getSQL(); - updateWhereArgs = new String[]{statusId}; - } - for (final Uri uri : STATUSES_URIS) { - cr.delete(uri, deleteWhere, deleteWhereArgs); - if (status != null) { - final ContentValues values = new ContentValues(); - values.putNull(Statuses.MY_RETWEET_ID); - values.put(Statuses.RETWEET_COUNT, status.retweet_count - 1); - cr.update(uri, values, updateWhere, updateWhereArgs); - } - } - } - - public static void deleteActivityStatus(@NonNull ContentResolver cr, @NonNull UserKey accountKey, - @NonNull final String statusId, @Nullable final ParcelableStatus result) { - - final String host = accountKey.getHost(); - final String deleteWhere, updateWhere; - final String[] deleteWhereArgs, updateWhereArgs; - if (host != null) { - deleteWhere = Expression.and( - Expression.likeRaw(new Column(Activities.ACCOUNT_KEY), "'%@'||?"), - Expression.or( - Expression.equalsArgs(Activities.STATUS_ID), - Expression.equalsArgs(Activities.STATUS_RETWEET_ID) - )).getSQL(); - deleteWhereArgs = new String[]{host, statusId, statusId}; - updateWhere = Expression.and( - Expression.likeRaw(new Column(Activities.ACCOUNT_KEY), "'%@'||?"), - Expression.equalsArgs(Activities.STATUS_MY_RETWEET_ID) - ).getSQL(); - updateWhereArgs = new String[]{host, statusId}; - } else { - deleteWhere = Expression.or( - Expression.equalsArgs(Activities.STATUS_ID), - Expression.equalsArgs(Activities.STATUS_RETWEET_ID) - ).getSQL(); - deleteWhereArgs = new String[]{statusId, statusId}; - updateWhere = Expression.equalsArgs(Activities.STATUS_MY_RETWEET_ID).getSQL(); - updateWhereArgs = new String[]{statusId}; - } - for (final Uri uri : ACTIVITIES_URIS) { - cr.delete(uri, deleteWhere, deleteWhereArgs); - updateActivity(cr, uri, updateWhere, updateWhereArgs, new UpdateActivityAction() { - - @Override - public void process(ParcelableActivity activity) { - activity.status_my_retweet_id = null; - ParcelableStatus[][] statusesMatrix = {activity.target_statuses, - activity.target_object_statuses}; - for (ParcelableStatus[] statusesArray : statusesMatrix) { - if (statusesArray == null) continue; - for (ParcelableStatus status : statusesArray) { - if (statusId.equals(status.id) || statusId.equals(status.retweet_id) - || statusId.equals(status.my_retweet_id)) { - status.my_retweet_id = null; - if (result != null) { - status.reply_count = result.reply_count; - status.retweet_count = result.retweet_count - 1; - status.favorite_count = result.favorite_count; - } - } - } - } - } - }); - } - } - - @WorkerThread - public static void updateActivity(ContentResolver cr, Uri uri, String where, String[] whereArgs, - UpdateActivityAction action) { - final Cursor c = cr.query(uri, Activities.COLUMNS, where, whereArgs, null); - if (c == null) return; - LongSparseArray values = new LongSparseArray<>(); - try { - ParcelableActivityCursorIndices ci = new ParcelableActivityCursorIndices(c); - c.moveToFirst(); - while (!c.isAfterLast()) { - final ParcelableActivity activity = ci.newObject(c); - action.process(activity); - values.put(activity._id, ParcelableActivityValuesCreator.create(activity)); - c.moveToNext(); - } - } catch (IOException e) { - return; - } finally { - c.close(); - } - String updateWhere = Expression.equalsArgs(Activities._ID).getSQL(); - String[] updateWhereArgs = new String[1]; - for (int i = 0, j = values.size(); i < j; i++) { - updateWhereArgs[0] = String.valueOf(values.keyAt(i)); - cr.update(uri, values.valueAt(i), updateWhere, updateWhereArgs); - } - } - - public static void updateActivityStatus(@NonNull ContentResolver resolver, - @NonNull UserKey accountKey, - @NonNull String statusId, - @NonNull UpdateActivityAction action) { - final String activityWhere = Expression.and( - Expression.equalsArgs(Activities.ACCOUNT_KEY), - Expression.or( - Expression.equalsArgs(Activities.STATUS_ID), - Expression.equalsArgs(Activities.STATUS_RETWEET_ID) - ) - ).getSQL(); - final String[] activityWhereArgs = {accountKey.toString(), statusId, statusId}; - for (final Uri uri : ACTIVITIES_URIS) { - updateActivity(resolver, uri, activityWhere, activityWhereArgs, action); - } - } - - public static void processTabExtras(List expressions, List expressionArgs, HomeTabExtras extras) { - 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(new Column(Statuses.IN_REPLY_TO_STATUS_ID))); - } - } - - public static void prepareDatabase(@NonNull Context context) { - final ContentResolver cr = context.getContentResolver(); - final Cursor cursor = cr.query(TwidereDataStore.CONTENT_URI_DATABASE_PREPARE, null, null, - null, null); - if (cursor == null) return; - cursor.close(); - } - - interface FieldArrayCreator { - T newArray(int size); - - void assign(T array, int arrayIdx, Cursor cur, int colIdx); - } - - public static int queryCount(@NonNull final Context context, @NonNull final Uri uri, - @Nullable final String selection, @Nullable final String[] selectionArgs) { - final ContentResolver resolver = context.getContentResolver(); - final String[] projection = new String[]{SQLFunctions.COUNT()}; - final Cursor cur = resolver.query(uri, projection, selection, selectionArgs, null); - if (cur == null) return -1; - try { - if (cur.moveToFirst()) { - return cur.getInt(0); - } - return -1; - } finally { - cur.close(); - } - } - - public static int getInteractionsCount(@NonNull final Context context, @Nullable final Bundle extraArgs, - final UserKey[] accountIds, final long since, final String sinceColumn) { - Expression extraWhere = null; - String[] extraWhereArgs = null; - boolean followingOnly = false; - if (extraArgs != null) { - final TabExtras extras = extraArgs.getParcelable(EXTRA_EXTRAS); - if (extras instanceof InteractionsTabExtras) { - InteractionsTabExtras ite = ((InteractionsTabExtras) extras); - if (ite.isMentionsOnly()) { - extraWhere = Expression.inArgs(Activities.ACTION, 3); - extraWhereArgs = new String[]{Activity.Action.MENTION, - Activity.Action.REPLY, Activity.Action.QUOTE}; - } - if (ite.isMyFollowingOnly()) { - followingOnly = true; - } - } - } - return getActivitiesCount(context, Activities.AboutMe.CONTENT_URI, extraWhere, extraWhereArgs, - since, sinceColumn, followingOnly, accountIds); - } - - public static void addToFilter(Context context, Collection users, boolean filterAnywhere) { - final ContentResolver cr = context.getContentResolver(); - - try { - List userValues = new ArrayList<>(); - List keywordValues = new ArrayList<>(); - List linkValues = new ArrayList<>(); - for (ParcelableUser user : users) { - final FiltersData.UserItem userItem = new FiltersData.UserItem(); - userItem.setUserKey(user.key); - userItem.setScreenName(user.screen_name); - userItem.setName(user.name); - userValues.add(FiltersData$UserItemValuesCreator.create(userItem)); - - final FiltersData.BaseItem keywordItem = new FiltersData.BaseItem(); - keywordItem.setValue("@" + user.screen_name); - keywordValues.add(FiltersData$BaseItemValuesCreator.create(keywordItem)); - - // Insert user link (without scheme) to links - final FiltersData.BaseItem linkItem = new FiltersData.BaseItem(); - Uri userLink = LinkCreator.getUserWebLink(user); - String linkWithoutScheme = userLink.toString(); - int idx; - if ((idx = linkWithoutScheme.indexOf("://")) >= 0) { - linkWithoutScheme = linkWithoutScheme.substring(idx + 3); - } - linkItem.setValue(linkWithoutScheme); - linkValues.add(FiltersData$BaseItemValuesCreator.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 (IOException e) { - throw new RuntimeException(e); - } - } - - public static void removeFromFilter(Context context, Collection users) { - List userKeyValues = new ArrayList<>(); - List linkValues = new ArrayList<>(); - List keywordValues = new ArrayList<>(); - final ContentResolver cr = context.getContentResolver(); - for (ParcelableUser user : users) { - // Delete from filtered users - userKeyValues.add(user.key.toString()); - // Delete user mention from keywords - keywordValues.add("@" + user.screen_name); - - // Delete user link (without scheme) from links - Uri userLink = LinkCreator.getUserWebLink(user); - String linkWithoutScheme = userLink.toString(); - int idx; - if ((idx = linkWithoutScheme.indexOf("://")) >= 0) { - linkWithoutScheme = linkWithoutScheme.substring(idx + 3); - } - linkValues.add(linkWithoutScheme); - } - ContentResolverUtils.bulkDelete(cr, Filters.Users.CONTENT_URI, Filters.Users.USER_KEY, false, userKeyValues, null); - ContentResolverUtils.bulkDelete(cr, Filters.Keywords.CONTENT_URI, Filters.Keywords.VALUE, false, keywordValues, null); - ContentResolverUtils.bulkDelete(cr, Filters.Links.CONTENT_URI, Filters.Links.VALUE, false, linkValues, null); - } - - public interface UpdateActivityAction { - - void process(@NonNull ParcelableActivity activity); - } -} diff --git a/twidere/src/main/java/org/mariotaku/twidere/util/Utils.java b/twidere/src/main/java/org/mariotaku/twidere/util/Utils.java index 44e627ab8..41bdb4595 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/util/Utils.java +++ b/twidere/src/main/java/org/mariotaku/twidere/util/Utils.java @@ -35,7 +35,6 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.res.Resources; -import android.database.Cursor; import android.graphics.PorterDuff.Mode; import android.graphics.Rect; import android.graphics.drawable.Drawable; @@ -53,7 +52,6 @@ import android.support.annotation.DrawableRes; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.StringRes; -import android.support.annotation.WorkerThread; import android.support.v4.net.ConnectivityManagerCompat; import android.support.v4.view.GravityCompat; import android.support.v4.view.accessibility.AccessibilityEventCompat; @@ -80,10 +78,8 @@ import android.widget.Toast; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.math.NumberUtils; import org.json.JSONException; -import org.mariotaku.microblog.library.MicroBlog; import org.mariotaku.microblog.library.MicroBlogException; import org.mariotaku.microblog.library.twitter.model.RateLimitStatus; -import org.mariotaku.microblog.library.twitter.model.Status; import org.mariotaku.pickncrop.library.PNCUtils; import org.mariotaku.sqliteqb.library.AllColumns; import org.mariotaku.sqliteqb.library.Columns; @@ -98,17 +94,12 @@ import org.mariotaku.twidere.graphic.PaddingDrawable; import org.mariotaku.twidere.model.AccountDetails; import org.mariotaku.twidere.model.AccountPreferences; import org.mariotaku.twidere.model.ParcelableStatus; -import org.mariotaku.twidere.model.ParcelableStatusCursorIndices; -import org.mariotaku.twidere.model.ParcelableStatusValuesCreator; import org.mariotaku.twidere.model.ParcelableUserMention; import org.mariotaku.twidere.model.PebbleMessage; import org.mariotaku.twidere.model.UserKey; import org.mariotaku.twidere.model.util.AccountUtils; -import org.mariotaku.twidere.model.util.ParcelableStatusUtils; -import org.mariotaku.twidere.provider.TwidereDataStore.CachedStatuses; import org.mariotaku.twidere.provider.TwidereDataStore.CachedUsers; import org.mariotaku.twidere.provider.TwidereDataStore.DirectMessages; -import org.mariotaku.twidere.provider.TwidereDataStore.Statuses; import org.mariotaku.twidere.view.TabPagerIndicator; import java.io.Closeable; @@ -126,7 +117,6 @@ import java.util.regex.Pattern; import edu.tsinghua.hotmobi.HotMobiLogger; import edu.tsinghua.hotmobi.model.NotificationEvent; -import static org.mariotaku.twidere.util.DataStoreUtils.STATUSES_URIS; import static org.mariotaku.twidere.util.TwidereLinkify.PATTERN_TWITTER_PROFILE_IMAGES; public final class Utils implements Constants { @@ -310,7 +300,7 @@ public final class Utils implements Constants { } catch (NumberFormatException e) { // Ignore } - final UserKey accountKey = DataStoreUtils.findAccountKey(context, accountId); + final UserKey accountKey = DataStoreUtils.INSTANCE.findAccountKey(context, accountId); args.putParcelable(EXTRA_ACCOUNT_KEY, accountKey); if (accountKey == null) return new UserKey[]{new UserKey(accountId, null)}; return new UserKey[]{accountKey}; @@ -332,59 +322,6 @@ public final class Utils implements Constants { return accountKey + ":" + tag; } - @NonNull - @WorkerThread - public static ParcelableStatus findStatus(@NonNull final Context context, - @NonNull final UserKey accountKey, - @NonNull final String statusId) - throws MicroBlogException { - final ParcelableStatus cached = findStatusInDatabases(context, accountKey, statusId); - if (cached != null) return cached; - final MicroBlog twitter = MicroBlogAPIFactory.getInstance(context, accountKey); - if (twitter == null) throw new MicroBlogException("Account does not exist"); - final Status result = twitter.showStatus(statusId); - final String where = Expression.and(Expression.equalsArgs(Statuses.ACCOUNT_KEY), - Expression.equalsArgs(Statuses.STATUS_ID)).getSQL(); - final String[] whereArgs = {accountKey.toString(), statusId}; - final ContentResolver resolver = context.getContentResolver(); - final ParcelableStatus status = ParcelableStatusUtils.INSTANCE.fromStatus(result, accountKey, false); - resolver.delete(CachedStatuses.CONTENT_URI, where, whereArgs); - try { - resolver.insert(CachedStatuses.CONTENT_URI, ParcelableStatusValuesCreator.create(status)); - } catch (IOException e) { - // Ignore - } - return status; - } - - @Nullable - @WorkerThread - public static ParcelableStatus findStatusInDatabases(@NonNull final Context context, - @NonNull final UserKey accountKey, - @NonNull final String statusId) { - final ContentResolver resolver = context.getContentResolver(); - ParcelableStatus status = null; - final String where = Expression.and(Expression.equalsArgs(Statuses.ACCOUNT_KEY), - Expression.equalsArgs(Statuses.STATUS_ID)).getSQL(); - final String[] whereArgs = {accountKey.toString(), statusId}; - for (final Uri uri : STATUSES_URIS) { - final Cursor cur = resolver.query(uri, Statuses.COLUMNS, where, whereArgs, null); - if (cur == null) { - continue; - } - try { - if (cur.getCount() > 0 && cur.moveToFirst()) { - status = ParcelableStatusCursorIndices.fromCursor(cur); - } - } catch (IOException e) { - // Ignore - } finally { - cur.close(); - } - } - return status; - } - @SuppressWarnings("deprecation") public static String formatSameDayTime(final Context context, final long timestamp) { if (context == null) return null; @@ -461,7 +398,7 @@ public final class Utils implements Constants { Context.MODE_PRIVATE); final String string = prefs.getString(KEY_DEFAULT_ACCOUNT_KEY, null); UserKey accountKey = string != null ? UserKey.valueOf(string) : null; - final UserKey[] accountKeys = DataStoreUtils.getAccountKeys(context); + final UserKey[] accountKeys = DataStoreUtils.INSTANCE.getAccountKeys(context); int idMatchIdx = -1; for (int i = 0, accountIdsLength = accountKeys.length; i < accountIdsLength; i++) { if (accountKeys[i].equals(accountKey)) { @@ -715,7 +652,7 @@ public final class Utils implements Constants { } public static boolean hasAutoRefreshAccounts(final Context context) { - final UserKey[] accountKeys = DataStoreUtils.getAccountKeys(context); + final UserKey[] accountKeys = DataStoreUtils.INSTANCE.getAccountKeys(context); return !ArrayUtils.isEmpty(AccountPreferences.getAutoRefreshEnabledAccountIds(context, accountKeys)); } diff --git a/twidere/src/main/kotlin/org/mariotaku/ktextension/ArrayExtensions.kt b/twidere/src/main/kotlin/org/mariotaku/ktextension/ArrayExtensions.kt index adb644a1c..19c4814f2 100644 --- a/twidere/src/main/kotlin/org/mariotaku/ktextension/ArrayExtensions.kt +++ b/twidere/src/main/kotlin/org/mariotaku/ktextension/ArrayExtensions.kt @@ -13,3 +13,7 @@ fun Array<*>?.isNullOrEmpty(): Boolean { return this == null || this.isEmpty() } +fun Array.toNulls(): Array { + @Suppress("UNCHECKED_CAST") + return this as Array +} \ No newline at end of file diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/constant/PreferenceKeys.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/constant/PreferenceKeys.kt index 09be9cc2f..1868aa5f8 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/constant/PreferenceKeys.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/constant/PreferenceKeys.kt @@ -40,6 +40,7 @@ val attachLocationKey = KBooleanKey(KEY_ATTACH_LOCATION, false) val attachPreciseLocationKey = KBooleanKey(KEY_ATTACH_PRECISE_LOCATION, false) val noCloseAfterTweetSentKey = KBooleanKey(KEY_NO_CLOSE_AFTER_TWEET_SENT, false) val loadItemLimitKey = KIntKey(KEY_LOAD_ITEM_LIMIT, DEFAULT_LOAD_ITEM_LIMIT) +val databaseItemLimitKey = KIntKey(KEY_DATABASE_ITEM_LIMIT, DEFAULT_DATABASE_ITEM_LIMIT) val defaultFeatureLastUpdated = KLongKey("default_feature_last_updated", -1) val drawerTutorialCompleted = KBooleanKey(KEY_SETTINGS_WIZARD_COMPLETED, false) val stopAutoRefreshWhenBatteryLowKey = KBooleanKey(KEY_STOP_AUTO_REFRESH_WHEN_BATTERY_LOW, true) diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/CursorActivitiesFragment.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/CursorActivitiesFragment.kt index 14729b501..6b5c7166c 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/CursorActivitiesFragment.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/CursorActivitiesFragment.kt @@ -32,8 +32,8 @@ import android.widget.Toast import com.squareup.otto.Subscribe import org.mariotaku.ktextension.addOnAccountsUpdatedListenerSafe import org.mariotaku.ktextension.removeOnAccountsUpdatedListenerSafe +import org.mariotaku.ktextension.toNulls import org.mariotaku.library.objectcursor.ObjectCursor -import org.mariotaku.sqliteqb.library.ArgsArray import org.mariotaku.sqliteqb.library.Columns.Column import org.mariotaku.sqliteqb.library.Expression import org.mariotaku.twidere.R @@ -77,15 +77,12 @@ abstract class CursorActivitiesFragment : AbsActivitiesFragment() { showContentOrError() } - override fun onCreateActivitiesLoader(context: Context, - args: Bundle, - fromUser: Boolean): Loader> { + override fun onCreateActivitiesLoader(context: Context, args: Bundle, fromUser: Boolean): Loader> { val uri = contentUri - val table = getTableNameByUri(uri) + val table = getTableNameByUri(uri)!! val sortOrder = sortOrder val accountKeys = accountKeys - val accountWhere = Expression.`in`(Column(Activities.ACCOUNT_KEY), - ArgsArray(accountKeys.size)) + val accountWhere = Expression.inArgs(Column(Activities.ACCOUNT_KEY), accountKeys.size) val filterWhere = getFiltersWhere(table) val where: Expression if (filterWhere != null) { @@ -147,8 +144,8 @@ abstract class CursorActivitiesFragment : AbsActivitiesFragment() { super.onLoadMoreContents(position) if (position == 0L) return getActivities(object : SimpleRefreshTaskParam() { - override fun getAccountKeysWorker(): Array { - return this@CursorActivitiesFragment.accountKeys + override val accountKeys: Array by lazy { + this@CursorActivitiesFragment.accountKeys } override val maxIds: Array? @@ -158,7 +155,7 @@ abstract class CursorActivitiesFragment : AbsActivitiesFragment() { get() { val context = context ?: return null return DataStoreUtils.getOldestActivityMaxSortPositions(context, - contentUri, accountKeys) + contentUri, accountKeys.toNulls()) } override val hasMaxIds: Boolean @@ -172,8 +169,8 @@ abstract class CursorActivitiesFragment : AbsActivitiesFragment() { override fun triggerRefresh(): Boolean { super.triggerRefresh() getActivities(object : SimpleRefreshTaskParam() { - override fun getAccountKeysWorker(): Array { - return this@CursorActivitiesFragment.accountKeys + override val accountKeys: Array by lazy { + this@CursorActivitiesFragment.accountKeys } override val sinceIds: Array? @@ -183,7 +180,7 @@ abstract class CursorActivitiesFragment : AbsActivitiesFragment() { get() { val context = context ?: return null return DataStoreUtils.getNewestActivityMaxSortPositions(context, - contentUri, accountKeys) + contentUri, accountKeys.toNulls()) } override val hasSinceIds: Boolean @@ -202,7 +199,7 @@ abstract class CursorActivitiesFragment : AbsActivitiesFragment() { protected fun getNewestActivityIds(accountKeys: Array): Array? { val context = context ?: return null - return DataStoreUtils.getNewestActivityMaxPositions(context, contentUri, accountKeys) + return DataStoreUtils.getNewestActivityMaxPositions(context, contentUri, accountKeys.toNulls()) } protected abstract val notificationType: Int @@ -220,7 +217,7 @@ abstract class CursorActivitiesFragment : AbsActivitiesFragment() { protected fun getOldestActivityIds(accountKeys: Array): Array? { val context = context ?: return null - return DataStoreUtils.getOldestActivityMaxPositions(context, contentUri, accountKeys) + return DataStoreUtils.getOldestActivityMaxPositions(context, contentUri, accountKeys.toNulls()) } protected abstract val isFilterEnabled: Boolean diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/CursorStatusesFragment.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/CursorStatusesFragment.kt index a0a798957..eee82522a 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/CursorStatusesFragment.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/CursorStatusesFragment.kt @@ -32,7 +32,7 @@ import com.squareup.otto.Subscribe import kotlinx.android.synthetic.main.fragment_content_recyclerview.* import org.mariotaku.ktextension.addOnAccountsUpdatedListenerSafe import org.mariotaku.ktextension.removeOnAccountsUpdatedListenerSafe -import org.mariotaku.sqliteqb.library.ArgsArray +import org.mariotaku.ktextension.toNulls import org.mariotaku.sqliteqb.library.Columns.Column import org.mariotaku.sqliteqb.library.Expression import org.mariotaku.twidere.R @@ -79,11 +79,10 @@ abstract class CursorStatusesFragment : AbsStatusesFragment() { override fun onCreateStatusesLoader(context: Context, args: Bundle, fromUser: Boolean): Loader?> { val uri = contentUri - val table = DataStoreUtils.getTableNameByUri(uri) + val table = DataStoreUtils.getTableNameByUri(uri)!! val sortOrder = Statuses.DEFAULT_SORT_ORDER val accountKeys = this.accountKeys - val accountWhere = Expression.`in`(Column(Statuses.ACCOUNT_KEY), - ArgsArray(accountKeys.size)) + val accountWhere = Expression.inArgs(Column(Statuses.ACCOUNT_KEY), accountKeys.size) val filterWhere = getFiltersWhere(table) val where: Expression if (filterWhere != null) { @@ -178,8 +177,8 @@ abstract class CursorStatusesFragment : AbsStatusesFragment() { super.onLoadMoreContents(position) if (position == 0L) return getStatuses(object : SimpleRefreshTaskParam() { - override fun getAccountKeysWorker(): Array { - return this@CursorStatusesFragment.accountKeys + override val accountKeys: Array by lazy { + this@CursorStatusesFragment.accountKeys } override val maxIds: Array? @@ -189,7 +188,7 @@ abstract class CursorStatusesFragment : AbsStatusesFragment() { get() { val context = context ?: return null return DataStoreUtils.getOldestStatusSortIds(context, contentUri, - accountKeys) + accountKeys.toNulls()) } override val hasMaxIds: Boolean @@ -203,8 +202,8 @@ abstract class CursorStatusesFragment : AbsStatusesFragment() { override fun triggerRefresh(): Boolean { super.triggerRefresh() getStatuses(object : SimpleRefreshTaskParam() { - override fun getAccountKeysWorker(): Array { - return this@CursorStatusesFragment.accountKeys + override val accountKeys: Array by lazy { + this@CursorStatusesFragment.accountKeys } override val hasMaxIds: Boolean @@ -214,7 +213,7 @@ abstract class CursorStatusesFragment : AbsStatusesFragment() { get() = getNewestStatusIds(accountKeys) override val sinceSortIds: LongArray? - get() = DataStoreUtils.getNewestStatusSortIds(context, contentUri, accountKeys) + get() = DataStoreUtils.getNewestStatusSortIds(context, contentUri, accountKeys.toNulls()) override val shouldAbort: Boolean get() = context == null @@ -229,7 +228,7 @@ abstract class CursorStatusesFragment : AbsStatusesFragment() { protected fun getNewestStatusIds(accountKeys: Array): Array? { val context = context ?: return null - return DataStoreUtils.getNewestStatusIds(context, contentUri, accountKeys) + return DataStoreUtils.getNewestStatusIds(context, contentUri, accountKeys.toNulls()) } override fun setUserVisibleHint(isVisibleToUser: Boolean) { @@ -245,7 +244,7 @@ abstract class CursorStatusesFragment : AbsStatusesFragment() { protected fun getOldestStatusIds(accountKeys: Array): Array? { val context = context ?: return null - return DataStoreUtils.getOldestStatusIds(context, contentUri, accountKeys) + return DataStoreUtils.getOldestStatusIds(context, contentUri, accountKeys.toNulls()) } protected open fun processWhere(where: Expression, whereArgs: Array): ParameterizedExpression { diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/MessagesEntriesFragment.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/MessagesEntriesFragment.kt index 850357679..79a4b729e 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/MessagesEntriesFragment.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/MessagesEntriesFragment.kt @@ -1,14 +1,17 @@ package org.mariotaku.twidere.fragment +import android.accounts.AccountManager import android.content.Context import android.os.Bundle import android.support.v4.app.LoaderManager import android.support.v4.content.Loader import org.mariotaku.kpreferences.get +import org.mariotaku.ktextension.toNulls import org.mariotaku.sqliteqb.library.OrderBy import org.mariotaku.twidere.R import org.mariotaku.twidere.adapter.MessagesEntriesAdapter import org.mariotaku.twidere.adapter.iface.ILoadMoreSupportAdapter +import org.mariotaku.twidere.annotation.AccountType import org.mariotaku.twidere.constant.newDocumentApiKey import org.mariotaku.twidere.extension.model.user import org.mariotaku.twidere.loader.ObjectCursorLoader @@ -16,6 +19,8 @@ import org.mariotaku.twidere.model.ParcelableMessageConversation import org.mariotaku.twidere.model.ParcelableMessageConversationCursorIndices import org.mariotaku.twidere.model.SimpleRefreshTaskParam import org.mariotaku.twidere.model.UserKey +import org.mariotaku.twidere.model.util.AccountUtils +import org.mariotaku.twidere.provider.TwidereDataStore.Messages import org.mariotaku.twidere.provider.TwidereDataStore.Messages.Conversations import org.mariotaku.twidere.util.DataStoreUtils import org.mariotaku.twidere.util.ErrorInfoStore @@ -65,9 +70,36 @@ class MessagesEntriesFragment : AbsContentListRecyclerViewFragment { - return this@MessagesEntriesFragment.accountKeys + private val accounts by lazy { + AccountUtils.getAllAccountDetails(AccountManager.get(context), accountKeys, false) } + + override val accountKeys: Array by lazy { + this@MessagesEntriesFragment.accountKeys + } + + override val sinceIds: Array? + get() { + val result = arrayOfNulls(accountKeys.size) + val hasSinceAccountKeys = accounts.mapNotNull { account -> + when (account?.type) { + AccountType.FANFOU -> { + return@mapNotNull null + } + } + return@mapNotNull account?.key + }.toTypedArray() + val incomingIds = DataStoreUtils.getMessageIds(context, Messages.CONTENT_URI, + hasSinceAccountKeys.toNulls(), false) + val outgoingIds = DataStoreUtils.getMessageIds(context, Messages.CONTENT_URI, + hasSinceAccountKeys.toNulls(), true) + loop@ for (idx in 0..accountKeys.lastIndex) { + + } + return result + } + override val hasSinceIds: Boolean = true + override val hasMaxIds: Boolean = false }) return true } diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/UserListFragment.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/UserListFragment.kt index 33eb47acd..491c61752 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/UserListFragment.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/UserListFragment.kt @@ -30,7 +30,6 @@ import android.nfc.NdefRecord import android.nfc.NfcAdapter.CreateNdefMessageCallback import android.os.Bundle import android.support.v4.app.LoaderManager.LoaderCallbacks -import android.support.v4.content.AsyncTaskLoader import android.support.v4.content.FixedAsyncTaskLoader import android.support.v4.content.Loader import android.support.v7.app.AlertDialog @@ -329,42 +328,31 @@ class UserListFragment : AbsToolbarTabPagesFragment(), OnClickListener, LoaderCa class EditUserListDialogFragment : BaseDialogFragment(), DialogInterface.OnClickListener { - private var mName: String? = null - private var mDescription: String? = null - private var mAccountKey: UserKey? = null - private var mListId: String? = null - private var mIsPublic: Boolean = false + private val accountKey by lazy { arguments.getParcelable(EXTRA_ACCOUNT_KEY) } + private val listId: String by lazy { arguments.getString(EXTRA_LIST_ID) } override fun onClick(dialog: DialogInterface, which: Int) { when (which) { DialogInterface.BUTTON_POSITIVE -> { val alertDialog = dialog as AlertDialog - val editName = alertDialog.findViewById(R.id.name) as MaterialEditText? - val editDescription = alertDialog.findViewById(R.id.description) as MaterialEditText? - val editIsPublic = alertDialog.findViewById(R.id.is_public) as CheckBox? - assert(editName != null && editDescription != null && editIsPublic != null) - val name = ParseUtils.parseString(editName!!.text) - val description = ParseUtils.parseString(editDescription!!.text) - val isPublic = editIsPublic!!.isChecked + val editName = alertDialog.findViewById(R.id.name) as MaterialEditText + val editDescription = alertDialog.findViewById(R.id.description) as MaterialEditText + val editIsPublic = alertDialog.findViewById(R.id.is_public) as CheckBox + val name = ParseUtils.parseString(editName.text) + val description = ParseUtils.parseString(editDescription.text) + val isPublic = editIsPublic.isChecked if (TextUtils.isEmpty(name)) return val update = UserListUpdate() update.setMode(if (isPublic) UserList.Mode.PUBLIC else UserList.Mode.PRIVATE) update.setName(name) update.setDescription(description) - twitterWrapper.updateUserListDetails(mAccountKey, mListId, update) + twitterWrapper.updateUserListDetails(accountKey, listId, update) } } } override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { - val bundle = savedInstanceState ?: arguments - mAccountKey = bundle?.getParcelable(EXTRA_ACCOUNT_KEY) - mListId = bundle?.getString(EXTRA_LIST_ID) - mName = bundle?.getString(EXTRA_LIST_NAME) - mDescription = bundle?.getString(EXTRA_DESCRIPTION) - mIsPublic = bundle == null || bundle.getBoolean(EXTRA_IS_PUBLIC, true) - val context = activity val builder = AlertDialog.Builder(context) builder.setView(R.layout.dialog_user_list_detail_editor) builder.setTitle(R.string.user_list) @@ -374,31 +362,19 @@ class UserListFragment : AbsToolbarTabPagesFragment(), OnClickListener, LoaderCa dialog.setOnShowListener { dialog -> dialog as AlertDialog dialog.applyTheme() - val editName = dialog.findViewById(R.id.name) as MaterialEditText? - val editDescription = dialog.findViewById(R.id.description) as MaterialEditText? - val editPublic = dialog.findViewById(R.id.is_public) as CheckBox? - assert(editName != null && editDescription != null && editPublic != null) - editName!!.addValidator(UserListNameValidator(getString(R.string.invalid_list_name))) - if (mName != null) { - editName.setText(mName) + val editName = dialog.findViewById(R.id.name) as MaterialEditText + val editDescription = dialog.findViewById(R.id.description) as MaterialEditText + val editPublic = dialog.findViewById(R.id.is_public) as CheckBox + editName.addValidator(UserListNameValidator(getString(R.string.invalid_list_name))) + if (savedInstanceState == null) { + editName.setText(arguments.getString(EXTRA_LIST_NAME)) + editDescription.setText(arguments.getString(EXTRA_DESCRIPTION)) + editPublic.isChecked = arguments.getBoolean(EXTRA_IS_PUBLIC, true) } - if (mDescription != null) { - editDescription!!.setText(mDescription) - } - editPublic!!.isChecked = mIsPublic } return dialog } - override fun onSaveInstanceState(outState: Bundle?) { - outState!!.putParcelable(EXTRA_ACCOUNT_KEY, mAccountKey) - outState.putString(EXTRA_LIST_ID, mListId) - outState.putString(EXTRA_LIST_NAME, mName) - outState.putString(EXTRA_DESCRIPTION, mDescription) - outState.putBoolean(EXTRA_IS_PUBLIC, mIsPublic) - super.onSaveInstanceState(outState) - } - } internal class ParcelableUserListLoader( @@ -415,10 +391,10 @@ class UserListFragment : AbsToolbarTabPagesFragment(), OnClickListener, LoaderCa override fun loadInBackground(): SingleResponse { if (!omitIntentExtra && extras != null) { val cache = extras.getParcelable(EXTRA_USER_LIST) - if (cache != null) return SingleResponse.Companion.getInstance(cache) + if (cache != null) return SingleResponse(cache) } - val twitter = MicroBlogAPIFactory.getInstance(context, accountKey - ) ?: return SingleResponse.Companion.getInstance() + val twitter = MicroBlogAPIFactory.getInstance(context, accountKey) + ?: return SingleResponse(MicroBlogException("No account")) try { val list: UserList when { @@ -432,12 +408,12 @@ class UserListFragment : AbsToolbarTabPagesFragment(), OnClickListener, LoaderCa list = twitter.showUserListByScrenName(listName, screenName) } else -> { - return SingleResponse.Companion.getInstance() + return SingleResponse(MicroBlogException("Invalid argument")) } } - return SingleResponse.Companion.getInstance(ParcelableUserListUtils.from(list, accountKey)) + return SingleResponse(ParcelableUserListUtils.from(list, accountKey)) } catch (e: MicroBlogException) { - return SingleResponse.Companion.getInstance(e) + return SingleResponse(e) } } diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/loader/ParcelableStatusLoader.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/loader/ParcelableStatusLoader.kt index 798b28f6d..04a9fa434 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/loader/ParcelableStatusLoader.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/loader/ParcelableStatusLoader.kt @@ -36,8 +36,8 @@ import org.mariotaku.twidere.model.util.AccountUtils import org.mariotaku.twidere.model.util.ParcelableStatusUtils import org.mariotaku.twidere.util.DataStoreUtils import org.mariotaku.twidere.util.UserColorNameManager -import org.mariotaku.twidere.util.Utils.findStatus import org.mariotaku.twidere.util.dagger.GeneralComponentHelper +import org.mariotaku.twidere.util.deleteActivityStatus import javax.inject.Inject /** @@ -75,7 +75,7 @@ class ParcelableStatusLoader( } if (details == null) return SingleResponse(MicroBlogException("No account")) try { - val status = findStatus(context, accountKey, statusId) + val status = DataStoreUtils.findStatus(context, accountKey, statusId) ParcelableStatusUtils.updateExtraInformation(status, details) val response = SingleResponse(status) response.extras[EXTRA_ACCOUNT] = details @@ -85,7 +85,7 @@ class ParcelableStatusLoader( // Delete all deleted status val cr = context.contentResolver DataStoreUtils.deleteStatus(cr, accountKey, statusId, null) - DataStoreUtils.deleteActivityStatus(cr, accountKey, statusId, null) + deleteActivityStatus(cr, accountKey, statusId, null) } return SingleResponse(e) } diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/model/SimpleRefreshTaskParam.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/model/SimpleRefreshTaskParam.kt index 4683c1fe0..ea14a9a57 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/model/SimpleRefreshTaskParam.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/model/SimpleRefreshTaskParam.kt @@ -7,15 +7,6 @@ abstract class SimpleRefreshTaskParam : RefreshTaskParam { internal var cached: Array? = null - override val accountKeys: Array - get() { - if (cached != null) return cached!! - cached = getAccountKeysWorker() - return cached!! - } - - abstract fun getAccountKeysWorker(): Array - override val maxIds: Array? get() = null diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/preference/ClearDatabasesPreference.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/preference/ClearDatabasesPreference.kt new file mode 100644 index 000000000..3e82c50bf --- /dev/null +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/preference/ClearDatabasesPreference.kt @@ -0,0 +1,58 @@ +/* + * Twidere - Twitter client for Android + * + * Copyright (C) 2012-2014 Mariotaku Lee + * + * 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 . + */ + +package org.mariotaku.twidere.preference + +import android.content.Context +import android.util.AttributeSet +import org.mariotaku.twidere.R +import org.mariotaku.twidere.TwidereConstants.TIMELINE_POSITIONS_PREFERENCES_NAME +import org.mariotaku.twidere.provider.TwidereDataStore.* +import org.mariotaku.twidere.util.DataStoreUtils + +class ClearDatabasesPreference(context: Context, attrs: AttributeSet? = null) : AsyncTaskPreference(context, attrs, R.attr.preferenceStyle) { + + override fun doInBackground() { + val context = context ?: return + val resolver = context.contentResolver + for (uri in DataStoreUtils.STATUSES_URIS) { + if (CachedStatuses.CONTENT_URI == uri) { + continue + } + resolver.delete(uri, null, null) + } + for (uri in DataStoreUtils.MESSAGES_URIS) { + resolver.delete(uri, null, null) + } + for (uri in DataStoreUtils.CACHE_URIS) { + resolver.delete(uri, null, null) + } + resolver.delete(Activities.AboutMe.CONTENT_URI, null, null) + resolver.delete(Activities.ByFriends.CONTENT_URI, null, null) + resolver.delete(Notifications.CONTENT_URI, null, null) + resolver.delete(UnreadCounts.CONTENT_URI, null, null) + resolver.delete(SavedSearches.CONTENT_URI, null, null) + + val prefs = context.getSharedPreferences(TIMELINE_POSITIONS_PREFERENCES_NAME, Context.MODE_PRIVATE) + val editor = prefs.edit() + editor.clear() + editor.apply() + } + +} diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/task/AbsFriendshipOperationTask.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/task/AbsFriendshipOperationTask.kt index 4015387e6..c9d1f223a 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/task/AbsFriendshipOperationTask.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/task/AbsFriendshipOperationTask.kt @@ -8,7 +8,6 @@ import org.mariotaku.microblog.library.twitter.model.User import org.mariotaku.twidere.extension.model.newMicroBlogInstance import org.mariotaku.twidere.model.AccountDetails import org.mariotaku.twidere.model.ParcelableUser -import org.mariotaku.twidere.model.SingleResponse import org.mariotaku.twidere.model.UserKey import org.mariotaku.twidere.model.event.FriendshipTaskEvent import org.mariotaku.twidere.model.util.AccountUtils @@ -20,7 +19,8 @@ import org.mariotaku.twidere.model.util.ParcelableUserUtils abstract class AbsFriendshipOperationTask( context: Context, @FriendshipTaskEvent.Action protected val action: Int -) : BaseAbstractTask, Any?>(context) { +) : ExceptionHandlingAbstractTask(context) { override fun beforeExecute() { microBlogWrapper.addUpdatingRelationshipId(params.accountKey, params.userKey) @@ -30,34 +30,31 @@ abstract class AbsFriendshipOperationTask( bus.post(event) } - override fun afterExecute(handler: Any?, result: SingleResponse?) { + override fun afterExecute(callback: Any?, result: ParcelableUser?, exception: MicroBlogException?) { microBlogWrapper.removeUpdatingRelationshipId(params.accountKey, params.userKey) val event = FriendshipTaskEvent(action, params.accountKey, params.userKey) event.isFinished = true - if (result!!.hasData()) { - val user = result.data!! + if (result != null) { + val user = result showSucceededMessage(params, user) event.isSucceeded = true - event.user = result.data + event.user = user } else { - showErrorMessage(params, result.exception) + showErrorMessage(params, exception) } bus.post(event) } - override fun doLongOperation(args: Arguments): SingleResponse { - val details = AccountUtils.getAccountDetails(AccountManager.get(context), args.accountKey, true) ?: return SingleResponse.getInstance() + override fun onExecute(params: Arguments): ParcelableUser { + val am = AccountManager.get(context) + val details = AccountUtils.getAccountDetails(am, params.accountKey, true) + ?: throw MicroBlogException("No account") val twitter = details.newMicroBlogInstance(context, cls = MicroBlog::class.java) - try { - val user = perform(twitter, details, args) - val parcelableUser = ParcelableUserUtils.fromUser(user, args.accountKey) - succeededWorker(twitter, details, args, parcelableUser) - return SingleResponse.getInstance(parcelableUser) - } catch (e: MicroBlogException) { - return SingleResponse.getInstance(e) - } - + val user = perform(twitter, details, params) + val parcelableUser = ParcelableUserUtils.fromUser(user, params.accountKey) + succeededWorker(twitter, details, params, parcelableUser) + return parcelableUser } @Throws(MicroBlogException::class) diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/task/AcceptFriendshipTask.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/task/AcceptFriendshipTask.kt index 0cd823b16..6a91db34f 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/task/AcceptFriendshipTask.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/task/AcceptFriendshipTask.kt @@ -5,7 +5,6 @@ import android.widget.Toast import org.mariotaku.microblog.library.MicroBlog import org.mariotaku.microblog.library.MicroBlogException import org.mariotaku.microblog.library.twitter.model.User -import org.mariotaku.twidere.Constants import org.mariotaku.twidere.R import org.mariotaku.twidere.annotation.AccountType import org.mariotaku.twidere.constant.nameFirstKey @@ -17,7 +16,7 @@ import org.mariotaku.twidere.util.Utils /** * Created by mariotaku on 16/3/11. */ -class AcceptFriendshipTask(context: Context) : AbsFriendshipOperationTask(context, FriendshipTaskEvent.Action.ACCEPT), Constants { +class AcceptFriendshipTask(context: Context) : AbsFriendshipOperationTask(context, FriendshipTaskEvent.Action.ACCEPT) { @Throws(MicroBlogException::class) override fun perform(twitter: MicroBlog, details: AccountDetails, args: AbsFriendshipOperationTask.Arguments): User { diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/task/AddUserListMembersTask.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/task/AddUserListMembersTask.kt index 48cf3bd77..486af79c8 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/task/AddUserListMembersTask.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/task/AddUserListMembersTask.kt @@ -20,23 +20,24 @@ class AddUserListMembersTask( context: Context, private val accountKey: UserKey, private val listId: String, - private val users: Array -) : ManagedAsyncTask>(context) { + private val users: Array +) : BaseAbstractTask, Any?>(context) { - override fun doInBackground(vararg params: Any): SingleResponse { - val microBlog = MicroBlogAPIFactory.getInstance(context, accountKey) ?: return SingleResponse.getInstance() + override fun doLongOperation(params: Any?): SingleResponse { try { + val microBlog = MicroBlogAPIFactory.getInstance(context, accountKey) ?: + throw MicroBlogException("No account") val userIds = users.map(ParcelableUser::key).toTypedArray() val result = microBlog.addUserListMembers(listId, UserKey.getIds(userIds)) val list = ParcelableUserListUtils.from(result, accountKey) - return SingleResponse.getInstance(list) + return SingleResponse(list) } catch (e: MicroBlogException) { - return SingleResponse.getInstance(e) + return SingleResponse(e) } } - override fun onPostExecute(result: SingleResponse) { + override fun afterExecute(callback: Any?, result: SingleResponse) { if (result.data != null) { val message: String if (users.size == 1) { @@ -56,7 +57,6 @@ class AddUserListMembersTask( } else { Utils.showErrorMessage(context, R.string.action_adding_member, result.exception, true) } - super.onPostExecute(result) } } diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/task/CacheUsersStatusesTask.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/task/CacheUsersStatusesTask.kt index efd8f2469..52d7b48a3 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/task/CacheUsersStatusesTask.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/task/CacheUsersStatusesTask.kt @@ -33,7 +33,7 @@ import java.util.* class CacheUsersStatusesTask( private val context: Context -) : AbstractTask, Unit, Unit>() { +) : AbstractTask, Unit?, Unit?>() { override fun doLongOperation(params: TwitterListResponse) { val resolver = context.contentResolver diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/task/CreateFavoriteTask.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/task/CreateFavoriteTask.kt index 3687499f4..92cf56155 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/task/CreateFavoriteTask.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/task/CreateFavoriteTask.kt @@ -25,10 +25,11 @@ import org.mariotaku.twidere.model.util.AccountUtils import org.mariotaku.twidere.model.util.ParcelableStatusUtils import org.mariotaku.twidere.provider.TwidereDataStore import org.mariotaku.twidere.task.twitter.UpdateStatusTask -import org.mariotaku.twidere.util.AsyncTwitterWrapper.calculateHashCode +import org.mariotaku.twidere.util.AsyncTwitterWrapper.Companion.calculateHashCode import org.mariotaku.twidere.util.DataStoreUtils import org.mariotaku.twidere.util.DebugLog import org.mariotaku.twidere.util.Utils +import org.mariotaku.twidere.util.updateActivityStatus /** * Created by mariotaku on 2017/2/7. @@ -37,7 +38,7 @@ class CreateFavoriteTask( context: Context, private val accountKey: UserKey, private val status: ParcelableStatus -) : BaseAbstractTask, Any>(context) { +) : BaseAbstractTask, Any?>(context) { private val statusId = status.id @@ -82,7 +83,7 @@ class CreateFavoriteTask( for (uri in DataStoreUtils.STATUSES_URIS) { resolver.update(uri, values, statusWhere, statusWhereArgs) } - DataStoreUtils.updateActivityStatus(resolver, accountKey, statusId) { activity -> + updateActivityStatus(resolver, accountKey, statusId) { activity -> val statusesMatrix = arrayOf(activity.target_statuses, activity.target_object_statuses) for (statusesArray in statusesMatrix) { if (statusesArray == null) continue diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/task/DestroyFavoriteTask.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/task/DestroyFavoriteTask.kt index e9788790b..3229e767b 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/task/DestroyFavoriteTask.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/task/DestroyFavoriteTask.kt @@ -22,9 +22,10 @@ import org.mariotaku.twidere.model.util.AccountUtils import org.mariotaku.twidere.model.util.ParcelableStatusUtils import org.mariotaku.twidere.provider.TwidereDataStore import org.mariotaku.twidere.util.AsyncTwitterWrapper -import org.mariotaku.twidere.util.AsyncTwitterWrapper.calculateHashCode +import org.mariotaku.twidere.util.AsyncTwitterWrapper.Companion.calculateHashCode import org.mariotaku.twidere.util.DataStoreUtils import org.mariotaku.twidere.util.Utils +import org.mariotaku.twidere.util.updateActivityStatus /** * Created by mariotaku on 2017/2/7. @@ -33,10 +34,8 @@ class DestroyFavoriteTask( context: Context, private val accountKey: UserKey, private val statusId: String -) : ManagedAsyncTask>(context) { - - - override fun doInBackground(vararg params: Any): SingleResponse { +) : BaseAbstractTask, Any?>(context) { + override fun doLongOperation(params: Any?): SingleResponse { val resolver = context.contentResolver val details = AccountUtils.getAccountDetails(AccountManager.get(context), accountKey, true) ?: return SingleResponse.getInstance() @@ -67,7 +66,7 @@ class DestroyFavoriteTask( resolver.update(uri, values, where.sql, whereArgs) } - DataStoreUtils.updateActivityStatus(resolver, accountKey, statusId, DataStoreUtils.UpdateActivityAction { activity -> + updateActivityStatus(resolver, accountKey, statusId) { activity -> val statusesMatrix = arrayOf(activity.target_statuses, activity.target_object_statuses) for (statusesArray in statusesMatrix) { if (statusesArray == null) continue @@ -79,7 +78,7 @@ class DestroyFavoriteTask( status.favorite_count = result.favorite_count - 1 } } - }) + } return SingleResponse.getInstance(result) } catch (e: MicroBlogException) { return SingleResponse.getInstance(e) @@ -87,8 +86,7 @@ class DestroyFavoriteTask( } - override fun onPreExecute() { - super.onPreExecute() + override fun beforeExecute() { val hashCode = AsyncTwitterWrapper.calculateHashCode(accountKey, statusId) if (!destroyingFavoriteIds.contains(hashCode)) { destroyingFavoriteIds.add(hashCode) @@ -96,7 +94,7 @@ class DestroyFavoriteTask( bus.post(StatusListChangedEvent()) } - override fun onPostExecute(result: SingleResponse) { + override fun afterExecute(callback: Any?, result: SingleResponse) { destroyingFavoriteIds.removeElement(AsyncTwitterWrapper.calculateHashCode(accountKey, statusId)) val taskEvent = FavoriteTaskEvent(FavoriteTaskEvent.Action.DESTROY, accountKey, statusId) @@ -117,7 +115,6 @@ class DestroyFavoriteTask( } bus.post(taskEvent) bus.post(StatusListChangedEvent()) - super.onPostExecute(result) } companion object { diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/task/DestroyStatusTask.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/task/DestroyStatusTask.kt index f7cbf08d1..3751b262e 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/task/DestroyStatusTask.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/task/DestroyStatusTask.kt @@ -17,6 +17,7 @@ import org.mariotaku.twidere.model.util.ParcelableStatusUtils import org.mariotaku.twidere.util.AsyncTwitterWrapper import org.mariotaku.twidere.util.DataStoreUtils import org.mariotaku.twidere.util.Utils +import org.mariotaku.twidere.util.deleteActivityStatus /** * Created by mariotaku on 2016/12/9. @@ -25,9 +26,9 @@ class DestroyStatusTask( context: Context, private val accountKey: UserKey, private val statusId: String -) : ManagedAsyncTask>(context) { +) : BaseAbstractTask, Any?>(context) { - override fun doInBackground(vararg params: Any): SingleResponse { + override fun doLongOperation(params: Any?): SingleResponse { val details = AccountUtils.getAccountDetails(AccountManager.get(context), accountKey, true) ?: return SingleResponse() val microBlog = details.newMicroBlogInstance(context, cls = MicroBlog::class.java) @@ -45,22 +46,22 @@ class DestroyStatusTask( } finally { if (deleteStatus) { DataStoreUtils.deleteStatus(context.contentResolver, accountKey, statusId, status) - DataStoreUtils.deleteActivityStatus(context.contentResolver, accountKey, statusId, status) + deleteActivityStatus(context.contentResolver, accountKey, statusId, status) } } } - override fun onPreExecute() { - super.onPreExecute() + override fun beforeExecute() { val hashCode = AsyncTwitterWrapper.calculateHashCode(accountKey, statusId) - if (!asyncTwitterWrapper.destroyingStatusIds.contains(hashCode)) { - asyncTwitterWrapper.destroyingStatusIds.add(hashCode) + if (!microBlogWrapper.destroyingStatusIds.contains(hashCode)) { + microBlogWrapper.destroyingStatusIds.add(hashCode) } bus.post(StatusListChangedEvent()) } - override fun onPostExecute(result: SingleResponse) { - asyncTwitterWrapper.destroyingStatusIds.removeElement(AsyncTwitterWrapper.calculateHashCode(accountKey, statusId)) + override fun afterExecute(callback: Any?, result: SingleResponse) { + + microBlogWrapper.destroyingStatusIds.removeElement(AsyncTwitterWrapper.calculateHashCode(accountKey, statusId)) if (result.hasData()) { val status = result.data!! if (status.retweet_id != null) { @@ -72,7 +73,6 @@ class DestroyStatusTask( } else { Utils.showErrorMessage(context, R.string.action_deleting, result.exception, true) } - super.onPostExecute(result) } } diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/task/DestroyUserListTask.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/task/DestroyUserListTask.kt new file mode 100644 index 000000000..18f5360d3 --- /dev/null +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/task/DestroyUserListTask.kt @@ -0,0 +1,66 @@ +/* + * Twidere - Twitter client for Android + * + * Copyright (C) 2012-2017 Mariotaku Lee + * + * 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 . + */ + +package org.mariotaku.twidere.task + +import android.content.Context +import org.mariotaku.microblog.library.MicroBlogException +import org.mariotaku.twidere.R +import org.mariotaku.twidere.model.ParcelableUserList +import org.mariotaku.twidere.model.SingleResponse +import org.mariotaku.twidere.model.UserKey +import org.mariotaku.twidere.model.event.UserListDestroyedEvent +import org.mariotaku.twidere.model.util.ParcelableUserListUtils +import org.mariotaku.twidere.util.MicroBlogAPIFactory +import org.mariotaku.twidere.util.Utils + +/** + * Created by mariotaku on 2017/2/10. + */ +class DestroyUserListTask( + context: Context, + private val accountKey: UserKey, + private val listId: String +) : BaseAbstractTask, Any?>(context) { + + override fun doLongOperation(params: Any?): SingleResponse { + val microBlog = MicroBlogAPIFactory.getInstance(context, accountKey) ?: + return SingleResponse(MicroBlogException("No account")) + try { + val userList = microBlog.destroyUserList(listId) + val list = ParcelableUserListUtils.from(userList, accountKey) + return SingleResponse(list) + } catch (e: MicroBlogException) { + return SingleResponse(e) + } + + } + + override fun afterExecute(callback: Any?, result: SingleResponse) { + val context = context + if (result.data != null) { + val message = context.getString(R.string.deleted_list, result.data.name) + Utils.showInfoMessage(context, message, false) + bus.post(UserListDestroyedEvent(result.data)) + } else { + Utils.showErrorMessage(context, R.string.action_deleting, result.exception, true) + } + } + +} diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/task/ExceptionHandlingAbstractTask.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/task/ExceptionHandlingAbstractTask.kt new file mode 100644 index 000000000..826e114b4 --- /dev/null +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/task/ExceptionHandlingAbstractTask.kt @@ -0,0 +1,62 @@ +/* + * Twidere - Twitter client for Android + * + * Copyright (C) 2012-2017 Mariotaku Lee + * + * 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 . + */ + +package org.mariotaku.twidere.task + +import android.content.Context +import org.mariotaku.twidere.model.SingleResponse + +/** + * Created by mariotaku on 2017/2/10. + */ + +abstract class ExceptionHandlingAbstractTask( + context: Context +) : BaseAbstractTask, Callback>(context) { + + override final fun afterExecute(callback: Callback?, result: SingleResponse) { + @Suppress("UNCHECKED_CAST") + afterExecute(callback, result.data, result.exception as? TaskException) + if (result.data != null) { + onSucceed(callback, result.data) + } else if (result.exception != null) { + @Suppress("UNCHECKED_CAST") + onException(callback, result.exception as TaskException) + } + } + + override final fun doLongOperation(params: Params): SingleResponse { + try { + return SingleResponse(onExecute(params)) + } catch (tr: TaskException) { + return SingleResponse(tr) + } + } + + open fun afterExecute(callback: Callback?, result: Result?, exception: TaskException?) { + } + + open fun onSucceed(callback: Callback?, result: Result) { + } + + open fun onException(callback: Callback?, exception: TaskException) { + } + + abstract fun onExecute(params: Params): Result +} diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/task/GetMessagesTask.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/task/GetMessagesTask.kt index c54114a96..4f341e663 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/task/GetMessagesTask.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/task/GetMessagesTask.kt @@ -76,12 +76,31 @@ class GetMessagesTask(context: Context) : BaseAbstractTask + updateActivityStatus(resolver, accountKey, statusId) { activity -> val statusesMatrix = arrayOf(activity.target_statuses, activity.target_object_statuses) activity.status_my_retweet_id = result.my_retweet_id for (statusesArray in statusesMatrix) { @@ -87,7 +83,7 @@ class RetweetStatusTask( } } } - }) + } UpdateStatusTask.deleteDraft(context, draftId) return SingleResponse(result) } catch (e: MicroBlogException) { @@ -127,7 +123,7 @@ class RetweetStatusTask( companion object { private val creatingRetweetIds = ArrayIntList() fun isCreatingRetweet(accountKey: UserKey?, statusId: String?): Boolean { - return creatingRetweetIds.contains(calculateHashCode(accountKey, statusId)) + return creatingRetweetIds.contains(AsyncTwitterWrapper.calculateHashCode(accountKey, statusId)) } } diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/task/SendMessageTask.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/task/SendMessageTask.kt index e6f4f6e9a..d283295df 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/task/SendMessageTask.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/task/SendMessageTask.kt @@ -7,7 +7,9 @@ import org.mariotaku.twidere.model.SingleResponse /** * Created by mariotaku on 2017/2/8. */ -class SendMessageTask(context: Context) : BaseAbstractTask, Unit>(context) { +class SendMessageTask( + context: Context +) : BaseAbstractTask, Unit>(context) { override fun doLongOperation(params: Unit?): SingleResponse { return SingleResponse(UnsupportedOperationException()) } diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/task/UpdateProfileBackgroundImageTask.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/task/UpdateProfileBackgroundImageTask.kt index 8e65e5fe4..7b75dbc9b 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/task/UpdateProfileBackgroundImageTask.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/task/UpdateProfileBackgroundImageTask.kt @@ -2,9 +2,6 @@ package org.mariotaku.twidere.task import android.content.Context import android.net.Uri -import android.util.Log -import com.squareup.otto.Bus -import org.mariotaku.abstask.library.AbstractTask import org.mariotaku.microblog.library.MicroBlogException import org.mariotaku.twidere.R import org.mariotaku.twidere.TwidereConstants.LOGTAG @@ -13,12 +10,11 @@ import org.mariotaku.twidere.model.SingleResponse import org.mariotaku.twidere.model.UserKey import org.mariotaku.twidere.model.event.ProfileUpdatedEvent import org.mariotaku.twidere.model.util.ParcelableUserUtils +import org.mariotaku.twidere.util.DebugLog import org.mariotaku.twidere.util.MicroBlogAPIFactory import org.mariotaku.twidere.util.TwitterWrapper import org.mariotaku.twidere.util.Utils -import org.mariotaku.twidere.util.dagger.GeneralComponentHelper import java.io.IOException -import javax.inject.Inject /** * Created by mariotaku on 16/3/11. @@ -52,7 +48,7 @@ open class UpdateProfileBackgroundImageTask( try { Thread.sleep(5000L) } catch (e: InterruptedException) { - Log.w(LOGTAG, e) + DebugLog.w(LOGTAG, tr = e) } val user = twitter.verifyCredentials() return SingleResponse(ParcelableUserUtils.fromUser(user, accountKey)) diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/task/UpdateUserListDetailsTask.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/task/UpdateUserListDetailsTask.kt new file mode 100644 index 000000000..8d5821abc --- /dev/null +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/task/UpdateUserListDetailsTask.kt @@ -0,0 +1,68 @@ +/* + * Twidere - Twitter client for Android + * + * Copyright (C) 2012-2017 Mariotaku Lee + * + * 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 . + */ + +package org.mariotaku.twidere.task + +import android.content.Context +import org.mariotaku.microblog.library.MicroBlogException +import org.mariotaku.microblog.library.twitter.model.UserListUpdate +import org.mariotaku.twidere.R +import org.mariotaku.twidere.model.ParcelableUserList +import org.mariotaku.twidere.model.SingleResponse +import org.mariotaku.twidere.model.UserKey +import org.mariotaku.twidere.model.event.UserListUpdatedEvent +import org.mariotaku.twidere.model.util.ParcelableUserListUtils +import org.mariotaku.twidere.util.MicroBlogAPIFactory +import org.mariotaku.twidere.util.Utils + +/** + * Created by mariotaku on 2017/2/10. + */ +class UpdateUserListDetailsTask( + context: Context, + private val accountKey: UserKey, + private val listId: String, + private val update: UserListUpdate +) : BaseAbstractTask, Any>(context) { + + override fun doLongOperation(o: Any): SingleResponse { + val microBlog = MicroBlogAPIFactory.getInstance(context, accountKey) + if (microBlog != null) { + try { + val list = microBlog.updateUserList(listId, update) + return SingleResponse(ParcelableUserListUtils.from(list, accountKey)) + } catch (e: MicroBlogException) { + return SingleResponse(e) + } + + } + return SingleResponse.getInstance() + } + + override fun afterExecute(callback: Any?, result: SingleResponse) { + if (result.data != null) { + val message = context.getString(R.string.updated_list_details, result.data.name) + Utils.showOkMessage(context, message, false) + bus.post(UserListUpdatedEvent(result.data)) + } else { + Utils.showErrorMessage(context, R.string.action_updating_details, result.exception, true) + } + } + +} diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/GetActivitiesTask.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/GetActivitiesTask.kt index 7d11975ca..ad8f3f959 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/GetActivitiesTask.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/GetActivitiesTask.kt @@ -155,7 +155,7 @@ abstract class GetActivitiesTask( var olderCount = -1 if (minPositionKey > 0) { olderCount = DataStoreUtils.getActivitiesCount(context, contentUri, minPositionKey, - Activities.POSITION_KEY, false, details.key) + Activities.POSITION_KEY, false, arrayOf(details.key)) } val writeUri = UriUtils.appendQueryParameters(contentUri, QUERY_PARAM_NOTIFY, notify) if (deleteBound[0] > 0 && deleteBound[1] > 0) { diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/util/AsyncTwitterWrapper.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/util/AsyncTwitterWrapper.kt new file mode 100644 index 000000000..dfdaec538 --- /dev/null +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/util/AsyncTwitterWrapper.kt @@ -0,0 +1,780 @@ +/* + * Twidere - Twitter client for Android + * + * Copyright (C) 2012-2017 Mariotaku Lee + * + * 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 . + */ + +package org.mariotaku.twidere.util + +import android.content.ContentValues +import android.content.Context +import android.net.Uri +import android.os.AsyncTask +import android.support.v4.util.SimpleArrayMap +import com.squareup.otto.Bus +import com.squareup.otto.Subscribe +import org.apache.commons.collections.primitives.ArrayIntList +import org.apache.commons.collections.primitives.ArrayLongList +import org.apache.commons.collections.primitives.IntList +import org.mariotaku.abstask.library.TaskStarter +import org.mariotaku.kpreferences.get +import org.mariotaku.ktextension.toNulls +import org.mariotaku.microblog.library.MicroBlogException +import org.mariotaku.microblog.library.twitter.http.HttpResponseCode +import org.mariotaku.microblog.library.twitter.model.* +import org.mariotaku.sqliteqb.library.Expression +import org.mariotaku.twidere.R +import org.mariotaku.twidere.TwidereConstants +import org.mariotaku.twidere.constant.SharedPreferenceConstants +import org.mariotaku.twidere.constant.nameFirstKey +import org.mariotaku.twidere.model.* +import org.mariotaku.twidere.model.event.* +import org.mariotaku.twidere.model.util.ParcelableUserListUtils +import org.mariotaku.twidere.provider.TwidereDataStore.* +import org.mariotaku.twidere.provider.TwidereDataStore.DirectMessages.Inbox +import org.mariotaku.twidere.provider.TwidereDataStore.DirectMessages.Outbox +import org.mariotaku.twidere.task.* +import org.mariotaku.twidere.util.collection.CompactHashSet +import java.util.* + +class AsyncTwitterWrapper( + val context: Context, + private val bus: Bus, + private val preferences: SharedPreferencesWrapper, + private val asyncTaskManager: AsyncTaskManager +) { + private val resolver = context.contentResolver + + + var destroyingStatusIds: IntList = ArrayIntList() + private val updatingRelationshipIds = ArrayIntList() + + private val sendingDraftIds = ArrayLongList() + + private val getMessageTasks = CompactHashSet() + private val getStatusTasks = CompactHashSet() + + init { + bus.register(object : Any() { + @Subscribe + fun onGetDirectMessagesTaskEvent(event: GetMessagesTaskEvent) { + if (event.running) { + getMessageTasks.add(event.uri) + } else { + getMessageTasks.remove(event.uri) + } + } + + @Subscribe + fun onGetStatusesTaskEvent(event: GetStatusesTaskEvent) { + if (event.running) { + getStatusTasks.add(event.uri) + } else { + getStatusTasks.remove(event.uri) + } + } + }) + } + + fun acceptFriendshipAsync(accountKey: UserKey, userKey: UserKey) { + val task = AcceptFriendshipTask(context) + task.setup(accountKey, userKey) + TaskStarter.execute(task) + } + + fun addSendingDraftId(id: Long) { + synchronized(sendingDraftIds) { + sendingDraftIds.add(id) + resolver.notifyChange(Drafts.CONTENT_URI_UNSENT, null) + } + } + + fun addUserListMembersAsync(accountKey: UserKey, listId: String, vararg users: ParcelableUser) { + val task = AddUserListMembersTask(context, accountKey, listId, users) + TaskStarter.execute(task) + } + + fun cancelRetweetAsync(accountKey: UserKey, statusId: String?, myRetweetId: String?) { + if (myRetweetId != null) { + destroyStatusAsync(accountKey, myRetweetId) + } else if (statusId != null) { + destroyStatusAsync(accountKey, statusId) + } + } + + fun clearNotificationAsync(notificationType: Int) { + clearNotificationAsync(notificationType, null) + } + + fun clearNotificationAsync(notificationId: Int, accountKey: UserKey?) { + val task = ClearNotificationTask(context, notificationId, accountKey) + AsyncTaskUtils.executeTask(task) + } + + fun clearUnreadCountAsync(position: Int) { + val task = ClearUnreadCountTask(position) + AsyncTaskUtils.executeTask(task) + } + + fun createBlockAsync(accountKey: UserKey, userKey: UserKey, filterEverywhere: Boolean) { + val task = CreateUserBlockTask(context, filterEverywhere) + task.setup(accountKey, userKey) + TaskStarter.execute(task) + } + + fun createFavoriteAsync(accountKey: UserKey, status: ParcelableStatus) { + val task = CreateFavoriteTask(context, accountKey, status) + TaskStarter.execute(task) + } + + fun createFriendshipAsync(accountKey: UserKey, userKey: UserKey) { + val task = CreateFriendshipTask(context) + task.setup(accountKey, userKey) + TaskStarter.execute(task) + } + + fun createMultiBlockAsync(accountKey: UserKey, userIds: Array): Int { + val task = CreateMultiBlockTask(context, accountKey, userIds) + return asyncTaskManager.add(task, true) + } + + fun createMuteAsync(accountKey: UserKey, userKey: UserKey, filterEverywhere: Boolean) { + val task = CreateUserMuteTask(context, filterEverywhere) + task.setup(accountKey, userKey) + TaskStarter.execute(task) + } + + fun createSavedSearchAsync(accountKey: UserKey, query: String): Int { + val task = CreateSavedSearchTask(accountKey, query) + return asyncTaskManager.add(task, true) + } + + fun createUserListAsync(accountKey: UserKey, listName: String, isPublic: Boolean, + description: String): Int { + val task = CreateUserListTask(context, accountKey, listName, isPublic, + description) + return asyncTaskManager.add(task, true) + } + + fun createUserListSubscriptionAsync(accountKey: UserKey, listId: String): Int { + val task = CreateUserListSubscriptionTask(accountKey, listId) + return asyncTaskManager.add(task, true) + } + + fun deleteUserListMembersAsync(accountKey: UserKey, listId: String, vararg users: ParcelableUser): Int { + val task = DeleteUserListMembersTask(accountKey, listId, users) + return asyncTaskManager.add(task, true) + } + + fun denyFriendshipAsync(accountKey: UserKey, userKey: UserKey) { + val task = DenyFriendshipTask(context) + task.setup(accountKey, userKey) + TaskStarter.execute(task) + } + + fun destroyBlockAsync(accountKey: UserKey, userKey: UserKey) { + val task = DestroyUserBlockTask(context) + task.setup(accountKey, userKey) + TaskStarter.execute(task) + } + + fun destroyFavoriteAsync(accountKey: UserKey, statusId: String) { + val task = DestroyFavoriteTask(context, accountKey, statusId) + TaskStarter.execute(task) + } + + fun destroyFriendshipAsync(accountKey: UserKey, userKey: UserKey) { + val task = DestroyFriendshipTask(context) + task.setup(accountKey, userKey) + TaskStarter.execute(task) + } + + fun destroyMuteAsync(accountKey: UserKey, userKey: UserKey) { + val task = DestroyUserMuteTask(context) + task.setup(accountKey, userKey) + TaskStarter.execute(task) + } + + fun destroySavedSearchAsync(accountKey: UserKey, searchId: Long): Int { + val task = DestroySavedSearchTask(context, accountKey, searchId) + return asyncTaskManager.add(task, true) + } + + fun destroyStatusAsync(accountKey: UserKey, statusId: String) { + val task = DestroyStatusTask(context, accountKey, statusId) + TaskStarter.execute(task) + } + + fun destroyUserListAsync(accountKey: UserKey, listId: String) { + val task = DestroyUserListTask(context, accountKey, listId) + TaskStarter.execute(task) + } + + fun destroyUserListSubscriptionAsync(accountKey: UserKey, listId: String): Int { + val task = DestroyUserListSubscriptionTask(context, accountKey, listId) + return asyncTaskManager.add(task, true) + } + + fun getHomeTimelineAsync(param: RefreshTaskParam): Boolean { + val task = GetHomeTimelineTask(context) + task.params = param + TaskStarter.execute(task) + return true + } + + fun getLocalTrendsAsync(accountId: UserKey, woeId: Int) { + val task = GetTrendsTask(context, accountId, woeId) + TaskStarter.execute(task) + } + + fun getMessagesAsync(param: RefreshTaskParam) { + val task = GetMessagesTask(context) + task.params = param + TaskStarter.execute(task) + } + + fun getSavedSearchesAsync(accountKeys: Array) { + val task = GetSavedSearchesTask(context) + task.params = accountKeys + TaskStarter.execute, SingleResponse, Any>(task) + } + + fun getSendingDraftIds(): LongArray { + return sendingDraftIds.toArray() + } + + fun isDestroyingStatus(accountId: UserKey?, statusId: String?): Boolean { + return destroyingStatusIds.contains(calculateHashCode(accountId, statusId)) + } + + fun isStatusTimelineRefreshing(uri: Uri): Boolean { + return getStatusTasks.contains(uri) + } + + fun refreshAll() { + refreshAll { DataStoreUtils.getActivatedAccountKeys(context) } + } + + fun refreshAll(accountKeys: Array): Boolean { + return refreshAll { accountKeys } + } + + fun refreshAll(action: () -> Array): Boolean { + getHomeTimelineAsync(object : SimpleRefreshTaskParam() { + + override val accountKeys: Array by lazy { action() } + + override val sinceIds: Array? by lazy { + DataStoreUtils.getNewestStatusIds(context, Statuses.CONTENT_URI, + accountKeys.toNulls()) + } + }) + if (preferences.getBoolean(SharedPreferenceConstants.KEY_HOME_REFRESH_MENTIONS)) { + getActivitiesAboutMeAsync(object : SimpleRefreshTaskParam() { + override val accountKeys: Array by lazy { action() } + + override val sinceIds: Array? by lazy { + DataStoreUtils.getNewestActivityMaxPositions(context, + Activities.AboutMe.CONTENT_URI, accountKeys.toNulls()) + } + }) + } + if (preferences.getBoolean(SharedPreferenceConstants.KEY_HOME_REFRESH_DIRECT_MESSAGES)) { + getMessagesAsync(object : SimpleRefreshTaskParam() { + override val accountKeys: Array by lazy { action() } + }) + } + if (preferences.getBoolean(SharedPreferenceConstants.KEY_HOME_REFRESH_SAVED_SEARCHES)) { + getSavedSearchesAsync(action()) + } + return true + } + + fun removeSendingDraftId(id: Long) { + synchronized(sendingDraftIds) { + sendingDraftIds.removeElement(id) + resolver.notifyChange(Drafts.CONTENT_URI_UNSENT, null) + } + } + + fun removeUnreadCountsAsync(position: Int, counts: SimpleArrayMap>) { + val task = RemoveUnreadCountsTask(context, position, counts) + AsyncTaskUtils.executeTask(task) + } + + fun reportMultiSpam(accountKey: UserKey, userIds: Array) { + // TODO implementation + } + + fun reportSpamAsync(accountKey: UserKey, userKey: UserKey) { + val task = ReportSpamAndBlockTask(context) + task.setup(accountKey, userKey) + TaskStarter.execute(task) + } + + fun retweetStatusAsync(accountKey: UserKey, status: ParcelableStatus) { + val task = RetweetStatusTask(context, accountKey, status) + TaskStarter.execute, Any>(task) + } + + fun updateUserListDetails(accountKey: UserKey, listId: String, update: UserListUpdate) { + val task = UpdateUserListDetailsTask(context, accountKey, listId, update) + TaskStarter.execute(task) + } + + fun updateFriendship(accountKey: UserKey, userKey: UserKey, update: FriendshipUpdate) { + TaskStarter.execute(object : ExceptionHandlingAbstractTask(context) { + override fun onExecute(params: Any): Relationship { + val microBlog = MicroBlogAPIFactory.getInstance(context, accountKey) + ?: throw MicroBlogException("No account") + val relationship = microBlog.updateFriendship(userKey.id, update) + if (!relationship.isSourceWantRetweetsFromTarget) { + // TODO remove cached retweets + val where = Expression.and( + Expression.equalsArgs(Statuses.ACCOUNT_KEY), + Expression.equalsArgs(Statuses.RETWEETED_BY_USER_KEY) + ) + val selectionArgs = arrayOf(accountKey.toString(), userKey.toString()) + context.contentResolver.delete(Statuses.CONTENT_URI, where.sql, selectionArgs) + } + return relationship + } + + override fun onSucceed(callback: Any?, result: Relationship) { + bus.post(FriendshipUpdatedEvent(accountKey, userKey, result)) + } + + override fun onException(callback: Any?, exception: MicroBlogException) { + DebugLog.w(TwidereConstants.LOGTAG, "Unable to update friendship", exception) + } + + }) + } + + fun getActivitiesAboutMeAsync(param: RefreshTaskParam) { + val task = GetActivitiesAboutMeTask(context) + task.params = param + TaskStarter.execute(task) + } + + fun setActivitiesAboutMeUnreadAsync(accountKeys: Array, cursor: Long) { + val task = object : ExceptionHandlingAbstractTask(context) { + override fun onExecute(params: Any?) { + for (accountId in accountKeys) { + val microBlog = MicroBlogAPIFactory.getInstance(context, accountId) ?: continue + if (!Utils.isOfficialCredentials(context, accountId)) continue + microBlog.setActivitiesAboutMeUnread(cursor) + } + } + } + TaskStarter.execute(task) + } + + fun addUpdatingRelationshipId(accountKey: UserKey, userId: UserKey) { + updatingRelationshipIds.add(ParcelableUser.calculateHashCode(accountKey, userId)) + } + + fun removeUpdatingRelationshipId(accountKey: UserKey, userId: UserKey) { + updatingRelationshipIds.removeElement(ParcelableUser.calculateHashCode(accountKey, userId)) + } + + fun isUpdatingRelationship(accountId: UserKey, userId: UserKey): Boolean { + return updatingRelationshipIds.contains(ParcelableUser.calculateHashCode(accountId, userId)) + } + + internal inner class ClearNotificationTask( + private val context: Context, + private val notificationType: Int, + private val accountKey: UserKey? + ) : AsyncTask() { + + override fun doInBackground(vararg params: Any): Int { + return TwitterWrapper.clearNotification(context, notificationType, accountKey) + } + + } + + internal inner class ClearUnreadCountTask(private val position: Int) : AsyncTask() { + + override fun doInBackground(vararg params: Any): Int { + return TwitterWrapper.clearUnreadCount(context, position) + } + + } + + internal inner class CreateMultiBlockTask( + context: Context, + private val accountKey: UserKey, + private val userIds: Array + ) : ManagedAsyncTask>(context) { + + private fun deleteCaches(list: List) { + // I bet you don't want to see these users in your auto complete list. + //TODO insert to blocked users data + val values = ContentValues() + values.put(CachedRelationships.BLOCKING, true) + values.put(CachedRelationships.FOLLOWING, false) + values.put(CachedRelationships.FOLLOWED_BY, false) + val where = Expression.inArgs(CachedRelationships.USER_KEY, list.size).sql + val selectionArgs = list.toTypedArray() + resolver.update(CachedRelationships.CONTENT_URI, values, where, selectionArgs) + } + + override fun doInBackground(vararg params: Any): ListResponse { + val blockedUsers = ArrayList() + val microBlog = MicroBlogAPIFactory.getInstance(context, accountKey) + if (microBlog != null) { + for (userId in userIds) { + try { + val user = microBlog.createBlock(userId) + blockedUsers.add(user.id) + } catch (e: MicroBlogException) { + deleteCaches(blockedUsers) + return ListResponse.getListInstance(e) + } + + } + } + deleteCaches(blockedUsers) + return ListResponse.getListInstance(blockedUsers) + } + + override fun onPostExecute(result: ListResponse) { + if (result.hasData()) { + Utils.showInfoMessage(context, R.string.users_blocked, false) + } else { + Utils.showErrorMessage(context, R.string.action_blocking, result.exception, true) + } + bus.post(UsersBlockedEvent(accountKey, userIds)) + super.onPostExecute(result) + } + + + } + + internal inner class CreateSavedSearchTask(private val mAccountKey: UserKey, private val mQuery: String) : ManagedAsyncTask>(context) { + + override fun doInBackground(vararg params: Any): SingleResponse? { + val microBlog = MicroBlogAPIFactory.getInstance(context, mAccountKey) ?: return null + try { + return SingleResponse.getInstance(microBlog.createSavedSearch(mQuery)) + } catch (e: MicroBlogException) { + return SingleResponse.getInstance(e) + } + + } + + override fun onPostExecute(result: SingleResponse) { + if (result.hasData()) { + val message = context.getString(R.string.message_toast_search_name_saved, result.data!!.query) + Utils.showOkMessage(context, message, false) + } else if (result.hasException()) { + val exception = result.exception + // https://github.com/TwidereProject/Twidere-Android/issues/244 + if (exception is MicroBlogException && exception.statusCode == 403) { + val desc = context.getString(R.string.saved_searches_already_saved_hint) + Utils.showErrorMessage(context, R.string.action_saving_search, desc, false) + } else { + Utils.showErrorMessage(context, R.string.action_saving_search, exception, false) + } + } + super.onPostExecute(result) + } + + } + + internal inner class CreateUserListSubscriptionTask(private val mAccountKey: UserKey, private val mListId: String) : ManagedAsyncTask>(context) { + + override fun doInBackground(vararg params: Any): SingleResponse { + val microBlog = MicroBlogAPIFactory.getInstance(context, mAccountKey) ?: return SingleResponse.getInstance() + try { + val userList = microBlog.createUserListSubscription(mListId) + val list = ParcelableUserListUtils.from(userList, mAccountKey) + return SingleResponse.getInstance(list) + } catch (e: MicroBlogException) { + return SingleResponse.getInstance(e) + } + + } + + override fun onPostExecute(result: SingleResponse) { + val succeed = result.hasData() + if (succeed) { + val message = context.getString(R.string.subscribed_to_list, result.data!!.name) + Utils.showOkMessage(context, message, false) + bus.post(UserListSubscriptionEvent(UserListSubscriptionEvent.Action.SUBSCRIBE, + result.data)) + } else { + Utils.showErrorMessage(context, R.string.action_subscribing_to_list, result.exception, true) + } + super.onPostExecute(result) + } + + } + + internal class CreateUserListTask(context: Context, private val mAccountKey: UserKey, private val mListName: String?, + private val mIsPublic: Boolean, private val mDescription: String) : ManagedAsyncTask>(context) { + + override fun doInBackground(vararg params: Any): SingleResponse { + val microBlog = MicroBlogAPIFactory.getInstance(context, mAccountKey + ) + if (microBlog == null || mListName == null) + return SingleResponse.getInstance() + try { + val userListUpdate = UserListUpdate() + userListUpdate.setName(mListName) + userListUpdate.setMode(if (mIsPublic) UserList.Mode.PUBLIC else UserList.Mode.PRIVATE) + userListUpdate.setDescription(mDescription) + val list = microBlog.createUserList(userListUpdate) + return SingleResponse.getInstance(ParcelableUserListUtils.from(list, mAccountKey)) + } catch (e: MicroBlogException) { + return SingleResponse.getInstance(e) + } + + } + + override fun onPostExecute(result: SingleResponse) { + val context = context + if (result.hasData()) { + val userList = result.data + val message = context.getString(R.string.created_list, userList!!.name) + Utils.showOkMessage(context, message, false) + bus.post(UserListCreatedEvent(userList)) + } else { + Utils.showErrorMessage(context, R.string.action_creating_list, result.exception, true) + } + super.onPostExecute(result) + } + + } + + internal inner class DeleteUserListMembersTask( + private val accountKey: UserKey, + private val userListId: String, + private val users: Array + ) : ManagedAsyncTask>(context) { + + override fun doInBackground(vararg params: Any): SingleResponse { + val microBlog = MicroBlogAPIFactory.getInstance(context, accountKey) ?: return SingleResponse.getInstance() + try { + val userIds = users.map { it.key }.toTypedArray() + val userList = microBlog.deleteUserListMembers(userListId, UserKey.getIds(userIds)) + val list = ParcelableUserListUtils.from(userList, accountKey) + return SingleResponse.getInstance(list) + } catch (e: MicroBlogException) { + return SingleResponse.getInstance(e) + } + + } + + override fun onPostExecute(result: SingleResponse) { + val succeed = result.hasData() + val message: String + if (succeed) { + if (users.size == 1) { + val user = users[0] + val nameFirst = preferences[nameFirstKey] + val displayName = userColorNameManager.getDisplayName(user.key, + user.name, user.screen_name, nameFirst) + message = context.getString(R.string.deleted_user_from_list, displayName, + result.data!!.name) + } else { + val res = context.resources + message = res.getQuantityString(R.plurals.deleted_N_users_from_list, users.size, users.size, + result.data!!.name) + } + bus.post(UserListMembersChangedEvent(UserListMembersChangedEvent.Action.REMOVED, + result.data, users)) + Utils.showInfoMessage(context, message, false) + } else { + Utils.showErrorMessage(context, R.string.action_deleting, result.exception, true) + } + super.onPostExecute(result) + } + + } + + + internal inner class DestroyDirectMessageTask(private val mAccountKey: UserKey, private val mMessageId: String) : ManagedAsyncTask>(context) { + + private fun deleteMessages() { + val where = Expression.and(Expression.equalsArgs(DirectMessages.ACCOUNT_KEY), + Expression.equalsArgs(DirectMessages.MESSAGE_ID)).sql + val whereArgs = arrayOf(mAccountKey.toString(), mMessageId) + resolver.delete(DirectMessages.Inbox.CONTENT_URI, where, whereArgs) + resolver.delete(DirectMessages.Outbox.CONTENT_URI, where, whereArgs) + } + + private fun isMessageNotFound(e: Exception?): Boolean { + if (e !is MicroBlogException) return false + return e.errorCode == ErrorInfo.PAGE_NOT_FOUND || e.statusCode == HttpResponseCode.NOT_FOUND + } + + override fun doInBackground(vararg args: Any): SingleResponse { + val microBlog = MicroBlogAPIFactory.getInstance(context, mAccountKey) ?: return SingleResponse.getInstance() + try { + val message = microBlog.destroyDirectMessage(mMessageId) + deleteMessages() + return SingleResponse.getInstance(message) + } catch (e: MicroBlogException) { + if (isMessageNotFound(e)) { + deleteMessages() + } + return SingleResponse.getInstance(e) + } + + } + + + override fun onPostExecute(result: SingleResponse) { + super.onPostExecute(result) + if (result.hasData() || isMessageNotFound(result.exception)) { + Utils.showInfoMessage(context, R.string.message_direct_message_deleted, false) + } else { + Utils.showErrorMessage(context, R.string.action_deleting, result.exception, true) + } + } + + + } + + + internal inner class DestroyMessageConversationTask(private val mAccountKey: UserKey, private val mUserId: String) : ManagedAsyncTask>(context) { + + private fun deleteMessages(accountKey: UserKey, userId: String) { + val whereArgs = arrayOf(accountKey.toString(), userId) + resolver.delete(DirectMessages.Inbox.CONTENT_URI, Expression.and( + Expression.equalsArgs(AccountSupportColumns.ACCOUNT_KEY), + Expression.equalsArgs(Inbox.SENDER_ID) + ).sql, whereArgs) + resolver.delete(DirectMessages.Outbox.CONTENT_URI, Expression.and( + Expression.equalsArgs(AccountSupportColumns.ACCOUNT_KEY), + Expression.equalsArgs(Outbox.RECIPIENT_ID) + ).sql, whereArgs) + } + + private fun isMessageNotFound(e: Exception?): Boolean { + if (e !is MicroBlogException) return false + return e.errorCode == ErrorInfo.PAGE_NOT_FOUND || e.statusCode == HttpResponseCode.NOT_FOUND + } + + override fun doInBackground(vararg args: Any): SingleResponse { + val microBlog = MicroBlogAPIFactory.getInstance(context, mAccountKey) ?: return SingleResponse(MicroBlogException("No account")) + try { + microBlog.destroyDirectMessagesConversation(mAccountKey.id, mUserId) + deleteMessages(mAccountKey, mUserId) + return SingleResponse(true) + } catch (e: MicroBlogException) { + if (isMessageNotFound(e)) { + deleteMessages(mAccountKey, mUserId) + } + return SingleResponse(e) + } + + } + + + override fun onPostExecute(result: SingleResponse) { + super.onPostExecute(result) + if (result.hasData() || isMessageNotFound(result.exception)) { + Utils.showInfoMessage(context, R.string.message_direct_message_deleted, false) + } else { + Utils.showErrorMessage(context, R.string.action_deleting, result.exception, true) + } + } + + + } + + + internal inner class DestroySavedSearchTask( + context: Context, + private val accountKey: UserKey, + private val searchId: Long + ) : ManagedAsyncTask>(context) { + + override fun doInBackground(vararg params: Any): SingleResponse { + val microBlog = MicroBlogAPIFactory.getInstance(context, accountKey) ?: return SingleResponse.getInstance() + try { + return SingleResponse.getInstance(microBlog.destroySavedSearch(searchId)) + } catch (e: MicroBlogException) { + return SingleResponse.getInstance(e) + } + + } + + override fun onPostExecute(result: SingleResponse) { + if (result.hasData()) { + val message = context.getString(R.string.message_toast_search_name_deleted, result.data!!.query) + Utils.showOkMessage(context, message, false) + bus.post(SavedSearchDestroyedEvent(accountKey, searchId)) + } else { + Utils.showErrorMessage(context, R.string.action_deleting_search, result.exception, false) + } + super.onPostExecute(result) + } + + } + + internal inner class DestroyUserListSubscriptionTask( + context: Context, + private val accountKey: UserKey, + private val listId: String + ) : ManagedAsyncTask>(context) { + + override fun doInBackground(vararg params: Any): SingleResponse { + + val microBlog = MicroBlogAPIFactory.getInstance(context, accountKey) ?: return SingleResponse.getInstance() + try { + val userList = microBlog.destroyUserListSubscription(listId) + val list = ParcelableUserListUtils.from(userList, accountKey) + return SingleResponse.getInstance(list) + } catch (e: MicroBlogException) { + return SingleResponse.getInstance(e) + } + + } + + override fun onPostExecute(result: SingleResponse) { + val succeed = result.hasData() + if (succeed) { + val message = context.getString(R.string.unsubscribed_from_list, result.data!!.name) + Utils.showOkMessage(context, message, false) + bus.post(UserListSubscriptionEvent(UserListSubscriptionEvent.Action.UNSUBSCRIBE, + result.data)) + } else { + Utils.showErrorMessage(context, R.string.action_unsubscribing_from_list, result.exception, true) + } + super.onPostExecute(result) + } + + } + + + companion object { + + fun calculateHashCode(accountId: UserKey?, statusId: String?): Int { + return (accountId?.hashCode() ?: 0) xor (statusId?.hashCode() ?: 0) + } + + fun > getException(responses: List): Exception? { + for (response in responses) { + if (response.hasException()) return response.exception + } + return null + } + } +} diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/util/DataStoreFunctions.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/util/DataStoreFunctions.kt index b876ec410..4203b53c4 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/util/DataStoreFunctions.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/util/DataStoreFunctions.kt @@ -2,18 +2,22 @@ package org.mariotaku.twidere.util import android.annotation.SuppressLint import android.content.ContentResolver +import android.content.ContentValues import android.content.Context import android.content.SharedPreferences import android.net.Uri +import android.support.annotation.WorkerThread +import android.support.v4.util.LongSparseArray import org.mariotaku.kpreferences.get import org.mariotaku.ktextension.useCursor import org.mariotaku.sqliteqb.library.* import org.mariotaku.twidere.constant.filterPossibilitySensitiveStatusesKey import org.mariotaku.twidere.constant.filterUnavailableQuoteStatusesKey -import org.mariotaku.twidere.model.DraftCursorIndices +import org.mariotaku.twidere.model.* import org.mariotaku.twidere.model.ParcelableStatus.FilterFlags -import org.mariotaku.twidere.model.UserKey import org.mariotaku.twidere.provider.TwidereDataStore.* +import org.mariotaku.twidere.util.DataStoreUtils.ACTIVITIES_URIS +import java.io.IOException /** * Created by mariotaku on 2016/12/24. @@ -113,4 +117,101 @@ fun deleteAccountData(resolver: ContentResolver, accountKey: UserKey) { resolver.delete(Activities.AboutMe.CONTENT_URI, where, whereArgs) resolver.delete(Messages.CONTENT_URI, where, whereArgs) resolver.delete(Messages.Conversations.CONTENT_URI, where, whereArgs) -} \ No newline at end of file +} + + +fun deleteActivityStatus(cr: ContentResolver, accountKey: UserKey, + statusId: String, result: ParcelableStatus?) { + + val host = accountKey.host + val deleteWhere: String + val updateWhere: String + val deleteWhereArgs: Array + val updateWhereArgs: Array + if (host != null) { + deleteWhere = Expression.and( + Expression.likeRaw(Columns.Column(Activities.ACCOUNT_KEY), "'%@'||?"), + Expression.or( + Expression.equalsArgs(Activities.STATUS_ID), + Expression.equalsArgs(Activities.STATUS_RETWEET_ID) + )).sql + deleteWhereArgs = arrayOf(host, statusId, statusId) + updateWhere = Expression.and( + Expression.likeRaw(Columns.Column(Activities.ACCOUNT_KEY), "'%@'||?"), + Expression.equalsArgs(Activities.STATUS_MY_RETWEET_ID) + ).sql + updateWhereArgs = arrayOf(host, statusId) + } else { + deleteWhere = Expression.or( + Expression.equalsArgs(Activities.STATUS_ID), + Expression.equalsArgs(Activities.STATUS_RETWEET_ID) + ).sql + deleteWhereArgs = arrayOf(statusId, statusId) + updateWhere = Expression.equalsArgs(Activities.STATUS_MY_RETWEET_ID).sql + updateWhereArgs = arrayOf(statusId) + } + for (uri in ACTIVITIES_URIS) { + cr.delete(uri, deleteWhere, deleteWhereArgs) + updateActivity(cr, uri, updateWhere, updateWhereArgs) { activity -> + activity.status_my_retweet_id = null + arrayOf(activity.target_statuses, activity.target_object_statuses).filterNotNull().forEach { + for (status in it) { + if (statusId == status.id || statusId == status.retweet_id || statusId == status.my_retweet_id) { + status.my_retweet_id = null + if (result != null) { + status.reply_count = result.reply_count + status.retweet_count = result.retweet_count - 1 + status.favorite_count = result.favorite_count + } + } + } + } + } + } +} + +fun updateActivityStatus(resolver: ContentResolver, + accountKey: UserKey, + statusId: String, + action: (ParcelableActivity) -> Unit) { + val activityWhere = Expression.and( + Expression.equalsArgs(Activities.ACCOUNT_KEY), + Expression.or( + Expression.equalsArgs(Activities.STATUS_ID), + Expression.equalsArgs(Activities.STATUS_RETWEET_ID) + ) + ).sql + val activityWhereArgs = arrayOf(accountKey.toString(), statusId, statusId) + for (uri in ACTIVITIES_URIS) { + updateActivity(resolver, uri, activityWhere, activityWhereArgs, action) + } +} + + +@WorkerThread +fun updateActivity(cr: ContentResolver, uri: Uri, + where: String?, whereArgs: Array?, + action: (ParcelableActivity) -> Unit) { + val c = cr.query(uri, Activities.COLUMNS, where, whereArgs, null) ?: return + val values = LongSparseArray() + try { + val ci = ParcelableActivityCursorIndices(c) + c.moveToFirst() + while (!c.isAfterLast) { + val activity = ci.newObject(c) + action(activity) + values.put(activity._id, ParcelableActivityValuesCreator.create(activity)) + c.moveToNext() + } + } catch (e: IOException) { + return + } finally { + c.close() + } + val updateWhere = Expression.equalsArgs(Activities._ID).sql + val updateWhereArgs = arrayOfNulls(1) + for (i in 0 until values.size()) { + updateWhereArgs[0] = values.keyAt(i).toString() + cr.update(uri, values.valueAt(i), updateWhere, updateWhereArgs) + } +} diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/util/DataStoreUtils.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/util/DataStoreUtils.kt new file mode 100644 index 000000000..387d687ed --- /dev/null +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/util/DataStoreUtils.kt @@ -0,0 +1,886 @@ +/* + * Twidere - Twitter client for Android + * + * Copyright (C) 2012-2017 Mariotaku Lee + * + * 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 . + */ + +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 com.bluelinelabs.logansquare.LoganSquare +import org.apache.commons.lang3.ArrayUtils +import org.apache.commons.lang3.StringUtils +import org.mariotaku.kpreferences.get +import org.mariotaku.ktextension.useCursor +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.getAccountKey +import org.mariotaku.twidere.extension.model.getAccountUser +import org.mariotaku.twidere.extension.model.getColor +import org.mariotaku.twidere.extension.model.isActivated +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.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, Messages.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, Activities.ByFriends.CONTENT_PATH, + TABLE_ID_ACTIVITIES_BY_FRIENDS) + 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, Messages.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, Notifications.CONTENT_PATH, + VIRTUAL_TABLE_ID_NOTIFICATIONS) + CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, Notifications.CONTENT_PATH + "/#", + VIRTUAL_TABLE_ID_NOTIFICATIONS) + CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, Notifications.CONTENT_PATH + "/#/*", + VIRTUAL_TABLE_ID_NOTIFICATIONS) + CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, Permissions.CONTENT_PATH, + VIRTUAL_TABLE_ID_PERMISSIONS) + CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, DNS.CONTENT_PATH + "/*", + VIRTUAL_TABLE_ID_DNS) + CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, CachedImages.CONTENT_PATH, + VIRTUAL_TABLE_ID_CACHED_IMAGES) + CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, CacheFiles.CONTENT_PATH + "/*", + VIRTUAL_TABLE_ID_CACHE_FILES) + CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, Preferences.CONTENT_PATH, + VIRTUAL_TABLE_ID_ALL_PREFERENCES) + CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, Preferences.CONTENT_PATH + "/*", + VIRTUAL_TABLE_ID_PREFERENCES) + CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, UnreadCounts.CONTENT_PATH, + VIRTUAL_TABLE_ID_UNREAD_COUNTS) + CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, UnreadCounts.CONTENT_PATH + "/#", + VIRTUAL_TABLE_ID_UNREAD_COUNTS) + CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, UnreadCounts.CONTENT_PATH + "/#/#/*", + VIRTUAL_TABLE_ID_UNREAD_COUNTS) + CONTENT_PROVIDER_URI_MATCHER.addURI(TwidereDataStore.AUTHORITY, UnreadCounts.ByType.CONTENT_PATH + "/*", + VIRTUAL_TABLE_ID_UNREAD_COUNTS_BY_TYPE) + 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): Array { + return getStringFieldArray(context, uri, accountKeys, Statuses.ACCOUNT_KEY, Statuses.STATUS_ID, + OrderBy(SQLFunctions.MAX(Statuses.STATUS_TIMESTAMP)), null, null) + } + + fun getMessageIds(context: Context, uri: Uri, accountKeys: Array, outgoing: Boolean): Array { + val having: Expression + if (outgoing) { + having = Expression.equals(Messages.IS_OUTGOING, 1) + } else { + having = Expression.notEquals(Messages.IS_OUTGOING, 1) + } + return getStringFieldArray(context, uri, accountKeys, Messages.ACCOUNT_KEY, Messages.MESSAGE_ID, + OrderBy(SQLFunctions.MAX(Messages.LOCAL_TIMESTAMP)), having, null) + } + + + fun getNewestStatusSortIds(context: Context, uri: Uri, accountKeys: Array): 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): Array { + 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): 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): Array { + return getStringFieldArray(context, uri, accountKeys, Activities.ACCOUNT_KEY, + Activities.MAX_REQUEST_POSITION, OrderBy(SQLFunctions.MAX(Activities.TIMESTAMP)), + null, null) + } + + fun getOldestActivityMaxPositions(context: Context, uri: Uri, accountKeys: Array): Array { + return getStringFieldArray(context, uri, accountKeys, Activities.ACCOUNT_KEY, + Activities.MAX_REQUEST_POSITION, OrderBy(SQLFunctions.MIN(Activities.TIMESTAMP)), + null, null) + } + + fun getNewestActivityMaxSortPositions(context: Context, uri: Uri, accountKeys: Array): LongArray { + return getLongFieldArray(context, uri, accountKeys, Activities.ACCOUNT_KEY, + Activities.MAX_SORT_POSITION, OrderBy(SQLFunctions.MAX(Activities.TIMESTAMP)), + null, null) + } + + fun getOldestActivityMaxSortPositions(context: Context, uri: Uri, accountKeys: Array): LongArray { + return getLongFieldArray(context, uri, accountKeys, Activities.ACCOUNT_KEY, + Activities.MAX_SORT_POSITION, OrderBy(SQLFunctions.MIN(Activities.TIMESTAMP)), + null, null) + } + + 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, uri, where, whereArgs) + } + + fun getActivitiesCount(context: Context, uri: Uri, + accountKey: UserKey): Int { + val where = Expression.equalsArgs(AccountSupportColumns.ACCOUNT_KEY).sql + return queryCount(context, uri, where, arrayOf(accountKey.toString())) + } + + + @SuppressLint("Recycle") + fun getFilteredUserIds(context: Context?): Array { + 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 { + val am = AccountManager.get(context) + val keys = ArrayList() + 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?): Int { + val keys = accountKeys ?: getActivatedAccountKeys(context) + + val expressions = ArrayList() + val expressionArgs = ArrayList() + + 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(EXTRA_EXTRAS) + if (extras is HomeTabExtras) { + processTabExtras(expressions, expressionArgs, extras) + } + } + + val selection = Expression.and(*expressions.toTypedArray()) + return queryCount(context, uri, selection.sql, expressionArgs.toTypedArray()) + } + + fun getActivitiesCount(context: Context, uri: Uri, compare: Long, + compareColumn: String, greaterThan: Boolean, accountKeys: Array?): 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() + keys.mapTo(whereArgs) { it.toString() } + whereArgs.add(compare.toString()) + return queryCount(context, uri, selection.sql, whereArgs.toTypedArray()) + } + + fun getActivitiesCount(context: Context, uri: Uri, + extraWhere: Expression?, extraWhereArgs: Array?, + since: Long, sinceColumn: String, followingOnly: Boolean, + accountKeys: Array?): Int { + val keys = (accountKeys ?: getActivatedAccountKeys(context)).map { it.toString() }.toTypedArray() + val expressions = ArrayList() + 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 + if (extraWhereArgs != null) { + selectionArgs = keys + since.toString() + extraWhereArgs + } else { + selectionArgs = keys + since.toString() + } + // If followingOnly option is on, we have to iterate over items + if (followingOnly) { + val resolver = context.contentResolver + val projection = arrayOf(Activities.SOURCES) + val cur = resolver.query(uri, projection, selection.sql, selectionArgs, null) ?: return -1 + try { + val mapper = LoganSquare.mapperFor(UserFollowState::class.java) + 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 mapper.parseList(string)) { + if (state.is_following) { + hasFollowing = true + break + } + } + } catch (e: IOException) { + continue + } + + if (hasFollowing) { + total++ + } + cur.moveToNext() + } + return total + } finally { + cur.close() + } + } + return queryCount(context, 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_ACTIVITIES_BY_FRIENDS -> return Activities.ByFriends.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 Messages.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 + "||'%'"), + Expression.likeRaw(Column(Table(table), Activities.STATUS_QUOTE_SOURCE), + "'%>'||" + Filters.Sources.TABLE_NAME + "." + Filters.Sources.VALUE + "||'%'") + )) + .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): 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 (StringUtils.equalsIgnoreCase(screenName, user.screen_name)) { + return user.key + } + } + return null + } + + fun getAccountKeys(context: Context): Array { + val am = AccountManager.get(context) + val accounts = AccountUtils.getAccounts(am) + val keys = ArrayList(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 getStringFieldArray(context: Context, uri: Uri, + keys: Array, keyField: String, + valueField: String, sortExpression: OrderBy?, + extraHaving: Expression?, extraHavingArgs: Array?): Array { + return getFieldArray(context, uri, keys, keyField, valueField, sortExpression, extraHaving, + extraHavingArgs, object : FieldArrayCreator> { + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + + override fun assign(array: Array, arrayIdx: Int, cur: Cursor, colIdx: Int) { + array[arrayIdx] = cur.getString(colIdx) + } + }) + } + + private fun getLongFieldArray(context: Context, uri: Uri, + keys: Array, keyField: String, + valueField: String, sortExpression: OrderBy?, + extraHaving: Expression?, extraHavingArgs: Array?): LongArray { + return getFieldArray(context, uri, keys, keyField, valueField, sortExpression, extraHaving, + extraHavingArgs, object : FieldArrayCreator { + override fun newArray(size: Int): LongArray { + return LongArray(size) + } + + override fun assign(array: LongArray, arrayIdx: Int, cur: Cursor, colIdx: Int) { + array[arrayIdx] = cur.getLong(colIdx) + } + }) + } + + @SuppressLint("Recycle") + private fun getFieldArray(context: Context, uri: Uri, + keys: Array, keyField: String, + valueField: String, sortExpression: OrderBy?, + extraHaving: Expression?, extraHavingArgs: Array?, + creator: FieldArrayCreator): 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 + if (extraHaving != null) { + having = Expression.and(extraHaving, Expression.inArgs(keyField, nonNullKeys.size)) + } else { + having = Expression.inArgs(keyField, nonNullKeys.size) + } + val havingArgs: Array + if (extraHavingArgs != null) { + havingArgs = extraHavingArgs + nonNullKeys + } else { + havingArgs = nonNullKeys + } + val builder = SQLQueryBuilder.select(Columns(keyField, valueField)) + .from(Table(tableName)) + .groupBy(Column(keyField)) + .having(having) + if (sortExpression != null) { + builder.orderBy(sortExpression) + } + val rawUri = Uri.withAppendedPath(TwidereDataStore.CONTENT_URI_RAW_QUERY, builder.buildSQL()) + resolver.query(rawUri, null, null, havingArgs, null)?.useCursor { cur -> + cur.moveToFirst() + while (!cur.isAfterLast) { + val string = cur.getString(0) + if (string != null) { + val accountKey = UserKey.valueOf(string) + val idx = ArrayUtils.indexOf(keys, accountKey) + if (idx >= 0) { + creator.assign(resultArray, idx, cur, 1) + } + } + 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 + val updateWhereArgs: Array + 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, expressionArgs: MutableList, 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 { + fun newArray(size: Int): T + + fun assign(array: T, arrayIdx: Int, cur: Cursor, colIdx: Int) + } + + fun queryCount(context: Context, uri: Uri, + selection: String?, selectionArgs: Array?): Int { + val resolver = context.contentResolver + val projection = arrayOf(SQLFunctions.COUNT()) + val cur = resolver.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, since: Long, sinceColumn: String): Int { + var extraWhere: Expression? = null + var extraWhereArgs: Array? = null + var followingOnly = false + if (extraArgs != null) { + val extras = extraArgs.getParcelable(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, filterAnywhere: Boolean) { + val cr = context.contentResolver + + try { + val userValues = ArrayList() + val keywordValues = ArrayList() + val linkValues = ArrayList() + for (user in users) { + val userItem = FiltersData.UserItem() + userItem.userKey = user.key + userItem.screenName = user.screen_name + userItem.name = user.name + userValues.add(`FiltersData$UserItemValuesCreator`.create(userItem)) + + val keywordItem = FiltersData.BaseItem() + keywordItem.value = "@" + user.screen_name + keywordValues.add(`FiltersData$BaseItemValuesCreator`.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 + linkValues.add(`FiltersData$BaseItemValuesCreator`.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) { + val userKeyValues = ArrayList() + val linkValues = ArrayList() + val keywordValues = ArrayList() + val cr = context.contentResolver + for (user in users) { + // Delete from filtered users + userKeyValues.add(user.key.toString()) + // Delete user mention from keywords + keywordValues.add("@" + user.screen_name) + + // Delete user link (without scheme) from links + val userLink = LinkCreator.getUserWebLink(user) + val linkWithoutScheme = userLink.toString().substringAfter("://") + linkValues.add(linkWithoutScheme) + } + ContentResolverUtils.bulkDelete(cr, Filters.Users.CONTENT_URI, Filters.Users.USER_KEY, false, userKeyValues, null) + ContentResolverUtils.bulkDelete(cr, Filters.Keywords.CONTENT_URI, Filters.Keywords.VALUE, false, keywordValues, null) + ContentResolverUtils.bulkDelete(cr, Filters.Links.CONTENT_URI, Filters.Links.VALUE, false, linkValues, 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.count > 0 && cur.moveToFirst()) { + status = ParcelableStatusCursorIndices.fromCursor(cur) + } + } catch (e: IOException) { + // Ignore + } 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 twitter = MicroBlogAPIFactory.getInstance(context, accountKey) ?: throw MicroBlogException("Account does not exist") + val result = twitter.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, false) + resolver.delete(CachedStatuses.CONTENT_URI, where, whereArgs) + try { + resolver.insert(CachedStatuses.CONTENT_URI, ParcelableStatusValuesCreator.create(status)) + } catch (e: IOException) { + // Ignore + } + + return status + } +} diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/util/TaskServiceRunner.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/util/TaskServiceRunner.kt index 01b02c691..c8028dbbb 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/util/TaskServiceRunner.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/util/TaskServiceRunner.kt @@ -6,13 +6,15 @@ import com.squareup.otto.Bus import org.mariotaku.abstask.library.AbstractTask import org.mariotaku.abstask.library.TaskStarter import org.mariotaku.kpreferences.KPreferences +import org.mariotaku.ktextension.toNulls import org.mariotaku.twidere.constant.IntentConstants.INTENT_PACKAGE_PREFIX import org.mariotaku.twidere.constant.dataSyncProviderInfoKey import org.mariotaku.twidere.constant.stopAutoRefreshWhenBatteryLowKey import org.mariotaku.twidere.model.AccountPreferences import org.mariotaku.twidere.model.SimpleRefreshTaskParam import org.mariotaku.twidere.model.UserKey -import org.mariotaku.twidere.provider.TwidereDataStore.* +import org.mariotaku.twidere.provider.TwidereDataStore.Activities +import org.mariotaku.twidere.provider.TwidereDataStore.Statuses import org.mariotaku.twidere.task.GetActivitiesAboutMeTask import org.mariotaku.twidere.task.GetHomeTimelineTask import org.mariotaku.twidere.task.GetMessagesTask @@ -54,21 +56,22 @@ class TaskServiceRunner( ACTION_REFRESH_HOME_TIMELINE -> { val task = GetHomeTimelineTask(context) task.params = AutoRefreshTaskParam(context, AccountPreferences::isAutoRefreshHomeTimelineEnabled) { accountKeys -> - DataStoreUtils.getNewestStatusIds(context, Statuses.CONTENT_URI, accountKeys) + DataStoreUtils.getNewestStatusIds(context, Statuses.CONTENT_URI, accountKeys.toNulls()) } return task } ACTION_REFRESH_NOTIFICATIONS -> { val task = GetActivitiesAboutMeTask(context) task.params = AutoRefreshTaskParam(context, AccountPreferences::isAutoRefreshMentionsEnabled) { accountKeys -> - DataStoreUtils.getNewestActivityMaxPositions(context, Activities.AboutMe.CONTENT_URI, accountKeys) + DataStoreUtils.getNewestActivityMaxPositions(context, Activities.AboutMe.CONTENT_URI, + accountKeys.toNulls()) } return task } ACTION_REFRESH_DIRECT_MESSAGES -> { val task = GetMessagesTask(context) task.params = AutoRefreshTaskParam(context, AccountPreferences::isAutoRefreshDirectMessagesEnabled) { accountKeys -> - DataStoreUtils.getNewestMessageIds(context, DirectMessages.Inbox.CONTENT_URI, accountKeys) + arrayOfNulls(accountKeys.size) } return task } @@ -90,11 +93,10 @@ class TaskServiceRunner( val refreshable: (AccountPreferences) -> Boolean, val getSinceIds: (Array) -> Array? ) : SimpleRefreshTaskParam() { - override fun getAccountKeysWorker(): Array { - val prefs = AccountPreferences.getAccountPreferences(context, - DataStoreUtils.getAccountKeys(context)).filter(AccountPreferences::isAutoRefreshEnabled) - return prefs.filter(refreshable) - .map(AccountPreferences::getAccountKey).toTypedArray() + override val accountKeys: Array by lazy { + return@lazy AccountPreferences.getAccountPreferences(context, DataStoreUtils.getAccountKeys(context)).filter { + it.isAutoRefreshEnabled && refreshable(it) + }.map(AccountPreferences::getAccountKey).toTypedArray() } override val sinceIds: Array?