aboutsummaryrefslogtreecommitdiff
path: root/WordPress/src/main/java/org/xmlrpc/android/ApiHelper.java
diff options
context:
space:
mode:
Diffstat (limited to 'WordPress/src/main/java/org/xmlrpc/android/ApiHelper.java')
-rw-r--r--WordPress/src/main/java/org/xmlrpc/android/ApiHelper.java1189
1 files changed, 1189 insertions, 0 deletions
diff --git a/WordPress/src/main/java/org/xmlrpc/android/ApiHelper.java b/WordPress/src/main/java/org/xmlrpc/android/ApiHelper.java
new file mode 100644
index 000000000..a07bc2e6a
--- /dev/null
+++ b/WordPress/src/main/java/org/xmlrpc/android/ApiHelper.java
@@ -0,0 +1,1189 @@
+package org.xmlrpc.android;
+
+import android.content.Context;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.text.TextUtils;
+import android.webkit.URLUtil;
+
+import com.android.volley.DefaultRetryPolicy;
+import com.android.volley.NetworkResponse;
+import com.android.volley.RedirectError;
+import com.android.volley.TimeoutError;
+import com.android.volley.toolbox.RequestFuture;
+import com.android.volley.toolbox.StringRequest;
+import com.google.gson.Gson;
+
+import org.wordpress.android.R;
+import org.wordpress.android.WordPress;
+import org.wordpress.android.analytics.AnalyticsTracker;
+import org.wordpress.android.datasets.CommentTable;
+import org.wordpress.android.models.Blog;
+import org.wordpress.android.models.BlogIdentifier;
+import org.wordpress.android.models.Comment;
+import org.wordpress.android.models.CommentList;
+import org.wordpress.android.models.CommentStatus;
+import org.wordpress.android.models.FeatureSet;
+import org.wordpress.android.ui.media.MediaGridFragment.Filter;
+import org.wordpress.android.ui.stats.StatsUtils;
+import org.wordpress.android.ui.stats.StatsWidgetProvider;
+import org.wordpress.android.util.AnalyticsUtils;
+import org.wordpress.android.util.AppLog;
+import org.wordpress.android.util.AppLog.T;
+import org.wordpress.android.util.CoreEvents;
+import org.wordpress.android.util.DateTimeUtils;
+import org.wordpress.android.util.MapUtils;
+import org.wordpress.android.util.helpers.MediaFile;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import javax.net.ssl.SSLHandshakeException;
+
+import de.greenrobot.event.EventBus;
+
+public class ApiHelper {
+
+ public static final class Method {
+ public static final String GET_MEDIA_LIBRARY = "wp.getMediaLibrary";
+ public static final String GET_POST_FORMATS = "wp.getPostFormats";
+ public static final String GET_CATEGORIES = "wp.getCategories";
+ public static final String GET_MEDIA_ITEM = "wp.getMediaItem";
+ public static final String GET_COMMENT = "wp.getComment";
+ public static final String GET_COMMENTS = "wp.getComments";
+ public static final String GET_BLOGS = "wp.getUsersBlogs";
+ public static final String GET_OPTIONS = "wp.getOptions";
+ public static final String GET_PROFILE = "wp.getProfile";
+ public static final String GET_PAGES = "wp.getPages";
+ public static final String GET_TERM = "wp.getTerm";
+ public static final String GET_PAGE = "wp.getPage";
+
+ public static final String DELETE_COMMENT = "wp.deleteComment";
+ public static final String DELETE_PAGE = "wp.deletePage";
+ public static final String DELETE_POST = "wp.deletePost";
+
+ public static final String NEW_CATEGORY = "wp.newCategory";
+ public static final String NEW_COMMENT = "wp.newComment";
+
+ public static final String EDIT_POST = "wp.editPost";
+ public static final String EDIT_COMMENT = "wp.editComment";
+
+ public static final String SET_OPTIONS = "wp.setOptions";
+
+ public static final String UPLOAD_FILE = "wp.uploadFile";
+
+ public static final String WPCOM_GET_FEATURES = "wpcom.getFeatures";
+
+ public static final String LIST_METHODS = "system.listMethods";
+ }
+
+ public static final class Param {
+ public static final String SHOW_SUPPORTED_POST_FORMATS = "show-supported";
+ }
+
+ public enum ErrorType {
+ NO_ERROR, UNKNOWN_ERROR, INVALID_CURRENT_BLOG, NETWORK_XMLRPC, INVALID_CONTEXT,
+ INVALID_RESULT, NO_UPLOAD_FILES_CAP, CAST_EXCEPTION, TASK_CANCELLED, UNAUTHORIZED
+ }
+
+ public static final Map<String, String> blogOptionsXMLRPCParameters = new HashMap<String, String>();
+
+ static {
+ blogOptionsXMLRPCParameters.put("software_version", "software_version");
+ blogOptionsXMLRPCParameters.put("post_thumbnail", "post_thumbnail");
+ blogOptionsXMLRPCParameters.put("jetpack_client_id", "jetpack_client_id");
+ blogOptionsXMLRPCParameters.put("blog_public", "blog_public");
+ blogOptionsXMLRPCParameters.put("home_url", "home_url");
+ blogOptionsXMLRPCParameters.put("admin_url", "admin_url");
+ blogOptionsXMLRPCParameters.put("login_url", "login_url");
+ blogOptionsXMLRPCParameters.put("blog_title", "blog_title");
+ blogOptionsXMLRPCParameters.put("time_zone", "time_zone");
+ }
+
+ public static abstract class HelperAsyncTask<Params, Progress, Result> extends AsyncTask<Params, Progress, Result> {
+ protected String mErrorMessage;
+ protected ErrorType mErrorType = ErrorType.NO_ERROR;
+ protected Throwable mThrowable;
+
+ protected void setError(@NonNull ErrorType errorType, String errorMessage) {
+ mErrorMessage = errorMessage;
+ mErrorType = errorType;
+ AppLog.e(T.API, mErrorType.name() + " - " + mErrorMessage);
+ }
+
+ protected void setError(@NonNull ErrorType errorType, String errorMessage, Throwable throwable) {
+ mErrorMessage = errorMessage;
+ mErrorType = errorType;
+ mThrowable = throwable;
+ AppLog.e(T.API, mErrorType.name() + " - " + mErrorMessage, throwable);
+ }
+ }
+
+ public interface GenericErrorCallback {
+ public void onFailure(ErrorType errorType, String errorMessage, Throwable throwable);
+ }
+
+ public interface GenericCallback extends GenericErrorCallback {
+ public void onSuccess();
+ }
+
+ public interface DatabasePersistCallback {
+ void onDataReadyToSave(List list);
+ }
+
+ public static class GetPostFormatsTask extends HelperAsyncTask<Blog, Void, Object> {
+ private Blog mBlog;
+
+ @Override
+ protected Object doInBackground(Blog... blog) {
+ mBlog = blog[0];
+ XMLRPCClientInterface client = XMLRPCFactory.instantiate(mBlog.getUri(), mBlog.getHttpuser(),
+ mBlog.getHttppassword());
+ Object result = null;
+ Object[] params = { mBlog.getRemoteBlogId(), mBlog.getUsername(),
+ mBlog.getPassword(), Param.SHOW_SUPPORTED_POST_FORMATS };
+ try {
+ result = client.call(Method.GET_POST_FORMATS, params);
+ } catch (ClassCastException cce) {
+ setError(ErrorType.INVALID_RESULT, cce.getMessage(), cce);
+ } catch (XMLRPCException e) {
+ setError(ErrorType.NETWORK_XMLRPC, e.getMessage(), e);
+ } catch (IOException e) {
+ setError(ErrorType.NETWORK_XMLRPC, e.getMessage(), e);
+ } catch (XmlPullParserException e) {
+ setError(ErrorType.NETWORK_XMLRPC, e.getMessage(), e);
+ }
+ return result;
+ }
+
+ protected void onPostExecute(Object result) {
+ if (result != null && result instanceof HashMap) {
+ Map<?, ?> postFormats = (HashMap<?, ?>) result;
+ if (postFormats.size() > 0) {
+ Gson gson = new Gson();
+ String postFormatsJson = gson.toJson(postFormats);
+ if (postFormatsJson != null) {
+ if (mBlog.bsetPostFormats(postFormatsJson)) {
+ WordPress.wpDB.saveBlog(mBlog);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ public static synchronized void updateBlogOptions(Blog currentBlog, Map<?, ?> blogOptions) {
+ boolean isModified = false;
+ Gson gson = new Gson();
+ String blogOptionsJson = gson.toJson(blogOptions);
+ if (blogOptionsJson != null) {
+ isModified |= currentBlog.bsetBlogOptions(blogOptionsJson);
+ }
+
+ // Software version
+ if (!currentBlog.isDotcomFlag()) {
+ Map<?, ?> sv = (Map<?, ?>) blogOptions.get("software_version");
+ String wpVersion = MapUtils.getMapStr(sv, "value");
+ if (wpVersion.length() > 0) {
+ isModified |= currentBlog.bsetWpVersion(wpVersion);
+ }
+ }
+
+ // Featured image support
+ Map<?, ?> featuredImageHash = (Map<?, ?>) blogOptions.get("post_thumbnail");
+ if (featuredImageHash != null) {
+ boolean featuredImageCapable = MapUtils.getMapBool(featuredImageHash, "value");
+ isModified |= currentBlog.bsetFeaturedImageCapable(featuredImageCapable);
+ } else {
+ isModified |= currentBlog.bsetFeaturedImageCapable(false);
+ }
+
+ // Blog name
+ Map<?, ?> blogNameHash = (Map<?, ?>) blogOptions.get("blog_title");
+ if (blogNameHash != null) {
+ String blogName = MapUtils.getMapStr(blogNameHash, "value");
+ if (blogName != null && !blogName.equals(currentBlog.getBlogName())) {
+ currentBlog.setBlogName(blogName);
+ isModified = true;
+ }
+ }
+
+ if (isModified) {
+ WordPress.wpDB.saveBlog(currentBlog);
+ }
+ }
+
+ /**
+ * Task to refresh blog level information (WP version number) and stuff
+ * related to the active theme (available post types, recent comments, etc).
+ */
+ public static class RefreshBlogContentTask extends HelperAsyncTask<Boolean, Void, Boolean> {
+ private static HashSet<BlogIdentifier> refreshedBlogs = new HashSet<BlogIdentifier>();
+ private Blog mBlog;
+ private BlogIdentifier mBlogIdentifier;
+ private GenericCallback mCallback;
+
+ public RefreshBlogContentTask(Blog blog, GenericCallback callback) {
+ if (blog == null) {
+ cancel(true);
+ return;
+ }
+
+ mBlogIdentifier = new BlogIdentifier(blog.getUrl(), blog.getRemoteBlogId());
+ if (refreshedBlogs.contains(mBlogIdentifier)) {
+ cancel(true);
+ } else {
+ refreshedBlogs.add(mBlogIdentifier);
+ }
+
+ mBlog = blog;
+ mCallback = callback;
+ }
+
+ private void updateBlogAdmin(Map<String, Object> userInfos) {
+ if (userInfos.containsKey("roles") && ( userInfos.get("roles") instanceof Object[])) {
+ boolean isAdmin = false;
+ Object[] userRoles = (Object[])userInfos.get("roles");
+ for (int i = 0; i < userRoles.length; i++) {
+ if (userRoles[i].toString().equals("administrator")) {
+ isAdmin = true;
+ break;
+ }
+ }
+ if (mBlog.bsetAdmin(isAdmin)) {
+ WordPress.wpDB.saveBlog(mBlog);
+ EventBus.getDefault().post(new CoreEvents.BlogListChanged());
+ }
+ }
+ }
+
+ @Override
+ protected Boolean doInBackground(Boolean... params) {
+ boolean commentsOnly = params[0];
+ XMLRPCClientInterface client = XMLRPCFactory.instantiate(mBlog.getUri(), mBlog.getHttpuser(),
+ mBlog.getHttppassword());
+
+ boolean alreadyTrackedAsJetpackBlog = mBlog.isJetpackPowered();
+
+ if (!commentsOnly) {
+ // check the WP number if self-hosted
+ Map<String, String> hPost = ApiHelper.blogOptionsXMLRPCParameters;
+
+ Object[] vParams = {mBlog.getRemoteBlogId(),
+ mBlog.getUsername(),
+ mBlog.getPassword(),
+ hPost};
+ Object versionResult = null;
+ try {
+ versionResult = client.call(Method.GET_OPTIONS, vParams);
+ } catch (ClassCastException cce) {
+ setError(ErrorType.INVALID_RESULT, cce.getMessage(), cce);
+ return false;
+ } catch (Exception e) {
+ setError(ErrorType.NETWORK_XMLRPC, e.getMessage(), e);
+ return false;
+ }
+
+ if (versionResult != null) {
+ Map<?, ?> blogOptions = (HashMap<?, ?>) versionResult;
+ ApiHelper.updateBlogOptions(mBlog, blogOptions);
+ }
+
+ if (mBlog.isJetpackPowered() && !alreadyTrackedAsJetpackBlog) {
+ // blog just added to the app, or the value of jetpack_client_id has just changed
+ AnalyticsUtils.trackWithBlogDetails(AnalyticsTracker.Stat.SIGNED_INTO_JETPACK, mBlog);
+ }
+
+ // get theme post formats
+ new GetPostFormatsTask().execute(mBlog);
+
+ //Update Stats widgets if necessary
+ String currentBlogID = String.valueOf(mBlog.getRemoteBlogId());
+ if (StatsWidgetProvider.isBlogDisplayedInWidget(mBlog.getRemoteBlogId())) {
+ AppLog.d(AppLog.T.STATS, "The blog with remoteID " + currentBlogID + " is NOT displayed in a widget. Blog Refresh Task doesn't call an update of the widget.");
+ String currentDate = StatsUtils.getCurrentDateTZ(mBlog.getLocalTableBlogId());
+ StatsWidgetProvider.enqueueStatsRequestForBlog(WordPress.getContext(), currentBlogID, currentDate);
+ }
+ }
+
+ // Check if user is an admin
+ Object[] userParams = {mBlog.getRemoteBlogId(), mBlog.getUsername(), mBlog.getPassword()};
+ try {
+ Map<String, Object> userInfos = (HashMap<String, Object>) client.call(Method.GET_PROFILE, userParams);
+ updateBlogAdmin(userInfos);
+ } catch (ClassCastException cce) {
+ setError(ErrorType.INVALID_RESULT, cce.getMessage(), cce);
+ return false;
+ } catch (XMLRPCException e) {
+ setError(ErrorType.NETWORK_XMLRPC, e.getMessage(), e);
+ } catch (IOException e) {
+ setError(ErrorType.NETWORK_XMLRPC, e.getMessage(), e);
+ } catch (XmlPullParserException e) {
+ setError(ErrorType.NETWORK_XMLRPC, e.getMessage(), e);
+ }
+
+ // refresh the comments
+ Map<String, Object> hPost = new HashMap<String, Object>();
+ hPost.put("number", 30);
+ Object[] commentParams = {mBlog.getRemoteBlogId(), mBlog.getUsername(),
+ mBlog.getPassword(), hPost};
+ try {
+ ApiHelper.refreshComments(mBlog, commentParams, new DatabasePersistCallback() {
+ @Override
+ public void onDataReadyToSave(List list) {
+ int localBlogId = mBlog.getLocalTableBlogId();
+ CommentTable.deleteCommentsForBlog(localBlogId);
+ CommentTable.saveComments(localBlogId, (CommentList)list);
+ }
+ });
+ } catch (Exception e) {
+ setError(ErrorType.NETWORK_XMLRPC, e.getMessage(), e);
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ protected void onPostExecute(Boolean success) {
+ if (mCallback != null) {
+ if (success) {
+ mCallback.onSuccess();
+ } else {
+ mCallback.onFailure(mErrorType, mErrorMessage, mThrowable);
+ }
+ }
+ refreshedBlogs.remove(mBlogIdentifier);
+ }
+ }
+
+ /**
+ * request deleted comments for passed blog and remove them from local db
+ * @param blog blog to check
+ * @return count of comments that were removed from db
+ */
+ public static int removeDeletedComments(Blog blog) {
+ if (blog == null) {
+ return 0;
+ }
+
+ XMLRPCClientInterface client = XMLRPCFactory.instantiate(
+ blog.getUri(),
+ blog.getHttpuser(),
+ blog.getHttppassword());
+
+ Map<String, Object> hPost = new HashMap<String, Object>();
+ hPost.put("status", "trash");
+
+ Object[] params = { blog.getRemoteBlogId(),
+ blog.getUsername(),
+ blog.getPassword(),
+ hPost };
+
+ int numDeleted = 0;
+ try {
+ Object[] result = (Object[]) client.call(Method.GET_COMMENTS, params);
+ if (result == null || result.length == 0) {
+ return 0;
+ }
+ Map<?, ?> contentHash;
+ for (Object aComment : result) {
+ contentHash = (Map<?, ?>) aComment;
+ long commentId = Long.parseLong(contentHash.get("comment_id").toString());
+ if (CommentTable.deleteComment(blog.getLocalTableBlogId(), commentId))
+ numDeleted++;
+ }
+ if (numDeleted > 0) {
+ AppLog.d(T.COMMENTS, String.format("removed %d deleted comments", numDeleted));
+ }
+ } catch (XMLRPCException e) {
+ AppLog.e(T.COMMENTS, e);
+ } catch (IOException e) {
+ AppLog.e(T.COMMENTS, e);
+ } catch (XmlPullParserException e) {
+ AppLog.e(T.COMMENTS, e);
+ }
+
+ return numDeleted;
+ }
+
+ public static CommentList refreshComments(Blog blog, Object[] commentParams, DatabasePersistCallback dbCallback)
+ throws XMLRPCException, IOException, XmlPullParserException {
+ if (blog == null) {
+ return null;
+ }
+ XMLRPCClientInterface client = XMLRPCFactory.instantiate(blog.getUri(), blog.getHttpuser(),
+ blog.getHttppassword());
+ Object[] result;
+ result = (Object[]) client.call(Method.GET_COMMENTS, commentParams);
+
+ if (result.length == 0) {
+ return null;
+ }
+
+ Map<?, ?> contentHash;
+ long commentID, postID;
+ String authorName, content, status, authorEmail, authorURL, postTitle, pubDate;
+ java.util.Date date;
+ CommentList comments = new CommentList();
+
+ for (int ctr = 0; ctr < result.length; ctr++) {
+ contentHash = (Map<?, ?>) result[ctr];
+ content = contentHash.get("content").toString();
+ status = contentHash.get("status").toString();
+ postID = Long.parseLong(contentHash.get("post_id").toString());
+ commentID = Long.parseLong(contentHash.get("comment_id").toString());
+ authorName = contentHash.get("author").toString();
+ authorURL = contentHash.get("author_url").toString();
+ authorEmail = contentHash.get("author_email").toString();
+ postTitle = contentHash.get("post_title").toString();
+ date = (java.util.Date) contentHash.get("date_created_gmt");
+ pubDate = DateTimeUtils.iso8601FromDate(date);
+
+ Comment comment = new Comment(
+ postID,
+ commentID,
+ authorName,
+ pubDate,
+ content,
+ status,
+ postTitle,
+ authorURL,
+ authorEmail,
+ null);
+
+ comments.add(comment);
+ }
+
+ if (dbCallback != null){
+ dbCallback.onDataReadyToSave(comments);
+ }
+
+ return comments;
+ }
+
+ /**
+ * Delete a single post or page via XML-RPC API parameters follow those of FetchSinglePostTask
+ */
+ public static class DeleteSinglePostTask extends HelperAsyncTask<Object, Boolean, Boolean> {
+
+ @Override
+ protected Boolean doInBackground(Object... arguments) {
+ Blog blog = (Blog) arguments[0];
+ if (blog == null) {
+ return false;
+ }
+
+ String postId = (String) arguments[1];
+ boolean isPage = (Boolean) arguments[2];
+ XMLRPCClientInterface client = XMLRPCFactory.instantiate(blog.getUri(), blog.getHttpuser(),
+ blog.getHttppassword());
+ Object[] params = {blog.getRemoteBlogId(), blog.getUsername(), blog.getPassword(), postId};
+ try {
+ client.call(isPage ? Method.DELETE_PAGE : Method.DELETE_POST, params);
+ return true;
+ } catch (XMLRPCException | IOException | XmlPullParserException e) {
+ mErrorMessage = e.getMessage();
+ return false;
+ }
+ }
+ }
+
+ public static class SyncMediaLibraryTask extends HelperAsyncTask<java.util.List<?>, Void, Integer> {
+ public interface Callback extends GenericErrorCallback {
+ public void onSuccess(int results);
+ }
+
+ private Callback mCallback;
+ private int mOffset;
+ private Filter mFilter;
+
+ public SyncMediaLibraryTask(int offset, Filter filter, Callback callback) {
+ mOffset = offset;
+ mCallback = callback;
+ mFilter = filter;
+ }
+
+ @Override
+ protected Integer doInBackground(List<?>... params) {
+ List<?> arguments = params[0];
+ WordPress.currentBlog = (Blog) arguments.get(0);
+ Blog blog = WordPress.currentBlog;
+ if (blog == null) {
+ setError(ErrorType.INVALID_CURRENT_BLOG, "ApiHelper - current blog is null");
+ return 0;
+ }
+
+ String blogId = String.valueOf(blog.getLocalTableBlogId());
+ XMLRPCClientInterface client = XMLRPCFactory.instantiate(blog.getUri(), blog.getHttpuser(),
+ blog.getHttppassword());
+ Map<String, Object> filter = new HashMap<String, Object>();
+ filter.put("number", 50);
+ filter.put("offset", mOffset);
+
+ if (mFilter == Filter.IMAGES) {
+ filter.put("mime_type","image/*");
+ } else if(mFilter == Filter.UNATTACHED) {
+ filter.put("parent_id", 0);
+ }
+
+ Object[] apiParams = {blog.getRemoteBlogId(), blog.getUsername(), blog.getPassword(),
+ filter};
+
+ Object[] results = null;
+ try {
+ results = (Object[]) client.call(Method.GET_MEDIA_LIBRARY, apiParams);
+ } catch (ClassCastException cce) {
+ setError(ErrorType.INVALID_RESULT, cce.getMessage(), cce);
+ return 0;
+ } catch (XMLRPCException e) {
+ prepareErrorMessage(e);
+ return 0;
+ } catch (IOException e) {
+ prepareErrorMessage(e);
+ return 0;
+ } catch (XmlPullParserException e) {
+ prepareErrorMessage(e);
+ return 0;
+ }
+
+ if (blogId == null) {
+ setError(ErrorType.INVALID_CURRENT_BLOG, "Invalid blogId");
+ return 0;
+ }
+
+ if (results == null) {
+ setError(ErrorType.INVALID_RESULT, "Invalid blogId");
+ return 0;
+ }
+
+ Map<?, ?> resultMap;
+ // results returned, so mark everything existing to deleted
+ // since offset is 0, we are doing a full refresh
+ if (mOffset == 0) {
+ WordPress.wpDB.setMediaFilesMarkedForDeleted(blogId);
+ }
+ for (Object result : results) {
+ resultMap = (Map<?, ?>) result;
+ boolean isDotCom = (WordPress.getCurrentBlog() != null && WordPress.getCurrentBlog().isDotcomFlag());
+ MediaFile mediaFile = new MediaFile(blogId, resultMap, isDotCom);
+ WordPress.wpDB.saveMediaFile(mediaFile);
+ }
+ WordPress.wpDB.deleteFilesMarkedForDeleted(blogId);
+ return results.length;
+ }
+
+ private void prepareErrorMessage(Exception e) {
+ // user does not have permission to view media gallery
+ if (e.getMessage() != null && e.getMessage().contains("401")) {
+ setError(ErrorType.NO_UPLOAD_FILES_CAP, e.getMessage(), e);
+ } else {
+ setError(ErrorType.NETWORK_XMLRPC, e.getMessage(), e);
+ }
+ }
+
+ @Override
+ protected void onPostExecute(Integer result) {
+ if (mCallback != null) {
+ if (mErrorType == ErrorType.NO_ERROR) {
+ mCallback.onSuccess(result);
+ } else {
+ mCallback.onFailure(mErrorType, mErrorMessage, mThrowable);
+ }
+ }
+ }
+ }
+
+ public static class EditMediaItemTask extends HelperAsyncTask<List<?>, Void, Boolean> {
+ private GenericCallback mCallback;
+ private String mMediaId;
+ private String mTitle;
+ private String mDescription;
+ private String mCaption;
+
+ public EditMediaItemTask(String mediaId, String title, String description, String caption,
+ GenericCallback callback) {
+ mMediaId = mediaId;
+ mCallback = callback;
+ mTitle = title;
+ mCaption = caption;
+ mDescription = description;
+ }
+ @Override
+ protected Boolean doInBackground(List<?>... params) {
+ List<?> arguments = params[0];
+ WordPress.currentBlog = (Blog) arguments.get(0);
+ Blog blog = WordPress.currentBlog;
+
+ if (blog == null) {
+ setError(ErrorType.INVALID_CURRENT_BLOG, "ApiHelper - current blog is null");
+ return null;
+ }
+ XMLRPCClientInterface client = XMLRPCFactory.instantiate(blog.getUri(), blog.getHttpuser(),
+ blog.getHttppassword());
+ Map<String, Object> contentStruct = new HashMap<String, Object>();
+ contentStruct.put("post_title", mTitle);
+ contentStruct.put("post_content", mDescription);
+ contentStruct.put("post_excerpt", mCaption);
+
+ Object[] apiParams = {
+ blog.getRemoteBlogId(),
+ blog.getUsername(),
+ blog.getPassword(),
+ mMediaId,
+ contentStruct
+ };
+
+ Boolean result = null;
+ try {
+ result = (Boolean) client.call(Method.EDIT_POST, apiParams);
+ } catch (ClassCastException cce) {
+ setError(ErrorType.INVALID_RESULT, cce.getMessage(), cce);
+ } catch (XMLRPCException e) {
+ setError(ErrorType.NETWORK_XMLRPC, e.getMessage(), e);
+ } catch (IOException e) {
+ setError(ErrorType.NETWORK_XMLRPC, e.getMessage(), e);
+ } catch (XmlPullParserException e) {
+ setError(ErrorType.NETWORK_XMLRPC, e.getMessage(), e);
+ }
+
+ return result;
+ }
+
+ @Override
+ protected void onPostExecute(Boolean result) {
+ if (mCallback != null) {
+ if (mErrorType == ErrorType.NO_ERROR) {
+ mCallback.onSuccess();
+ } else {
+ mCallback.onFailure(mErrorType, mErrorMessage, mThrowable);
+ }
+ }
+ }
+ }
+
+ public static class GetMediaItemTask extends HelperAsyncTask<List<?>, Void, MediaFile> {
+ public interface Callback extends GenericErrorCallback {
+ public void onSuccess(MediaFile results);
+ }
+ private Callback mCallback;
+ private int mMediaId;
+
+ public GetMediaItemTask(int mediaId, Callback callback) {
+ mMediaId = mediaId;
+ mCallback = callback;
+ }
+
+ @Override
+ protected MediaFile doInBackground(List<?>... params) {
+ List<?> arguments = params[0];
+ WordPress.currentBlog = (Blog) arguments.get(0);
+ Blog blog = WordPress.currentBlog;
+ if (blog == null) {
+ setError(ErrorType.INVALID_CURRENT_BLOG, "ApiHelper - current blog is null");
+ return null;
+ }
+
+ String blogId = String.valueOf(blog.getLocalTableBlogId());
+
+ XMLRPCClientInterface client = XMLRPCFactory.instantiate(blog.getUri(), blog.getHttpuser(),
+ blog.getHttppassword());
+ Object[] apiParams = {
+ blog.getRemoteBlogId(),
+ blog.getUsername(),
+ blog.getPassword(),
+ mMediaId
+ };
+ Map<?, ?> results = null;
+ try {
+ results = (Map<?, ?>) client.call(Method.GET_MEDIA_ITEM, apiParams);
+ } catch (ClassCastException cce) {
+ setError(ErrorType.INVALID_RESULT, cce.getMessage(), cce);
+ } catch (XMLRPCException e) {
+ setError(ErrorType.NETWORK_XMLRPC, e.getMessage(), e);
+ } catch (IOException e) {
+ setError(ErrorType.NETWORK_XMLRPC, e.getMessage(), e);
+ } catch (XmlPullParserException e) {
+ setError(ErrorType.NETWORK_XMLRPC, e.getMessage(), e);
+ }
+
+ if (results != null && blogId != null) {
+ boolean isDotCom = (WordPress.getCurrentBlog() != null && WordPress.getCurrentBlog().isDotcomFlag());
+ MediaFile mediaFile = new MediaFile(blogId, results, isDotCom);
+ WordPress.wpDB.saveMediaFile(mediaFile);
+ return mediaFile;
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ protected void onPostExecute(MediaFile result) {
+ if (mCallback != null) {
+ if (result != null) {
+ mCallback.onSuccess(result);
+ } else {
+ mCallback.onFailure(mErrorType, mErrorMessage, mThrowable);
+ }
+ }
+ }
+ }
+
+ public static class UploadMediaTask extends HelperAsyncTask<List<?>, Void, Map<?, ?>> {
+ public interface Callback extends GenericErrorCallback {
+ void onSuccess(String remoteId, String remoteUrl, String secondaryId);
+ void onProgressUpdate(float progress);
+ }
+ private Callback mCallback;
+ private Context mContext;
+ private MediaFile mMediaFile;
+
+ public UploadMediaTask(Context applicationContext, MediaFile mediaFile,
+ Callback callback) {
+ mContext = applicationContext;
+ mMediaFile = mediaFile;
+ mCallback = callback;
+ }
+
+ @Override
+ protected Map<?, ?> doInBackground(List<?>... params) {
+ List<?> arguments = params[0];
+ WordPress.currentBlog = (Blog) arguments.get(0);
+ Blog blog = WordPress.currentBlog;
+
+ if (blog == null) {
+ setError(ErrorType.INVALID_CURRENT_BLOG, "current blog is null");
+ return null;
+ }
+
+ final XMLRPCClientInterface client = XMLRPCFactory.instantiate(blog.getUri(), blog.getHttpuser(),
+ blog.getHttppassword());
+
+ Map<String, Object> data = new HashMap<String, Object>();
+ data.put("name", mMediaFile.getFileName());
+ data.put("type", mMediaFile.getMimeType());
+ data.put("bits", mMediaFile);
+ data.put("overwrite", true);
+
+ Object[] apiParams = {
+ blog.getRemoteBlogId(),
+ blog.getUsername(),
+ blog.getPassword(),
+ data
+ };
+
+ final File tempFile = getTempFile(mContext);
+
+ if (client instanceof XMLRPCClient) {
+ ((XMLRPCClient) client).setOnBytesUploadedListener(new XMLRPCClient.OnBytesUploadedListener() {
+ @Override
+ public void onBytesUploaded(long uploadedBytes) {
+ if (isCancelled()) {
+ // Stop the upload if the task has been cancelled
+ ((XMLRPCClient) client).cancel();
+ }
+
+ if (tempFile == null || tempFile.length() == 0) {
+ return;
+ }
+
+ float fractionUploaded = uploadedBytes / (float) tempFile.length();
+ mCallback.onProgressUpdate(fractionUploaded);
+ }
+ });
+ }
+
+ if (mContext == null) {
+ return null;
+ }
+
+ Map<?, ?> resultMap;
+ try {
+ resultMap = (HashMap<?, ?>) client.call(Method.UPLOAD_FILE, apiParams, tempFile);
+ } catch (ClassCastException cce) {
+ setError(ErrorType.INVALID_RESULT, null, cce);
+ return null;
+ } catch (XMLRPCFault e) {
+ if (e.getFaultCode() == 401) {
+ setError(ErrorType.NETWORK_XMLRPC,
+ mContext.getString(R.string.media_error_no_permission_upload), e);
+ } else {
+ // getFaultString() returns the error message from the server without the "[Code 403]" part.
+ setError(ErrorType.NETWORK_XMLRPC, e.getFaultString(), e);
+ }
+ return null;
+ } catch (XMLRPCException e) {
+ setError(ErrorType.NETWORK_XMLRPC, null, e);
+ return null;
+ } catch (IOException e) {
+ setError(ErrorType.NETWORK_XMLRPC, null, e);
+ return null;
+ } catch (XmlPullParserException e) {
+ setError(ErrorType.NETWORK_XMLRPC, null, e);
+ return null;
+ }
+
+ if (resultMap != null && resultMap.containsKey("id")) {
+ return resultMap;
+ } else {
+ setError(ErrorType.INVALID_RESULT, null);
+ }
+
+ return null;
+ }
+
+ // Create a temp file for media upload
+ private File getTempFile(Context context) {
+ String tempFileName = "wp-" + System.currentTimeMillis();
+ try {
+ context.openFileOutput(tempFileName, Context.MODE_PRIVATE);
+ } catch (FileNotFoundException e) {
+ return null;
+ }
+ return context.getFileStreamPath(tempFileName);
+ }
+
+ @Override
+ protected void onPostExecute(Map<?, ?> result) {
+ if (mCallback != null) {
+ if (result != null) {
+ String remoteId = (String) result.get("id");
+ String remoteUrl = (String) result.get("url");
+ String videoPressId = (String) result.get("videopress_shortcode");
+ mCallback.onSuccess(remoteId, remoteUrl, videoPressId);
+ } else {
+ mCallback.onFailure(mErrorType, mErrorMessage, mThrowable);
+ }
+ }
+ }
+ }
+
+ public static class DeleteMediaTask extends HelperAsyncTask<List<?>, Void, Void> {
+ private GenericCallback mCallback;
+ private String mMediaId;
+
+ public DeleteMediaTask(String mediaId, GenericCallback callback) {
+ mMediaId = mediaId;
+ mCallback = callback;
+ }
+
+ @Override
+ protected Void doInBackground(List<?>... params) {
+ List<?> arguments = params[0];
+ Blog blog = (Blog) arguments.get(0);
+
+ if (blog == null) {
+ setError(ErrorType.INVALID_CONTEXT, "ApiHelper - invalid blog");
+ return null;
+ }
+
+ XMLRPCClientInterface client = XMLRPCFactory.instantiate(blog.getUri(), blog.getHttpuser(),
+ blog.getHttppassword());
+ Object[] apiParams = new Object[]{blog.getRemoteBlogId(), blog.getUsername(),
+ blog.getPassword(), mMediaId};
+
+ try {
+ if (client != null) {
+ Boolean result = (Boolean) client.call(Method.DELETE_POST, apiParams);
+ if (!result) {
+ setError(ErrorType.INVALID_RESULT, "wp.deletePost returned false");
+ }
+ }
+ } catch (ClassCastException cce) {
+ setError(ErrorType.INVALID_RESULT, cce.getMessage(), cce);
+ } catch (XMLRPCException e) {
+ setError(ErrorType.NETWORK_XMLRPC, e.getMessage(), e);
+ } catch (IOException e) {
+ setError(ErrorType.NETWORK_XMLRPC, e.getMessage(), e);
+ } catch (XmlPullParserException e) {
+ setError(ErrorType.NETWORK_XMLRPC, e.getMessage(), e);
+ }
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Void v) {
+ if (mCallback != null) {
+ if (mErrorType == ErrorType.NO_ERROR) {
+ mCallback.onSuccess();
+ } else {
+ mCallback.onFailure(mErrorType, mErrorMessage, mThrowable);
+ }
+ }
+ }
+ }
+
+ public static class GetFeatures extends AsyncTask<List<?>, Void, FeatureSet> {
+ public interface Callback {
+ void onResult(FeatureSet featureSet);
+ }
+
+ private Callback mCallback;
+
+ public GetFeatures() {
+ }
+
+ public GetFeatures(Callback callback) {
+ mCallback = callback;
+ }
+
+ public FeatureSet doSynchronously(List<?>... params) {
+ return doInBackground(params);
+ }
+
+ @Override
+ protected FeatureSet doInBackground(List<?>... params) {
+ List<?> arguments = params[0];
+ Blog blog = (Blog) arguments.get(0);
+
+ if (blog == null)
+ return null;
+
+ XMLRPCClientInterface client = XMLRPCFactory.instantiate(blog.getUri(), blog.getHttpuser(),
+ blog.getHttppassword());
+
+ Object[] apiParams = new Object[] {
+ blog.getRemoteBlogId(),
+ blog.getUsername(),
+ blog.getPassword(),
+ };
+
+ Map<?, ?> resultMap = null;
+ try {
+ resultMap = (HashMap<?, ?>) client.call(Method.WPCOM_GET_FEATURES, apiParams);
+ } catch (ClassCastException cce) {
+ AppLog.e(T.API, "wpcom.getFeatures error", cce);
+ } catch (XMLRPCException e) {
+ AppLog.e(T.API, "wpcom.getFeatures error", e);
+ } catch (IOException e) {
+ AppLog.e(T.API, "wpcom.getFeatures error", e);
+ } catch (XmlPullParserException e) {
+ AppLog.e(T.API, "wpcom.getFeatures error", e);
+ }
+
+ if (resultMap != null) {
+ return new FeatureSet(blog.getRemoteBlogId(), resultMap);
+ }
+
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(FeatureSet result) {
+ if (mCallback != null)
+ mCallback.onResult(result);
+ }
+
+ }
+
+ /**
+ * Synchronous method to fetch the String content at the specified HTTP URL.
+ *
+ * @param stringUrl URL to fetch contents for.
+ * @return content of the resource, or null if URL was invalid or resource could not be retrieved.
+ */
+ public static String getResponse(final String stringUrl) throws SSLHandshakeException, TimeoutError, TimeoutException {
+ return getResponse(stringUrl, 0);
+ }
+
+ private static String getRedirectURL(String oldURL, NetworkResponse networkResponse) {
+ if (networkResponse.headers != null && networkResponse.headers.containsKey("Location")) {
+ String newURL = networkResponse.headers.get("Location");
+ // Relative URL
+ if (newURL != null && newURL.startsWith("/")) {
+ Uri oldUri = Uri.parse(oldURL);
+ if (oldUri.getScheme() == null || oldUri.getAuthority() == null) {
+ return null;
+ }
+ return oldUri.getScheme() + "://" + oldUri.getAuthority() + newURL;
+ }
+ // Absolute URL
+ return newURL;
+ }
+ return null;
+ }
+
+ public static String getResponse(final String stringUrl, int numberOfRedirects) throws SSLHandshakeException, TimeoutError, TimeoutException {
+ RequestFuture<String> future = RequestFuture.newFuture();
+ StringRequest request = new StringRequest(stringUrl, future, future);
+ request.setRetryPolicy(new DefaultRetryPolicy(XMLRPCClient.DEFAULT_SOCKET_TIMEOUT_MS, 0, 1));
+ WordPress.requestQueue.add(request);
+ try {
+ return future.get(XMLRPCClient.DEFAULT_SOCKET_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ AppLog.e(T.API, e);
+ } catch (ExecutionException e) {
+ if (e.getCause() != null && e.getCause() instanceof RedirectError) {
+ // Maximum 5 redirects or die
+ if (numberOfRedirects > 5) {
+ AppLog.e(T.API, "Maximum of 5 redirects reached, aborting.", e);
+ return null;
+ }
+ // Follow redirect
+ RedirectError re = (RedirectError) e.getCause();
+ if (re.networkResponse != null) {
+ String newURL = getRedirectURL(stringUrl, re.networkResponse);
+ if (newURL == null) {
+ AppLog.e(T.API, "Invalid server response", e);
+ return null;
+ }
+ // Abort redirect if old URL was HTTPS and not the new one
+ if (URLUtil.isHttpsUrl(stringUrl) && !URLUtil.isHttpsUrl(newURL)) {
+ AppLog.e(T.API, "Redirect from HTTPS to HTTP not allowed.", e);
+ return null;
+ }
+ // Retry getResponse
+ AppLog.i(T.API, "Follow redirect from " + stringUrl + " to " + newURL);
+ return getResponse(newURL, numberOfRedirects + 1);
+ }
+ } else if (e.getCause() != null && e.getCause() instanceof com.android.volley.TimeoutError) {
+ AppLog.e(T.API, e);
+ throw (com.android.volley.TimeoutError) e.getCause();
+ } else {
+ AppLog.e(T.API, e);
+ }
+ }
+ return null;
+ }
+
+ /*
+ * fetches a single post saves it to the db - note that this should NOT be called from main thread
+ */
+ public static boolean updateSinglePost(int localBlogId, String remotePostId, boolean isPage) {
+ Blog blog = WordPress.getBlog(localBlogId);
+ if (blog == null || TextUtils.isEmpty(remotePostId)) {
+ return false;
+ }
+
+ XMLRPCClientInterface client = XMLRPCFactory.instantiate(
+ blog.getUri(),
+ blog.getHttpuser(),
+ blog.getHttppassword());
+
+ Object[] apiParams;
+ if (isPage) {
+ apiParams = new Object[]{
+ blog.getRemoteBlogId(),
+ remotePostId,
+ blog.getUsername(),
+ blog.getPassword()
+ };
+ } else {
+ apiParams = new Object[]{
+ remotePostId,
+ blog.getUsername(),
+ blog.getPassword()
+ };
+ }
+
+ try {
+ Object result = client.call(isPage ? Method.GET_PAGE : "metaWeblog.getPost", apiParams);
+
+ if (result != null && result instanceof Map) {
+ Map postMap = (HashMap) result;
+ List<Map<?, ?>> postsList = new ArrayList<>();
+ postsList.add(postMap);
+
+ WordPress.wpDB.savePosts(postsList, localBlogId, isPage, true);
+ return true;
+ } else {
+ return false;
+ }
+
+ } catch (XMLRPCException | IOException | XmlPullParserException e) {
+ AppLog.e(AppLog.T.POSTS, e);
+ return false;
+ }
+ }
+
+ public static boolean editComment(Blog blog, Comment comment, CommentStatus newStatus) {
+ if (blog == null) {
+ return false;
+ }
+
+ XMLRPCClientInterface client = XMLRPCFactory.instantiate(blog.getUri(), blog.getHttpuser(),
+ blog.getHttppassword());
+
+ Map<String, String> postHash = new HashMap<>();
+ postHash.put("status", CommentStatus.toString(newStatus));
+ postHash.put("content", comment.getCommentText());
+ postHash.put("author", comment.getAuthorName());
+ postHash.put("author_url", comment.getAuthorUrl());
+ postHash.put("author_email", comment.getAuthorEmail());
+
+ Object[] params = { blog.getRemoteBlogId(),
+ blog.getUsername(),
+ blog.getPassword(),
+ Long.toString(comment.commentID),
+ postHash};
+
+ try {
+ Object result = client.call(Method.EDIT_COMMENT, params);
+ return (result != null && Boolean.parseBoolean(result.toString()));
+ } catch (XMLRPCFault xmlrpcFault) {
+ if (xmlrpcFault.getFaultCode() == 500) {
+ // let's check whether the comment is already marked as _newStatus_
+ CommentStatus remoteStatus = getCommentStatus(blog, comment);
+ if (remoteStatus != null && remoteStatus.equals(newStatus)) {
+ // Happy days! Remote is already marked as the desired status
+ return true;
+ }
+ }
+ AppLog.e(T.COMMENTS, "Error while editing comment", xmlrpcFault);
+ } catch (XMLRPCException e) {
+ AppLog.e(T.COMMENTS, "Error while editing comment", e);
+ } catch (IOException e) {
+ AppLog.e(T.COMMENTS, "Error while editing comment", e);
+ } catch (XmlPullParserException e) {
+ AppLog.e(T.COMMENTS, "Error while editing comment", e);
+ }
+
+ return false;
+ }
+
+ /**
+ * Fetches the status of a comment
+ * @param blog the blog the comment is in
+ * @param comment the comment to fetch its status
+ * @return the status of the comment on the server, null if error
+ */
+ public static @Nullable CommentStatus getCommentStatus(Blog blog, Comment comment) {
+ if (blog == null || comment == null) {
+ return null;
+ }
+
+ XMLRPCClientInterface client = XMLRPCFactory.instantiate(blog.getUri(), blog.getHttpuser(),
+ blog.getHttppassword());
+
+ Object[] params = { blog.getRemoteBlogId(),
+ blog.getUsername(),
+ blog.getPassword(),
+ Long.toString(comment.commentID)};
+
+ try {
+ Map<?, ?> contentHash = (Map<?, ?>) client.call(Method.GET_COMMENT, params);
+ final Object status = contentHash.get("status");
+ return status == null ? null : CommentStatus.fromString(status.toString());
+ } catch (XMLRPCException e) {
+ AppLog.e(T.COMMENTS, "Error while getting comment", e);
+ } catch (IOException e) {
+ AppLog.e(T.COMMENTS, "Error while getting comment", e);
+ } catch (XmlPullParserException e) {
+ AppLog.e(T.COMMENTS, "Error while getting comment", e);
+ }
+
+ return null;
+ }
+}