kotlin migration

This commit is contained in:
Mariotaku Lee 2017-02-10 23:38:59 +08:00
parent 7e577b5957
commit 90cb5770e9
No known key found for this signature in database
GPG Key ID: 15C10F89D7C33535
38 changed files with 2340 additions and 2579 deletions

View File

@ -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'

View File

@ -98,7 +98,7 @@ public class SessionEvent extends BaseEvent implements Parcelable {
public void dumpPreferences(Context context) {
final HashMap<String, String> 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()));

View File

@ -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;

View File

@ -1,83 +0,0 @@
/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2014 Mariotaku Lee <mariotaku.lee@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.mariotaku.twidere.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();
}
}

View File

@ -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;
}

View File

@ -0,0 +1,50 @@
/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2017 Mariotaku Lee <mariotaku.lee@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.mariotaku.twidere.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<Object, Object, Integer> {
private final Context context;
private final int position;
private final SimpleArrayMap<UserKey, Set<String>> counts;
public RemoveUnreadCountsTask(Context context, final int position, final SimpleArrayMap<UserKey, Set<String>> counts) {
this.context = context;
this.position = position;
this.counts = counts;
}
@Override
protected Integer doInBackground(final Object... params) {
return TwitterWrapper.removeUnreadCounts(context, position, counts);
}
}

View File

@ -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));
}

View File

@ -13,3 +13,7 @@ fun Array<*>?.isNullOrEmpty(): Boolean {
return this == null || this.isEmpty()
}
fun <T : Any> Array<T>.toNulls(): Array<T?> {
@Suppress("UNCHECKED_CAST")
return this as Array<T?>
}

View File

@ -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)

View File

