CB-7707 Added multipart PluginResult (close #125)

Corresponds to cordova-js commit: a1f866606b3
This commit is contained in:
Rui Zhao 2014-10-06 12:05:06 -04:00 committed by Andrew Grieve
parent 2dcd50c11b
commit fbeb379f1b
3 changed files with 141 additions and 87 deletions

View File

@ -1,5 +1,5 @@
// Platform: android // Platform: android
// 8ca0f3b2b87e0759c5236b91c80f18438544409c // 1fc2526faa6197e1637ecb48ebe0f876f008ba0f
/* /*
Licensed to the Apache Software Foundation (ASF) under one Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file or more contributor license agreements. See the NOTICE file
@ -263,11 +263,7 @@ var cordova = {
* Called by native code when returning successful result from an action. * Called by native code when returning successful result from an action.
*/ */
callbackSuccess: function(callbackId, args) { callbackSuccess: function(callbackId, args) {
try { cordova.callbackFromNative(callbackId, true, args.status, [args.message], args.keepCallback);
cordova.callbackFromNative(callbackId, true, args.status, [args.message], args.keepCallback);
} catch (e) {
console.log("Error in success callback: " + callbackId + " = "+e);
}
}, },
/** /**
@ -276,30 +272,34 @@ var cordova = {
callbackError: function(callbackId, args) { callbackError: function(callbackId, args) {
// TODO: Deprecate callbackSuccess and callbackError in favour of callbackFromNative. // TODO: Deprecate callbackSuccess and callbackError in favour of callbackFromNative.
// Derive success from status. // Derive success from status.
try { cordova.callbackFromNative(callbackId, false, args.status, [args.message], args.keepCallback);
cordova.callbackFromNative(callbackId, false, args.status, [args.message], args.keepCallback);
} catch (e) {
console.log("Error in error callback: " + callbackId + " = "+e);
}
}, },
/** /**
* Called by native code when returning the result from an action. * Called by native code when returning the result from an action.
*/ */
callbackFromNative: function(callbackId, success, status, args, keepCallback) { callbackFromNative: function(callbackId, isSuccess, status, args, keepCallback) {
var callback = cordova.callbacks[callbackId]; try {
if (callback) { var callback = cordova.callbacks[callbackId];
if (success && status == cordova.callbackStatus.OK) { if (callback) {
callback.success && callback.success.apply(null, args); if (isSuccess && status == cordova.callbackStatus.OK) {
} else if (!success) { callback.success && callback.success.apply(null, args);
callback.fail && callback.fail.apply(null, args); } else {
} callback.fail && callback.fail.apply(null, args);
}
// Clear callback if not expecting any more results // Clear callback if not expecting any more results
if (!keepCallback) { if (!keepCallback) {
delete cordova.callbacks[callbackId]; delete cordova.callbacks[callbackId];
}
} }
} }
catch (err) {
var msg = "Error in " + (isSuccess ? "Success" : "Error") + " callbackId: " + callbackId + " : " + err;
console && console.log && console.log(msg);
cordova.fireWindowEvent("cordovacallbackerror", { 'message': msg });
throw err;
}
}, },
addConstructor: function(func) { addConstructor: function(func) {
channel.onCordovaReady.subscribe(function() { channel.onCordovaReady.subscribe(function() {
@ -1013,6 +1013,42 @@ androidExec.setNativeToJsBridgeMode = function(mode) {
} }
}; };
function buildPayload(payload, message) {
var payloadKind = message.charAt(0);
if (payloadKind == 's') {
payload.push(message.slice(1));
} else if (payloadKind == 't') {
payload.push(true);
} else if (payloadKind == 'f') {
payload.push(false);
} else if (payloadKind == 'N') {
payload.push(null);
} else if (payloadKind == 'n') {
payload.push(+message.slice(1));
} else if (payloadKind == 'A') {
var data = message.slice(1);
var bytes = window.atob(data);
var arraybuffer = new Uint8Array(bytes.length);
for (var i = 0; i < bytes.length; i++) {
arraybuffer[i] = bytes.charCodeAt(i);
}
payload.push(arraybuffer.buffer);
} else if (payloadKind == 'S') {
payload.push(window.atob(message.slice(1)));
} else if (payloadKind == 'M') {
var multipartMessages = message.slice(1);
while (multipartMessages !== "") {
var spaceIdx = multipartMessages.indexOf(' ');
var msgLen = +multipartMessages.slice(0, spaceIdx);
var multipartMessage = multipartMessages.substr(spaceIdx + 1, msgLen);
multipartMessages = multipartMessages.slice(spaceIdx + msgLen + 1);
buildPayload(payload, multipartMessage);
}
} else {
payload.push(JSON.parse(message));
}
}
// Processes a single message, as encoded by NativeToJsMessageQueue.java. // Processes a single message, as encoded by NativeToJsMessageQueue.java.
function processMessage(message) { function processMessage(message) {
try { try {
@ -1026,32 +1062,10 @@ function processMessage(message) {
var status = +message.slice(2, spaceIdx); var status = +message.slice(2, spaceIdx);
var nextSpaceIdx = message.indexOf(' ', spaceIdx + 1); var nextSpaceIdx = message.indexOf(' ', spaceIdx + 1);
var callbackId = message.slice(spaceIdx + 1, nextSpaceIdx); var callbackId = message.slice(spaceIdx + 1, nextSpaceIdx);
var payloadKind = message.charAt(nextSpaceIdx + 1); var payloadMessage = message.slice(nextSpaceIdx + 1);
var payload; var payload = [];
if (payloadKind == 's') { buildPayload(payload, payloadMessage);
payload = message.slice(nextSpaceIdx + 2); cordova.callbackFromNative(callbackId, success, status, payload, keepCallback);
} else if (payloadKind == 't') {
payload = true;
} else if (payloadKind == 'f') {
payload = false;
} else if (payloadKind == 'N') {
payload = null;
} else if (payloadKind == 'n') {
payload = +message.slice(nextSpaceIdx + 2);
} else if (payloadKind == 'A') {
var data = message.slice(nextSpaceIdx + 2);
var bytes = window.atob(data);
var arraybuffer = new Uint8Array(bytes.length);
for (var i = 0; i < bytes.length; i++) {
arraybuffer[i] = bytes.charCodeAt(i);
}
payload = arraybuffer.buffer;
} else if (payloadKind == 'S') {
payload = window.atob(message.slice(nextSpaceIdx + 2));
} else {
payload = JSON.parse(message.slice(nextSpaceIdx + 1));
}
cordova.callbackFromNative(callbackId, success, status, [payload], keepCallback);
} else { } else {
console.log("processMessage failed: invalid message: " + JSON.stringify(message)); console.log("processMessage failed: invalid message: " + JSON.stringify(message));
} }

View File

@ -37,6 +37,7 @@ public class NativeToJsMessageQueue {
// Set this to true to force plugin results to be encoding as // Set this to true to force plugin results to be encoding as
// JS instead of the custom format (useful for benchmarking). // JS instead of the custom format (useful for benchmarking).
// Doesn't work for multipart messages.
private static final boolean FORCE_ENCODE_USING_EVAL = false; private static final boolean FORCE_ENCODE_USING_EVAL = false;
// Disable sending back native->JS messages during an exec() when the active // Disable sending back native->JS messages during an exec() when the active
@ -419,53 +420,43 @@ public class NativeToJsMessageQueue {
this.pluginResult = pluginResult; this.pluginResult = pluginResult;
} }
static int calculateEncodedLengthHelper(PluginResult pluginResult) {
switch (pluginResult.getMessageType()) {
case PluginResult.MESSAGE_TYPE_BOOLEAN: // f or t
case PluginResult.MESSAGE_TYPE_NULL: // N
return 1;
case PluginResult.MESSAGE_TYPE_NUMBER: // n
return 1 + pluginResult.getMessage().length();
case PluginResult.MESSAGE_TYPE_STRING: // s
return 1 + pluginResult.getStrMessage().length();
case PluginResult.MESSAGE_TYPE_BINARYSTRING:
return 1 + pluginResult.getMessage().length();
case PluginResult.MESSAGE_TYPE_ARRAYBUFFER:
return 1 + pluginResult.getMessage().length();
case PluginResult.MESSAGE_TYPE_MULTIPART:
int ret = 1;
for (int i = 0; i < pluginResult.getMultipartMessagesSize(); i++) {
int length = calculateEncodedLengthHelper(pluginResult.getMultipartMessage(i));
int argLength = String.valueOf(length).length();
ret += argLength + 1 + length;
}
return ret;
case PluginResult.MESSAGE_TYPE_JSON:
default:
return pluginResult.getMessage().length();
}
}
int calculateEncodedLength() { int calculateEncodedLength() {
if (pluginResult == null) { if (pluginResult == null) {
return jsPayloadOrCallbackId.length() + 1; return jsPayloadOrCallbackId.length() + 1;
} }
int statusLen = String.valueOf(pluginResult.getStatus()).length(); int statusLen = String.valueOf(pluginResult.getStatus()).length();
int ret = 2 + statusLen + 1 + jsPayloadOrCallbackId.length() + 1; int ret = 2 + statusLen + 1 + jsPayloadOrCallbackId.length() + 1;
switch (pluginResult.getMessageType()) { return ret + calculateEncodedLengthHelper(pluginResult);
case PluginResult.MESSAGE_TYPE_BOOLEAN: // f or t
case PluginResult.MESSAGE_TYPE_NULL: // N
ret += 1;
break;
case PluginResult.MESSAGE_TYPE_NUMBER: // n
ret += 1 + pluginResult.getMessage().length();
break;
case PluginResult.MESSAGE_TYPE_STRING: // s
ret += 1 + pluginResult.getStrMessage().length();
break;
case PluginResult.MESSAGE_TYPE_BINARYSTRING:
ret += 1 + pluginResult.getMessage().length();
break;
case PluginResult.MESSAGE_TYPE_ARRAYBUFFER:
ret += 1 + pluginResult.getMessage().length();
break;
case PluginResult.MESSAGE_TYPE_JSON:
default:
ret += pluginResult.getMessage().length();
} }
return ret;
}
void encodeAsMessage(StringBuilder sb) { static void encodeAsMessageHelper(StringBuilder sb, PluginResult pluginResult) {
if (pluginResult == null) {
sb.append('J')
.append(jsPayloadOrCallbackId);
return;
}
int status = pluginResult.getStatus();
boolean noResult = status == PluginResult.Status.NO_RESULT.ordinal();
boolean resultOk = status == PluginResult.Status.OK.ordinal();
boolean keepCallback = pluginResult.getKeepCallback();
sb.append((noResult || resultOk) ? 'S' : 'F')
.append(keepCallback ? '1' : '0')
.append(status)
.append(' ')
.append(jsPayloadOrCallbackId)
.append(' ');
switch (pluginResult.getMessageType()) { switch (pluginResult.getMessageType()) {
case PluginResult.MESSAGE_TYPE_BOOLEAN: case PluginResult.MESSAGE_TYPE_BOOLEAN:
sb.append(pluginResult.getMessage().charAt(0)); // t or f. sb.append(pluginResult.getMessage().charAt(0)); // t or f.
@ -489,12 +480,42 @@ public class NativeToJsMessageQueue {
sb.append('A'); sb.append('A');
sb.append(pluginResult.getMessage()); sb.append(pluginResult.getMessage());
break; break;
case PluginResult.MESSAGE_TYPE_MULTIPART:
sb.append('M');
for (int i = 0; i < pluginResult.getMultipartMessagesSize(); i++) {
PluginResult multipartMessage = pluginResult.getMultipartMessage(i);
sb.append(String.valueOf(calculateEncodedLengthHelper(multipartMessage)));
sb.append(' ');
encodeAsMessageHelper(sb, multipartMessage);
}
break;
case PluginResult.MESSAGE_TYPE_JSON: case PluginResult.MESSAGE_TYPE_JSON:
default: default:
sb.append(pluginResult.getMessage()); // [ or { sb.append(pluginResult.getMessage()); // [ or {
} }
} }
void encodeAsMessage(StringBuilder sb) {
if (pluginResult == null) {
sb.append('J')
.append(jsPayloadOrCallbackId);
return;
}
int status = pluginResult.getStatus();
boolean noResult = status == PluginResult.Status.NO_RESULT.ordinal();
boolean resultOk = status == PluginResult.Status.OK.ordinal();
boolean keepCallback = pluginResult.getKeepCallback();
sb.append((noResult || resultOk) ? 'S' : 'F')
.append(keepCallback ? '1' : '0')
.append(status)
.append(' ')
.append(jsPayloadOrCallbackId)
.append(' ');
encodeAsMessageHelper(sb, pluginResult);
}
void encodeAsJsMessage(StringBuilder sb) { void encodeAsJsMessage(StringBuilder sb) {
if (pluginResult == null) { if (pluginResult == null) {
sb.append(jsPayloadOrCallbackId); sb.append(jsPayloadOrCallbackId);

19
framework/src/org/apache/cordova/PluginResult.java Executable file → Normal file
View File

@ -18,6 +18,8 @@
*/ */
package org.apache.cordova; package org.apache.cordova;
import java.util.List;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONObject; import org.json.JSONObject;
@ -29,6 +31,7 @@ public class PluginResult {
private boolean keepCallback = false; private boolean keepCallback = false;
private String strMessage; private String strMessage;
private String encodedMessage; private String encodedMessage;
private List<PluginResult> multipartMessages;
public PluginResult(Status status) { public PluginResult(Status status) {
this(status, PluginResult.StatusMessages[status.ordinal()]); this(status, PluginResult.StatusMessages[status.ordinal()]);
@ -80,6 +83,13 @@ public class PluginResult {
this.encodedMessage = Base64.encodeToString(data, Base64.NO_WRAP); this.encodedMessage = Base64.encodeToString(data, Base64.NO_WRAP);
} }
// The keepCallback and status of multipartMessages are ignored.
public PluginResult(Status status, List<PluginResult> multipartMessages) {
this.status = status.ordinal();
this.messageType = MESSAGE_TYPE_MULTIPART;
this.multipartMessages = multipartMessages;
}
public void setKeepCallback(boolean b) { public void setKeepCallback(boolean b) {
this.keepCallback = b; this.keepCallback = b;
} }
@ -99,6 +109,14 @@ public class PluginResult {
return encodedMessage; return encodedMessage;
} }
public int getMultipartMessagesSize() {
return multipartMessages.size();
}
public PluginResult getMultipartMessage(int index) {
return multipartMessages.get(index);
}
/** /**
* If messageType == MESSAGE_TYPE_STRING, then returns the message string. * If messageType == MESSAGE_TYPE_STRING, then returns the message string.
* Otherwise, returns null. * Otherwise, returns null.
@ -150,6 +168,7 @@ public class PluginResult {
// Use BINARYSTRING when your string may contain null characters. // Use BINARYSTRING when your string may contain null characters.
// This is required to work around a bug in the platform :(. // This is required to work around a bug in the platform :(.
public static final int MESSAGE_TYPE_BINARYSTRING = 7; public static final int MESSAGE_TYPE_BINARYSTRING = 7;
public static final int MESSAGE_TYPE_MULTIPART = 8;
public static String[] StatusMessages = new String[] { public static String[] StatusMessages = new String[] {
"No result", "No result",