Skip to content

Commit 553b6a3

Browse files
author
Oriental Sensation
committed
Upload files in JsonEntity either by Streams or File params.
1 parent 892dfb2 commit 553b6a3

File tree

3 files changed

+244
-171
lines changed

3 files changed

+244
-171
lines changed

library/src/main/java/com/loopj/android/http/JsonStreamerEntity.java

Lines changed: 164 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
import org.apache.http.HttpEntity;
2525
import org.apache.http.message.BasicHeader;
2626

27-
import java.io.BufferedOutputStream;
27+
import java.io.FileInputStream;
2828
import java.io.IOException;
2929
import java.io.InputStream;
3030
import java.io.OutputStream;
@@ -46,15 +46,16 @@ class JsonStreamerEntity implements HttpEntity {
4646
private static final UnsupportedOperationException ERR_UNSUPPORTED =
4747
new UnsupportedOperationException("Unsupported operation in this implementation.");
4848

49-
// Size of the byte-array buffer used to read from streams.
50-
private static final int BUFFER_SIZE = 2048;
49+
// Size of the byte-array buffer used in I/O streams.
50+
private static final int BUFFER_SIZE = 4096;
51+
52+
// Buffer used for reading from input streams.
53+
private final byte[] buffer = new byte[BUFFER_SIZE];
5154

5255
// Reusable StringBuilder used by escape() method.
53-
// Base64, at worst, will make a binary stream grow in size by approximately
54-
// (n + 2 - ((n + 2) % 3)) / 3 * 4, which is roughly 1.3333333% for a
55-
// large 'n'.
56-
private static final StringBuilder BUILDER =
57-
new StringBuilder((int)(BUFFER_SIZE * 1.35f));
56+
// Its size is just initial, if more space is needed, the system will
57+
// automatically enlarge the buffer.
58+
private static final StringBuilder BUILDER = new StringBuilder(128);
5859

5960
private static final byte[] JSON_TRUE = "true".getBytes();
6061
private static final byte[] JSON_FALSE = "false".getBytes();
@@ -68,33 +69,28 @@ class JsonStreamerEntity implements HttpEntity {
6869
new BasicHeader("Content-Type", "application/json");
6970
private static final Header HEADER_GZIP_ENCODING =
7071
new BasicHeader("Content-Encoding", "gzip");
71-
private static final String APPLICATION_OCTET_STREAM =
72-
"application/octet-stream";
73-
74-
// K/V objects to be uploaded.
75-
private final Map<String, Object> kvParams =
76-
new HashMap();
7772

78-
// Streams and their associated meta-data to be uploaded.
79-
private final Map<String, RequestParams.StreamWrapper> streamParams =
80-
new HashMap();
73+
// JSON data and associated meta-data to be uploaded.
74+
private final Map<String, Object> jsonParams = new HashMap();
8175

8276
// Whether to use gzip compression while uploading
8377
private final Header contentEncoding;
8478

85-
public JsonStreamerEntity(boolean contentEncoding) {
86-
this.contentEncoding = contentEncoding ? HEADER_GZIP_ENCODING : null;
87-
}
79+
private final ResponseHandlerInterface progressHandler;
8880

89-
public void addPart(String key, Object value) {
90-
kvParams.put(key, value);
81+
public JsonStreamerEntity(ResponseHandlerInterface progressHandler, boolean useGZipCompression) {
82+
this.progressHandler = progressHandler;
83+
this.contentEncoding = useGZipCompression ? HEADER_GZIP_ENCODING : null;
9184
}
9285

93-
public void addPart(String key, InputStream inputStream, String name, String type) {
94-
if (type == null) {
95-
type = APPLICATION_OCTET_STREAM;
96-
}
97-
streamParams.put(key, new RequestParams.StreamWrapper(inputStream, name, type));
86+
/**
87+
* Add content parameter, identified by the given key, to the request.
88+
*
89+
* @param key entity's name
90+
* @param value entity's value (Scalar, FileWrapper, StreamWrapper)
91+
*/
92+
public void addPart(String key, Object value) {
93+
jsonParams.put(key, value);
9894
}
9995

10096
@Override
@@ -137,122 +133,176 @@ public InputStream getContent() throws IOException, UnsupportedOperationExceptio
137133
}
138134

139135
@Override
140-
public void writeTo(final OutputStream outstream) throws IOException {
141-
if (outstream == null) {
136+
public void writeTo(final OutputStream out) throws IOException {
137+
if (out == null) {
142138
throw new IllegalStateException("Output stream cannot be null.");
143139
}
144140

145141
// Record the time when uploading started.
146142
long now = System.currentTimeMillis();
147143

148-
// Keys used by the HashMaps.
149-
Set<String> keys;
150-
151144
// Use GZIP compression when sending streams, otherwise just use
152145
// a buffered output stream to speed things up a bit.
153-
OutputStream upload;
154-
if (null != contentEncoding) {
155-
upload = new GZIPOutputStream(new BufferedOutputStream(outstream), BUFFER_SIZE);
156-
} else {
157-
upload = new BufferedOutputStream(outstream);
158-
}
146+
OutputStream os = null != contentEncoding
147+
? new GZIPOutputStream(out, BUFFER_SIZE)
148+
: out;
159149

160150
// Always send a JSON object.
161-
upload.write('{');
151+
os.write('{');
162152

163-
// Send the K/V values.
164-
keys = kvParams.keySet();
165-
for (String key : keys) {
166-
// Write the JSON object's key.
167-
upload.write(escape(key));
168-
upload.write(':');
153+
// Keys used by the HashMaps.
154+
Set<String> keys = jsonParams.keySet();
155+
156+
boolean isFileWrapper;
169157

158+
// Go over all keys and handle each's value.
159+
for (String key : keys) {
170160
// Evaluate the value (which cannot be null).
171-
Object value = kvParams.get(key);
161+
Object value = jsonParams.get(key);
162+
163+
// Bail out prematurely if value's null.
164+
if (value == null) {
165+
continue;
166+
}
172167

173-
if (value instanceof Boolean) {
174-
upload.write((Boolean)value ? JSON_TRUE : JSON_FALSE);
168+
// Write the JSON object's key.
169+
os.write(escape(key));
170+
os.write(':');
171+
172+
// Check if this is a FileWrapper.
173+
isFileWrapper = value instanceof RequestParams.FileWrapper;
174+
175+
// If a file should be uploaded.
176+
if (isFileWrapper || value instanceof RequestParams.StreamWrapper) {
177+
// All uploads are sent as an object containing the file's details.
178+
os.write('{');
179+
180+
// Determine how to handle this entry.
181+
if (isFileWrapper) {
182+
writeToFromFile(os, (RequestParams.FileWrapper)value);
183+
} else {
184+
writeToFromStream(os, (RequestParams.StreamWrapper)value);
185+
}
186+
187+
// End the file's object and prepare for next one.
188+
os.write('}');
189+
} else if (value instanceof Boolean) {
190+
os.write((Boolean)value ? JSON_TRUE : JSON_FALSE);
175191
} else if (value instanceof Long) {
176-
upload.write((((Number)value).longValue() + "").getBytes());
192+
os.write((((Number)value).longValue() + "").getBytes());
177193
} else if (value instanceof Double) {
178-
upload.write((((Number)value).doubleValue() + "").getBytes());
194+
os.write((((Number)value).doubleValue() + "").getBytes());
179195
} else if (value instanceof Float) {
180-
upload.write((((Number)value).floatValue() + "").getBytes());
196+
os.write((((Number)value).floatValue() + "").getBytes());
181197
} else if (value instanceof Integer) {
182-
upload.write((((Number)value).intValue() + "").getBytes());
198+
os.write((((Number)value).intValue() + "").getBytes());
183199
} else {
184-
upload.write(value.toString().getBytes());
200+
os.write(value.toString().getBytes());
185201
}
186202

187-
upload.write(',');
188-
}
189-
190-
// Buffer used for reading from input streams.
191-
byte[] buffer = new byte[BUFFER_SIZE];
192-
193-
// Send the stream params.
194-
keys = streamParams.keySet();
195-
for (String key : keys) {
196-
RequestParams.StreamWrapper entry = streamParams.get(key);
197-
198-
// Write the JSON object's key.
199-
upload.write(escape(key));
200-
201-
// All uploads are sent as an object containing the file's details.
202-
upload.write(':');
203-
upload.write('{');
204-
205-
// Send the streams's name.
206-
upload.write(STREAM_NAME);
207-
upload.write(':');
208-
upload.write(escape(entry.name));
209-
upload.write(',');
210-
211-
// Send the streams's content type.
212-
upload.write(STREAM_TYPE);
213-
upload.write(':');
214-
upload.write(escape(entry.contentType));
215-
upload.write(',');
216-
217-
// Prepare the file content's key.
218-
upload.write(STREAM_CONTENTS);
219-
upload.write(':');
220-
upload.write('"');
221-
222-
// Upload the file's contents in Base64.
223-
Base64OutputStream outputStream =
224-
new Base64OutputStream(upload, Base64.NO_CLOSE | Base64.NO_WRAP);
225-
226-
// Read from input stream until no more data's left to read.
227-
int bytesRead;
228-
while ((bytesRead = entry.inputStream.read(buffer)) != -1) {
229-
outputStream.write(buffer, 0, bytesRead);
230-
}
231-
232-
// Close the Base64 output stream.
233-
outputStream.close();
234-
235-
// End the file's object and prepare for next one.
236-
upload.write('"');
237-
upload.write('}');
238-
upload.write(',');
203+
os.write(',');
239204
}
240205

241206
// Include the elapsed time taken to upload everything.
242207
// This might be useful for somebody, but it serves us well since
243208
// there will almost always be a ',' as the last sent character.
244-
upload.write(STREAM_ELAPSED);
245-
upload.write(':');
209+
os.write(STREAM_ELAPSED);
210+
os.write(':');
246211
long elapsedTime = System.currentTimeMillis() - now;
247-
upload.write((elapsedTime + "}").getBytes());
212+
os.write((elapsedTime + "}").getBytes());
248213

249214
Log.i(LOG_TAG, "Uploaded JSON in " + Math.floor(elapsedTime / 1000) + " seconds");
250215

251216
// Flush the contents up the stream.
252-
upload.flush();
253-
upload.close();
217+
os.flush();
218+
AsyncHttpClient.silentCloseOutputStream(os);
254219
}
255220

221+
private void writeToFromStream(OutputStream os, RequestParams.StreamWrapper entry)
222+
throws IOException {
223+
224+
// Send the meta data.
225+
writeMetaData(os, entry.name, entry.contentType);
226+
227+
int bytesRead;
228+
229+
// Upload the file's contents in Base64.
230+
Base64OutputStream bos =
231+
new Base64OutputStream(os, Base64.NO_CLOSE | Base64.NO_WRAP);
232+
233+
// Read from input stream until no more data's left to read.
234+
while ((bytesRead = entry.inputStream.read(buffer)) != -1) {
235+
bos.write(buffer, 0, bytesRead);
236+
}
237+
238+
// Close the Base64 output stream.
239+
AsyncHttpClient.silentCloseOutputStream(bos);
240+
241+
// End the meta data.
242+
endMetaData(os);
243+
244+
// Close input stream.
245+
if (entry.autoClose) {
246+
// Safely close the input stream.
247+
AsyncHttpClient.silentCloseInputStream(entry.inputStream);
248+
}
249+
}
250+
251+
private void writeToFromFile(OutputStream os, RequestParams.FileWrapper wrapper)
252+
throws IOException {
253+
254+
// Send the meta data.
255+
writeMetaData(os, wrapper.file.getName(), wrapper.contentType);
256+
257+
int bytesRead, bytesWritten = 0, totalSize = (int)wrapper.file.length();
258+
259+
// Open the file for reading.
260+
FileInputStream in = new FileInputStream(wrapper.file);
261+
262+
// Upload the file's contents in Base64.
263+
Base64OutputStream bos =
264+
new Base64OutputStream(os, Base64.NO_CLOSE | Base64.NO_WRAP);
265+
266+
// Read from file until no more data's left to read.
267+
while ((bytesRead = in.read(buffer)) != -1) {
268+
bos.write(buffer, 0, bytesRead);
269+
bytesWritten += bytesRead;
270+
progressHandler.sendProgressMessage(bytesWritten, totalSize);
271+
}
272+
273+
// Close the Base64 output stream.
274+
AsyncHttpClient.silentCloseOutputStream(bos);
275+
276+
// End the meta data.
277+
endMetaData(os);
278+
279+
// Safely close the input stream.
280+
AsyncHttpClient.silentCloseInputStream(in);
281+
}
282+
283+
private void writeMetaData(OutputStream os, String name, String contentType) throws IOException {
284+
// Send the streams's name.
285+
os.write(STREAM_NAME);
286+
os.write(':');
287+
os.write(escape(name));
288+
os.write(',');
289+
290+
// Send the streams's content type.
291+
os.write(STREAM_TYPE);
292+
os.write(':');
293+
os.write(escape(contentType));
294+
os.write(',');
295+
296+
// Prepare the file content's key.
297+
os.write(STREAM_CONTENTS);
298+
os.write(':');
299+
os.write('"');
300+
}
301+
302+
private void endMetaData(OutputStream os) throws IOException {
303+
os.write('"');
304+
}
305+
256306
// Curtosy of Simple-JSON: http://goo.gl/XoW8RF
257307
// Changed a bit to suit our needs in this class.
258308
static byte[] escape(String string) {
@@ -310,11 +360,11 @@ static byte[] escape(String string) {
310360
BUILDER.append('"');
311361

312362
try {
313-
return BUILDER.toString().getBytes();
363+
return BUILDER.toString().getBytes();
314364
} finally {
315-
// Empty the String buffer.
316-
// This is 20-30% faster than instantiating a new object.
317-
BUILDER.setLength(0);
365+
// Empty the String buffer.
366+
// This is 20-30% faster than instantiating a new object.
367+
BUILDER.setLength(0);
318368
}
319369
}
320370
}

0 commit comments

Comments
 (0)