Skip to content

Instantly share code, notes, and snippets.

@eduardb
Last active April 4, 2018 08:40
Show Gist options
  • Save eduardb/42e53ff9e49b52ca30ca to your computer and use it in GitHub Desktop.
Save eduardb/42e53ff9e49b52ca30ca to your computer and use it in GitHub Desktop.
Uploading a file with a progress displayed using Apache's HttpClient
public class CustomMultiPartEntity implements HttpEntity
{
private final ProgressListener progressListener;
private HttpEntity mHttpEntity;
public CustomMultiPartEntity(ProgressListener progressListener, HttpEntity httpEntity)
{
this.progressListener = progressListener;
mHttpEntity = httpEntity;
}
@Override
public void consumeContent() throws IOException {
mHttpEntity.consumeContent();
}
@Override
public InputStream getContent() throws IOException,
IllegalStateException {
return mHttpEntity.getContent();
}
@Override
public void writeTo(OutputStream outputStream) throws IOException {
mHttpEntity.writeTo(new ProgressiveOutputStream(outputStream, this.progressListener));
}
@Override
public Header getContentEncoding() {
return mHttpEntity.getContentEncoding();
}
@Override
public long getContentLength() {
return mHttpEntity.getContentLength();
}
@Override
public Header getContentType() {
return mHttpEntity.getContentType();
}
@Override
public boolean isChunked() {
return mHttpEntity.isChunked();
}
@Override
public boolean isRepeatable() {
return mHttpEntity.isRepeatable();
}
@Override
public boolean isStreaming() {
return mHttpEntity.isStreaming();
} // CONSIDER put a _real_ delegator into here!
static class ProxyOutputStream extends FilterOutputStream {
public ProxyOutputStream(OutputStream proxy) {
super(proxy);
}
@Override
public void write(int idx) throws IOException {
out.write(idx);
}
@Override
public void write(byte[] bts) throws IOException {
out.write(bts);
}
@Override
public void write(byte[] bts, int st, int end) throws IOException {
out.write(bts, st, end);
}
@Override
public void flush() throws IOException {
out.flush();
}
@Override
public void close() throws IOException {
out.close();
}
} // CONSIDER import this class (and risk more Jar File Hell)
static class ProgressiveOutputStream extends ProxyOutputStream {
private long transferred;
private final ProgressListener listener;
public ProgressiveOutputStream(OutputStream proxy, final ProgressListener listener) {
super(proxy);
this.listener = listener;
this.transferred = 0;
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
int BUFFER_SIZE = 10000;
int chunkSize;
int currentOffset = 0;
while (len>currentOffset) {
chunkSize = len - currentOffset;
if (chunkSize > BUFFER_SIZE) {
chunkSize = BUFFER_SIZE;
}
out.write(b, currentOffset, chunkSize);
currentOffset += chunkSize;
this.transferred += chunkSize;
//Log.i("CustomOutputStream WRITE","" + off + "|" + len + "|" + len + "|" + currentOffset + "|" + chunkSize + "|" + this.transferred);
this.listener.transferred(this.transferred);
}
}
@Override
public void write(int b) throws IOException
{
out.write(b);
this.transferred++;
this.listener.transferred(this.transferred);
}
}
public static interface ProgressListener
{
void transferred(long num);
}
}
public class MyPicturesAdapter extends BaseAdapter {
private static final int ITEM_TYPE_ONLINE = 0;
private static final int ITEM_TYPE_DISK = 1;
private List<UserImage> userImages;
private Context context;
private OnActionListener onActionListener;
private LayoutInflater inflater;
public MyPicturesAdapter(Context context) {
super();
this.context = context;
this.userImages = new ArrayList<UserImage>();
this.inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
@Override
public int getCount() {
return userImages.size();
}
@Override
public int getViewTypeCount() {
return 2;
}
@Override
public int getItemViewType(int position) {
return getItem(position).status >= 0 ? ITEM_TYPE_ONLINE : ITEM_TYPE_DISK;
}
@Override
public UserImage getItem(int position) {
return userImages.get(position);
}
@Override
public long getItemId(int position) {
return userImages.get(position).image.hashCode();
}
public void clear() {
this.userImages.clear();
}
public void addAll(Collection<UserImage> images) {
if (images != null) {
this.userImages.addAll(images);
}
}
public void remove(UserImage userImage) {
this.userImages.remove(userImage);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView != null) {
holder = (ViewHolder) convertView.getTag();
} else {
int itemType = getItemViewType(position);
if (itemType == ITEM_TYPE_ONLINE)
convertView = inflater.inflate(R.layout.item_my_pictures_image, parent, false);
else
convertView = inflater.inflate(R.layout.item_my_pictures_upload, parent, false);
holder = new ViewHolder(convertView);
if (itemType == ITEM_TYPE_DISK) {
holder.cancelButtonListener = new CancelButtonListener();
holder.statusIcon.setOnClickListener(holder.cancelButtonListener);
}
convertView.setTag(holder);
}
final UserImage userImage = getItem(position);
if (getItemViewType(position) == ITEM_TYPE_DISK) {
Picasso.with(holder.avatar.getContext())
.load(Uri.fromFile(new File(UploadsHandler.getInstance().getImagePath(userImage.image))))
.noFade()
.fit()
.into(holder.avatar);
userImage.status = UploadsHandler.getInstance().getUploadStatus(userImage.image);
holder.cancelButtonListener.updatePosition(position);
switch (userImage.status) {
case UploadsHandler.UPLOAD_LOADING:
holder.uploadProgress.setVisibility(View.VISIBLE);
UploadsHandler.getInstance().setProgressBar(userImage.image, holder.uploadProgress);
holder.statusText.setVisibility(View.GONE);
holder.statusIcon.setImageResource(R.drawable.icon_mb_cancelupload);
break;
case UploadsHandler.UPLOAD_ERROR:
holder.uploadProgress.setVisibility(View.GONE);
holder.statusText.setVisibility(View.VISIBLE);
holder.statusText.setText(R.string.picture_upload_error);
holder.statusIcon.setImageResource(R.drawable.icon_mb_errorupload);
break;
case UploadsHandler.UPLOAD_ABORTED:
holder.uploadProgress.setVisibility(View.GONE);
holder.statusText.setVisibility(View.VISIBLE);
holder.statusText.setText(R.string.picture_upload_aborted);
holder.statusIcon.setImageResource(R.drawable.icon_mb_refreshupload);
break;
}
holder.statusIndicator.setBackgroundColor(Color.TRANSPARENT);
holder.uploadProgress.setProgress(UploadsHandler.getInstance().getProgress(userImage.image));
} else {
switch (userImage.status) {
case 0:
holder.statusIndicator.setBackgroundColor(context.getResources().getColor(R.color.my_pictures_check_divider));
holder.statusIcon.setImageResource(R.drawable.icon_mb_check);
holder.actionIcon.setImageResource(R.drawable.icon_mb_settings);
break;
// etc
}
holder.statusText.setText(userImage.description);
UIUtils.loadImage(context, holder.avatar, userImage.image);
}
return convertView;
}
public void add(UserImage image) {
this.userImages.add(image);
}
public void setOnActionListener(OnActionListener onActionListener) {
this.onActionListener = onActionListener;
}
static class ViewHolder {
@InjectView(R.id.user_image_avatar)
ImageView avatar;
@InjectView(R.id.user_image_status_indicator)
View statusIndicator;
@InjectView(R.id.user_image_status_icon)
ImageView statusIcon;
@InjectView(R.id.user_image_status_text)
TextView statusText;
@Optional @InjectView(R.id.user_image_upload_progress)
ProgressBar uploadProgress;
@Optional @InjectView(R.id.user_image_action)
ImageView actionIcon;
CancelButtonListener cancelButtonListener;
public ViewHolder(View view) {
ButterKnife.inject(this, view);
}
}
public interface OnActionListener {
public void onAction(int position);
}
private class CancelButtonListener implements View.OnClickListener {
private int position;
private void updatePosition(int pos){
position = pos;
}
@Override
public void onClick(View v) {
if (onActionListener != null)
onActionListener.onAction(position);
}
}
}
public class UploadsHandler {
public static final int UPLOAD_LOADING = -1;
public static final int UPLOAD_ABORTED = -2;
public static final int UPLOAD_ERROR = -3;
private static UploadsHandler ourInstance;
private Context context;
private volatile HashMap<String, UploadData> uploads;
private List<Pair<String, String>> uploadTempQueue;§
private final Gson gson;
private final Handler handler;
public static UploadsHandler getInstance() {
if (ourInstance == null)
ourInstance = new UploadsHandler(Utils.getAppContext());
return ourInstance;
}
private UploadsHandler(Context context) {
this.context = context;
uploads = new HashMap<>();
uploadTempQueue = new ArrayList<>();
gson = new Gson();
handler = new Handler(context.getMainLooper());
}
/**
* Starts uploading an image to the server
*
* @param apiPath api URL; e.g. /api/userImage/upload/
* @param imagePath Path to the image on disk
* @param tempId The temoporary name that the image gets until it's uploaded
* @param callback Callback to be executed after the upload is finished
*/
public void uploadImage(String apiPath, String imagePath, final String tempId, final ApiCallback<StateModel> callback) {
final UploadData uploadData = new UploadData();
uploadData.imagePath = imagePath;
uploadData.progressValue = 0;
uploadData.status = UPLOAD_LOADING;
uploadData.apiMethod = apiPath;
uploadData.httpClient = HttpClients.createDefault();
uploadData.httpContext = new BasicHttpContext();
final File file = new File(imagePath);
final long totalSize = file.length();
String url = Constants.BASE_URL + apiPath;
uploadData.httpPost = new HttpPost(url);
MultipartEntityBuilder builder = MultipartEntityBuilder.create();
builder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);
builder.addPart("image", new FileBody(file));
CustomMultiPartEntity multiPartEntity = new CustomMultiPartEntity(
new CustomMultiPartEntity.ProgressListener() {
@Override
public void transferred(long num) {
float progress = (num / (float) totalSize) * 100;
uploadData.progressValue = (int) progress;
handler.post(new Runnable() {
@Override
public void run() {
updateProgressBar(tempId);
}
});
}
}, builder.build());
uploadData.httpPost.setEntity(multiPartEntity);
uploads.put(tempId, uploadData);
// make sure this executes in a background thread
uploadData.httpResponse = httpClient.execute(httpPost, httpContext);
// handle the response here
String serverResponse = EntityUtils.toString(uploadData.httpResponse.getEntity());
// blah blah; see the other gist, "Uploading a file with a progress displayed using OkHttp"
}
public List<UserImage> getCurrentUploadsList() {
List<UserImage> currentUploads = new ArrayList<UserImage>();
for (Map.Entry<String, UploadData> entry : uploads.entrySet()) {
UserImage image = new UserImage();
image.image = entry.getKey();
image.status = entry.getValue().status;
currentUploads.add(image);
}
return currentUploads;
}
public boolean isUploading(String image) {
return uploads.containsKey(image);
}
public String getImagePath(String image) {
if (uploads.containsKey(image)) {
return uploads.get(image).imagePath;
}
return null;
}
public String getApiPath(String image) {
if (uploads.containsKey(image)) {
return uploads.get(image).apiMethod;
}
return null;
}
public int getProgress(String image) {
if (uploads.containsKey(image)) {
return uploads.get(image).progressValue;
}
return -1;
}
public void setProgressBar(String image, ProgressBar progressBar) {
if (uploads.containsKey(image)) {
uploads.get(image).uploadProgressBar = progressBar;
}
}
private void updateProgressBar(String image) {
if (uploads.containsKey(image) && uploads.get(image).uploadProgressBar != null) {
uploads.get(image).uploadProgressBar.setProgress(getProgress(image));
}
}
public int getUploadStatus(String image) {
if (uploads.containsKey(image))
return uploads.get(image).status;
return 0;
}
public boolean uploadCanceled(String image) {
return uploads.containsKey(image) && uploads.get(image).canceled;
}
public void cancelUpload(String image) {
if (uploads.containsKey(image)) {
UploadData uploadData = uploads.get(image);
uploadData.canceled = true;
if (uploadData.httpPost != null)
uploadData.httpPost.abort();
if (uploadData.httpResponse != null)
try {
uploadData.httpResponse.close();
} catch (IOException e) {
e.printStackTrace();
}
if (uploadData.httpClient != null)
try {
uploadData.httpClient.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public void retryUpload(String image, ApiCallback<StateModel> callback) {
if (uploads.containsKey(image)) {
uploadImage(getApiPath(image), getImagePath(image), image, callback);
}
}
private static class UploadData {
ProgressBar uploadProgressBar;
int progressValue;
String imagePath;
int status;
boolean canceled;
String apiMethod;
// kinda ugly leaving these here, but we just have to :D
HttpPost httpPost;
CloseableHttpClient httpClient;
CloseableHttpResponse httpResponse;
UploadData() {
progressValue = -1;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment