package org.wordpress.android.task; import java.io.DataInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Random; import java.util.regex.Matcher; import java.util.regex.Pattern; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.res.Resources; import android.database.Cursor; import android.graphics.BitmapFactory; import android.net.Uri; import android.preference.PreferenceManager; import android.provider.MediaStore.Images; import android.provider.MediaStore.Video; import android.text.Spannable; import android.text.SpannableString; import android.text.TextUtils; import android.webkit.MimeTypeMap; import com.justsystems.hpb.pad.R; import com.justsystems.hpb.pad.util.Debug; import org.xmlrpc.android.XMLRPCClient; import org.xmlrpc.android.XMLRPCException; import org.wordpress.android.WordPress; import org.wordpress.android.models.Blog; import org.wordpress.android.models.MediaFile; import org.wordpress.android.models.Post; import org.wordpress.android.models.Postable; import org.wordpress.android.util.ImageHelper; import org.wordpress.android.util.PostUploadService; import org.wordpress.android.util.WPHtml; import org.wordpress.android.util.WPImageSpan; public abstract class AbsUploadTask extends MultiAsyncTask<Post, Boolean, Boolean> { protected final static String MORE = "<!--more-->"; private PostUploadService service; protected Context context; protected String error = ""; protected boolean mediaError = false; protected NotificationManager nm; protected int notificationID; protected Notification n; protected int featuredImageID = -1; public AbsUploadTask(PostUploadService service) { this.service = service; this.context = service.getApplicationContext(); } @Override protected void onPostExecute(Boolean postUploadedSuccessfully) { if (postUploadedSuccessfully) { WordPress.postUploaded(); nm.cancel(notificationID); } else { showErrorNotifination(); } service.postUploaded(); } abstract void showErrorNotifination(); protected void showNotification(Postable post, String message) { // add the uploader to the notification bar nm = (NotificationManager) context.getSystemService("notification"); n = new Notification(R.drawable.notification_icon, message, System.currentTimeMillis()); Intent notificationIntent = createNotificationIntent(); PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, notificationIntent, Intent.FLAG_ACTIVITY_CLEAR_TOP); n.setLatestEventInfo(context, message, message, pendingIntent); notificationID = (new Random()).nextInt() + Integer.valueOf(post.getBlog().getId()); nm.notify(notificationID, n); // needs a unique id } protected abstract Intent createNotificationIntent(); protected String makeContent(Resources res, Postable postable, String content, String moreText, String postOrPage) { final boolean isMoreTextNull = moreText == null || "".equals(moreText); String descriptionContent = "", moreContent = null; String imgTags = "<img[^>]+android-uri\\s*=\\s*['\"]([^'\"]+)['\"][^>]*>"; Pattern pattern = Pattern.compile(imgTags); if (postable.isLocalDraft()) { descriptionContent = localDraftToHtml(res, postable, content, postOrPage); } else { descriptionContent = contentToHtml(postable, content, pattern); } if (!isMoreTextNull) { if (postable.isLocalDraft()) { moreContent = localDraftToHtml(res, postable, moreText, postOrPage); } else { moreContent = contentToHtml(postable, moreText, pattern); } } if (postable.getType() == Postable.TYP_POST && postable.isLocalDraft()) { // add the tagline SharedPreferences prefs = PreferenceManager .getDefaultSharedPreferences(context); if (prefs.getBoolean("wp_pref_signature_enabled", false)) { String tagline = prefs .getString("wp_pref_post_signature", null); if (tagline != null) { String tag = "\n\n<span class=\"post_sig\">" + tagline + "</span>\n\n"; if (isMoreTextNull) descriptionContent += tag; else moreContent += tag; } } } if (!isMoreTextNull) { descriptionContent = descriptionContent.trim() + MORE + moreContent; if (postable.getType() == Postable.TYP_PAGE || postable.getType() == Postable.TYP_POST) { ((Post) postable).setMt_text_more(""); } } // get rid of the p and br tags that the editor adds. if (postable.isLocalDraft()) { descriptionContent = descriptionContent.replace("<p>", "") .replace("</p>", "\n").replace("<br>", ""); } // gets rid of the weird character android inserts after images descriptionContent = descriptionContent.replaceAll("\uFFFC", ""); return descriptionContent; } private String localDraftToHtml(Resources res, Postable postable, String content, String type) { Spannable s = (Spannable) WPHtml.fromHtml(content, context, postable); WPImageSpan[] click_spans = s .getSpans(0, s.length(), WPImageSpan.class); if (click_spans.length != 0) { for (int i = 0; i < click_spans.length; i++) { String prompt = res.getText(R.string.uploading_media_item) + String.valueOf(i + 1); n.setLatestEventInfo(context, res.getText(R.string.uploading) + " " + type, prompt, n.contentIntent); nm.notify(notificationID, n); WPImageSpan wpIS = click_spans[i]; int start = s.getSpanStart(wpIS); int end = s.getSpanEnd(wpIS); MediaFile mf = new MediaFile(); mf.setPostID(postable.getId()); mf.setTitle(wpIS.getTitle()); mf.setCaption(wpIS.getCaption()); mf.setDescription(wpIS.getDescription()); mf.setFeatured(wpIS.isFeatured()); mf.setFeaturedInPost(wpIS.isFeaturedInPost()); mf.setFileName(wpIS.getImageSource().toString()); mf.setHorizontalAlignment(wpIS.getHorizontalAlignment()); mf.setWidth(wpIS.getWidth()); String imgHTML = uploadMediaFile(mf, postable); if (imgHTML != null) { SpannableString ss = new SpannableString(imgHTML); s.setSpan(ss, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); s.removeSpan(wpIS); } else { s.removeSpan(wpIS); mediaError = true; } } } return WPHtml.toHtml(s); } private String contentToHtml(Postable postable, String content, Pattern pattern) { Matcher matcher = pattern.matcher(content); List<String> imageTags = new ArrayList<String>(); while (matcher.find()) { imageTags.add(matcher.group()); } for (String tag : imageTags) { Pattern p = Pattern.compile("android-uri=\"([^\"]+)\""); Matcher m = p.matcher(tag); String imgPath = ""; if (m.find()) { continue; } imgPath = m.group(1); if (imgPath.equals("")) { continue; } MediaFile mf = WordPress.wpDB.getMediaFile(imgPath, Long.parseLong(postable.getPostId())); if (mf == null) { continue; } String imgHTML = uploadMediaFile(mf, postable); if (imgHTML != null) { content = content.replace(tag, imgHTML); } else { content = content.replace(tag, ""); mediaError = true; } } return content; } private String uploadMediaFile(MediaFile mf, Postable post) { final Blog blog = post.getBlog(); String content = ""; // image variables String finalThumbnailUrl = null; String finalImageUrl = null; // check for image, and upload it if (mf.getFileName() == null) { return ""; } String curImagePath = ""; curImagePath = mf.getFileName(); if (curImagePath.contains("video")) { // upload the video XMLRPCClient client = new XMLRPCClient(blog.getUrl(), blog.getHttpuser(), blog.getHttppassword()); content = doVideo(mf, client, post); if (content == null) { return content; } } else { curImagePath = mf.getFileName(); Uri imageUri = Uri.parse(curImagePath); File jpeg = null; String mimeType = "", orientation = "", path = ""; if (imageUri.toString().contains("content:")) { String[] projection; Uri imgPath; projection = new String[] { Images.Media._ID, Images.Media.DATA, Images.Media.MIME_TYPE, Images.Media.ORIENTATION }; imgPath = imageUri; Cursor cur = context.getContentResolver().query(imgPath, projection, null, null, null); String thumbData = ""; if (cur.moveToFirst()) { int dataColumn, mimeTypeColumn, orientationColumn; dataColumn = cur.getColumnIndex(Images.Media.DATA); mimeTypeColumn = cur.getColumnIndex(Images.Media.MIME_TYPE); orientationColumn = cur .getColumnIndex(Images.Media.ORIENTATION); orientation = cur.getString(orientationColumn); thumbData = cur.getString(dataColumn); mimeType = cur.getString(mimeTypeColumn); jpeg = new File(thumbData); path = thumbData; mf.setFilePath(jpeg.getPath()); } } else { // file is not in media library path = imageUri.toString().replace("file://", ""); jpeg = new File(path); String extension = MimeTypeMap.getFileExtensionFromUrl(path); if (extension != null) { MimeTypeMap mime = MimeTypeMap.getSingleton(); mimeType = mime.getMimeTypeFromExtension(extension); if (mimeType == null) mimeType = "image/jpeg"; } mf.setFilePath(path); } // check if the file exists if (jpeg == null) { error = context.getString(R.string.file_not_found); mediaError = true; return null; } ImageHelper ih = new ImageHelper(); orientation = ih.getExifOrientation(path, orientation); String imageTitle = jpeg.getName(); String resizedPictureURL = null; //We need to upload a resized version of the picture when the blog settings != original size, or when //the user has selected a smaller size for the current picture in the picture settings screen boolean shouldUploadResizedVersion = !post.getBlog() .getMaxImageWidth().equals("Original Size"); if (shouldUploadResizedVersion == false) { //check the picture settings int pictureSettingWidth = mf.getWidth(); BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeFile(path, options); int imageHeight = options.outHeight; int imageWidth = options.outWidth; int[] dimensions = { imageWidth, imageHeight }; if (dimensions[0] != 0 && dimensions[0] != pictureSettingWidth) { shouldUploadResizedVersion = true; } } if (shouldUploadResizedVersion) { byte[] bytes; byte[] finalBytes = null; try { bytes = new byte[(int) jpeg.length()]; } catch (OutOfMemoryError er) { error = context.getString(R.string.out_of_memory); mediaError = true; return null; } DataInputStream in = null; try { in = new DataInputStream(new FileInputStream(jpeg)); } catch (FileNotFoundException e) { e.printStackTrace(); } try { in.readFully(bytes); } catch (IOException e) { e.printStackTrace(); } try { in.close(); } catch (IOException e) { e.printStackTrace(); } String width = String.valueOf(mf.getWidth()); ImageHelper ih2 = new ImageHelper(); finalBytes = ih2.createThumbnail(bytes, width, orientation, false); if (finalBytes == null) { error = context.getString(R.string.out_of_memory); mediaError = true; return null; } //upload picture Map<String, Object> m = new HashMap<String, Object>(); m.put("name", imageTitle); m.put("type", mimeType); m.put("bits", finalBytes); m.put("overwrite", true); resizedPictureURL = uploadPicture(post, m, mf); if (resizedPictureURL == null) return null; } String fullsizeURL = null; //Upload the full size picture if "Original Size" is selected in settings, or if 'link to full size' is checked. if (!shouldUploadResizedVersion || post.getBlog().isFullSizeImage()) { // try to upload the image Map<String, Object> m = new HashMap<String, Object>(); m.put("name", imageTitle); m.put("type", mimeType); m.put("bits", mf); m.put("overwrite", true); fullsizeURL = uploadPicture(post, m, mf); if (fullsizeURL == null) return null; } String alignment = ""; switch (mf.getHorizontalAlignment()) { case 0: alignment = "alignnone"; break; case 1: alignment = "alignleft"; break; case 2: alignment = "aligncenter"; break; case 3: alignment = "alignright"; break; } String alignmentCSS = "class=\"" + alignment + " size-full\" "; //Check if we uploaded a featured picture that is not added to the post content (normal case) if ((fullsizeURL != null && fullsizeURL.equalsIgnoreCase("")) || (resizedPictureURL != null && resizedPictureURL .equalsIgnoreCase(""))) { return ""; //Not featured in post. Do not add to the content. } if (fullsizeURL != null && resizedPictureURL != null) { } else if (fullsizeURL == null) { fullsizeURL = resizedPictureURL; } else { resizedPictureURL = fullsizeURL; } content = content + "<a href=\"" + fullsizeURL + "\"><img title=\"" + mf.getTitle() + "\" " + alignmentCSS + "alt=\"image\" src=\"" + resizedPictureURL + "\" /></a>"; if (!mf.getCaption().equals("")) { content = String .format("[caption id=\"\" align=\"%s\" width=\"%d\" caption=\"%s\"]%s[/caption]", alignment, mf.getWidth(), TextUtils.htmlEncode(mf.getCaption()), content); } } return content; } private String doVideo(MediaFile mf, XMLRPCClient client, Postable post) { // create temp file for media upload String tempFileName = "wp-" + System.currentTimeMillis(); try { context.openFileOutput(tempFileName, Context.MODE_PRIVATE); } catch (FileNotFoundException e) { error = context.getResources() .getString(R.string.file_error_create); mediaError = true; return null; } File tempFile = context.getFileStreamPath(tempFileName); final String curImagePath = mf.getFileName(); Uri videoUri = Uri.parse(curImagePath); File fVideo = null; String mimeType = "", xRes = "", yRes = ""; if (videoUri.toString().contains("content:")) { String[] projection; Uri imgPath; projection = new String[] { Video.Media._ID, Video.Media.DATA, Video.Media.MIME_TYPE, Video.Media.RESOLUTION }; imgPath = videoUri; Cursor cur = context.getContentResolver().query(imgPath, projection, null, null, null); String thumbData = ""; if (cur.moveToFirst()) { int mimeTypeColumn, resolutionColumn, dataColumn; dataColumn = cur.getColumnIndex(Video.Media.DATA); mimeTypeColumn = cur.getColumnIndex(Video.Media.MIME_TYPE); resolutionColumn = cur.getColumnIndex(Video.Media.RESOLUTION); mf = new MediaFile(); thumbData = cur.getString(dataColumn); mimeType = cur.getString(mimeTypeColumn); fVideo = new File(thumbData); mf.setFilePath(fVideo.getPath()); String resolution = cur.getString(resolutionColumn); if (resolution != null) { String[] resx = resolution.split("x"); xRes = resx[0]; yRes = resx[1]; } else { // set the width of the video to the // thumbnail // width, else 640x480 if (!post.getBlog().getMaxImageWidth() .equals("Original Size")) { xRes = post.getBlog().getMaxImageWidth(); yRes = String.valueOf(Math.round(Integer.valueOf(post .getBlog().getMaxImageWidth()) * 0.75)); } else { xRes = "640"; yRes = "480"; } } cur.close(); } } else { // file is not in media library fVideo = new File(videoUri.toString().replace("file://", "")); } if (fVideo == null) { error = context.getResources().getString( R.string.error_media_upload) + "."; return null; } final String imageTitle = fVideo.getName(); Object[] params = createVideoParams(post, imageTitle, mimeType, mf); Object result = null; try { result = client.call("wp.uploadFile", params, tempFile); } catch (XMLRPCException e) { error = context.getResources().getString( R.string.error_media_upload) + ": " + cleanXMLRPCErrorMessage(e.getMessage()); return null; } if (result == null) { return null; } if (!(result instanceof HashMap<?, ?>)) { Debug.logd(this.getClass().toString(), "class cast " + result.getClass().toString() + " cast to hashmap"); return null; } Map<?, ?> contentHash = (HashMap<?, ?>) result; String resultURL = contentHash.get("url").toString(); if (contentHash.containsKey("videopress_shortcode")) { resultURL = contentHash.get("videopress_shortcode").toString() + "\n"; } else { resultURL = String .format("<video width=\"%s\" height=\"%s\" controls=\"controls\"><source src=\"%s\" type=\"%s\" /><a href=\"%s\">Click to view video</a>.</video>", xRes, yRes, resultURL, mimeType, resultURL); } return resultURL; } private Object[] createVideoParams(Postable post, String imageTitle, String mimeType, MediaFile mf) { // try to upload the video Map<String, Object> m = new HashMap<String, Object>(); m.put("name", imageTitle); m.put("type", mimeType); m.put("bits", mf); m.put("overwrite", true); Object[] params = { 1, post.getBlog().getUsername(), post.getBlog().getPassword(), m }; return params; } private String uploadPicture(Postable post, Map<String, Object> pictureParams, MediaFile mf) { XMLRPCClient client = new XMLRPCClient(post.getBlog().getUrl(), post .getBlog().getHttpuser(), post.getBlog().getHttppassword()); // create temp file for media upload String tempFileName = "wp-" + System.currentTimeMillis(); try { context.openFileOutput(tempFileName, Context.MODE_PRIVATE); } catch (FileNotFoundException e) { mediaError = true; error = context.getString(R.string.file_not_found); return null; } File tempFile = context.getFileStreamPath(tempFileName); Object[] params = { 1, post.getBlog().getUsername(), post.getBlog().getPassword(), pictureParams }; Object result = null; try { result = (Object) client.call("wp.uploadFile", params, tempFile); } catch (XMLRPCException e) { error = context.getResources().getString( R.string.error_media_upload) + ": " + cleanXMLRPCErrorMessage(e.getMessage()); mediaError = true; return null; } Map<?, ?> contentHash = (HashMap<?, ?>) result; String pictureURL = contentHash.get("url").toString(); if (mf.isFeatured()) { try { if (contentHash.get("id") != null) { featuredImageID = Integer.parseInt(contentHash.get("id") .toString()); if (!mf.isFeaturedInPost()) return ""; } } catch (NumberFormatException e) { e.printStackTrace(); } } return pictureURL; } protected String cleanXMLRPCErrorMessage(String message) { if (message != null) { if (message.indexOf(": ") > -1) message = message.substring(message.indexOf(": ") + 2, message.length()); if (message.indexOf("[code") > -1) message = message.substring(0, message.indexOf("[code")); return message; } else { return ""; } } }