/*
* Copyright (C) 2013 www.418log.org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.ab.http;
import android.util.Log;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.message.BasicHeader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
// TODO: Auto-generated Javadoc
/**
*
* Copyright (c) 2012 All rights reserved
* 名称:AbMultipartEntity.java
* 描述:用于文件上传
* @author zhaoqp
* @date:2013-11-13 下午1:09:20
* @version v1.0
*/
public class AbMultipartEntity implements HttpEntity {
/** The Constant TAG. */
private static final String TAG = "AbMultipartEntity";
/** The Constant APPLICATION_OCTET_STREAM. */
private static final String APPLICATION_OCTET_STREAM = "application/octet-stream";
/** The Constant CR_LF. */
private static final byte[] CR_LF = ("\r\n").getBytes();
/** The Constant TRANSFER_ENCODING_BINARY. */
private static final byte[] TRANSFER_ENCODING_BINARY = "Content-Transfer-Encoding: binary\r\n"
.getBytes();
/** The Constant MULTIPART_CHARS. */
private final static char[] MULTIPART_CHARS = "-_1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();
/** The boundary. */
private String boundary;
/** The boundary line. */
private byte[] boundaryLine;
/** The boundary end. */
private byte[] boundaryEnd;
/** The file parts. */
private List<FilePart> fileParts = new ArrayList<FilePart>();
/** The out. */
private ByteArrayOutputStream out = new ByteArrayOutputStream();
/** The progress handler. */
private AbHttpResponseListener responseListener;
/** The bytes written. */
private int bytesWritten;
/** The total size. */
private int totalSize;
/**
* Instantiates a new simple multipart entity.
*
* @param progressHandler the progress handler
*/
public AbMultipartEntity(AbHttpResponseListener responseListener) {
final StringBuilder buf = new StringBuilder();
final Random rand = new Random();
for (int i = 0; i < 30; i++) {
buf.append(MULTIPART_CHARS[rand.nextInt(MULTIPART_CHARS.length)]);
}
boundary = buf.toString();
boundaryLine = ("--" + boundary + "\r\n").getBytes();
boundaryEnd = ("--" + boundary + "--\r\n").getBytes();
this.responseListener = responseListener;
}
/**
* Adds the part.
*
* @param key the key
* @param value the value
* @param contentType the content type
*/
public void addPart(final String key, final String value, final String contentType) {
try {
out.write(boundaryLine);
out.write(createContentDisposition(key));
out.write(createContentType(contentType));
out.write(CR_LF);
out.write(value.getBytes());
out.write(CR_LF);
} catch (final IOException e) {
// Can't happen on ByteArrayOutputStream
Log.e(TAG, "addPart ByteArrayOutputStream exception", e);
}
}
/**
* Adds the part.
*
* @param key the key
* @param value the value
*/
public void addPart(final String key, final String value) {
addPart(key, value, "text/plain; charset=UTF-8");
}
/**
* Adds the part.
*
* @param key the key
* @param file the file
*/
public void addPart(String key, File file) {
addPart(key, file, null);
}
/**
* Adds the part.
*
* @param key the key
* @param file the file
* @param type the type
*/
public void addPart(final String key, File file, String type) {
if (type == null) {
type = APPLICATION_OCTET_STREAM;
}
fileParts.add(new FilePart(key, file, type));
}
/**
* Adds the part.
*
* @param key the key
* @param streamName the stream name
* @param inputStream the input stream
* @param type the type
* @throws IOException Signals that an I/O exception has occurred.
*/
public void addPart(String key, String streamName, InputStream inputStream, String type)
throws IOException {
if (type == null) {
type = APPLICATION_OCTET_STREAM;
}
out.write(boundaryLine);
// Headers
out.write(createContentDisposition(key, streamName));
out.write(createContentType(type));
out.write(TRANSFER_ENCODING_BINARY);
out.write(CR_LF);
// Stream (file)
final byte[] tmp = new byte[4096];
int l;
while ((l = inputStream.read(tmp)) != -1) {
out.write(tmp, 0, l);
}
out.write(CR_LF);
out.flush();
try {
inputStream.close();
} catch (final IOException e) {
// Not important, just log it
Log.w(TAG, "Cannot close input stream", e);
}
}
/**
* Creates the content type.
*
* @param type the type
* @return the byte[]
*/
private byte[] createContentType(String type) {
String result = "Content-Type: " + type + "\r\n";
return result.getBytes();
}
/**
* Creates the content disposition.
*
* @param key the key
* @return the byte[]
*/
private byte[] createContentDisposition(final String key) {
return ("Content-Disposition: form-data; name=\"" + key + "\"\r\n")
.getBytes();
}
/**
* Creates the content disposition.
*
* @param key the key
* @param fileName the file name
* @return the byte[]
*/
private byte[] createContentDisposition(final String key, final String fileName) {
return ("Content-Disposition: form-data; name=\"" + key + "\"; filename=\"" + fileName + "\"\r\n")
.getBytes();
}
/**
* Update progress.
*
* @param count the count
*/
private void updateProgress(int count) {
bytesWritten += count;
responseListener.sendProgressMessage(bytesWritten, totalSize);
}
/**
* The Class FilePart.
*/
private class FilePart {
/** The file. */
public File file;
/** The header. */
public byte[] header;
/**
* Instantiates a new file part.
*
* @param key the key
* @param file the file
* @param type the type
*/
public FilePart(String key, File file, String type) {
header = createHeader(key, file.getName(), type);
this.file = file;
}
/**
* Creates the header.
*
* @param key the key
* @param filename the filename
* @param type the type
* @return the byte[]
*/
private byte[] createHeader(String key, String filename, String type) {
ByteArrayOutputStream headerStream = new ByteArrayOutputStream();
try {
headerStream.write(boundaryLine);
// Headers
headerStream.write(createContentDisposition(key, filename));
headerStream.write(createContentType(type));
headerStream.write(TRANSFER_ENCODING_BINARY);
headerStream.write(CR_LF);
} catch (IOException e) {
// Can't happen on ByteArrayOutputStream
Log.e(TAG, "createHeader ByteArrayOutputStream exception", e);
}
return headerStream.toByteArray();
}
/**
* Gets the total length.
*
* @return the total length
*/
public long getTotalLength() {
long streamLength = file.length();
return header.length + streamLength;
}
/**
* Write to.
*
* @param out the out
* @throws IOException Signals that an I/O exception has occurred.
*/
public void writeTo(OutputStream out) throws IOException {
out.write(header);
updateProgress(header.length);
FileInputStream inputStream = new FileInputStream(file);
final byte[] tmp = new byte[4096];
int l;
while ((l = inputStream.read(tmp)) != -1) {
out.write(tmp, 0, l);
updateProgress(l);
}
out.write(CR_LF);
updateProgress(CR_LF.length);
out.flush();
try {
inputStream.close();
} catch (final IOException e) {
// Not important, just log it
Log.w(TAG, "Cannot close input stream", e);
}
}
}
// The following methods are from the HttpEntity interface
/**
* 描述:TODO
* @see org.apache.http.HttpEntity#getContentLength()
*/
@Override
public long getContentLength() {
long contentLen = out.size();
for (FilePart filePart : fileParts) {
long len = filePart.getTotalLength();
if (len < 0) {
return -1; // Should normally not happen
}
contentLen += len;
}
contentLen += boundaryEnd.length;
return contentLen;
}
/**
* 描述:TODO
* @see org.apache.http.HttpEntity#getContentType()
* @author: zhaoqp
* @date:2013-10-22 下午4:23:15
* @version v1.0
*/
@Override
public Header getContentType() {
return new BasicHeader("Content-Type", "multipart/form-data; boundary=" + boundary);
}
/**
* 描述:TODO
* @see org.apache.http.HttpEntity#isChunked()
*/
@Override
public boolean isChunked() {
return false;
}
/**
* 描述:TODO
* @see org.apache.http.HttpEntity#isRepeatable()
*/
@Override
public boolean isRepeatable() {
return false;
}
/**
* 描述:TODO
* @see org.apache.http.HttpEntity#isStreaming()
*/
@Override
public boolean isStreaming() {
return false;
}
/**
* 描述:TODO
* @see org.apache.http.HttpEntity#writeTo(java.io.OutputStream)
*/
@Override
public void writeTo(final OutputStream outstream) throws IOException {
bytesWritten = 0;
totalSize = (int) getContentLength();
out.writeTo(outstream);
updateProgress(out.size());
for (FilePart filePart : fileParts) {
filePart.writeTo(outstream);
}
outstream.write(boundaryEnd);
updateProgress(boundaryEnd.length);
}
/**
* 描述:TODO
* @see org.apache.http.HttpEntity#getContentEncoding()
*/
@Override
public Header getContentEncoding() {
return null;
}
/**
* 描述:TODO
* @see org.apache.http.HttpEntity#consumeContent()
*/
@Override
public void consumeContent() throws IOException, UnsupportedOperationException {
if (isStreaming()) {
throw new UnsupportedOperationException(
"Streaming entity does not implement #consumeContent()");
}
}
/**
* 描述:TODO
* @see org.apache.http.HttpEntity#getContent()
*/
@Override
public InputStream getContent() throws IOException, UnsupportedOperationException {
throw new UnsupportedOperationException(
"getContent() is not supported. Use writeTo() instead.");
}
}