Unified story fetching.

This commit is contained in:
dosiecki 2014-07-08 14:55:51 -07:00
parent dcc6d89dd3
commit 2ffef06fab
6 changed files with 107 additions and 245 deletions

View file

@ -13,12 +13,11 @@ import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
/**
* This utility class is simply a Map<String,String> that serializes to JSON.
* A String-to-String multimap that serializes to JSON or HTTP request params.
*/
@SuppressWarnings("serial")
public class ValueMultimap implements Serializable {
private static final long serialVersionUID = 3102965432185825759L;
private Map<String, List<String>> multimap;
private String TAG = "ValueMultimap";

View file

@ -9,6 +9,7 @@ import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import android.content.ContentResolver;
@ -49,6 +50,7 @@ import com.newsblur.serialization.BooleanTypeAdapter;
import com.newsblur.serialization.DateStringTypeAdapter;
import com.newsblur.serialization.StoryTypeAdapter;
import com.newsblur.util.AppConstants;
import com.newsblur.util.FeedSet;
import com.newsblur.util.NetworkUtils;
import com.newsblur.util.PrefConstants;
import com.newsblur.util.PrefsUtils;
@ -218,18 +220,6 @@ public class APIManager {
return (UnreadStoryHashesResponse) response.getResponse(gson, UnreadStoryHashesResponse.class);
}
public StoriesResponse getStoriesForFeed(String feedId, int pageNumber, StoryOrder order, ReadFilter filter) {
final ContentValues values = new ContentValues();
Uri feedUri = Uri.parse(APIConstants.URL_FEED_STORIES).buildUpon().appendPath(feedId).build();
values.put(APIConstants.PARAMETER_FEEDS, feedId);
values.put(APIConstants.PARAMETER_PAGE_NUMBER, Integer.toString(pageNumber));
values.put(APIConstants.PARAMETER_ORDER, order.getParameterValue());
values.put(APIConstants.PARAMETER_READ_FILTER, filter.getParameterValue());
APIResponse response = get(feedUri.toString(), values);
return (StoriesResponse) response.getResponse(gson, StoriesResponse.class);
}
public StoriesResponse getStoriesByHash(List<String> storyHashes) {
ValueMultimap values = new ValueMultimap();
for (String hash : storyHashes) {
@ -239,126 +229,47 @@ public class APIManager {
return (StoriesResponse) response.getResponse(gson, StoriesResponse.class);
}
public StoriesResponse getStoriesForFeeds(String[] feedIds, int pageNumber, StoryOrder order, ReadFilter filter) {
final ValueMultimap values = new ValueMultimap();
for (String feedId : feedIds) {
values.put(APIConstants.PARAMETER_FEEDS, feedId);
}
/**
* Fetches stories for the given FeedSet, choosing the correct API and the right
* request parameters as needed.
*/
public StoriesResponse getStories(FeedSet fs, int pageNumber, StoryOrder order, ReadFilter filter) {
Uri uri;
ValueMultimap values = new ValueMultimap();
// create the URI and populate request params depending on what kind of stories we want
if (fs.getSingleFeed() != null) {
uri = Uri.parse(APIConstants.URL_FEED_STORIES).buildUpon().appendPath(fs.getSingleFeed()).build();
values.put(APIConstants.PARAMETER_FEEDS, fs.getSingleFeed());
} else if (fs.getMultipleFeeds() != null) {
uri = Uri.parse(APIConstants.URL_RIVER_STORIES);
for (String feedId : fs.getMultipleFeeds()) values.put(APIConstants.PARAMETER_FEEDS, feedId);
} else if (fs.getSingleSocialFeed() != null) {
String feedId = fs.getSingleSocialFeed().getKey();
String username = fs.getSingleSocialFeed().getValue();
uri = Uri.parse(APIConstants.URL_SOCIALFEED_STORIES).buildUpon().appendPath(feedId).appendPath(username).build();
values.put(APIConstants.PARAMETER_USER_ID, feedId);
values.put(APIConstants.PARAMETER_USERNAME, username);
} else if (fs.getMultipleSocialFeeds() != null) {
uri = Uri.parse(APIConstants.URL_SHARED_RIVER_STORIES);
for (Map.Entry<String,String> entry : fs.getMultipleSocialFeeds().entrySet()) {
values.put(APIConstants.PARAMETER_FEEDS, entry.getKey());
}
} else if (fs.isAllSaved()) {
uri = Uri.parse(APIConstants.URL_STARRED_STORIES);
} else {
Log.wtf(this.getClass().getName(), "Asked to get stories for FeedSet of unknown type.");
return null;
}
// request params common to all stories
values.put(APIConstants.PARAMETER_PAGE_NUMBER, Integer.toString(pageNumber));
values.put(APIConstants.PARAMETER_ORDER, order.getParameterValue());
values.put(APIConstants.PARAMETER_READ_FILTER, filter.getParameterValue());
final APIResponse response = get(APIConstants.URL_RIVER_STORIES, values);
StoriesResponse storiesResponse = (StoriesResponse) response.getResponse(gson, StoriesResponse.class);
if (!response.isError()) {
for (Story story : storiesResponse.stories) {
Uri storyUri = FeedProvider.FEED_STORIES_URI.buildUpon().appendPath(story.feedId).build();
contentResolver.insert(storyUri, story.getValues());
insertComments(story);
}
for (UserProfile user : storiesResponse.users) {
contentResolver.insert(FeedProvider.USERS_URI, user.getValues());
}
}
return storiesResponse;
}
public StoriesResponse getStarredStories(int pageNumber) {
ValueMultimap values = new ValueMultimap();
values.put(APIConstants.PARAMETER_PAGE_NUMBER, Integer.toString(pageNumber));
APIResponse response = get(APIConstants.URL_STARRED_STORIES, values);
StoriesResponse storiesResponse = (StoriesResponse) response.getResponse(gson, StoriesResponse.class);
if (!response.isError()) {
for (Story story : storiesResponse.stories) {
contentResolver.insert(FeedProvider.STARRED_STORIES_URI, story.getValues());
insertComments(story);
}
for (UserProfile user : storiesResponse.users) {
contentResolver.insert(FeedProvider.USERS_URI, user.getValues());
}
}
return storiesResponse;
}
public SocialFeedResponse getSharedStoriesForFeeds(String[] feedIds, int pageNumber, StoryOrder order, ReadFilter filter) {
final ValueMultimap values = new ValueMultimap();
for (String feedId : feedIds) {
values.put(APIConstants.PARAMETER_FEEDS, feedId);
}
values.put(APIConstants.PARAMETER_PAGE_NUMBER, Integer.toString(pageNumber));
values.put(APIConstants.PARAMETER_ORDER, order.getParameterValue());
values.put(APIConstants.PARAMETER_READ_FILTER, filter.getParameterValue());
final APIResponse response = get(APIConstants.URL_SHARED_RIVER_STORIES, values);
SocialFeedResponse storiesResponse = (SocialFeedResponse) response.getResponse(gson, SocialFeedResponse.class);
if (!response.isError()) {
for (Story story : storiesResponse.stories) {
for (String userId : story.sharedUserIds) {
Uri storySocialUri = FeedProvider.SOCIALFEED_STORIES_URI.buildUpon().appendPath(userId).build();
contentResolver.insert(storySocialUri, story.getValues());
}
Uri storyUri = FeedProvider.FEED_STORIES_URI.buildUpon().appendPath(story.feedId).build();
contentResolver.insert(storyUri, story.getValues());
insertComments(story);
}
for (UserProfile user : storiesResponse.userProfiles) {
contentResolver.insert(FeedProvider.USERS_URI, user.getValues());
}
if (storiesResponse != null && storiesResponse.feeds!= null) {
for (Feed feed : storiesResponse.feeds) {
contentResolver.insert(FeedProvider.FEEDS_URI, feed.getValues());
}
}
}
return storiesResponse;
}
public SocialFeedResponse getStoriesForSocialFeed(String userId, String username, int pageNumber, StoryOrder order, ReadFilter filter) {
final ContentValues values = new ContentValues();
values.put(APIConstants.PARAMETER_USER_ID, userId);
values.put(APIConstants.PARAMETER_USERNAME, username);
values.put(APIConstants.PARAMETER_ORDER, order.getParameterValue());
values.put(APIConstants.PARAMETER_READ_FILTER, filter.getParameterValue());
values.put(APIConstants.PARAMETER_PAGE_NUMBER, Integer.toString(pageNumber));
Uri feedUri = Uri.parse(APIConstants.URL_SOCIALFEED_STORIES).buildUpon().appendPath(userId).appendPath(username).build();
final APIResponse response = get(feedUri.toString(), values);
SocialFeedResponse socialFeedResponse = (SocialFeedResponse) response.getResponse(gson, SocialFeedResponse.class);
if (!response.isError()) {
for (Story story : socialFeedResponse.stories) {
insertComments(story);
Uri storyUri = FeedProvider.FEED_STORIES_URI.buildUpon().appendPath(story.feedId).build();
Uri storySocialUri = FeedProvider.SOCIALFEED_STORIES_URI.buildUpon().appendPath(userId).build();
contentResolver.insert(storyUri, story.getValues());
contentResolver.insert(storySocialUri, story.getValues());
}
if (socialFeedResponse.userProfiles != null) {
for (UserProfile user : socialFeedResponse.userProfiles) {
contentResolver.insert(FeedProvider.USERS_URI, user.getValues());
}
}
if (socialFeedResponse.feeds != null) {
for (Feed feed : socialFeedResponse.feeds) {
contentResolver.insert(FeedProvider.FEEDS_URI, feed.getValues());
}
}
return socialFeedResponse;
} else {
return null;
}
}
APIResponse response = get(uri.toString(), values);
return (StoriesResponse) response.getResponse(gson, StoriesResponse.class);
}
private void insertComments(Story story) {
for (Comment comment : story.publicComments) {

View file

@ -303,7 +303,7 @@ public class NBSyncService extends Service {
if (HaltNow) return;
pageNumber++;
StoriesResponse apiResponse /*= apiManager.getStoriesForFeed(feedId, pageNumber, order, filter)*/ = null;
StoriesResponse apiResponse = apiManager.getStories(fs, pageNumber, order, filter);
if (! isStoryResponseGood(apiResponse)) break feedloop;

View file

@ -8,6 +8,7 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
/**
@ -108,12 +109,40 @@ public class FeedSet implements Serializable {
return fs;
}
public Set<String> getFeeds() {
return this.feeds;
/**
* Gets a single feed ID iff there is only one or null otherwise.
*/
public String getSingleFeed() {
if (feeds != null && feeds.size() == 1) return feeds.iterator().next(); else return null;
}
public Map<String,String> getSocialFeeds() {
return this.socialFeeds;
/**
* Gets a set of feed IDs iff there are multiples or null otherwise.
*/
public Set<String> getMultipleFeeds() {
if (feeds != null && feeds.size() > 1) return feeds; else return null;
}
/**
* Gets a single social feed ID and username iff there is only one or null otherwise.
*/
public Map.Entry<String,String> getSingleSocialFeed() {
if (socialFeeds != null && socialFeeds.size() == 1) return socialFeeds.entrySet().iterator().next(); else return null;
}
/**
* Gets a set of social feed IDs and usernames iff there are multiples or null otherwise.
*/
public Map<String,String> getMultipleSocialFeeds() {
if (socialFeeds != null && socialFeeds.size() > 1) return socialFeeds; else return null;
}
public boolean isAllNormal() {
return this.isAllNormal;
}
public boolean isAllSocial() {
return this.isAllSocial;
}
public boolean isAllSaved() {

View file

@ -37,83 +37,6 @@ import com.newsblur.util.AppConstants;
public class FeedUtils {
public static void updateFeeds(final Context context, final ActionCompletionListener receiver, final String[] feedIds, final int pageNumber, final StoryOrder order, final ReadFilter filter) {
new AsyncTask<Void, Void, StoriesResponse>() {
@Override
protected StoriesResponse doInBackground(Void... arg) {
APIManager apiManager = new APIManager(context);
return apiManager.getStoriesForFeeds(feedIds, pageNumber, order, filter);
}
@Override
protected void onPostExecute(StoriesResponse result) {
handleStoryResponse(context, result, result.stories, receiver);
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
public static void updateSocialFeed(final Context context, final ActionCompletionListener receiver, final String feedId, final String socialUsername, final int pageNumber, final StoryOrder order, final ReadFilter filter) {
new AsyncTask<Void, Void, SocialFeedResponse>() {
@Override
protected SocialFeedResponse doInBackground(Void... arg) {
APIManager apiManager = new APIManager(context);
return apiManager.getStoriesForSocialFeed(feedId, socialUsername, pageNumber, order, filter);
}
@Override
protected void onPostExecute(SocialFeedResponse result) {
handleStoryResponse(context, result, result.stories, receiver);
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
public static void updateSocialFeeds(final Context context, final ActionCompletionListener receiver, final String[] feedIds, final int pageNumber, final StoryOrder order, final ReadFilter filter) {
new AsyncTask<Void, Void, SocialFeedResponse>() {
@Override
protected SocialFeedResponse doInBackground(Void... arg) {
APIManager apiManager = new APIManager(context);
return apiManager.getSharedStoriesForFeeds(feedIds, pageNumber, order, filter);
}
@Override
protected void onPostExecute(SocialFeedResponse result) {
handleStoryResponse(context, result, result.stories, receiver);
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
public static void updateSavedStories(final Context context, final ActionCompletionListener receiver, final int pageNumber) {
new AsyncTask<Void, Void, StoriesResponse>() {
@Override
protected StoriesResponse doInBackground(Void... arg) {
APIManager apiManager = new APIManager(context);
return apiManager.getStarredStories(pageNumber);
}
@Override
protected void onPostExecute(StoriesResponse result) {
handleStoryResponse(context, result, result.stories, receiver);
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
private static void handleStoryResponse(Context context, NewsBlurResponse response, Story[] stories, ActionCompletionListener receiver) {
// the API may return both a valid stories block *and* an error, so check for stories first
if (stories != null) {
if (receiver != null) {
receiver.actionCompleteCallback(stories.length == 0); // only keep loading if there are more stories left
}
return;
}
if (response.isError()) {
Log.e(FeedUtils.class.getName(), "Error response received loading stories.");
Toast.makeText(context, response.getErrorMessage(context.getString(R.string.toast_error_loading_stories)), Toast.LENGTH_LONG).show();
} else {
Log.e(FeedUtils.class.getName(), "Null stories member received while loading stories with no error found.");
Toast.makeText(context, R.string.toast_error_loading_stories, Toast.LENGTH_LONG).show();
}
// NB: we do not keep loading on error, since the calling class would need to know to adjust pagination
receiver.actionCompleteCallback(true);
}
private static void setStorySaved(final Story story, final boolean saved, final Context context, final APIManager apiManager) {
new AsyncTask<Void, Void, NewsBlurResponse>() {
@Override

View file

@ -293,24 +293,24 @@ public class PrefsUtils {
}
public static StoryOrder getStoryOrder(Context context, FeedSet fs) {
if (fs.getFeeds() != null) {
if (fs.getFeeds().size() == 0) {
return getStoryOrderForFolder(context, PrefConstants.ALL_STORIES_FOLDER_NAME);
} else if (fs.getFeeds().size() == 1) {
return getStoryOrderForFeed(context, fs.getFeeds().iterator().next());
} else {
return getStoryOrderForFolder(context, fs.getFolderName());
}
if (fs.isAllNormal()) {
return getStoryOrderForFolder(context, PrefConstants.ALL_STORIES_FOLDER_NAME);
}
if (fs.getSingleFeed() != null) {
return getStoryOrderForFeed(context, fs.getSingleFeed());
}
if (fs.getMultipleFeeds() != null) {
return getStoryOrderForFolder(context, fs.getFolderName());
}
if (fs.getSocialFeeds() != null) {
if (fs.getSocialFeeds().size() == 0) {
return getStoryOrderForFolder(context, PrefConstants.ALL_SHARED_STORIES_FOLDER_NAME);
} else if (fs.getSocialFeeds().size() == 1) {
return getStoryOrderForFeed(context, fs.getSocialFeeds().keySet().iterator().next());
} else {
throw new IllegalArgumentException( "requests for multiple social feeds not supported" );
}
if (fs.isAllSocial()) {
return getStoryOrderForFolder(context, PrefConstants.ALL_SHARED_STORIES_FOLDER_NAME);
}
if (fs.getSingleSocialFeed() != null) {
return getStoryOrderForFeed(context, fs.getSingleSocialFeed().getKey());
}
if (fs.getMultipleSocialFeeds() != null) {
throw new IllegalArgumentException( "requests for multiple social feeds not supported" );
}
if (fs.isAllSaved()) {
@ -321,24 +321,24 @@ public class PrefsUtils {
}
public static ReadFilter getReadFilter(Context context, FeedSet fs) {
if (fs.getFeeds() != null) {
if (fs.getFeeds().size() == 0) {
return getReadFilterForFolder(context, PrefConstants.ALL_STORIES_FOLDER_NAME);
} else if (fs.getFeeds().size() == 1) {
return getReadFilterForFeed(context, fs.getFeeds().iterator().next());
} else {
return getReadFilterForFolder(context, fs.getFolderName());
}
if (fs.isAllNormal()) {
return getReadFilterForFolder(context, PrefConstants.ALL_STORIES_FOLDER_NAME);
}
if (fs.getSingleFeed() != null) {
return getReadFilterForFeed(context, fs.getSingleFeed());
}
if (fs.getMultipleFeeds() != null) {
return getReadFilterForFolder(context, fs.getFolderName());
}
if (fs.getSocialFeeds() != null) {
if (fs.getSocialFeeds().size() == 0) {
return getReadFilterForFolder(context, PrefConstants.ALL_SHARED_STORIES_FOLDER_NAME);
} else if (fs.getSocialFeeds().size() == 1) {
return getReadFilterForFeed(context, fs.getSocialFeeds().keySet().iterator().next());
} else {
throw new IllegalArgumentException( "requests for multiple social feeds not supported" );
}
if (fs.isAllSocial()) {
return getReadFilterForFolder(context, PrefConstants.ALL_SHARED_STORIES_FOLDER_NAME);
}
if (fs.getSingleSocialFeed() != null) {
return getReadFilterForFeed(context, fs.getSingleSocialFeed().getKey());
}
if (fs.getMultipleSocialFeeds() != null) {
throw new IllegalArgumentException( "requests for multiple social feeds not supported" );
}
if (fs.isAllSaved()) {