From d68755f4fe2e7bd349d745aa05b28d4aed4f848f Mon Sep 17 00:00:00 2001 From: Pierre Date: Sat, 21 Apr 2018 14:27:12 +0200 Subject: [PATCH] Can post files without having to base64 them. --- .../client/data/helpers/APIRequestHelper.java | 128 +++++++++++++++++- .../helpers/ConversationMessagesHelper.java | 29 +++- .../client/data/models/APIFileRequest.java | 51 +++++++ .../client/data/models/APIPostData.java | 48 ++++++- .../client/data/models/APIPostFile.java | 61 +++++++++ .../client/data/models/APIRequest.java | 9 ++ .../ui/fragments/ConversationFragment.java | 10 +- 7 files changed, 320 insertions(+), 16 deletions(-) create mode 100644 app/src/main/java/org/communiquons/android/comunic/client/data/models/APIFileRequest.java create mode 100644 app/src/main/java/org/communiquons/android/comunic/client/data/models/APIPostFile.java diff --git a/app/src/main/java/org/communiquons/android/comunic/client/data/helpers/APIRequestHelper.java b/app/src/main/java/org/communiquons/android/comunic/client/data/helpers/APIRequestHelper.java index e043e22..d9a45da 100644 --- a/app/src/main/java/org/communiquons/android/comunic/client/data/helpers/APIRequestHelper.java +++ b/app/src/main/java/org/communiquons/android/comunic/client/data/helpers/APIRequestHelper.java @@ -5,6 +5,9 @@ import android.net.ConnectivityManager; import android.net.NetworkInfo; import org.communiquons.android.comunic.client.BuildConfig; +import org.communiquons.android.comunic.client.data.models.APIFileRequest; +import org.communiquons.android.comunic.client.data.models.APIPostData; +import org.communiquons.android.comunic.client.data.models.APIPostFile; import org.communiquons.android.comunic.client.data.models.APIRequest; import org.communiquons.android.comunic.client.data.models.APIResponse; @@ -16,8 +19,11 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; +import java.io.PrintWriter; import java.net.HttpURLConnection; +import java.net.MalformedURLException; import java.net.URL; +import java.net.URLConnection; import java.util.ArrayList; /** @@ -115,6 +121,126 @@ public class APIRequestHelper { return result; } + /** + * Execute an API request over the server + * + * Note : this methods is based on a StackOverflow answer: + * https://stackoverflow.com/a/33149413/3781411 + * + * @param req Information about the request + * @return The response of the server + * @throws Exception In case of failure during the connection with the API + */ + public APIResponse execPostFile(APIFileRequest req) throws Exception { + + //Add API and login tokens to the request + addAPItokens(req); + addLoginTokens(req); + + //Prepare response + APIResponse response = new APIResponse(); + HttpURLConnection conn = null; + OutputStream out; + PrintWriter writer; + + //Create unique boundary + String boundary = "===" + System.currentTimeMillis() + "==="; + String LINE_FEED = "\r\n"; + + try { + + //Initialize connection + URL url = new URL(BuildConfig.api_url + req.getRequest_uri()); + conn = (HttpURLConnection) url.openConnection(); + conn.setUseCaches(false); + conn.setDoInput(true); + conn.setDoOutput(true); + conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary); + + //Get output stream + out = conn.getOutputStream(); + writer = new PrintWriter(new OutputStreamWriter(out, "UTF-8")); + + //Append values + for(APIPostData value : req.getParameters()){ + + writer.append("--" + boundary).append(LINE_FEED); + writer.append("Content-Disposition: form-data; name=\"" + value.getEncodedKeyName() + "\"") + .append(LINE_FEED); + writer.append("Content-Type: form-data; charset=UTF-8").append( + LINE_FEED); + writer.append(LINE_FEED); + writer.append(value.getKey_value()).append(LINE_FEED); + writer.flush(); + + } + + //Append files + for(APIPostFile file : req.getFiles()){ + + String fileName = file.getFileName(); + writer.append("--" + boundary).append(LINE_FEED); + writer.append( + "Content-Disposition: form-data; name=\"" + file.getFieldName() + + "\"; filename=\"" + fileName + "\"") + .append(LINE_FEED); + writer.append( + "Content-Type: " + + URLConnection.guessContentTypeFromName(fileName)) + .append(LINE_FEED); + writer.append("Content-Transfer-Encoding: binary").append(LINE_FEED); + writer.append(LINE_FEED); + writer.flush(); + + out.write(file.getByteArray()); + out.flush(); + + writer.append(LINE_FEED); + writer.flush(); + } + + //Finish request and get response + writer.append(LINE_FEED).flush(); + writer.append("--" + boundary + "--").append(LINE_FEED); + writer.close(); + + StringBuilder responseBuffer = new StringBuilder(); + BufferedReader reader = new BufferedReader(new InputStreamReader( + conn.getInputStream())); + String line; + while ((line = reader.readLine()) != null) { + responseBuffer.append(line); + } + reader.close(); + conn.disconnect(); + + //Return the response + response.setResponse_code(conn.getResponseCode()); + response.setResponse(responseBuffer.toString()); + } + + //Malformed URL Exceptions must be fixed by dev + catch (MalformedURLException e) { + e.printStackTrace(); + throw new RuntimeException("MalformedURLException should never occur..."); + } + + catch (IOException e) { + e.printStackTrace(); + response.setResponse_code(0); + + if(req.isTryContinueOnError() && conn != null){ + response.setResponse_code(conn.getResponseCode()); + return response; + } + + //Throw an exception + throw new Exception("Could not connect to the server"); + } + + return response; + } + // Reads an InputStream and converts it to a String. private String readIt(InputStream stream) throws IOException { @@ -133,7 +259,7 @@ public class APIRequestHelper { /** * Add the API client tokens to API request object * - * @param params The request parametres to update + * @param params The request parameters to update */ private void addAPItokens(APIRequest params){ params.addString("serviceName", BuildConfig.api_service_name); diff --git a/app/src/main/java/org/communiquons/android/comunic/client/data/helpers/ConversationMessagesHelper.java b/app/src/main/java/org/communiquons/android/comunic/client/data/helpers/ConversationMessagesHelper.java index 81576db..16da5b2 100644 --- a/app/src/main/java/org/communiquons/android/comunic/client/data/helpers/ConversationMessagesHelper.java +++ b/app/src/main/java/org/communiquons/android/comunic/client/data/helpers/ConversationMessagesHelper.java @@ -1,9 +1,13 @@ package org.communiquons.android.comunic.client.data.helpers; import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.drawable.Drawable; import android.support.annotation.Nullable; import android.util.Log; +import org.communiquons.android.comunic.client.data.models.APIFileRequest; +import org.communiquons.android.comunic.client.data.models.APIPostFile; import org.communiquons.android.comunic.client.data.models.APIRequest; import org.communiquons.android.comunic.client.data.models.APIResponse; import org.communiquons.android.comunic.client.data.models.ConversationMessage; @@ -82,23 +86,36 @@ public class ConversationMessagesHelper { * * @param convID Target conversation ID * @param message The message to send - * @param image Base64 encoded image to include with the message (can be null) + * @param image Image to include with the request, as bitmap (can be null) * @return true in case of success / false else */ - public boolean sendMessage(int convID, String message, @Nullable String image){ + public boolean sendMessage(int convID, String message, @Nullable Bitmap image){ //Make an API request - APIRequest params = new APIRequest(mContext, + APIFileRequest params = new APIFileRequest(mContext, "conversations/sendMessage"); params.addString("conversationID", ""+convID); params.addString("message", message); //Include image (if any) - if(image != null) - params.addString("image", "data:image/png;base64," + image); + if(image != null) { + APIPostFile file = new APIPostFile(); + file.setFieldName("image"); + file.setFileName("conversationImage.png"); + file.setBitmap(image); + params.addFile(file); + } try { - new APIRequestHelper().exec(params); + + if(image != null){ + //Perform a POST request + new APIRequestHelper().execPostFile(params); + } + else + //Perform normal request + new APIRequestHelper().exec(params); + return true; } catch (Exception e){ e.printStackTrace(); diff --git a/app/src/main/java/org/communiquons/android/comunic/client/data/models/APIFileRequest.java b/app/src/main/java/org/communiquons/android/comunic/client/data/models/APIFileRequest.java new file mode 100644 index 0000000..fda131b --- /dev/null +++ b/app/src/main/java/org/communiquons/android/comunic/client/data/models/APIFileRequest.java @@ -0,0 +1,51 @@ +package org.communiquons.android.comunic.client.data.models; + +import android.content.Context; + +import java.util.ArrayList; + +/** + * This class handles information about the request that includes files to the server + * + * @author Pierre HUBERT + * Created by pierre on 4/21/18. + */ + +public class APIFileRequest extends APIRequest { + + /** + * The list of post files + */ + private ArrayList files; + + /** + * The class constructor + * + * @param context The context of the request + * @param uri The request URI on the server + */ + public APIFileRequest(Context context, String uri) { + super(context, uri); + + //Create files list + files = new ArrayList<>(); + } + + /** + * Add a file to the request + * + * @param file The file to add + */ + public void addFile(APIPostFile file){ + files.add(file); + } + + /** + * Get the list of files + * + * @return The list of the files to include into the request + */ + public ArrayList getFiles() { + return files; + } +} diff --git a/app/src/main/java/org/communiquons/android/comunic/client/data/models/APIPostData.java b/app/src/main/java/org/communiquons/android/comunic/client/data/models/APIPostData.java index 948132c..e817012 100644 --- a/app/src/main/java/org/communiquons/android/comunic/client/data/models/APIPostData.java +++ b/app/src/main/java/org/communiquons/android/comunic/client/data/models/APIPostData.java @@ -9,7 +9,7 @@ import java.net.URLEncoder; * Created by pierre on 10/31/17. */ -class APIPostData { +public class APIPostData { /** * The name of the key @@ -32,6 +32,52 @@ class APIPostData { key_value = value; } + /** + * Get the name of the key + * + * @return The name of key + */ + public String getKey_name() { + return key_name; + } + + /** + * Get the value associated with the ky + * + * @return The value of the key + */ + public String getKey_value() { + return key_value; + } + + /** + * Get the key name, as an encoded string + * + * @return The encoded key name + */ + public String getEncodedKeyName(){ + try { + return URLEncoder.encode(getKey_name(), "UTF-8"); + } catch (java.io.UnsupportedEncodingException e){ + e.printStackTrace(); + throw new RuntimeException("Unsupported encoding : UTF-8 !", e); + } + } + + /** + * Get the key value, as an encoded string + * + * @return The encoded key value + */ + public String getEncodedKeyValue(){ + try { + return URLEncoder.encode(getKey_value(), "UTF-8"); + } catch (java.io.UnsupportedEncodingException e){ + e.printStackTrace(); + throw new RuntimeException("Unsupported encoding : UTF-8 !", e); + } + } + /** * Get the the name and the value of the key in an encoded form * diff --git a/app/src/main/java/org/communiquons/android/comunic/client/data/models/APIPostFile.java b/app/src/main/java/org/communiquons/android/comunic/client/data/models/APIPostFile.java new file mode 100644 index 0000000..1b2f883 --- /dev/null +++ b/app/src/main/java/org/communiquons/android/comunic/client/data/models/APIPostFile.java @@ -0,0 +1,61 @@ +package org.communiquons.android.comunic.client.data.models; + +import android.graphics.Bitmap; +import android.support.annotation.NonNull; + +import java.io.ByteArrayOutputStream; + +/** + * Single file information included in a request to the API + * + * @author Pierre HUBERT + * Created by pierre on 4/21/18. + */ + +public class APIPostFile { + + //Private fields + private String fieldName; + private String fileName; + private byte[] byteArray; + + + //Set and get field name + public void setFieldName(String fieldName) { + this.fieldName = fieldName; + } + + public String getFieldName() { + return fieldName; + } + + + //Set and get file name + public void setFileName(String fileName) { + this.fileName = fileName; + } + + public String getFileName() { + return fileName; + } + + //Set and get byte array + public void setByteArray(byte[] byteArray) { + this.byteArray = byteArray; + } + + public byte[] getByteArray() { + return byteArray; + } + + /** + * Set a bitmap as the file + * + * @param bmp Bitmap to set + */ + public void setBitmap(@NonNull Bitmap bmp){ + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + bmp.compress(Bitmap.CompressFormat.PNG, 100, stream); + setByteArray(stream.toByteArray()); + } +} diff --git a/app/src/main/java/org/communiquons/android/comunic/client/data/models/APIRequest.java b/app/src/main/java/org/communiquons/android/comunic/client/data/models/APIRequest.java index 8f40ba4..672e599 100644 --- a/app/src/main/java/org/communiquons/android/comunic/client/data/models/APIRequest.java +++ b/app/src/main/java/org/communiquons/android/comunic/client/data/models/APIRequest.java @@ -112,6 +112,15 @@ public class APIRequest { return result; } + /** + * Get the list of parameters + * + * @return The list of parameters + */ + public ArrayList getParameters() { + return parameters; + } + /** * Get the context of the request * diff --git a/app/src/main/java/org/communiquons/android/comunic/client/ui/fragments/ConversationFragment.java b/app/src/main/java/org/communiquons/android/comunic/client/ui/fragments/ConversationFragment.java index d52da1f..0f098a5 100644 --- a/app/src/main/java/org/communiquons/android/comunic/client/ui/fragments/ConversationFragment.java +++ b/app/src/main/java/org/communiquons/android/comunic/client/ui/fragments/ConversationFragment.java @@ -535,14 +535,8 @@ public class ConversationFragment extends Fragment @Override protected Boolean doInBackground(Void... params) { - String message_image = null; - - //Reduce Bitmap and convert it to a base64-encoded string - if(new_message_bitmap != null) - message_image = BitmapUtils.bitmapToBase64( - BitmapUtils.reduceBitmap(new_message_bitmap, 1199, 1199)); - - return convMessHelper.sendMessage(conversation_id, message_content, message_image); + return convMessHelper.sendMessage(conversation_id, + message_content, new_message_bitmap); } @Override