Fix loading of saved stories to use normal stories table.

This commit is contained in:
dosiecki 2014-07-27 00:39:40 -07:00
parent 5376494a68
commit 30dd6d0d42
11 changed files with 183 additions and 65 deletions

View file

@ -7,7 +7,6 @@ import android.content.Loader;
import com.newsblur.R;
import com.newsblur.database.DatabaseConstants;
import com.newsblur.database.FeedProvider;
import com.newsblur.database.MixedFeedsReadingAdapter;
import com.newsblur.util.FeedUtils;
import com.newsblur.util.StoryOrder;
@ -32,7 +31,7 @@ public class SavedStoriesReading extends Reading {
@Override
public Loader<Cursor> onCreateLoader(int loaderId, Bundle bundle) {
return new CursorLoader(this, FeedProvider.STARRED_STORIES_URI, null, null, null, DatabaseConstants.STARRED_STORY_ORDER);
return dbHelper.getSavedStoriesLoader();
}
}

View file

@ -126,8 +126,6 @@ public class BlurDatabase extends SQLiteOpenHelper {
"PRIMARY KEY (" + DatabaseConstants.SOCIALFEED_STORY_STORYID + ", " + DatabaseConstants.SOCIALFEED_STORY_USER_ID + ") " +
")";
private final String STARRED_STORIES_SQL = "CREATE TABLE " + DatabaseConstants.STARRED_STORIES_TABLE + " (" + STORY_TABLES_COLS + ")";
private final String STARRED_STORIES_COUNT_SQL = "CREATE TABLE " + DatabaseConstants.STARRED_STORY_COUNT_TABLE + " (" +
DatabaseConstants.STARRED_STORY_COUNT_COUNT + INTEGER + " NOT NULL" +
")";
@ -145,7 +143,6 @@ public class BlurDatabase extends SQLiteOpenHelper {
db.execSQL(CLASSIFIER_SQL);
db.execSQL(FEED_FOLDER_SQL);
db.execSQL(SOCIALFEED_STORIES_SQL);
db.execSQL(STARRED_STORIES_SQL);
db.execSQL(STARRED_STORIES_COUNT_SQL);
}
@ -162,7 +159,6 @@ public class BlurDatabase extends SQLiteOpenHelper {
db.execSQL(drop + DatabaseConstants.CLASSIFIER_TABLE);
db.execSQL(drop + DatabaseConstants.FEED_FOLDER_MAP_TABLE);
db.execSQL(drop + DatabaseConstants.SOCIALFEED_STORY_MAP_TABLE);
db.execSQL(drop + DatabaseConstants.STARRED_STORIES_TABLE);
db.execSQL(drop + DatabaseConstants.STARRED_STORY_COUNT_TABLE);
onCreate(db);

View file

@ -2,6 +2,7 @@ package com.newsblur.database;
import android.content.ContentValues;
import android.content.Context;
import android.content.Loader;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.text.TextUtils;
@ -30,11 +31,13 @@ import java.util.List;
*/
public class BlurDatabaseHelper {
private Context context;
private BlurDatabase dbWrapper;
private SQLiteDatabase dbRO;
private SQLiteDatabase dbRW;
public BlurDatabaseHelper(Context context) {
this.context = context;
dbWrapper = new BlurDatabase(context);
dbRO = dbWrapper.getRO();
dbRW = dbWrapper.getRW();
@ -145,7 +148,7 @@ public class BlurDatabaseHelper {
// handle supplemental feed data that may have been included (usually in social requests)
if (apiResponse.feeds != null) {
List<ContentValues> feedValues = new ArrayList<ContentValues>(apiResponse.feeds.length);
List<ContentValues> feedValues = new ArrayList<ContentValues>(apiResponse.feeds.size());
for (Feed feed : apiResponse.feeds) {
feedValues.add(feed.getValues());
}
@ -247,4 +250,17 @@ public class BlurDatabaseHelper {
return Math.max(countFromFeedsTable, countFromStoriesTable);
}
public Loader<Cursor> getSavedStoriesLoader() {
return new QueryCursorLoader(context) {
protected Cursor createCursor() {return getSavedStoriesCursor();}
};
}
public Cursor getSavedStoriesCursor() {
String q = DatabaseConstants.MULTIFEED_STORIES_QUERY_BASE +
" WHERE " + DatabaseConstants.STORY_STARRED + " = 1" +
" ORDER BY " + DatabaseConstants.STARRED_STORY_ORDER;
return dbRO.rawQuery(q, null);
}
}

View file

@ -1,5 +1,6 @@
package com.newsblur.database;
import android.text.TextUtils;
import android.provider.BaseColumns;
import com.newsblur.util.AppConstants;
@ -46,8 +47,6 @@ public class DatabaseConstants {
public static final String SOCIALFEED_STORY_USER_ID = "socialfeed_story_user_id";
public static final String SOCIALFEED_STORY_STORYID = "socialfeed_story_storyid";
public static final String STARRED_STORIES_TABLE = "starred_stories";
public static final String STARRED_STORY_COUNT_TABLE = "starred_story_count";
public static final String STARRED_STORY_COUNT_COUNT = "count";
@ -168,12 +167,12 @@ public class DatabaseConstants {
STORY_INTELLIGENCE_TITLE, STORY_PERMALINK, STORY_READ, STORY_STARRED, STORY_STARRED_DATE, STORY_SHARE_COUNT, STORY_TAGS, STORY_TITLE,
STORY_SOCIAL_USER_ID, STORY_SOURCE_USER_ID, STORY_SHARED_USER_IDS, STORY_FRIEND_USER_IDS, STORY_PUBLIC_USER_IDS, STORY_SUM_TOTAL, STORY_HASH
};
public static final String[] STARRED_STORY_COLUMNS = {
STORY_AUTHORS, STORY_COMMENT_COUNT, STORY_CONTENT, STORY_SHORT_CONTENT, STORY_TIMESTAMP, STORY_SHARED_DATE, STORY_SHORTDATE, STORY_LONGDATE,
STARRED_STORIES_TABLE + "." + STORY_FEED_ID, STARRED_STORIES_TABLE + "." + STORY_ID, STORY_INTELLIGENCE_AUTHORS, STORY_INTELLIGENCE_FEED, STORY_INTELLIGENCE_TAGS,
STORY_INTELLIGENCE_TITLE, STORY_PERMALINK, STORY_READ, STORY_STARRED, STORY_STARRED_DATE, STORY_SHARE_COUNT, STORY_TAGS, STORY_TITLE,
STORY_SOCIAL_USER_ID, STORY_SOURCE_USER_ID, STORY_SHARED_USER_IDS, STORY_FRIEND_USER_IDS, STORY_PUBLIC_USER_IDS, STORY_SUM_TOTAL, STORY_HASH
};
public static final String MULTIFEED_STORIES_QUERY_BASE =
"SELECT " + TextUtils.join(",", STORY_COLUMNS) + ", " +
FEED_TITLE + ", " + FEED_FAVICON_URL + ", " + FEED_FAVICON_COLOR + ", " + FEED_FAVICON_BORDER + ", " + FEED_FAVICON_FADE + ", " + FEED_FAVICON_TEXT +
" FROM " + STORY_TABLE +
" INNER JOIN " + FEED_TABLE + " ON " + STORY_TABLE + "." + STORY_FEED_ID + " = " + FEED_TABLE + "." + FEED_ID;
public static final String STARRED_STORY_ORDER = STORY_STARRED_DATE + " ASC";

View file

@ -35,7 +35,6 @@ public class FeedProvider extends ContentProvider {
public static final Uri SOCIALCOUNT_URI = Uri.parse("content://" + AUTHORITY + "/" + VERSION + "/socialfeedcount/");
public static final Uri ALL_STORIES_URI = Uri.parse("content://" + AUTHORITY + "/" + VERSION + "/stories/");
public static final Uri USERS_URI = Uri.parse("content://" + AUTHORITY + "/" + VERSION + "/users/");
public static final Uri STARRED_STORIES_URI = Uri.parse("content://" + AUTHORITY + "/" + VERSION + "/starred_stories/");
public static final Uri STARRED_STORIES_COUNT_URI = Uri.parse("content://" + AUTHORITY + "/" + VERSION + "/starred_stories_count/");
public static final Uri FEED_STORIES_URI = Uri.parse("content://" + AUTHORITY + "/" + VERSION + "/stories/feed/");
@ -69,7 +68,6 @@ public class FeedProvider extends ContentProvider {
private static final int FEED_STORIES_NO_UPDATE = 18;
private static final int CLASSIFIERS_FOR_FEED = 19;
private static final int USERS = 21;
private static final int STARRED_STORIES = 22;
private static final int STARRED_STORIES_COUNT = 23;
private BlurDatabase databaseHelper;
@ -101,7 +99,6 @@ public class FeedProvider extends ContentProvider {
uriMatcher.addURI(AUTHORITY, VERSION + "/folders/", ALL_FOLDERS);
uriMatcher.addURI(AUTHORITY, VERSION + "/folders/*/", INDIVIDUAL_FOLDER);
uriMatcher.addURI(AUTHORITY, VERSION + "/users/", USERS);
uriMatcher.addURI(AUTHORITY, VERSION + "/starred_stories/", STARRED_STORIES);
uriMatcher.addURI(AUTHORITY, VERSION + "/starred_stories_count/", STARRED_STORIES_COUNT);
}
@ -121,7 +118,6 @@ public class FeedProvider extends ContentProvider {
case ALL_STORIES:
db.delete(DatabaseConstants.STORY_TABLE, null, null);
db.delete(DatabaseConstants.STARRED_STORIES_TABLE, null, null);
return 1;
case SOCIALFEED_STORIES:
@ -147,9 +143,6 @@ public class FeedProvider extends ContentProvider {
case CLASSIFIERS_FOR_FEED:
return db.delete(DatabaseConstants.CLASSIFIER_TABLE, DatabaseConstants.CLASSIFIER_ID + " = ?", new String[] { uri.getLastPathSegment() });
case STARRED_STORIES:
return db.delete(DatabaseConstants.STARRED_STORIES_TABLE, null, null);
default:
return 0;
}
@ -290,10 +283,6 @@ public class FeedProvider extends ContentProvider {
db.insertWithOnConflict(DatabaseConstants.STORY_TABLE, null, values, SQLiteDatabase.CONFLICT_REPLACE);
break;
case STARRED_STORIES:
db.insertWithOnConflict(DatabaseConstants.STARRED_STORIES_TABLE, null, values, SQLiteDatabase.CONFLICT_REPLACE);
break;
case STARRED_STORIES_COUNT:
db.insertWithOnConflict(DatabaseConstants.STARRED_STORY_COUNT_TABLE, null, values, SQLiteDatabase.CONFLICT_REPLACE);
break;
@ -371,20 +360,6 @@ public class FeedProvider extends ContentProvider {
case USERS:
return db.query(DatabaseConstants.USER_TABLE, projection, selection, selectionArgs, null, null, null);
case STARRED_STORIES:
String savedStoriesQuery = "SELECT " + TextUtils.join(",", DatabaseConstants.STARRED_STORY_COLUMNS) + ", " + DatabaseConstants.FEED_TITLE + ", " +
DatabaseConstants.FEED_FAVICON_URL + ", " + DatabaseConstants.FEED_FAVICON_COLOR + ", " + DatabaseConstants.FEED_FAVICON_BORDER + ", " +
DatabaseConstants.FEED_FAVICON_FADE + ", " + DatabaseConstants.FEED_FAVICON_TEXT +
" FROM " + DatabaseConstants.STARRED_STORIES_TABLE +
" INNER JOIN " + DatabaseConstants.FEED_TABLE +
" ON " + DatabaseConstants.STARRED_STORIES_TABLE + "." + DatabaseConstants.STORY_FEED_ID + " = " + DatabaseConstants.FEED_TABLE + "." + DatabaseConstants.FEED_ID;
StringBuilder q = new StringBuilder();
q.append(savedStoriesQuery);
if (!TextUtils.isEmpty(sortOrder)) {
q.append(" ORDER BY " + sortOrder);
}
return db.rawQuery(q.toString(), null);
case STARRED_STORIES_COUNT:
return db.query(DatabaseConstants.STARRED_STORY_COUNT_TABLE, projection, selection, selectionArgs, null, null, null);
@ -614,7 +589,6 @@ public class FeedProvider extends ContentProvider {
case INDIVIDUAL_STORY:
int count = 0;
count += db.update(DatabaseConstants.STORY_TABLE, values, DatabaseConstants.STORY_ID + " = ?", new String[] { uri.getLastPathSegment() });
count += db.update(DatabaseConstants.STARRED_STORIES_TABLE, values, DatabaseConstants.STORY_ID + " = ?", new String[] { uri.getLastPathSegment() });
return count;
// In order to run a raw SQL query whereby we make decrement the column we need to a dynamic reference - something the usual content provider can't easily handle. Hence this circuitous hack.
case FEED_COUNT:

View file

@ -0,0 +1,106 @@
package com.newsblur.database;
import android.content.AsyncTaskLoader;
import android.content.Context;
import android.database.Cursor;
import android.os.CancellationSignal;
import android.os.OperationCanceledException;
/**
* A partial copy of android.content.CursorLoader with the bits related to ContentProviders
* gutted out so plain old SQLiteDatabase queries can be used where a ContentProvider is
* contraindicated. (Why this isn't in core Android I will never understand)
*/
public abstract class QueryCursorLoader extends AsyncTaskLoader<Cursor> {
private Cursor cursor;
private CancellationSignal cancellationSignal;
public QueryCursorLoader(Context context) {
super(context);
}
protected abstract Cursor createCursor();
@Override
public Cursor loadInBackground() {
synchronized (this) {
if (isLoadInBackgroundCanceled()) {
throw new OperationCanceledException();
}
cancellationSignal = new CancellationSignal();
}
try {
Cursor c = createCursor();
if (c != null) {
c.getCount();
}
return c;
} finally {
synchronized (this) {
cancellationSignal = null;
}
}
}
@Override
public void cancelLoadInBackground() {
super.cancelLoadInBackground();
synchronized (this) {
if (cancellationSignal != null) {
cancellationSignal.cancel();
}
}
}
@Override
public void deliverResult(Cursor cursor) {
if (isReset()) {
if (cursor != null) {
cursor.close();
}
return;
}
Cursor oldCursor = cursor;
cursor = cursor;
if (isStarted()) {
super.deliverResult(cursor);
}
if (oldCursor != null && oldCursor != cursor && !oldCursor.isClosed()) {
oldCursor.close();
}
}
@Override
protected void onStartLoading() {
if (cursor != null) {
deliverResult(cursor);
}
if (takeContentChanged() || cursor == null) {
forceLoad();
}
}
@Override
protected void onStopLoading() {
cancelLoad();
}
@Override
public void onCanceled(Cursor cursor) {
if (cursor != null && !cursor.isClosed()) {
cursor.close();
}
}
@Override
protected void onReset() {
super.onReset();
onStopLoading();
if (cursor != null && !cursor.isClosed()) {
cursor.close();
}
cursor = null;
}
}

View file

@ -1,16 +0,0 @@
package com.newsblur.domain;
import java.util.List;
import java.util.Map;
public class FolderStructure {
// Gson seemingly only deserialises into custom objects rather than being able to deserialise a given element using a specific deserializer.
public Map<String, List<Long>> folders;
public FolderStructure(Map<String, List<Long>> folders) {
this.folders = folders;
}
}

View file

@ -1,6 +1,5 @@
package com.newsblur.fragment;
import android.content.ContentResolver;
import android.content.CursorLoader;
import android.content.Intent;
import android.content.Loader;
@ -19,8 +18,8 @@ import com.newsblur.R;
import com.newsblur.activity.Reading;
import com.newsblur.activity.SavedStoriesReading;
import com.newsblur.activity.FeedReading;
import com.newsblur.database.BlurDatabaseHelper;
import com.newsblur.database.DatabaseConstants;
import com.newsblur.database.FeedProvider;
import com.newsblur.database.MultipleFeedItemsAdapter;
import com.newsblur.util.DefaultFeedView;
import com.newsblur.util.StoryOrder;
@ -28,7 +27,7 @@ import com.newsblur.view.SocialItemViewBinder;
public class SavedStoriesItemListFragment extends ItemListFragment implements OnItemClickListener {
private ContentResolver contentResolver;
private BlurDatabaseHelper dbHelper;
@Override
public void onCreate(Bundle savedInstanceState) {
@ -39,14 +38,15 @@ public class SavedStoriesItemListFragment extends ItemListFragment implements On
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
dbHelper = new BlurDatabaseHelper(getActivity());
View v = inflater.inflate(R.layout.fragment_itemlist, null);
ListView itemList = (ListView) v.findViewById(R.id.itemlistfragment_list);
setupBezelSwipeDetector(itemList);
itemList.setEmptyView(v.findViewById(R.id.empty_view));
contentResolver = getActivity().getContentResolver();
Cursor cursor = contentResolver.query(FeedProvider.STARRED_STORIES_URI, null, null, null, DatabaseConstants.STARRED_STORY_ORDER);
Cursor cursor = dbHelper.getSavedStoriesCursor();
String[] groupFrom = new String[] { DatabaseConstants.STORY_TITLE, DatabaseConstants.STORY_SHORT_CONTENT, DatabaseConstants.STORY_AUTHORS, DatabaseConstants.STORY_TIMESTAMP, DatabaseConstants.STORY_INTELLIGENCE_AUTHORS, DatabaseConstants.FEED_TITLE };
int[] groupTo = new int[] { R.id.row_item_title, R.id.row_item_content, R.id.row_item_author, R.id.row_item_date, R.id.row_item_sidebar, R.id.row_item_feedtitle };
@ -84,8 +84,7 @@ public class SavedStoriesItemListFragment extends ItemListFragment implements On
@Override
public Loader<Cursor> onCreateLoader(int arg0, Bundle arg1) {
// TODO: we are no longer inserting starred stories into their own table. query the stories table for the starred column!
return new CursorLoader(getActivity(), FeedProvider.STARRED_STORIES_URI, null, null, null, DatabaseConstants.STARRED_STORY_ORDER);
return dbHelper.getSavedStoriesLoader();
}
}

View file

@ -25,6 +25,7 @@ import android.webkit.CookieSyncManager;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import com.newsblur.database.DatabaseConstants;
import com.newsblur.database.FeedProvider;
import com.newsblur.domain.Classifier;
@ -47,6 +48,7 @@ import com.newsblur.network.domain.StoryTextResponse;
import com.newsblur.network.domain.UnreadStoryHashesResponse;
import com.newsblur.serialization.BooleanTypeAdapter;
import com.newsblur.serialization.DateStringTypeAdapter;
import com.newsblur.serialization.FeedListTypeAdapter;
import com.newsblur.serialization.StoryTypeAdapter;
import com.newsblur.util.AppConstants;
import com.newsblur.util.FeedSet;
@ -66,11 +68,13 @@ public class APIManager {
public APIManager(final Context context) {
this.context = context;
this.contentResolver = context.getContentResolver();
this.gson = new GsonBuilder()
.registerTypeAdapter(Date.class, new DateStringTypeAdapter())
.registerTypeAdapter(Boolean.class, new BooleanTypeAdapter())
.registerTypeAdapter(boolean.class, new BooleanTypeAdapter())
.registerTypeAdapter(Story.class, new StoryTypeAdapter())
.registerTypeAdapter(new TypeToken<List<Feed>>(){}.getType(), new FeedListTypeAdapter())
.create();
String appVersion = context.getSharedPreferences(PrefConstants.PREFERENCES, 0).getString(AppConstants.LAST_APP_VERSION, "unknown_version");

View file

@ -1,6 +1,9 @@
package com.newsblur.network.domain;
import java.util.List;
import com.google.gson.annotations.SerializedName;
import com.newsblur.domain.Classifier;
import com.newsblur.domain.Feed;
import com.newsblur.domain.Story;
@ -18,6 +21,6 @@ public class StoriesResponse extends NewsBlurResponse {
// some stories responses (like those from social feeds) also include feed data for non-subscribed feeds
@SerializedName("feeds")
public Feed[] feeds;
public List<Feed> feeds;
}

View file

@ -0,0 +1,38 @@
package com.newsblur.serialization;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import com.newsblur.domain.Feed;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* Special handler for the "feeds" field that appears in API responses and is sometimes
* a list and sometimes a set and sometimes null and sometimes an empty object.
*/
public class FeedListTypeAdapter implements JsonDeserializer<List<Feed>> {
@Override
public List<Feed> deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException {
List<Feed> result = new ArrayList<Feed>();
if (jsonElement.isJsonObject()) {
Feed feed = (Feed) jsonDeserializationContext.deserialize(jsonElement, Feed.class);
result.add(feed);
} else if (jsonElement.isJsonArray()) {
for (JsonElement arrayMember : jsonElement.getAsJsonArray()) {
Feed feed = (Feed) jsonDeserializationContext.deserialize(jsonElement, Feed.class);
result.add(feed);
}
}
return result;
}
}