diff options
Diffstat (limited to 'WordPress/src/main/java/org/xmlrpc/android/ApiHelper.java')
-rw-r--r-- | WordPress/src/main/java/org/xmlrpc/android/ApiHelper.java | 1189 |
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; + } +} |