Refactor/fix mark-as-read-while-reading workflow.

This commit is contained in:
ojiikun 2013-05-08 10:09:36 +00:00
parent 70877d4323
commit 35b680c0d9
16 changed files with 138 additions and 362 deletions

View file

@ -10,14 +10,11 @@ import com.newsblur.R;
import com.newsblur.database.DatabaseConstants;
import com.newsblur.database.FeedProvider;
import com.newsblur.database.MixedFeedsReadingAdapter;
import com.newsblur.domain.ValueMultimap;
import com.newsblur.network.MarkMixedStoriesAsReadTask;
import com.newsblur.service.SyncService;
public class AllSharedStoriesReading extends Reading {
private Cursor stories;
private ValueMultimap storiesToMarkAsRead;
private int currentPage;
private ArrayList<String> feedIds;
private boolean requestingPage = false;
@ -33,13 +30,11 @@ public class AllSharedStoriesReading extends Reading {
stories = contentResolver.query(FeedProvider.ALL_SHARED_STORIES_URI, null, DatabaseConstants.getStorySelectionFromState(currentState), null, DatabaseConstants.STORY_DATE + " desc");
setTitle(getResources().getString(R.string.all_shared_stories));
storiesToMarkAsRead = new ValueMultimap();
readingAdapter = new MixedFeedsReadingAdapter(getSupportFragmentManager(), getContentResolver(), stories);
setupPager();
addStoryToMarkAsRead(readingAdapter.getStory(passedPosition));
storiesToMarkAsRead.put(readingAdapter.getStory(passedPosition).feedId, readingAdapter.getStory(passedPosition).id);
}
private void setupCountCursor() {
@ -55,17 +50,10 @@ public class AllSharedStoriesReading extends Reading {
@Override
public void onPageSelected(int position) {
super.onPageSelected(position);
storiesToMarkAsRead.put(readingAdapter.getStory(position).feedId, readingAdapter.getStory(position).id);
addStoryToMarkAsRead(readingAdapter.getStory(position));
checkStoryCount(position);
}
@Override
protected void onDestroy() {
new MarkMixedStoriesAsReadTask(this, syncFragment, storiesToMarkAsRead).execute();
super.onDestroy();
}
@Override
public void triggerRefresh() {
triggerRefresh(1);

View file

@ -11,14 +11,11 @@ import com.newsblur.R;
import com.newsblur.database.DatabaseConstants;
import com.newsblur.database.FeedProvider;
import com.newsblur.database.MixedFeedsReadingAdapter;
import com.newsblur.domain.ValueMultimap;
import com.newsblur.network.MarkMixedStoriesAsReadTask;
import com.newsblur.service.SyncService;
public class AllStoriesReading extends Reading {
private Cursor stories;
private ValueMultimap storiesToMarkAsRead;
private int currentPage;
private ArrayList<String> feedIds;
private boolean stopLoading = false;
@ -34,15 +31,13 @@ public class AllStoriesReading extends Reading {
stories = contentResolver.query(FeedProvider.ALL_STORIES_URI, null, DatabaseConstants.getStorySelectionFromState(currentState), null, null);
setTitle(getResources().getString(R.string.all_stories));
storiesToMarkAsRead = new ValueMultimap();
readingAdapter = new MixedFeedsReadingAdapter(getSupportFragmentManager(), getContentResolver(), stories);
setupPager();
addStoryToMarkAsRead(readingAdapter.getStory(passedPosition));
storiesToMarkAsRead.put(readingAdapter.getStory(passedPosition).feedId, readingAdapter.getStory(passedPosition).id);
addStoryToMarkAsRead(readingAdapter.getStory(passedPosition));
}
private void setupCountCursor() {
Cursor cursor = getContentResolver().query(FeedProvider.FEEDS_URI, null, DatabaseConstants.getStorySelectionFromState(currentState), null, null);
feedIds = new ArrayList<String>();
@ -54,17 +49,10 @@ public class AllStoriesReading extends Reading {
@Override
public void onPageSelected(int position) {
super.onPageSelected(position);
storiesToMarkAsRead.put(readingAdapter.getStory(position).feedId, readingAdapter.getStory(position).id);
addStoryToMarkAsRead(readingAdapter.getStory(position));
checkStoryCount(position);
}
@Override
protected void onDestroy() {
new MarkMixedStoriesAsReadTask(this, syncFragment, storiesToMarkAsRead).execute();
super.onDestroy();
}
@Override
public void triggerRefresh() {
triggerRefresh(1);

View file

@ -17,13 +17,11 @@ import com.newsblur.domain.Classifier;
import com.newsblur.domain.Feed;
import com.newsblur.domain.Story;
import com.newsblur.fragment.SyncUpdateFragment;
import com.newsblur.network.MarkStoryAsReadTask;
import com.newsblur.service.SyncService;
import com.newsblur.util.AppConstants;
public class FeedReading extends Reading {
protected Set<String> storiesToMarkAsRead;
String feedId;
private Feed feed;
private int currentPage;
@ -42,7 +40,6 @@ public class FeedReading extends Reading {
Classifier classifier = Classifier.fromCursor(feedClassifierCursor);
Uri storiesURI = FeedProvider.FEED_STORIES_URI.buildUpon().appendPath(feedId).build();
storiesToMarkAsRead = new HashSet<String>();
stories = contentResolver.query(storiesURI, null, DatabaseConstants.getStorySelectionFromState(currentState), null, DatabaseConstants.STORY_DATE + " DESC");
final Uri feedUri = FeedProvider.FEEDS_URI.buildUpon().appendPath(feedId).build();
@ -62,19 +59,14 @@ public class FeedReading extends Reading {
fragmentManager.beginTransaction().add(syncFragment, SyncUpdateFragment.TAG).commit();
}
updateReadStories(readingAdapter.getStory(passedPosition));
}
private void updateReadStories(Story story) {
storiesToMarkAsRead.add(story.id);
addStoryToMarkAsRead(story);
addStoryToMarkAsRead(readingAdapter.getStory(passedPosition));
}
@Override
public void onPageSelected(int position) {
super.onPageSelected(position);
if (readingAdapter.getStory(position) != null) {
updateReadStories(readingAdapter.getStory(position));
addStoryToMarkAsRead(readingAdapter.getStory(position));
checkStoryCount(position);
}
}
@ -97,14 +89,6 @@ public class FeedReading extends Reading {
}
}
@Override
protected void onDestroy() {
ArrayList<String> storyIds = new ArrayList<String>();
storyIds.addAll(storiesToMarkAsRead);
new MarkStoryAsReadTask(this, syncFragment, storyIds, feedId).execute();
super.onDestroy();
}
@Override
public void triggerRefresh() {
triggerRefresh(1);

View file

@ -8,12 +8,10 @@ import android.util.Log;
import com.newsblur.database.DatabaseConstants;
import com.newsblur.database.FeedProvider;
import com.newsblur.database.MixedFeedsReadingAdapter;
import com.newsblur.domain.ValueMultimap;
import com.newsblur.network.MarkMixedStoriesAsReadTask;
import com.newsblur.service.SyncService;
public class FolderReading extends Reading {
protected ValueMultimap storiesToMarkAsRead;
private String[] feedIds;
private String folderName;
private boolean stopLoading = false;
@ -31,30 +29,21 @@ public class FolderReading extends Reading {
setTitle(folderName);
Uri storiesURI = FeedProvider.MULTIFEED_STORIES_URI;
storiesToMarkAsRead = new ValueMultimap();
stories = contentResolver.query(storiesURI, null, DatabaseConstants.getStorySelectionFromState(currentState), feedIds, null);
readingAdapter = new MixedFeedsReadingAdapter(getSupportFragmentManager(), getContentResolver(), stories);
setupPager();
storiesToMarkAsRead.put(readingAdapter.getStory(passedPosition).feedId, readingAdapter.getStory(passedPosition).id);
addStoryToMarkAsRead(readingAdapter.getStory(passedPosition));
}
@Override
public void onPageSelected(int position) {
storiesToMarkAsRead.put(readingAdapter.getStory(position).feedId, readingAdapter.getStory(position).id);
addStoryToMarkAsRead(readingAdapter.getStory(position));
checkStoryCount(position);
super.onPageSelected(position);
}
@Override
protected void onDestroy() {
new MarkMixedStoriesAsReadTask(this, syncFragment, storiesToMarkAsRead).execute();
super.onDestroy();
}
@Override
public void triggerRefresh() {
triggerRefresh(1);

View file

@ -2,6 +2,7 @@ package com.newsblur.activity;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
import android.content.ContentProviderOperation;
import android.content.ContentResolver;
@ -41,7 +42,6 @@ import com.newsblur.util.UIUtils;
public abstract class Reading extends NbFragmentActivity implements OnPageChangeListener, SyncUpdateFragment.SyncUpdateFragmentInterface, OnSeekBarChangeListener {
public static final String EXTRA_FEED = "feed_selected";
public static final String TAG = "ReadingActivity";
public static final String EXTRA_POSITION = "feed_position";
public static final String EXTRA_USERID = "user_id";
public static final String EXTRA_USERNAME = "username";
@ -58,9 +58,8 @@ public abstract class Reading extends NbFragmentActivity implements OnPageChange
protected ContentResolver contentResolver;
private APIManager apiManager;
protected SyncUpdateFragment syncFragment;
private ArrayList<ContentProviderOperation> operations;
protected Cursor stories;
private HashSet<String> storiesToMarkAsRead;
private Set<Story> storiesToMarkAsRead;
@Override
protected void onCreate(Bundle savedInstanceBundle) {
@ -69,10 +68,9 @@ public abstract class Reading extends NbFragmentActivity implements OnPageChange
super.onCreate(savedInstanceBundle);
setContentView(R.layout.activity_reading);
operations = new ArrayList<ContentProviderOperation>();
fragmentManager = getSupportFragmentManager();
storiesToMarkAsRead = new HashSet<String>();
storiesToMarkAsRead = new HashSet<Story>();
passedPosition = getIntent().getIntExtra(EXTRA_POSITION, 0);
currentState = getIntent().getIntExtra(ItemsList.EXTRA_STATE, 0);
@ -192,27 +190,24 @@ public abstract class Reading extends NbFragmentActivity implements OnPageChange
@Override
protected void onPause() {
if (isFinishing()) {
try {
contentResolver.applyBatch(FeedProvider.AUTHORITY, operations);
} catch (RemoteException e) {
Log.e(TAG, "Failed to do any updating.");
e.printStackTrace();
} catch (OperationApplicationException e) {
Log.e(TAG, "Failed to do any updating.");
e.printStackTrace();
}
}
synchronized(this.storiesToMarkAsRead) {
FeedUtils.markStoriesAsRead(this.storiesToMarkAsRead, this);
this.storiesToMarkAsRead.clear();
}
super.onPause();
}
protected void addStoryToMarkAsRead(Story story) {
if (story.read != Story.READ && !storiesToMarkAsRead.contains(story.id)) {
storiesToMarkAsRead.add(story.id);
FeedUtils.appendStoryReadOperations(story, operations);
}
}
/**
* Log a story as having been read. The local DB and remote server will be updated
* batch-wise when the activity pauses.
*/
protected void addStoryToMarkAsRead(Story story) {
if (story == null) return;
if (story.read != Story.UNREAD) return;
synchronized (this.storiesToMarkAsRead) {
this.storiesToMarkAsRead.add(story);
}
}
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {

View file

@ -13,12 +13,10 @@ import com.newsblur.database.FeedProvider;
import com.newsblur.database.MixedFeedsReadingAdapter;
import com.newsblur.domain.SocialFeed;
import com.newsblur.domain.Story;
import com.newsblur.network.MarkSocialStoryAsReadTask;
import com.newsblur.service.SyncService;
public class SocialFeedReading extends Reading {
MarkSocialAsReadUpdate markSocialAsReadList;
private String userId;
private String username;
private SocialFeed socialFeed;
@ -34,7 +32,6 @@ public class SocialFeedReading extends Reading {
userId = getIntent().getStringExtra(Reading.EXTRA_USERID);
username = getIntent().getStringExtra(Reading.EXTRA_USERNAME);
markSocialAsReadList = new MarkSocialAsReadUpdate(userId);
Uri socialFeedUri = FeedProvider.SOCIAL_FEEDS_URI.buildUpon().appendPath(userId).build();
socialFeed = SocialFeed.fromCursor(contentResolver.query(socialFeedUri, null, null, null, null));
@ -47,55 +44,16 @@ public class SocialFeedReading extends Reading {
setupPager();
Story story = readingAdapter.getStory(passedPosition);
addStoryToMarkAsRead(story);
markSocialAsReadList.add(story.feedId, story.id);
addStoryToMarkAsRead(readingAdapter.getStory(passedPosition));
}
@Override
public void onPageSelected(int position) {
super.onPageSelected(position);
Story story = readingAdapter.getStory(position);
if (story != null) {
markSocialAsReadList.add(story.feedId, story.id);
addStoryToMarkAsRead(story);
}
addStoryToMarkAsRead(readingAdapter.getStory(position));
checkStoryCount(position);
}
@Override
protected void onDestroy() {
new MarkSocialStoryAsReadTask(this, syncFragment, markSocialAsReadList).execute();
super.onDestroy();
}
public class MarkSocialAsReadUpdate {
public String userId;
HashMap<String, Set<String>> feedStoryMap;
public MarkSocialAsReadUpdate(final String userId) {
this.userId = userId;
feedStoryMap = new HashMap<String, Set<String>>();
}
public void add(final String feedId, final String storyId) {
if (feedStoryMap.get(feedId) == null) {
Set<String> storiesForFeed = new HashSet<String>();
storiesForFeed.add(storyId);
feedStoryMap.put(feedId, storiesForFeed);
} else {
feedStoryMap.get(feedId).add(storyId);
}
}
public Object getJsonObject() {
HashMap<String, HashMap<String, Set<String>>> jsonMap = new HashMap<String, HashMap<String, Set<String>>>();
jsonMap.put(userId, feedStoryMap);
return jsonMap;
}
}
@Override
public void triggerRefresh() {
triggerRefresh(0);

View file

@ -1,31 +0,0 @@
package com.newsblur.domain;
import android.content.ContentValues;
import android.database.Cursor;
import android.text.TextUtils;
import com.newsblur.database.DatabaseConstants;
public class OfflineUpdate {
public int id;
public UpdateType type;
public String[] arguments;
public ContentValues getContentValues() {
ContentValues values = new ContentValues();
values.put(DatabaseConstants.UPDATE_ARGUMENTS, TextUtils.join(",", arguments));
values.put(DatabaseConstants.UPDATE_TYPE, type.name());
return values;
}
public static OfflineUpdate fromCursor(final Cursor cursor) {
OfflineUpdate update = new OfflineUpdate();
update.arguments = TextUtils.split(cursor.getString(cursor.getColumnIndex(DatabaseConstants.UPDATE_ARGUMENTS)), ",");
update.type = UpdateType.valueOf(cursor.getString(cursor.getColumnIndex(DatabaseConstants.UPDATE_TYPE)));
update.id = cursor.getInt(cursor.getColumnIndex(DatabaseConstants.UPDATE_ID));
return update;
}
public static enum UpdateType { MARK_FEED_AS_READ };
}

View file

@ -178,4 +178,27 @@ public class Story implements Serializable {
return score;
}
/**
* Custom equality based on storyID/feedID equality so that a Set can de-duplicate story objects.
*/
@Override
public boolean equals(Object o) {
if (o == null) return false;
if (!(o instanceof Story)) return false;
Story s = (Story) o;
return ( (this.id == null ? s.id == null : this.id.equals(s.id)) && (this.feedId == null ? s.feedId == null : this.feedId.equals(s.feedId)) );
}
/**
* Per the contract of Object, since we redefined equals(), we have to redefine hashCode().
*/
@Override
public int hashCode() {
int result = 17;
if (this.id == null) { result = 37*result; } else { result = 37*result + this.id.hashCode();}
if (this.feedId == null) { result = 37*result; } else { result = 37*result + this.feedId.hashCode();}
return result;
}
}

View file

@ -12,6 +12,9 @@ import android.text.TextUtils;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
/**
* This utility class is simply a Map<String,String> that serializes to JSON.
*/
public class ValueMultimap implements Serializable {
private static final long serialVersionUID = 3102965432185825759L;

View file

@ -39,7 +39,6 @@ import com.newsblur.database.FeedItemsAdapter;
import com.newsblur.database.FeedProvider;
import com.newsblur.domain.Feed;
import com.newsblur.domain.Story;
import com.newsblur.network.MarkStoryAsReadTask;
import com.newsblur.util.FeedUtils;
import com.newsblur.util.NetworkUtils;
import com.newsblur.view.FeedItemViewBinder;
@ -187,7 +186,8 @@ public class FeedItemListFragment extends ItemListFragment implements LoaderMana
if(story.read == Story.UNREAD) {
ArrayList<Story> storiesToMarkAsRead = new ArrayList<Story>();
storiesToMarkAsRead.add(story);
markStoriesRead(storiesToMarkAsRead);
FeedUtils.markStoriesAsRead(storiesToMarkAsRead, getActivity());
refreshStories();
}
} else if (item.getItemId() == R.id.menu_mark_previous_stories_as_read) {
final ArrayList<Story> previousStories = adapter.getPreviousStories(menuInfo.position);
@ -197,36 +197,12 @@ public class FeedItemListFragment extends ItemListFragment implements LoaderMana
storiesToMarkAsRead.add(story);
}
}
markStoriesRead(storiesToMarkAsRead);
FeedUtils.markStoriesAsRead(storiesToMarkAsRead, getActivity());
refreshStories();
}
return super.onContextItemSelected(item);
}
private void markStoriesRead(final ArrayList<Story> storiesToMarkRead) {
ArrayList<String> storyIdsToMarkRead = new ArrayList<String>();
for(Story story: storiesToMarkRead) {
storyIdsToMarkRead.add(story.id);
}
new MarkStoryAsReadTask(getActivity(), storyIdsToMarkRead, feedId) {
@Override
protected void onPostExecute(Void result) {
ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>();
for(Story story: storiesToMarkRead) {
FeedUtils.appendStoryReadOperations(story, operations);
}
try {
contentResolver.applyBatch(FeedProvider.AUTHORITY, operations);
} catch (Exception e) {
Log.e(TAG, "Failed to update feed read status in local DB for " + storiesToMarkRead.size() + " stories");
e.printStackTrace();
}
refreshStories();
}
}.execute();
}
@Override
public void onCreateContextMenu(ContextMenu menu, View v,
ContextMenuInfo menuInfo) {

View file

@ -126,21 +126,6 @@ public class APIManager {
}
}
public boolean markStoryAsRead(final String feedId, final ArrayList<String> storyIds) {
final APIClient client = new APIClient(context);
final ValueMultimap values = new ValueMultimap();
values.put(APIConstants.PARAMETER_FEEDID, feedId);
for (String storyId : storyIds) {
values.put(APIConstants.PARAMETER_STORYID, storyId);
}
final APIResponse response = client.post(APIConstants.URL_MARK_STORY_AS_READ, values, false);
if (!response.isOffline && response.responseCode == HttpStatus.SC_OK && !response.hasRedirected) {
return true;
} else {
return false;
}
}
public boolean markSocialStoryAsRead(final String updateJson) {
final APIClient client = new APIClient(context);
final ContentValues values = new ContentValues();

View file

@ -1,36 +0,0 @@
package com.newsblur.network;
import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
import com.newsblur.domain.ValueMultimap;
import com.newsblur.fragment.SyncUpdateFragment;
import com.newsblur.service.SyncService;
public class MarkMixedStoriesAsReadTask extends AsyncTask<Void, Void, Void> {
private Context context;
private SyncUpdateFragment receiver;
private final ValueMultimap stories;
public MarkMixedStoriesAsReadTask(final Context context, final SyncUpdateFragment fragment, final ValueMultimap stories) {
this.context = context;
this.receiver = fragment;
this.stories = stories;
}
@Override
protected Void doInBackground(Void... params) {
final Intent intent = new Intent(Intent.ACTION_SYNC, null, context, SyncService.class);
intent.putExtra(SyncService.EXTRA_STATUS_RECEIVER, receiver.receiver);
intent.putExtra(SyncService.SYNCSERVICE_TASK, SyncService.EXTRA_TASK_MARK_MULTIPLE_STORIES_READ);
intent.putExtra(SyncService.EXTRA_TASK_STORIES, stories);
context.startService(intent);
return null;
}
}

View file

@ -1,39 +0,0 @@
package com.newsblur.network;
import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
import com.google.gson.Gson;
import com.newsblur.activity.SocialFeedReading.MarkSocialAsReadUpdate;
import com.newsblur.domain.Story;
import com.newsblur.fragment.SyncUpdateFragment;
import com.newsblur.service.SyncService;
public class MarkSocialStoryAsReadTask extends AsyncTask<Story, Void, Void> {
private Context context;
private SyncUpdateFragment receiver;
private Gson gson;
private final MarkSocialAsReadUpdate readUpdate;
public MarkSocialStoryAsReadTask(final Context context, final SyncUpdateFragment fragment, MarkSocialAsReadUpdate update) {
this.context = context;
this.receiver = fragment;
this.readUpdate = update;
gson = new Gson();
}
protected Void doInBackground(Story... params) {
final Intent intent = new Intent(Intent.ACTION_SYNC, null, context, SyncService.class);
intent.putExtra(SyncService.EXTRA_STATUS_RECEIVER, receiver.receiver);
intent.putExtra(SyncService.SYNCSERVICE_TASK, SyncService.EXTRA_TASK_MARK_SOCIALSTORY_READ);
intent.putExtra(SyncService.EXTRA_TASK_MARK_SOCIAL_JSON, gson.toJson(readUpdate.getJsonObject()));
context.startService(intent);
return null;
}
}

View file

@ -1,44 +0,0 @@
package com.newsblur.network;
import java.util.ArrayList;
import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
import com.newsblur.fragment.SyncUpdateFragment;
import com.newsblur.service.SyncService;
public class MarkStoryAsReadTask extends AsyncTask<Void, Void, Void> {
private Context context;
private SyncUpdateFragment receiver;
private final String feedId;
private final ArrayList<String> storyIds;
public MarkStoryAsReadTask(final Context context, final ArrayList<String> storyIds, final String feedId) {
this(context, null, storyIds, feedId);
}
public MarkStoryAsReadTask(final Context context, final SyncUpdateFragment fragment, final ArrayList<String> storyIds, final String feedId) {
this.context = context;
this.receiver = fragment;
this.storyIds = storyIds;
this.feedId = feedId;
}
protected Void doInBackground(Void... stories) {
final Intent intent = new Intent(Intent.ACTION_SYNC, null, context, SyncService.class);
if(receiver != null) {
intent.putExtra(SyncService.EXTRA_STATUS_RECEIVER, receiver.receiver);
}
intent.putExtra(SyncService.SYNCSERVICE_TASK, SyncService.EXTRA_TASK_MARK_STORY_READ);
intent.putExtra(SyncService.EXTRA_TASK_FEED_ID, feedId);
intent.putStringArrayListExtra(SyncService.EXTRA_TASK_STORY_ID, storyIds);
context.startService(intent);
return null;
}
}

View file

@ -17,7 +17,6 @@ import android.widget.Toast;
import com.newsblur.R;
import com.newsblur.database.DatabaseConstants;
import com.newsblur.database.FeedProvider;
import com.newsblur.domain.OfflineUpdate;
import com.newsblur.domain.ValueMultimap;
import com.newsblur.network.APIClient;
import com.newsblur.network.APIConstants;
@ -58,7 +57,6 @@ public class SyncService extends IntentService {
public static final int EXTRA_TASK_FOLDER_UPDATE_TWO_STEP = 30;
public static final int EXTRA_TASK_FOLDER_UPDATE_WITH_COUNT = 41;
public static final int EXTRA_TASK_FEED_UPDATE = 31;
public static final int EXTRA_TASK_MARK_STORY_READ = 33;
public static final int EXTRA_TASK_SOCIALFEED_UPDATE = 34;
public static final int EXTRA_TASK_MARK_SOCIALSTORY_READ = 35;
public static final int EXTRA_TASK_MULTIFEED_UPDATE = 36;
@ -114,38 +112,11 @@ public class SyncService extends IntentService {
apiManager.getFolderFeedMapping(true);
break;
case EXTRA_TASK_MARK_STORY_READ:
final String feedId = intent.getStringExtra(EXTRA_TASK_FEED_ID);
final ArrayList<String> storyIds = intent.getStringArrayListExtra(EXTRA_TASK_STORY_ID);
if (!TextUtils.isEmpty(feedId) && storyIds.size() > 0) {
if (!apiManager.markStoryAsRead(feedId, storyIds)) {
for (String storyId : storyIds) {
OfflineUpdate update = new OfflineUpdate();
update.arguments = new String[] { feedId, storyId };
update.type = OfflineUpdate.UpdateType.MARK_FEED_AS_READ;
getContentResolver().insert(FeedProvider.OFFLINE_URI, update.getContentValues());
}
}
} else {
Log.e(this.getClass().getName(), "No feed/stories to mark as read included in SyncRequest");
receiver.send(STATUS_ERROR, Bundle.EMPTY);
}
break;
case EXTRA_TASK_MARK_MULTIPLE_STORIES_READ:
final ValueMultimap stories = (ValueMultimap) intent.getSerializableExtra(EXTRA_TASK_STORIES);
ContentValues values = new ContentValues();
values.put(APIConstants.PARAMETER_FEEDS_STORIES, stories.getJsonString());
if (!apiManager.markMultipleStoriesAsRead(values)) {
for (String key : stories.getKeys()) {
for (String value : stories.getValues(key)) {
OfflineUpdate update = new OfflineUpdate();
update.arguments = new String[] { key, value };
update.type = OfflineUpdate.UpdateType.MARK_FEED_AS_READ;
getContentResolver().insert(FeedProvider.OFFLINE_URI, update.getContentValues());
}
}
}
apiManager.markMultipleStoriesAsRead(values);
break;
case EXTRA_TASK_MARK_SOCIALSTORY_READ:
@ -258,19 +229,4 @@ public class SyncService extends IntentService {
}
}
/* TODO: this code existed in the refresh_feeds intent, but was never used. Now that
we are actually using the API as intended, it is possible this needs to
actually get called somewhere.
Cursor cursor = getContentResolver().query(FeedProvider.OFFLINE_URI, null, null, null, null);
while (cursor.moveToNext()) {
OfflineUpdate update = OfflineUpdate.fromCursor(cursor);
ArrayList<String> storyId = new ArrayList<String>();
storyId.add(update.arguments[1]);
if (apiManager.markStoryAsRead(update.arguments[0], storyId)) {
getContentResolver().delete(FeedProvider.OFFLINE_URI, DatabaseConstants.UPDATE_ID + " = ?", new String[] { Integer.toString(update.id) });
}
}
cursor.close();
*/
}

View file

@ -1,24 +1,36 @@
package com.newsblur.util;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import android.content.ContentProviderOperation;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.AsyncTask;
import android.text.TextUtils;
import android.util.Log;
import android.widget.Toast;
import com.google.gson.Gson;
import com.newsblur.R;
import com.newsblur.database.DatabaseConstants;
import com.newsblur.database.FeedProvider;
import com.newsblur.domain.Story;
import com.newsblur.domain.ValueMultimap;
import com.newsblur.network.APIManager;
import com.newsblur.service.SyncService;
public class FeedUtils {
private static Gson gson = new Gson();
public static void saveStory(final Story story, final Context context, final APIManager apiManager) {
if (story != null) {
final String feedId = story.feedId;
@ -42,7 +54,76 @@ public class FeedUtils {
}
}
public static void appendStoryReadOperations(Story story, ArrayList<ContentProviderOperation> operations) {
/**
* This utility method is a fast-returning way to mark as read a batch of stories in both
* the local DB and on the server.
*
* TODO: the next version of the NB API will let us mark-as-read by a UUID, so we can
* hopefully remove the ugly detection of social stories and their whole different
* API call.
*/
public static void markStoriesAsRead( Collection<Story> stories, Context context ) {
// the map of non-social feedIds->storyIds to mark (auto-serializing)
ValueMultimap storiesJson = new ValueMultimap();
// the map of social userIds->feedIds->storyIds to mark
Map<String, Map<String, Set<String>>> socialStories = new HashMap<String, Map<String, Set<String>>>();
// a list of local DB ops to perform
ArrayList<ContentProviderOperation> updateOps = new ArrayList<ContentProviderOperation>();
for (Story story : stories) {
appendStoryReadOperations(story, updateOps);
if (story.socialUserId != null) {
// TODO: some stories returned by /social/river_stories seem to have neither a
// socialUserId nor a sourceUserId, so they accidentally get submitted non-
// socially. If the API fixes this before we ditch social-specific logic,
// we can fix that bug right here.
putMapHeirarchy(socialStories, story.socialUserId, story.feedId, story.id);
} else {
storiesJson.put(story.feedId, story.id);
}
}
// first, update unread counts in the local DB
try {
context.getContentResolver().applyBatch(FeedProvider.AUTHORITY, updateOps);
} catch (Exception e) {
Log.w(FeedUtils.class.getName(), "Could not update unread counts in local storage.", e);
}
// next, update the server for normal stories
if (storiesJson.size() > 0) {
Intent intent = new Intent(Intent.ACTION_SYNC, null, context, SyncService.class);
intent.putExtra(SyncService.SYNCSERVICE_TASK, SyncService.EXTRA_TASK_MARK_MULTIPLE_STORIES_READ);
intent.putExtra(SyncService.EXTRA_TASK_STORIES, storiesJson);
context.startService(intent);
}
// finally, update the server for social stories
if (socialStories.size() > 0) {
Intent intent = new Intent(Intent.ACTION_SYNC, null, context, SyncService.class);
intent.putExtra(SyncService.SYNCSERVICE_TASK, SyncService.EXTRA_TASK_MARK_SOCIALSTORY_READ);
intent.putExtra(SyncService.EXTRA_TASK_MARK_SOCIAL_JSON, gson.toJson(socialStories));
context.startService(intent);
}
}
/**
* A utility method to help populate the 3-level map structure that the NB API uses in JSON calls.
*/
private static void putMapHeirarchy(Map<String, Map<String, Set<String>>> map, String s1, String s2, String s3) {
if (! map.containsKey(s1)) {
map.put(s1, new HashMap<String, Set<String>>());
}
Map<String, Set<String>> innerMap = map.get(s1);
if (! innerMap.containsKey(s2)) {
innerMap.put(s2, new HashSet<String>());
}
innerMap.get(s2).add(s3);
}
private static void appendStoryReadOperations(Story story, List<ContentProviderOperation> operations) {
String[] selectionArgs;
ContentValues emptyValues = new ContentValues();
emptyValues.put(DatabaseConstants.FEED_ID, story.feedId);