@ -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<List<ParcelableActivity>> {
override fun onCreateActivitiesLoader(context: Context, args: Bundle, fromUser: Boolean): Loader<List<ParcelableActivity>> {
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<UserKey> {
return this@CursorActivitiesFragment.accountKeys
override val accountKeys: Array<UserKey> by lazy {
this@CursorActivitiesFragment.accountKeys
}
override val maxIds: Array<String?>?
@ -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<UserKey> {
return this@CursorActivitiesFragment.accountKeys
override val accountKeys: Array<UserKey> by lazy {
this@CursorActivitiesFragment.accountKeys
}
override val sinceIds: Array<String?>?
@ -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<UserKey>): Array<String?>? {
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<UserKey>): Array<String?>? {
val context = context ?: return null
return DataStoreUtils.getOldestActivityMaxPositions(context, contentUri, accountKeys)
return DataStoreUtils.getOldestActivityMaxPositions(context, contentUri, accountKeys.toNulls())
}
protected abstract val isFilterEnabled: Boolean

View File

@ -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<List<ParcelableStatus>?> {
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<UserKey> {
return this@CursorStatusesFragment.accountKeys
override val accountKeys: Array<UserKey> by lazy {
this@CursorStatusesFragment.accountKeys
}
override val maxIds: Array<String?>?
@ -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<UserKey> {
return this@CursorStatusesFragment.accountKeys
override val accountKeys: Array<UserKey> 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<UserKey>): Array<String?>? {
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<UserKey>): Array<String?>? {
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<String>): ParameterizedExpression {

View File

@ -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<MessagesEntri
override fun triggerRefresh(): Boolean {
super.triggerRefresh()
twitterWrapper.getMessagesAsync(object : SimpleRefreshTaskParam() {
override fun getAccountKeysWorker(): Array<UserKey> {
return this@MessagesEntriesFragment.accountKeys
private val accounts by lazy {
AccountUtils.getAllAccountDetails(AccountManager.get(context), accountKeys, false)
}
override val accountKeys: Array<UserKey> by lazy {
this@MessagesEntriesFragment.accountKeys
}
override val sinceIds: Array<String?>?
get() {
val result = arrayOfNulls<String>(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
}

View File

@ -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<UserKey>(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<UserKey>(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<ParcelableUserList> {
if (!omitIntentExtra && extras != null) {
val cache = extras.getParcelable<ParcelableUserList>(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<ParcelableUserList>()
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<ParcelableUserList>()
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<ParcelableUserList>(e)
return SingleResponse(e)
}
}

View File

@ -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)
}

View File

@ -7,15 +7,6 @@ abstract class SimpleRefreshTaskParam : RefreshTaskParam {
internal var cached: Array<UserKey>? = null
override val accountKeys: Array<UserKey>
get() {
if (cached != null) return cached!!
cached = getAccountKeysWorker()
return cached!!
}
abstract fun getAccountKeysWorker(): Array<UserKey>
override val maxIds: Array<String?>?
get() = null

View File

@ -0,0 +1,58 @@
/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2014 Mariotaku Lee <mariotaku.lee@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.mariotaku.twidere.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()
}
}

View File

@ -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<AbsFriendshipOperationTask.Arguments, SingleResponse<ParcelableUser>, Any?>(context) {
) : ExceptionHandlingAbstractTask<AbsFriendshipOperationTask.Arguments, ParcelableUser,
MicroBlogException, Any?>(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<ParcelableUser>?) {
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<ParcelableUser> {
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<ParcelableUser>(e)
}
val user = perform(twitter, details, params)
val parcelableUser = ParcelableUserUtils.fromUser(user, params.accountKey)
succeededWorker(twitter, details, params, parcelableUser)
return parcelableUser
}
@Throws(MicroBlogException::class)

View File

@ -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 {

View File

@ -20,23 +20,24 @@ class AddUserListMembersTask(
context: Context,
private val accountKey: UserKey,
private val listId: String,
private val users: Array<ParcelableUser>
) : ManagedAsyncTask<Any, Any, SingleResponse<ParcelableUserList>>(context) {
private val users: Array<out ParcelableUser>
) : BaseAbstractTask<Any?, SingleResponse<ParcelableUserList>, Any?>(context) {
override fun doInBackground(vararg params: Any): SingleResponse<ParcelableUserList> {
val microBlog = MicroBlogAPIFactory.getInstance(context, accountKey) ?: return SingleResponse.getInstance<ParcelableUserList>()
override fun doLongOperation(params: Any?): SingleResponse<ParcelableUserList> {
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<ParcelableUserList>(e)
return SingleResponse(e)
}
}
override fun onPostExecute(result: SingleResponse<ParcelableUserList>) {
override fun afterExecute(callback: Any?, result: SingleResponse<ParcelableUserList>) {
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)
}
}

View File

@ -33,7 +33,7 @@ import java.util.*
class CacheUsersStatusesTask(
private val context: Context
) : AbstractTask<TwitterListResponse<Status>, Unit, Unit>() {
) : AbstractTask<TwitterListResponse<Status>, Unit?, Unit?>() {
override fun doLongOperation(params: TwitterListResponse<Status>) {
val resolver = context.contentResolver

View File

@ -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, SingleResponse<ParcelableStatus>, Any>(context) {
) : BaseAbstractTask<Any?, SingleResponse<ParcelableStatus>, 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

View File

@ -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<Any, Any, SingleResponse<ParcelableStatus>>(context) {
override fun doInBackground(vararg params: Any): SingleResponse<ParcelableStatus> {
) : BaseAbstractTask<Any?, SingleResponse<ParcelableStatus>, Any?>(context) {
override fun doLongOperation(params: Any?): SingleResponse<ParcelableStatus> {
val resolver = context.contentResolver
val details = AccountUtils.getAccountDetails(AccountManager.get(context), accountKey, true)
?: return SingleResponse.getInstance<ParcelableStatus>()
@ -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<ParcelableStatus>(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<ParcelableStatus>) {
override fun afterExecute(callback: Any?, result: SingleResponse<ParcelableStatus>) {
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 {

View File

@ -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<Any, Any, SingleResponse<ParcelableStatus>>(context) {
) : BaseAbstractTask<Any?, SingleResponse<ParcelableStatus>, Any?>(context) {
override fun doInBackground(vararg params: Any): SingleResponse<ParcelableStatus> {
override fun doLongOperation(params: Any?): SingleResponse<ParcelableStatus> {
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<ParcelableStatus>) {
asyncTwitterWrapper.destroyingStatusIds.removeElement(AsyncTwitterWrapper.calculateHashCode(accountKey, statusId))
override fun afterExecute(callback: Any?, result: SingleResponse<ParcelableStatus>) {
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)
}
}

View File

@ -0,0 +1,66 @@
/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2017 Mariotaku Lee <mariotaku.lee@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.mariotaku.twidere.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?, SingleResponse<ParcelableUserList>, Any?>(context) {
override fun doLongOperation(params: Any?): SingleResponse<ParcelableUserList> {
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<ParcelableUserList>) {
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)
}
}
}

View File

@ -0,0 +1,62 @@
/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2017 Mariotaku Lee <mariotaku.lee@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.mariotaku.twidere.task
import android.content.Context
import org.mariotaku.twidere.model.SingleResponse
/**
* Created by mariotaku on 2017/2/10.
*/
abstract class ExceptionHandlingAbstractTask<Params, Result, in TaskException : Exception, Callback>(
context: Context
) : BaseAbstractTask<Params, SingleResponse<Result>, Callback>(context) {
override final fun afterExecute(callback: Callback?, result: SingleResponse<Result>) {
@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<Result> {
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
}

View File

@ -76,12 +76,31 @@ class GetMessagesTask(context: Context) : BaseAbstractTask<RefreshTaskParam, Uni
private fun getDefaultMessages(microBlog: MicroBlog, details: AccountDetails, param: RefreshTaskParam, index: Int): GetMessagesData {
val accountKey = details.key
val sinceIds = if (param.hasSinceIds) param.sinceIds else null
val maxIds = if (param.hasMaxIds) param.maxIds else null
val received = microBlog.getDirectMessages(Paging().apply {
count(100)
val maxId = maxIds?.get(index)
val sinceId = sinceIds?.get(index)
if (maxId != null) {
maxId(maxId)
}
if (sinceIds != null) {
sinceId(sinceId)
}
})
val sent = microBlog.getSentDirectMessages(Paging().apply {
count(100)
val accountsCount = param.accountKeys.size
val maxId = maxIds?.get(accountsCount + index)
val sinceId = sinceIds?.get(accountsCount + index)
if (maxId != null) {
maxId(maxId)
}
if (sinceId != null) {
sinceId(sinceId)
}
})

View File

@ -1,11 +1,9 @@
package org.mariotaku.twidere.task
import android.content.Context
import android.util.Log
import org.mariotaku.abstask.library.AbstractTask
import org.mariotaku.microblog.library.MicroBlogException
import org.mariotaku.sqliteqb.library.Expression
import org.mariotaku.twidere.BuildConfig
import org.mariotaku.twidere.TwidereConstants.LOGTAG
import org.mariotaku.twidere.model.SingleResponse
import org.mariotaku.twidere.model.UserKey
@ -38,6 +36,6 @@ class GetSavedSearchesTask(
DebugLog.w(LOGTAG, tr = e)
}
}
return SingleResponse.getInstance(Unit)
return SingleResponse(Unit)
}
}

View File

@ -24,11 +24,7 @@ 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
import org.mariotaku.twidere.util.AsyncTwitterWrapper.calculateHashCode
import org.mariotaku.twidere.util.DataStoreUtils
import org.mariotaku.twidere.util.DebugLog
import org.mariotaku.twidere.util.Utils
import org.mariotaku.twidere.util.*
/**
* Created by mariotaku on 2017/2/7.
@ -72,7 +68,7 @@ class RetweetStatusTask(
for (uri in DataStoreUtils.STATUSES_URIS) {
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)
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))
}
}

View File

@ -7,7 +7,9 @@ import org.mariotaku.twidere.model.SingleResponse
/**
* Created by mariotaku on 2017/2/8.
*/
class SendMessageTask(context: Context) : BaseAbstractTask<Unit, SingleResponse<ParcelableMessage>, Unit>(context) {
class SendMessageTask(
context: Context
) : BaseAbstractTask<Unit, SingleResponse<ParcelableMessage>, Unit>(context) {
override fun doLongOperation(params: Unit?): SingleResponse<ParcelableMessage> {
return SingleResponse(UnsupportedOperationException())
}

View File

@ -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<ResultHandler>(
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))

View File

@ -0,0 +1,68 @@
/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2017 Mariotaku Lee <mariotaku.lee@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.mariotaku.twidere.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, SingleResponse<ParcelableUserList>, Any>(context) {
override fun doLongOperation(o: Any): SingleResponse<ParcelableUserList> {
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<ParcelableUserList>()
}
override fun afterExecute(callback: Any?, result: SingleResponse<ParcelableUserList>) {
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)
}
}
}

View File

@ -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) {

View File

@ -0,0 +1,780 @@
/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2017 Mariotaku Lee <mariotaku.lee@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.mariotaku.twidere.util
import android.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<Uri>()
private val getStatusTasks = CompactHashSet<Uri>()
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<String>): 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<Any, Unit, Any>(task)
}
fun getMessagesAsync(param: RefreshTaskParam) {
val task = GetMessagesTask(context)
task.params = param
TaskStarter.execute(task)
}
fun getSavedSearchesAsync(accountKeys: Array<UserKey>) {
val task = GetSavedSearchesTask(context)
task.params = accountKeys
TaskStarter.execute<Array<UserKey>, SingleResponse<Unit>, 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<UserKey>): Boolean {
return refreshAll { accountKeys }
}
fun refreshAll(action: () -> Array<UserKey>): Boolean {
getHomeTimelineAsync(object : SimpleRefreshTaskParam() {
override val accountKeys: Array<UserKey> by lazy { action() }
override val sinceIds: Array<String?>? 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<UserKey> by lazy { action() }
override val sinceIds: Array<String?>? 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<UserKey> 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<UserKey, Set<String>>) {
val task = RemoveUnreadCountsTask(context, position, counts)
AsyncTaskUtils.executeTask(task)
}
fun reportMultiSpam(accountKey: UserKey, userIds: Array<String>) {
// 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, SingleResponse<ParcelableStatus>, 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<Any, Relationship, MicroBlogException, Any>(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<UserKey>, cursor: Long) {
val task = object : ExceptionHandlingAbstractTask<Any?, Unit, MicroBlogException, Any?>(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<Any, Any, Int>() {
override fun doInBackground(vararg params: Any): Int {
return TwitterWrapper.clearNotification(context, notificationType, accountKey)
}
}
internal inner class ClearUnreadCountTask(private val position: Int) : AsyncTask<Any, Any, Int>() {
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<String>
) : ManagedAsyncTask<Any, Any, ListResponse<String>>(context) {
private fun deleteCaches(list: List<String>) {
// 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<String> {
val blockedUsers = ArrayList<String>()
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<String>(e)
}
}
}
deleteCaches(blockedUsers)
return ListResponse.getListInstance(blockedUsers)
}
override fun onPostExecute(result: ListResponse<String>) {
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<Any, Any, SingleResponse<SavedSearch>>(context) {
override fun doInBackground(vararg params: Any): SingleResponse<SavedSearch>? {
val microBlog = MicroBlogAPIFactory.getInstance(context, mAccountKey) ?: return null
try {
return SingleResponse.getInstance(microBlog.createSavedSearch(mQuery))
} catch (e: MicroBlogException) {
return SingleResponse.getInstance<SavedSearch>(e)
}
}
override fun onPostExecute(result: SingleResponse<SavedSearch>) {
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<Any, Any, SingleResponse<ParcelableUserList>>(context) {
override fun doInBackground(vararg params: Any): SingleResponse<ParcelableUserList> {
val microBlog = MicroBlogAPIFactory.getInstance(context, mAccountKey) ?: return SingleResponse.getInstance<ParcelableUserList>()
try {
val userList = microBlog.createUserListSubscription(mListId)
val list = ParcelableUserListUtils.from(userList, mAccountKey)
return SingleResponse.getInstance(list)
} catch (e: MicroBlogException) {
return SingleResponse.getInstance<ParcelableUserList>(e)
}
}
override fun onPostExecute(result: SingleResponse<ParcelableUserList>) {
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<Any, Any, SingleResponse<ParcelableUserList>>(context) {
override fun doInBackground(vararg params: Any): SingleResponse<ParcelableUserList> {
val microBlog = MicroBlogAPIFactory.getInstance(context, mAccountKey
)
if (microBlog == null || mListName == null)
return SingleResponse.getInstance<ParcelableUserList>()
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<ParcelableUserList>(e)
}
}
override fun onPostExecute(result: SingleResponse<ParcelableUserList>) {
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<out ParcelableUser>
) : ManagedAsyncTask<Any, Any, SingleResponse<ParcelableUserList>>(context) {
override fun doInBackground(vararg params: Any): SingleResponse<ParcelableUserList> {
val microBlog = MicroBlogAPIFactory.getInstance(context, accountKey) ?: return SingleResponse.getInstance<ParcelableUserList>()
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<ParcelableUserList>(e)
}
}
override fun onPostExecute(result: SingleResponse<ParcelableUserList>) {
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<Any, Any, SingleResponse<DirectMessage>>(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<DirectMessage> {
val microBlog = MicroBlogAPIFactory.getInstance(context, mAccountKey) ?: return SingleResponse.getInstance<DirectMessage>()
try {
val message = microBlog.destroyDirectMessage(mMessageId)
deleteMessages()
return SingleResponse.getInstance(message)
} catch (e: MicroBlogException) {
if (isMessageNotFound(e)) {
deleteMessages()
}
return SingleResponse.getInstance<DirectMessage>(e)
}
}
override fun onPostExecute(result: SingleResponse<DirectMessage>) {
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<Any, Any, SingleResponse<Boolean>>(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<Boolean> {
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<Boolean>) {
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<Any, Any, SingleResponse<SavedSearch>>(context) {
override fun doInBackground(vararg params: Any): SingleResponse<SavedSearch> {
val microBlog = MicroBlogAPIFactory.getInstance(context, accountKey) ?: return SingleResponse.getInstance<SavedSearch>()
try {
return SingleResponse.getInstance(microBlog.destroySavedSearch(searchId))
} catch (e: MicroBlogException) {
return SingleResponse.getInstance<SavedSearch>(e)
}
}
override fun onPostExecute(result: SingleResponse<SavedSearch>) {
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<Any, Any, SingleResponse<ParcelableUserList>>(context) {
override fun doInBackground(vararg params: Any): SingleResponse<ParcelableUserList> {
val microBlog = MicroBlogAPIFactory.getInstance(context, accountKey) ?: return SingleResponse.getInstance<ParcelableUserList>()
try {
val userList = microBlog.destroyUserListSubscription(listId)
val list = ParcelableUserListUtils.from(userList, accountKey)
return SingleResponse.getInstance(list)
} catch (e: MicroBlogException) {
return SingleResponse.getInstance<ParcelableUserList>(e)
}
}
override fun onPostExecute(result: SingleResponse<ParcelableUserList>) {
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 <T : Response<*>> getException(responses: List<T>): Exception? {
for (response in responses) {
if (response.hasException()) return response.exception
}
return null
}
}
}

View File

@ -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)
}
}
fun deleteActivityStatus(cr: ContentResolver, accountKey: UserKey,
statusId: String, result: ParcelableStatus?) {
val host = accountKey.host
val deleteWhere: String
val updateWhere: String
val deleteWhereArgs: Array<String>
val updateWhereArgs: Array<String>
if (host != null) {
deleteWhere = Expression.and(
Expression.likeRaw(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<String>?,
action: (ParcelableActivity) -> Unit) {
val c = cr.query(uri, Activities.COLUMNS, where, whereArgs, null) ?: return
val values = LongSparseArray<ContentValues>()
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<String>(1)
for (i in 0 until values.size()) {
updateWhereArgs[0] = values.keyAt(i).toString()
cr.update(uri, values.valueAt(i), updateWhere, updateWhereArgs)
}
}

View File

@ -0,0 +1,886 @@
/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2017 Mariotaku Lee <mariotaku.lee@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.mariotaku.twidere.util
import android.accounts.AccountManager
import android.annotation.SuppressLint
import android.content.*
import android.database.Cursor
import android.net.Uri
import android.os.Bundle
import android.os.Parcelable
import android.provider.BaseColumns
import android.support.annotation.WorkerThread
import android.text.TextUtils
import 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<UserKey?>): Array<String?> {
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<UserKey?>, outgoing: Boolean): Array<String?> {
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<UserKey?>): LongArray {
return getLongFieldArray(context, uri, accountKeys, Statuses.ACCOUNT_KEY, Statuses.SORT_ID,
OrderBy(SQLFunctions.MAX(Statuses.STATUS_TIMESTAMP)), null, null)
}
fun getOldestStatusIds(context: Context, uri: Uri, accountKeys: Array<UserKey?>): Array<String?> {
return getStringFieldArray(context, uri, accountKeys, Statuses.ACCOUNT_KEY, Statuses.STATUS_ID,
OrderBy(SQLFunctions.MIN(Statuses.STATUS_TIMESTAMP)), null, null)
}
fun getOldestStatusSortIds(context: Context, uri: Uri, accountKeys: Array<UserKey?>): LongArray {
return getLongFieldArray(context, uri, accountKeys, Statuses.ACCOUNT_KEY,
Statuses.SORT_ID, OrderBy(SQLFunctions.MIN(Statuses.STATUS_TIMESTAMP)), null,
null)
}
fun getNewestActivityMaxPositions(context: Context, uri: Uri, accountKeys: Array<UserKey?>): Array<String?> {
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<UserKey?>): Array<String?> {
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<UserKey?>): 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<UserKey?>): 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<UserKey> {
if (context == null) return emptyArray()
val resolver = context.contentResolver
val projection = arrayOf(Filters.Users.USER_KEY)
return resolver.query(Filters.Users.CONTENT_URI, projection, null, null, null)?.useCursor { cur ->
return@useCursor Array(cur.count) { i ->
cur.moveToPosition(i)
UserKey.valueOf(cur.getString(0))
}
} ?: emptyArray()
}
fun getAccountDisplayName(context: Context, accountKey: UserKey, nameFirst: Boolean): String? {
val name: String?
if (nameFirst) {
name = getAccountName(context, accountKey)
} else {
name = "@${getAccountScreenName(context, accountKey)}"
}
return name
}
fun getAccountName(context: Context, accountKey: UserKey): String? {
val am = AccountManager.get(context)
val account = AccountUtils.findByAccountKey(am, accountKey) ?: return null
return account.getAccountUser(am).name
}
fun getAccountScreenName(context: Context, accountKey: UserKey): String? {
val am = AccountManager.get(context)
val account = AccountUtils.findByAccountKey(am, accountKey) ?: return null
return account.getAccountUser(am).screen_name
}
fun getActivatedAccountKeys(context: Context): Array<UserKey> {
val am = AccountManager.get(context)
val keys = ArrayList<UserKey>()
for (account in AccountUtils.getAccounts(am)) {
if (account.isActivated(am)) {
keys.add(account.getAccountKey(am))
}
}
return keys.toTypedArray()
}
fun getStatusesCount(context: Context,
preferences: SharedPreferences,
uri: Uri,
extraArgs: Bundle?, compare: Long,
compareColumn: String, greaterThan: Boolean,
accountKeys: Array<UserKey>?): Int {
val keys = accountKeys ?: getActivatedAccountKeys(context)
val expressions = ArrayList<Expression>()
val expressionArgs = ArrayList<String>()
expressions.add(Expression.inArgs(Column(Statuses.ACCOUNT_KEY), keys.size))
for (accountKey in keys) {
expressionArgs.add(accountKey.toString())
}
if (greaterThan) {
expressions.add(Expression.greaterThanArgs(compareColumn))
} else {
expressions.add(Expression.lesserThanArgs(compareColumn))
}
expressionArgs.add(compare.toString())
expressions.add(buildStatusFilterWhereClause(preferences, getTableNameByUri(uri)!!, null))
if (extraArgs != null) {
val extras = extraArgs.getParcelable<Parcelable>(EXTRA_EXTRAS)
if (extras is HomeTabExtras) {
processTabExtras(expressions, expressionArgs, extras)
}
}
val selection = Expression.and(*expressions.toTypedArray())
return queryCount(context, uri, selection.sql, expressionArgs.toTypedArray())
}
fun getActivitiesCount(context: Context, uri: Uri, compare: Long,
compareColumn: String, greaterThan: Boolean, accountKeys: Array<UserKey>?): Int {
val keys = accountKeys ?: getActivatedAccountKeys(context)
val selection = Expression.and(
Expression.inArgs(Column(Activities.ACCOUNT_KEY), keys.size),
if (greaterThan) Expression.greaterThanArgs(compareColumn) else Expression.lesserThanArgs(compareColumn),
buildActivityFilterWhereClause(getTableNameByUri(uri)!!, null)
)
val whereArgs = arrayListOf<String>()
keys.mapTo(whereArgs) { it.toString() }
whereArgs.add(compare.toString())
return queryCount(context, uri, selection.sql, whereArgs.toTypedArray())
}
fun getActivitiesCount(context: Context, uri: Uri,
extraWhere: Expression?, extraWhereArgs: Array<String>?,
since: Long, sinceColumn: String, followingOnly: Boolean,
accountKeys: Array<UserKey>?): Int {
val keys = (accountKeys ?: getActivatedAccountKeys(context)).map { it.toString() }.toTypedArray()
val expressions = ArrayList<Expression>()
expressions.add(Expression.inArgs(Column(Activities.ACCOUNT_KEY), keys.size))
expressions.add(Expression.greaterThanArgs(sinceColumn))
expressions.add(buildActivityFilterWhereClause(getTableNameByUri(uri)!!, null))
if (extraWhere != null) {
expressions.add(extraWhere)
}
val selection = Expression.and(*expressions.toTypedArray())
val selectionArgs: Array<String>
if (extraWhereArgs != null) {
selectionArgs = keys + since.toString() + extraWhereArgs
} else {
selectionArgs = keys + since.toString()
}
// If followingOnly option is on, we have to iterate over items
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 + "||'</a>%'"),
Expression.likeRaw(Column(Table(table), Activities.STATUS_QUOTE_SOURCE),
"'%>'||" + Filters.Sources.TABLE_NAME + "." + Filters.Sources.VALUE + "||'</a>%'")
))
.union()
.select(Columns(Column(Table(table), Activities._ID)))
.from(Tables(table, Filters.Keywords.TABLE_NAME))
.where(Expression.or(
Expression.likeRaw(Column(Table(table), Activities.STATUS_TEXT_PLAIN),
"'%'||" + Filters.Keywords.TABLE_NAME + "." + Filters.Keywords.VALUE + "||'%'"),
Expression.likeRaw(Column(Table(table), Activities.STATUS_QUOTE_TEXT_PLAIN),
"'%'||" + Filters.Keywords.TABLE_NAME + "." + Filters.Keywords.VALUE + "||'%'")
))
.union()
.select(Columns(Column(Table(table), Activities._ID)))
.from(Tables(table, Filters.Links.TABLE_NAME))
.where(Expression.or(
Expression.likeRaw(Column(Table(table), Activities.STATUS_SPANS),
"'%'||" + Filters.Links.TABLE_NAME + "." + Filters.Links.VALUE + "||'%'"),
Expression.likeRaw(Column(Table(table), Activities.STATUS_QUOTE_SPANS),
"'%'||" + Filters.Links.TABLE_NAME + "." + Filters.Links.VALUE + "||'%'")
))
val filterExpression = Expression.or(
Expression.notIn(Column(Table(table), Activities._ID), filteredIdsQueryBuilder.build()),
Expression.equals(Column(Table(table), Activities.IS_GAP), 1)
)
if (extraSelection != null) {
return Expression.and(filterExpression, extraSelection)
}
return filterExpression
}
fun getAccountColors(context: Context, accountKeys: Array<UserKey>): IntArray {
val am = AccountManager.get(context)
val colors = IntArray(accountKeys.size)
for (i in accountKeys.indices) {
val account = AccountUtils.findByAccountKey(am, accountKeys[i])
if (account != null) {
colors[i] = account.getColor(am)
}
}
return colors
}
fun findAccountKeyByScreenName(context: Context, screenName: String): UserKey? {
val am = AccountManager.get(context)
for (account in AccountUtils.getAccounts(am)) {
val user = account.getAccountUser(am)
if (StringUtils.equalsIgnoreCase(screenName, user.screen_name)) {
return user.key
}
}
return null
}
fun getAccountKeys(context: Context): Array<UserKey> {
val am = AccountManager.get(context)
val accounts = AccountUtils.getAccounts(am)
val keys = ArrayList<UserKey>(accounts.size)
for (account in accounts) {
val keyString = am.getUserData(account, ACCOUNT_USER_DATA_KEY) ?: continue
keys.add(UserKey.valueOf(keyString))
}
return keys.toTypedArray()
}
fun findAccountKey(context: Context, accountId: String): UserKey? {
val am = AccountManager.get(context)
for (account in AccountUtils.getAccounts(am)) {
val key = account.getAccountKey(am)
if (accountId == key.id) {
return key
}
}
return null
}
fun hasAccount(context: Context): Boolean {
return AccountUtils.getAccounts(AccountManager.get(context)).isNotEmpty()
}
@Synchronized fun cleanDatabasesByItemLimit(context: Context) {
val resolver = context.contentResolver
val preferences = context.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE)
val itemLimit = preferences[databaseItemLimitKey]
for (accountKey in getAccountKeys(context)) {
// Clean statuses.
for (uri in STATUSES_URIS) {
if (CachedStatuses.CONTENT_URI == uri) {
continue
}
val table = getTableNameByUri(uri)
val qb = SQLSelectQuery.Builder()
qb.select(Column(Statuses._ID))
.from(Tables(table))
.where(Expression.equalsArgs(Statuses.ACCOUNT_KEY))
.orderBy(OrderBy(Statuses.POSITION_KEY, false))
.limit(itemLimit)
val where = Expression.and(
Expression.notIn(Column(Statuses._ID), qb.build()),
Expression.equalsArgs(Statuses.ACCOUNT_KEY)
)
val whereArgs = arrayOf(accountKey.toString(), accountKey.toString())
resolver.delete(uri, where.sql, whereArgs)
}
for (uri in ACTIVITIES_URIS) {
val table = getTableNameByUri(uri)
val qb = SQLSelectQuery.Builder()
qb.select(Column(Activities._ID))
.from(Tables(table))
.where(Expression.equalsArgs(Activities.ACCOUNT_KEY))
.orderBy(OrderBy(Activities.TIMESTAMP, false))
.limit(itemLimit)
val where = Expression.and(
Expression.notIn(Column(Activities._ID), qb.build()),
Expression.equalsArgs(Activities.ACCOUNT_KEY)
)
val whereArgs = arrayOf(accountKey.toString(), accountKey.toString())
resolver.delete(uri, where.sql, whereArgs)
}
}
// Clean cached values.
for (uri in CACHE_URIS) {
val table = getTableNameByUri(uri) ?: continue
val qb = SQLSelectQuery.Builder()
qb.select(Column(BaseColumns._ID))
.from(Tables(table))
.orderBy(OrderBy(BaseColumns._ID, false))
.limit(itemLimit * 20)
val where = Expression.notIn(Column(BaseColumns._ID), qb.build())
resolver.delete(uri, where.sql, null)
}
}
fun isFilteringUser(context: Context, userKey: UserKey): Boolean {
return isFilteringUser(context, userKey.toString())
}
fun isFilteringUser(context: Context, userKey: String): Boolean {
val cr = context.contentResolver
val where = Expression.equalsArgs(Filters.Users.USER_KEY)
val c = cr.query(Filters.Users.CONTENT_URI, arrayOf(SQLFunctions.COUNT()),
where.sql, arrayOf(userKey), null) ?: return false
try {
if (c.moveToFirst()) {
return c.getLong(0) > 0
}
} finally {
c.close()
}
return false
}
private fun getStringFieldArray(context: Context, uri: Uri,
keys: Array<UserKey?>, keyField: String,
valueField: String, sortExpression: OrderBy?,
extraHaving: Expression?, extraHavingArgs: Array<String>?): Array<String?> {
return getFieldArray(context, uri, keys, keyField, valueField, sortExpression, extraHaving,
extraHavingArgs, object : FieldArrayCreator<Array<String?>> {
override fun newArray(size: Int): Array<String?> {
return arrayOfNulls(size)
}
override fun assign(array: Array<String?>, arrayIdx: Int, cur: Cursor, colIdx: Int) {
array[arrayIdx] = cur.getString(colIdx)
}
})
}
private fun getLongFieldArray(context: Context, uri: Uri,
keys: Array<UserKey?>, keyField: String,
valueField: String, sortExpression: OrderBy?,
extraHaving: Expression?, extraHavingArgs: Array<String>?): LongArray {
return getFieldArray(context, uri, keys, keyField, valueField, sortExpression, extraHaving,
extraHavingArgs, object : FieldArrayCreator<LongArray> {
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 <T> getFieldArray(context: Context, uri: Uri,
keys: Array<UserKey?>, keyField: String,
valueField: String, sortExpression: OrderBy?,
extraHaving: Expression?, extraHavingArgs: Array<String>?,
creator: FieldArrayCreator<T>): 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<String>
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<String>
val updateWhereArgs: Array<String>
if (host != null) {
deleteWhere = Expression.and(
Expression.likeRaw(Column(Statuses.ACCOUNT_KEY), "'%@'||?"),
Expression.or(
Expression.equalsArgs(Statuses.STATUS_ID),
Expression.equalsArgs(Statuses.RETWEET_ID)
)).sql
deleteWhereArgs = arrayOf(host, statusId, statusId)
updateWhere = Expression.and(
Expression.likeRaw(Column(Statuses.ACCOUNT_KEY), "'%@'||?"),
Expression.equalsArgs(Statuses.MY_RETWEET_ID)
).sql
updateWhereArgs = arrayOf(host, statusId)
} else {
deleteWhere = Expression.or(
Expression.equalsArgs(Statuses.STATUS_ID),
Expression.equalsArgs(Statuses.RETWEET_ID)
).sql
deleteWhereArgs = arrayOf(statusId, statusId)
updateWhere = Expression.equalsArgs(Statuses.MY_RETWEET_ID).sql
updateWhereArgs = arrayOf(statusId)
}
for (uri in STATUSES_URIS) {
cr.delete(uri, deleteWhere, deleteWhereArgs)
if (status != null) {
val values = ContentValues()
values.putNull(Statuses.MY_RETWEET_ID)
values.put(Statuses.RETWEET_COUNT, status.retweet_count - 1)
cr.update(uri, values, updateWhere, updateWhereArgs)
}
}
}
fun processTabExtras(expressions: MutableList<Expression>, expressionArgs: MutableList<String>, extras: HomeTabExtras) {
if (extras.isHideRetweets) {
expressions.add(Expression.equalsArgs(Statuses.IS_RETWEET))
expressionArgs.add("0")
}
if (extras.isHideQuotes) {
expressions.add(Expression.equalsArgs(Statuses.IS_QUOTE))
expressionArgs.add("0")
}
if (extras.isHideReplies) {
expressions.add(Expression.isNull(Column(Statuses.IN_REPLY_TO_STATUS_ID)))
}
}
fun prepareDatabase(context: Context) {
val cr = context.contentResolver
val cursor = cr.query(TwidereDataStore.CONTENT_URI_DATABASE_PREPARE, null, null,
null, null) ?: return
cursor.close()
}
internal interface FieldArrayCreator<T> {
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<String>?): 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<UserKey>, since: Long, sinceColumn: String): Int {
var extraWhere: Expression? = null
var extraWhereArgs: Array<String>? = null
var followingOnly = false
if (extraArgs != null) {
val extras = extraArgs.getParcelable<TabExtras>(IntentConstants.EXTRA_EXTRAS)
if (extras is InteractionsTabExtras) {
if (extras.isMentionsOnly) {
extraWhere = Expression.inArgs(Activities.ACTION, 3)
extraWhereArgs = arrayOf(Activity.Action.MENTION, Activity.Action.REPLY, Activity.Action.QUOTE)
}
if (extras.isMyFollowingOnly) {
followingOnly = true
}
}
}
return getActivitiesCount(context, Activities.AboutMe.CONTENT_URI, extraWhere, extraWhereArgs,
since, sinceColumn, followingOnly, accountIds)
}
fun addToFilter(context: Context, users: Collection<ParcelableUser>, filterAnywhere: Boolean) {
val cr = context.contentResolver
try {
val userValues = ArrayList<ContentValues>()
val keywordValues = ArrayList<ContentValues>()
val linkValues = ArrayList<ContentValues>()
for (user in users) {
val userItem = FiltersData.UserItem()
userItem.userKey = user.key
userItem.screenName = user.screen_name
userItem.name = user.name
userValues.add(`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<ParcelableUser>) {
val userKeyValues = ArrayList<String>()
val linkValues = ArrayList<String>()
val keywordValues = ArrayList<String>()
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
}
}

View File

@ -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<UserKey>) -> Array<String?>?
) : SimpleRefreshTaskParam() {
override fun getAccountKeysWorker(): Array<UserKey> {
val prefs = AccountPreferences.getAccountPreferences(context,
DataStoreUtils.getAccountKeys(context)).filter(AccountPreferences::isAutoRefreshEnabled)
return prefs.filter(refreshable)
.map(AccountPreferences::getAccountKey).toTypedArray()
override val accountKeys: Array<UserKey> by lazy {
return@lazy AccountPreferences.getAccountPreferences(context, DataStoreUtils.getAccountKeys(context)).filter {
it.isAutoRefreshEnabled && refreshable(it)
}.map(AccountPreferences::getAccountKey).toTypedArray()
}
override val sinceIds: Array<String?>?