aboutsummaryrefslogtreecommitdiff
path: root/WordPress/src/main/java/org/wordpress/android/ui/reader/actions/ReaderBlogActions.java
diff options
context:
space:
mode:
Diffstat (limited to 'WordPress/src/main/java/org/wordpress/android/ui/reader/actions/ReaderBlogActions.java')
-rw-r--r--WordPress/src/main/java/org/wordpress/android/ui/reader/actions/ReaderBlogActions.java477
1 files changed, 477 insertions, 0 deletions
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/reader/actions/ReaderBlogActions.java b/WordPress/src/main/java/org/wordpress/android/ui/reader/actions/ReaderBlogActions.java
new file mode 100644
index 000000000..6398b1e07
--- /dev/null
+++ b/WordPress/src/main/java/org/wordpress/android/ui/reader/actions/ReaderBlogActions.java
@@ -0,0 +1,477 @@
+package org.wordpress.android.ui.reader.actions;
+
+import android.text.TextUtils;
+
+import com.android.volley.Request;
+import com.android.volley.Response;
+import com.android.volley.VolleyError;
+import com.android.volley.toolbox.StringRequest;
+import com.wordpress.rest.RestRequest;
+
+import org.json.JSONObject;
+import org.wordpress.android.WordPress;
+import org.wordpress.android.analytics.AnalyticsTracker;
+import org.wordpress.android.datasets.ReaderBlogTable;
+import org.wordpress.android.datasets.ReaderPostTable;
+import org.wordpress.android.models.ReaderBlog;
+import org.wordpress.android.models.ReaderPost;
+import org.wordpress.android.models.ReaderPostList;
+import org.wordpress.android.ui.reader.actions.ReaderActions.ActionListener;
+import org.wordpress.android.ui.reader.actions.ReaderActions.UpdateBlogInfoListener;
+import org.wordpress.android.util.AnalyticsUtils;
+import org.wordpress.android.util.AppLog;
+import org.wordpress.android.util.AppLog.T;
+import org.wordpress.android.util.UrlUtils;
+import org.wordpress.android.util.VolleyUtils;
+
+import java.net.HttpURLConnection;
+
+public class ReaderBlogActions {
+
+ public static class BlockedBlogResult {
+ public long blogId;
+ public ReaderPostList deletedPosts;
+ public boolean wasFollowing;
+ }
+
+ private static String jsonToString(JSONObject json) {
+ return (json != null ? json.toString() : "");
+ }
+
+ public static boolean followBlogById(final long blogId,
+ final boolean isAskingToFollow,
+ final ActionListener actionListener) {
+ if (blogId == 0) {
+ if (actionListener != null) {
+ actionListener.onActionResult(false);
+ }
+ return false;
+ }
+
+ ReaderBlogTable.setIsFollowedBlogId(blogId, isAskingToFollow);
+ ReaderPostTable.setFollowStatusForPostsInBlog(blogId, isAskingToFollow);
+
+ if (isAskingToFollow) {
+ AnalyticsUtils.trackWithBlogDetails(AnalyticsTracker.Stat.READER_BLOG_FOLLOWED, blogId);
+ } else {
+ AnalyticsUtils.trackWithBlogDetails(AnalyticsTracker.Stat.READER_BLOG_UNFOLLOWED, blogId);
+ }
+
+ final String actionName = (isAskingToFollow ? "follow" : "unfollow");
+ final String path = "sites/" + blogId + "/follows/" + (isAskingToFollow ? "new" : "mine/delete");
+
+ com.wordpress.rest.RestRequest.Listener listener = new RestRequest.Listener() {
+ @Override
+ public void onResponse(JSONObject jsonObject) {
+ boolean success = isFollowActionSuccessful(jsonObject, isAskingToFollow);
+ if (success) {
+ AppLog.d(T.READER, "blog " + actionName + " succeeded");
+ } else {
+ AppLog.w(T.READER, "blog " + actionName + " failed - " + jsonToString(jsonObject) + " - " + path);
+ localRevertFollowBlogId(blogId, isAskingToFollow);
+ }
+ if (actionListener != null) {
+ actionListener.onActionResult(success);
+ }
+ }
+ };
+ RestRequest.ErrorListener errorListener = new RestRequest.ErrorListener() {
+ @Override
+ public void onErrorResponse(VolleyError volleyError) {
+ AppLog.w(T.READER, "blog " + actionName + " failed with error");
+ AppLog.e(T.READER, volleyError);
+ localRevertFollowBlogId(blogId, isAskingToFollow);
+ if (actionListener != null) {
+ actionListener.onActionResult(false);
+ }
+ }
+ };
+ WordPress.getRestClientUtilsV1_1().post(path, listener, errorListener);
+
+ return true;
+ }
+
+ public static boolean followFeedById(final long feedId,
+ final boolean isAskingToFollow,
+ final ActionListener actionListener) {
+ ReaderBlog blogInfo = ReaderBlogTable.getFeedInfo(feedId);
+ if (blogInfo != null) {
+ return internalFollowFeed(blogInfo.feedId, blogInfo.getFeedUrl(), isAskingToFollow, actionListener);
+ }
+
+ updateFeedInfo(feedId, null, new UpdateBlogInfoListener() {
+ @Override
+ public void onResult(ReaderBlog blogInfo) {
+ if (blogInfo != null) {
+ internalFollowFeed(
+ blogInfo.feedId,
+ blogInfo.getFeedUrl(),
+ isAskingToFollow,
+ actionListener);
+ } else if (actionListener != null) {
+ actionListener.onActionResult(false);
+ }
+ }
+ });
+
+ return true;
+ }
+
+ public static void followFeedByUrl(final String feedUrl,
+ final ActionListener actionListener) {
+ if (TextUtils.isEmpty(feedUrl)) {
+ ReaderActions.callActionListener(actionListener, false);
+ return;
+ }
+
+ // use existing blog info if we can
+ ReaderBlog blogInfo = ReaderBlogTable.getFeedInfo(ReaderBlogTable.getFeedIdFromUrl(feedUrl));
+ if (blogInfo != null) {
+ internalFollowFeed(blogInfo.feedId, blogInfo.getFeedUrl(), true, actionListener);
+ return;
+ }
+
+ // otherwise, look it up via the endpoint
+ updateFeedInfo(0, feedUrl, new UpdateBlogInfoListener() {
+ @Override
+ public void onResult(ReaderBlog blogInfo) {
+ // note we attempt to follow even when the look up fails (blogInfo = null) because that
+ // endpoint doesn't perform feed discovery, whereas the endpoint to follow a feed does
+ long feedIdToFollow = blogInfo != null ? blogInfo.feedId : 0;
+ String feedUrlToFollow = (blogInfo != null && blogInfo.hasFeedUrl()) ? blogInfo.getFeedUrl() : feedUrl;
+ internalFollowFeed(
+ feedIdToFollow,
+ feedUrlToFollow,
+ true,
+ actionListener);
+ }
+ });
+ }
+
+ private static boolean internalFollowFeed(
+ final long feedId,
+ final String feedUrl,
+ final boolean isAskingToFollow,
+ final ActionListener actionListener)
+ {
+ // feedUrl is required
+ if (TextUtils.isEmpty(feedUrl)) {
+ if (actionListener != null) {
+ actionListener.onActionResult(false);
+ }
+ return false;
+ }
+
+ if (feedId != 0) {
+ ReaderBlogTable.setIsFollowedFeedId(feedId, isAskingToFollow);
+ ReaderPostTable.setFollowStatusForPostsInFeed(feedId, isAskingToFollow);
+ }
+
+ if (isAskingToFollow) {
+ AnalyticsTracker.track(AnalyticsTracker.Stat.READER_BLOG_FOLLOWED);
+ } else {
+ AnalyticsTracker.track(AnalyticsTracker.Stat.READER_BLOG_UNFOLLOWED);
+ }
+
+ final String actionName = (isAskingToFollow ? "follow" : "unfollow");
+ final String path = "read/following/mine/"
+ + (isAskingToFollow ? "new" : "delete")
+ + "?url=" + UrlUtils.urlEncode(feedUrl);
+
+ com.wordpress.rest.RestRequest.Listener listener = new RestRequest.Listener() {
+ @Override
+ public void onResponse(JSONObject jsonObject) {
+ boolean success = isFollowActionSuccessful(jsonObject, isAskingToFollow);
+ if (success) {
+ AppLog.d(T.READER, "feed " + actionName + " succeeded");
+ } else {
+ AppLog.w(T.READER, "feed " + actionName + " failed - " + jsonToString(jsonObject) + " - " + path);
+ localRevertFollowFeedId(feedId, isAskingToFollow);
+ }
+ if (actionListener != null) {
+ actionListener.onActionResult(success);
+ }
+ }
+ };
+ RestRequest.ErrorListener errorListener = new RestRequest.ErrorListener() {
+ @Override
+ public void onErrorResponse(VolleyError volleyError) {
+ AppLog.w(T.READER, "feed " + actionName + " failed with error");
+ AppLog.e(T.READER, volleyError);
+ localRevertFollowFeedId(feedId, isAskingToFollow);
+ if (actionListener != null) {
+ actionListener.onActionResult(false);
+ }
+ }
+ };
+ WordPress.getRestClientUtilsV1_1().post(path, listener, errorListener);
+
+ return true;
+ }
+
+ /*
+ * helper routine when following a blog from a post view
+ */
+ public static boolean followBlogForPost(ReaderPost post,
+ boolean isAskingToFollow,
+ ActionListener actionListener) {
+ if (post == null) {
+ AppLog.w(T.READER, "follow action performed with null post");
+ if (actionListener != null) {
+ actionListener.onActionResult(false);
+ }
+ return false;
+ }
+ if (post.feedId != 0) {
+ return followFeedById(post.feedId, isAskingToFollow, actionListener);
+ } else {
+ return followBlogById(post.blogId, isAskingToFollow, actionListener);
+ }
+ }
+
+ /*
+ * called when a follow/unfollow fails, restores local data to previous state
+ */
+ private static void localRevertFollowBlogId(long blogId, boolean isAskingToFollow) {
+ ReaderBlogTable.setIsFollowedBlogId(blogId, !isAskingToFollow);
+ ReaderPostTable.setFollowStatusForPostsInBlog(blogId, !isAskingToFollow);
+ }
+ private static void localRevertFollowFeedId(long feedId, boolean isAskingToFollow) {
+ ReaderBlogTable.setIsFollowedFeedId(feedId, !isAskingToFollow);
+ ReaderPostTable.setFollowStatusForPostsInFeed(feedId, !isAskingToFollow);
+ }
+
+ /*
+ * returns whether a follow/unfollow was successful based on the response to:
+ * read/follows/new
+ * read/follows/delete
+ * site/$site/follows/new
+ * site/$site/follows/mine/delete
+ */
+ private static boolean isFollowActionSuccessful(JSONObject json, boolean isAskingToFollow) {
+ if (json == null) {
+ return false;
+ }
+
+ boolean isSubscribed;
+ if (json.has("subscribed")) {
+ // read/follows/
+ isSubscribed = json.optBoolean("subscribed", false);
+ } else {
+ // site/$site/follows/
+ isSubscribed = json.has("is_following") && json.optBoolean("is_following", false);
+ }
+ return (isSubscribed == isAskingToFollow);
+ }
+
+ /*
+ * request info about a specific blog
+ */
+ public static void updateBlogInfo(long blogId,
+ final String blogUrl,
+ final UpdateBlogInfoListener infoListener) {
+ // must pass either a valid id or url
+ final boolean hasBlogId = (blogId != 0);
+ final boolean hasBlogUrl = !TextUtils.isEmpty(blogUrl);
+ if (!hasBlogId && !hasBlogUrl) {
+ AppLog.w(T.READER, "cannot get blog info without either id or url");
+ if (infoListener != null) {
+ infoListener.onResult(null);
+ }
+ return;
+ }
+
+ RestRequest.Listener listener = new RestRequest.Listener() {
+ @Override
+ public void onResponse(JSONObject jsonObject) {
+ handleUpdateBlogInfoResponse(jsonObject, infoListener);
+ }
+ };
+ RestRequest.ErrorListener errorListener = new RestRequest.ErrorListener() {
+ @Override
+ public void onErrorResponse(VolleyError volleyError) {
+ // authentication error may indicate that API access has been disabled for this blog
+ int statusCode = VolleyUtils.statusCodeFromVolleyError(volleyError);
+ boolean isAuthErr = (statusCode == HttpURLConnection.HTTP_FORBIDDEN);
+ // if we failed to get the blog info using the id and this isn't an authentication
+ // error, try again using just the domain
+ if (!isAuthErr && hasBlogId && hasBlogUrl) {
+ AppLog.w(T.READER, "failed to get blog info by id, retrying with url");
+ updateBlogInfo(0, blogUrl, infoListener);
+ } else {
+ AppLog.e(T.READER, volleyError);
+ if (infoListener != null) {
+ infoListener.onResult(null);
+ }
+ }
+ }
+ };
+
+ if (hasBlogId) {
+ WordPress.getRestClientUtilsV1_1().get("read/sites/" + blogId, listener, errorListener);
+ } else {
+ WordPress.getRestClientUtilsV1_1().get("read/sites/" + UrlUtils.urlEncode(UrlUtils.getHost(blogUrl)), listener, errorListener);
+ }
+ }
+ public static void updateFeedInfo(long feedId, String feedUrl, final UpdateBlogInfoListener infoListener) {
+ RestRequest.Listener listener = new RestRequest.Listener() {
+ @Override
+ public void onResponse(JSONObject jsonObject) {
+ handleUpdateBlogInfoResponse(jsonObject, infoListener);
+ }
+ };
+ RestRequest.ErrorListener errorListener = new RestRequest.ErrorListener() {
+ @Override
+ public void onErrorResponse(VolleyError volleyError) {
+ AppLog.e(T.READER, volleyError);
+ if (infoListener != null) {
+ infoListener.onResult(null);
+ }
+ }
+ };
+ String path;
+ if (feedId != 0) {
+ path = "read/feed/" + feedId;
+ } else {
+ path = "read/feed/" + UrlUtils.urlEncode(feedUrl);
+ }
+ WordPress.getRestClientUtilsV1_1().get(path, listener, errorListener);
+ }
+ private static void handleUpdateBlogInfoResponse(JSONObject jsonObject, UpdateBlogInfoListener infoListener) {
+ if (jsonObject == null) {
+ if (infoListener != null) {
+ infoListener.onResult(null);
+ }
+ return;
+ }
+
+ ReaderBlog blogInfo = ReaderBlog.fromJson(jsonObject);
+ ReaderBlogTable.addOrUpdateBlog(blogInfo);
+
+ if (infoListener != null) {
+ infoListener.onResult(blogInfo);
+ }
+ }
+
+ /*
+ * tests whether the passed url can be reached - does NOT use authentication, and does not
+ * account for 404 replacement pages used by ISPs such as Charter
+ */
+ public static void checkUrlReachable(final String blogUrl,
+ final ReaderActions.OnRequestListener requestListener) {
+ // listener is required
+ if (requestListener == null) return;
+
+ Response.Listener<String> listener = new Response.Listener<String>() {
+ @Override
+ public void onResponse(String response) {
+ requestListener.onSuccess();
+ }
+ };
+ Response.ErrorListener errorListener = new Response.ErrorListener() {
+ @Override
+ public void onErrorResponse(VolleyError volleyError) {
+ AppLog.e(T.READER, volleyError);
+ int statusCode;
+ // check specifically for auth failure class rather than relying on status code
+ // since a redirect to an unauthorized url may return a 301 rather than a 401
+ if (volleyError instanceof com.android.volley.AuthFailureError) {
+ statusCode = 401;
+ } else {
+ statusCode = VolleyUtils.statusCodeFromVolleyError(volleyError);
+ }
+ // Volley treats a 301 redirect as a failure here, we should treat it as
+ // success since it means the blog url is reachable
+ if (statusCode == 301) {
+ requestListener.onSuccess();
+ } else {
+ requestListener.onFailure(statusCode);
+ }
+ }
+ };
+
+ // TODO: this should be a HEAD request, but even though Volley supposedly supports HEAD
+ // using it results in "java.lang.IllegalStateException: Unknown method type"
+ StringRequest request = new StringRequest(
+ Request.Method.GET,
+ blogUrl,
+ listener,
+ errorListener);
+ WordPress.requestQueue.add(request);
+ }
+
+ /*
+ * block a blog - result includes the list of posts that were deleted by the block so they
+ * can be restored if the user undoes the block
+ */
+ public static BlockedBlogResult blockBlogFromReader(final long blogId, final ActionListener actionListener) {
+ final BlockedBlogResult blockResult = new BlockedBlogResult();
+ blockResult.blogId = blogId;
+ blockResult.deletedPosts = ReaderPostTable.getPostsInBlog(blogId, 0, false);
+ blockResult.wasFollowing = ReaderBlogTable.isFollowedBlog(blogId);
+
+ ReaderPostTable.deletePostsInBlog(blogId);
+ ReaderBlogTable.setIsFollowedBlogId(blogId, false);
+
+ com.wordpress.rest.RestRequest.Listener listener = new RestRequest.Listener() {
+ @Override
+ public void onResponse(JSONObject jsonObject) {
+ if (actionListener != null) {
+ actionListener.onActionResult(true);
+ }
+ }
+ };
+ RestRequest.ErrorListener errorListener = new RestRequest.ErrorListener() {
+ @Override
+ public void onErrorResponse(VolleyError volleyError) {
+ AppLog.e(T.READER, volleyError);
+ ReaderPostTable.addOrUpdatePosts(null, blockResult.deletedPosts);
+ if (blockResult.wasFollowing) {
+ ReaderBlogTable.setIsFollowedBlogId(blogId, true);
+ }
+ if (actionListener != null) {
+ actionListener.onActionResult(false);
+ }
+ }
+ };
+
+ AppLog.i(T.READER, "blocking blog " + blogId);
+ String path = "me/block/sites/" + Long.toString(blogId) + "/new";
+ WordPress.getRestClientUtilsV1_1().post(path, listener, errorListener);
+
+ return blockResult;
+ }
+
+ public static void undoBlockBlogFromReader(final BlockedBlogResult blockResult) {
+ if (blockResult == null) {
+ return;
+ }
+ if (blockResult.deletedPosts != null) {
+ ReaderPostTable.addOrUpdatePosts(null, blockResult.deletedPosts);
+ }
+
+ com.wordpress.rest.RestRequest.Listener listener = new RestRequest.Listener() {
+ @Override
+ public void onResponse(JSONObject jsonObject) {
+ boolean success = (jsonObject != null && jsonObject.optBoolean("success"));
+ // re-follow the blog if it was being followed prior to the block
+ if (success && blockResult.wasFollowing) {
+ followBlogById(blockResult.blogId, true, null);
+ } else if (!success) {
+ AppLog.w(T.READER, "failed to unblock blog " + blockResult.blogId);
+ }
+
+ }
+ };
+ RestRequest.ErrorListener errorListener = new RestRequest.ErrorListener() {
+ @Override
+ public void onErrorResponse(VolleyError volleyError) {
+ AppLog.e(T.READER, volleyError);
+ }
+ };
+
+ AppLog.i(T.READER, "unblocking blog " + blockResult.blogId);
+ String path = "me/block/sites/" + Long.toString(blockResult.blogId) + "/delete";
+ WordPress.getRestClientUtilsV1_1().post(path, listener, errorListener);
+ }
+}