| 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; |
| } |
| } |