Refactor result codes for SyncService into enums. Remove deleteFeed from SyncService in preference to standard API call.

This commit is contained in:
ojiikun 2013-07-19 08:48:08 +00:00
parent 5f172ace9d
commit b9d9376212
6 changed files with 106 additions and 149 deletions

View file

@ -106,6 +106,9 @@
<string name="toast_story_unread">Story marked as unread</string>
<string name="toast_story_unread_error">Error marking story as unread</string>
<string name="toast_feed_deleted">Feed deleted</string>
<string name="toast_feed_delete_error">There was an error deleting the feed.</string>
<string name="logout_warning">Are you sure you want to log out?</string>
@ -117,7 +120,6 @@
<string name="menu_mark_all_as_read">Mark all as read</string>
<string name="menu_logout">Log out</string>
<string name="error_deleting_feed">There was an error deleting the feed.</string>
<string name="login_registration_register">Register</string>
<string name="login_google_reader">Google Reader</string>
<string name="login_add_sites_from_google_reader">Import your sites from Google Reader</string>

View file

@ -4,6 +4,7 @@ import com.newsblur.R;
import com.newsblur.activity.Main;
import com.newsblur.database.FeedProvider;
import com.newsblur.network.APIManager;
import com.newsblur.util.FeedUtils;
import android.app.Activity;
import android.net.Uri;
@ -24,8 +25,6 @@ public class DeleteFeedFragment extends DialogFragment {
private static final String FEED_NAME = "feed_name";
private static final String FOLDER_NAME = "folder_name";
private APIManager apiManager;
public static DeleteFeedFragment newInstance(final long feedId, final String feedName, final String folderName) {
DeleteFeedFragment frag = new DeleteFeedFragment();
Bundle args = new Bundle();
@ -53,57 +52,27 @@ public class DeleteFeedFragment extends DialogFragment {
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
final String deleteFeedString = getResources().getString(R.string.delete_feed_message);
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
apiManager = new APIManager(getActivity());
View v = inflater.inflate(R.layout.fragment_confirm_dialog, null);
TextView messageView = (TextView) v.findViewById(R.id.dialog_message);
messageView.setText(String.format(deleteFeedString, getArguments().getString(FEED_NAME)));
messageView.setText(String.format(getResources().getString(R.string.delete_feed_message), getArguments().getString(FEED_NAME)));
Button okayButton = (Button) v.findViewById(R.id.dialog_button_okay);
okayButton.setOnClickListener(new OnClickListener() {
okayButton.setOnClickListener(new OnClickListener() {
public void onClick(final View v) {
v.setEnabled(false);
FeedUtils.deleteFeed(getArguments().getLong(FEED_ID), getArguments().getString(FOLDER_NAME), getActivity(), new APIManager(getActivity()));
// if called from main view then refresh otherwise it was
// called from the feed view so finish
Activity activity = DeleteFeedFragment.this.getActivity();
if (activity instanceof Main) {
((Main)activity).updateAfterSync();
} else {
activity.finish();
}
DeleteFeedFragment.this.dismiss();
}
new AsyncTask<Void, Void, Boolean>() {
@Override
protected Boolean doInBackground(Void... arg) {
long feedId = getArguments().getLong(FEED_ID);
String folderName = getArguments().getString(FOLDER_NAME);
if (apiManager.deleteFeed(feedId, folderName)) {
Uri feedUri = FeedProvider.FEEDS_URI.buildUpon().appendPath(Long.toString(feedId)).build();
DeleteFeedFragment.this.getActivity().getContentResolver().delete(feedUri, null, null);
return true;
}
else {
return false;
}
}
@Override
protected void onPostExecute(Boolean result) {
Activity activity = DeleteFeedFragment.this.getActivity();
if (result) {
Toast.makeText(activity, "Deleted feed", Toast.LENGTH_SHORT).show();
DeleteFeedFragment.this.dismiss();
// if called from main view then refresh otherwise it was
// called from the feed view so finish
if (activity instanceof Main) {
((Main)activity).updateAfterSync();
}
else {
activity.finish();
}
} else {
Toast.makeText(activity, getResources().getString(R.string.error_deleting_feed), Toast.LENGTH_LONG).show();
DeleteFeedFragment.this.dismiss();
}
};
}.execute();
}
});
Button cancelButton = (Button) v.findViewById(R.id.dialog_button_cancel);

View file

@ -34,38 +34,35 @@ public class SyncUpdateFragment extends Fragment implements Receiver {
@Override
public void onReceiverResult(int resultCode, Bundle resultData) {
switch (resultCode) {
case SyncService.STATUS_FINISHED:
final SyncService.SyncStatus resultStatus = SyncService.SyncStatus.values()[resultCode];
switch (resultStatus) {
case STATUS_FINISHED:
syncRunning = false;
if (getActivity() != null) {
((SyncUpdateFragmentInterface) getActivity()).updateAfterSync();
}
break;
case SyncService.STATUS_PARTIAL_PROGRESS:
case STATUS_PARTIAL_PROGRESS:
syncRunning = true;
if (getActivity() != null) {
((SyncUpdateFragmentInterface) getActivity()).updatePartialSync();
}
break;
case SyncService.STATUS_FINISHED_CLOSE:
case STATUS_FINISHED_CLOSE:
syncRunning = false;
if (getActivity() != null) {
((SyncUpdateFragmentInterface) getActivity()).closeAfterUpdate();
}
break;
case SyncService.STATUS_RUNNING:
case STATUS_RUNNING:
syncRunning = true;
break;
case SyncService.STATUS_NO_MORE_UPDATES:
case STATUS_NO_MORE_UPDATES:
syncRunning = false;
if (getActivity() != null) {
((SyncUpdateFragmentInterface) getActivity()).setNothingMoreToUpdate();
}
break;
case SyncService.STATUS_ERROR:
syncRunning = false;
Log.e(this.getClass().getName(), "Sync completed with an error.");
break;
default:
syncRunning = false;
Log.e(this.getClass().getName(), "Sync completed with an unknown result.");

View file

@ -673,14 +673,14 @@ public class APIManager {
}
}
public boolean deleteFeed(long feedId, String folderName) {
public NewsBlurResponse deleteFeed(long feedId, String folderName) {
ContentValues values = new ContentValues();
values.put(APIConstants.PARAMETER_FEEDID, Long.toString(feedId));
if (!TextUtils.isEmpty(folderName)) {
if ((!TextUtils.isEmpty(folderName)) && (!folderName.equals(AppConstants.ROOT_FOLDER))) {
values.put(APIConstants.PARAMETER_IN_FOLDER, folderName);
}
final APIResponse response = post(APIConstants.URL_DELETE_FEED, values);
return (!response.isError());
APIResponse response = post(APIConstants.URL_DELETE_FEED, values);
return response.getResponse(gson, NewsBlurResponse.class);
}
/* HTTP METHODS */

View file

@ -24,12 +24,11 @@ import com.newsblur.util.ReadFilter;
import com.newsblur.util.StoryOrder;
/**
* The SyncService is based on an app architecture that tries to place network calls
* (especially larger calls or those called regularly) on independent services, making the
* activity / fragment a passive receiver of its updates. This, along with data fragments for
* handling UI updates, throttles network access and ensures the UI is passively updated
* and decoupled from network calls. Examples of other apps using this architecture include
* the NBCSportsTalk and Google I/O 2011 apps.
* A background-sync intent that, by virtue of extending IntentService, has several notable
* features:
* * invocations are FIFO and executed serially
* * the OS picks an appropriate thread for execution that won't block the UI, but recycles
* * supports callbacks where necessary
*/
public class SyncService extends IntentService {
@ -46,22 +45,20 @@ public class SyncService extends IntentService {
public static final String EXTRA_TASK_READ_FILTER = "read_filter";
public static final String EXTRA_TASK_MULTIFEED_IDS = "multi_feedids";
// TODO: replace these with enums
public final static int STATUS_RUNNING = 0x02;
public final static int STATUS_FINISHED = 0x03;
public final static int STATUS_ERROR = 0x04;
public static final int STATUS_NO_MORE_UPDATES = 0x05;
public static final int STATUS_FINISHED_CLOSE = 0x06;
public static final int NOT_RUNNING = 0x01;
public static final int STATUS_PARTIAL_PROGRESS = 0x07;
public enum SyncStatus {
STATUS_RUNNING,
STATUS_FINISHED,
STATUS_NO_MORE_UPDATES,
STATUS_FINISHED_CLOSE,
NOT_RUNNING,
STATUS_PARTIAL_PROGRESS,
};
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_SOCIALFEED_UPDATE = 34;
public static final int EXTRA_TASK_MULTIFEED_UPDATE = 36;
public static final int EXTRA_TASK_MARK_STORIES_READ = 43;
public static final int EXTRA_TASK_DELETE_FEED = 39;
public static final int EXTRA_TASK_MULTISOCIALFEED_UPDATE = 40;
public static final int EXTRA_TASK_STARRED_STORIES_UPDATE = 42;
@ -86,13 +83,14 @@ public class SyncService extends IntentService {
final ResultReceiver receiver = intent.getParcelableExtra(EXTRA_STATUS_RECEIVER);
try {
if (receiver != null) {
receiver.send(STATUS_RUNNING, Bundle.EMPTY);
receiver.send(SyncStatus.STATUS_RUNNING.ordinal(), Bundle.EMPTY);
}
// TODO: is it ever valid for receiver to be null? if not, we could factor out all
// the checks below
Log.d( this.getClass().getName(), "Sync Intent: " + intent.getIntExtra(SYNCSERVICE_TASK , -1) );
// an extra result code to callback before the final STATUS_FINISHED that is always sent
SyncStatus resultStatus = null;
switch (intent.getIntExtra(SYNCSERVICE_TASK , -1)) {
case EXTRA_TASK_FOLDER_UPDATE_TWO_STEP:
@ -100,7 +98,7 @@ public class SyncService extends IntentService {
apiManager.getFolderFeedMapping(false);
// notify UI of progress
if (receiver != null) {
receiver.send(STATUS_PARTIAL_PROGRESS, Bundle.EMPTY);
receiver.send(SyncStatus.STATUS_PARTIAL_PROGRESS.ordinal(), Bundle.EMPTY);
}
// update feed counts
apiManager.refreshFeedCounts();
@ -111,93 +109,54 @@ public class SyncService extends IntentService {
apiManager.getFolderFeedMapping(true);
break;
case EXTRA_TASK_MARK_STORIES_READ:
final List<String> storyHashes = (List<String>) intent.getSerializableExtra(EXTRA_TASK_STORIES);
apiManager.markStoriesAsRead(storyHashes);
break;
case EXTRA_TASK_FEED_UPDATE:
if (!TextUtils.isEmpty(intent.getStringExtra(EXTRA_TASK_FEED_ID))) {
StoriesResponse storiesForFeed = apiManager.getStoriesForFeed(intent.getStringExtra(EXTRA_TASK_FEED_ID), intent.getStringExtra(EXTRA_TASK_PAGE_NUMBER), (StoryOrder) intent.getSerializableExtra(EXTRA_TASK_ORDER), (ReadFilter) intent.getSerializableExtra(EXTRA_TASK_READ_FILTER));
if (storiesForFeed != null && storiesForFeed.stories.length != 0) {
receiver.send(STATUS_FINISHED, null);
} else {
receiver.send(STATUS_NO_MORE_UPDATES, Bundle.EMPTY);
if (storiesForFeed == null || storiesForFeed.stories.length == 0) {
resultStatus = SyncStatus.STATUS_NO_MORE_UPDATES;
}
} else {
Log.e(this.getClass().getName(), "No feed to refresh included in SyncRequest");
receiver.send(STATUS_ERROR, Bundle.EMPTY);
}
break;
case EXTRA_TASK_MULTIFEED_UPDATE:
if (intent.getStringArrayExtra(EXTRA_TASK_MULTIFEED_IDS) != null) {
StoriesResponse storiesForFeeds = apiManager.getStoriesForFeeds(intent.getStringArrayExtra(EXTRA_TASK_MULTIFEED_IDS), intent.getStringExtra(EXTRA_TASK_PAGE_NUMBER), (StoryOrder) intent.getSerializableExtra(EXTRA_TASK_ORDER), (ReadFilter) intent.getSerializableExtra(EXTRA_TASK_READ_FILTER));
if (storiesForFeeds != null && storiesForFeeds.stories.length != 0) {
receiver.send(STATUS_FINISHED, Bundle.EMPTY);
} else {
receiver.send(STATUS_NO_MORE_UPDATES, Bundle.EMPTY);
if (storiesForFeeds == null || storiesForFeeds.stories.length == 0) {
resultStatus = SyncStatus.STATUS_NO_MORE_UPDATES;
}
} else {
Log.e(this.getClass().getName(), "No feed ids to refresh included in SyncRequest");
receiver.send(STATUS_ERROR, Bundle.EMPTY);
}
break;
case EXTRA_TASK_MULTISOCIALFEED_UPDATE:
if (intent.getStringArrayExtra(EXTRA_TASK_MULTIFEED_IDS) != null) {
SocialFeedResponse sharedStoriesForFeeds = apiManager.getSharedStoriesForFeeds(intent.getStringArrayExtra(EXTRA_TASK_MULTIFEED_IDS), intent.getStringExtra(EXTRA_TASK_PAGE_NUMBER), (StoryOrder) intent.getSerializableExtra(EXTRA_TASK_ORDER), (ReadFilter) intent.getSerializableExtra(EXTRA_TASK_READ_FILTER));
if (sharedStoriesForFeeds != null && sharedStoriesForFeeds.stories.length != 0) {
receiver.send(STATUS_FINISHED, null);
} else {
receiver.send(STATUS_NO_MORE_UPDATES, Bundle.EMPTY);
if (sharedStoriesForFeeds == null || sharedStoriesForFeeds.stories.length == 0) {
resultStatus = SyncStatus.STATUS_NO_MORE_UPDATES;
}
} else {
Log.e(this.getClass().getName(), "No socialfeed ids to refresh included in SyncRequest");
receiver.send(STATUS_ERROR, Bundle.EMPTY);
}
break;
case EXTRA_TASK_STARRED_STORIES_UPDATE:
StoriesResponse starredStories = apiManager.getStarredStories(intent.getStringExtra(EXTRA_TASK_PAGE_NUMBER));
if (starredStories != null && starredStories.stories.length != 0) {
receiver.send(STATUS_FINISHED, Bundle.EMPTY);
} else {
receiver.send(STATUS_NO_MORE_UPDATES, Bundle.EMPTY);
if (starredStories == null && starredStories.stories.length == 0) {
resultStatus = SyncStatus.STATUS_NO_MORE_UPDATES;
}
break;
case EXTRA_TASK_DELETE_FEED:
if (intent.getLongExtra(EXTRA_TASK_FEED_ID, -1) != -1) {
Long feedToBeDeleted = intent.getLongExtra(EXTRA_TASK_FEED_ID, -1);
if (apiManager.deleteFeed(feedToBeDeleted, intent.getStringExtra(EXTRA_TASK_FOLDER_NAME))) {
Uri feedUri = FeedProvider.FEEDS_URI.buildUpon().appendPath(Long.toString(feedToBeDeleted)).build();
contentResolver.delete(feedUri, null, null);
receiver.send(STATUS_FINISHED_CLOSE, Bundle.EMPTY);
return;
} else {
Log.e(this.getClass().getName(), "Error deleting feed");
Toast.makeText(this, getResources().getString(R.string.error_deleting_feed), Toast.LENGTH_LONG).show();
receiver.send(STATUS_ERROR, Bundle.EMPTY);
}
} else {
Log.e(this.getClass().getName(), "No feed id to delete include in SyncRequest");
receiver.send(STATUS_ERROR, Bundle.EMPTY);
}
break;
case EXTRA_TASK_SOCIALFEED_UPDATE:
if (!TextUtils.isEmpty(intent.getStringExtra(EXTRA_TASK_SOCIALFEED_ID)) && !TextUtils.isEmpty(intent.getStringExtra(EXTRA_TASK_SOCIALFEED_USERNAME))) {
SocialFeedResponse storiesForSocialFeed = apiManager.getStoriesForSocialFeed(intent.getStringExtra(EXTRA_TASK_SOCIALFEED_ID), intent.getStringExtra(EXTRA_TASK_SOCIALFEED_USERNAME), intent.getStringExtra(EXTRA_TASK_PAGE_NUMBER), (StoryOrder) intent.getSerializableExtra(EXTRA_TASK_ORDER), (ReadFilter) intent.getSerializableExtra(EXTRA_TASK_READ_FILTER));
if (storiesForSocialFeed != null && storiesForSocialFeed.stories.length != 0) {
receiver.send(STATUS_FINISHED, null);
} else {
receiver.send(STATUS_NO_MORE_UPDATES, Bundle.EMPTY);
if (storiesForSocialFeed == null || storiesForSocialFeed.stories.length == 0) {
resultStatus = SyncStatus.STATUS_NO_MORE_UPDATES;
}
} else {
Log.e(this.getClass().getName(), "Missing parameters for socialfeed SyncRequest");
receiver.send(STATUS_ERROR, Bundle.EMPTY);
}
break;
@ -206,23 +165,22 @@ public class SyncService extends IntentService {
break;
}
// send the first result code if it was set. The STATUS_FINISHED is sent below
if ((receiver != null) && (resultStatus != null)) {
receiver.send(resultStatus.ordinal(), Bundle.EMPTY);
}
Log.d( this.getClass().getName(), "Sync Intent complete");
} catch (Exception e) {
e.printStackTrace();
Log.e(this.getClass().getName(), "Couldn't synchronise with Newsblur servers: " + e.getMessage(), e.getCause());
if (receiver != null) {
final Bundle bundle = new Bundle();
bundle.putString(Intent.EXTRA_TEXT, e.toString());
receiver.send(STATUS_ERROR, bundle);
}
}
e.printStackTrace();
} finally {
if (receiver != null) {
receiver.send(SyncStatus.STATUS_FINISHED.ordinal(), Bundle.EMPTY);
}
}
if (receiver != null) {
receiver.send(STATUS_FINISHED, Bundle.EMPTY);
} else {
Log.e(this.getClass().getName(), "No receiver attached to Sync?");
}
}
}

View file

@ -53,7 +53,29 @@ public class FeedUtils {
}
}
public static void markStoryUnread( final Story story, final Context context, final APIManager apiManager) {
public static void deleteFeed( final long feedId, final String folderName, final Context context, final APIManager apiManager) {
new AsyncTask<Void, Void, NewsBlurResponse>() {
@Override
protected NewsBlurResponse doInBackground(Void... arg) {
return apiManager.deleteFeed(feedId, folderName);
}
@Override
protected void onPostExecute(NewsBlurResponse result) {
if (!result.isError()) {
Toast.makeText(context, R.string.toast_feed_deleted, Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(context, result.getErrorMessage(context.getString(R.string.toast_feed_delete_error)), Toast.LENGTH_LONG).show();
}
}
}.execute();
Uri feedUri = FeedProvider.FEEDS_URI.buildUpon().appendPath(Long.toString(feedId)).build();
context.getContentResolver().delete(feedUri, null, null);
}
public static void markStoryUnread( final Story story, final Context context, final APIManager apiManager ) {
new AsyncTask<Void, Void, NewsBlurResponse>() {
@Override
@ -76,10 +98,10 @@ public class FeedUtils {
* 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.
*/
public static void markStoriesAsRead( Collection<Story> stories, Context context ) {
public static void markStoriesAsRead( Collection<Story> stories, final Context context ) {
// the list of story hashes to mark read
ArrayList<String> storyHashes = new ArrayList<String>();
final ArrayList<String> storyHashes = new ArrayList<String>();
// a list of local DB ops to perform
ArrayList<ContentProviderOperation> updateOps = new ArrayList<ContentProviderOperation>();
@ -97,10 +119,19 @@ public class FeedUtils {
// next, update the server
if (storyHashes.size() > 0) {
Intent intent = new Intent(Intent.ACTION_SYNC, null, context, SyncService.class);
intent.putExtra(SyncService.SYNCSERVICE_TASK, SyncService.EXTRA_TASK_MARK_STORIES_READ);
intent.putExtra(SyncService.EXTRA_TASK_STORIES, storyHashes);
context.startService(intent);
new AsyncTask<Void, Void, NewsBlurResponse>() {
@Override
protected NewsBlurResponse doInBackground(Void... arg) {
APIManager apiManager = new APIManager(context);
return apiManager.markStoriesAsRead(storyHashes);
}
@Override
protected void onPostExecute(NewsBlurResponse result) {
if (!result.isError()) {
Log.e(FeedUtils.class.getName(), "Could not update unread counts via API: " + result.getErrorMessage());
}
}
}.execute();
}
}