Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upload progress recipe #1528

Closed
wants to merge 1 commit into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
/*
* Copyright (C) 2015 Square, Inc.
*
* 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.squareup.okhttp.recipes;

import com.squareup.okhttp.MediaType;
import com.squareup.okhttp.OkHttpClient;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.RequestBody;
import com.squareup.okhttp.Response;
import okio.Buffer;
import okio.BufferedSink;
import okio.ForwardingSink;
import okio.Okio;
import okio.Sink;
import okio.Source;

import java.io.File;
import java.io.IOException;

public final class UploadProgress {
/**
* The imgur client ID for OkHttp recipes. If you're using imgur for anything
* other than running these examples, please request your own client ID!
* https://api.imgur.com/oauth2
*/
private static final String IMGUR_CLIENT_ID = "9199fdef135c122";
private static final MediaType MEDIA_TYPE_PNG = MediaType.parse("image/png");

private final OkHttpClient client = new OkHttpClient();

public void run() throws Exception {
final ProgressListener progressListener = new ProgressListener() {
@Override public void update(long bytesWritten, long contentLength, boolean done) {
System.out.println(bytesWritten);
System.out.println(contentLength);
System.out.println(done);
System.out.format("%d%% done\n", (100 * bytesWritten) / contentLength);
}
};

// Use the imgur image upload API as documented at https://api.imgur.com/endpoints/image
Request request = new Request.Builder()
.header("Authorization", "Client-ID " + IMGUR_CLIENT_ID)
.url("https://api.imgur.com/3/image")
.post(new ProgressRequestBody(
MEDIA_TYPE_PNG,
new File("website/static/logo-square.png"),
progressListener))
.build();

Response response = client.newCall(request).execute();
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

System.out.println(response.body().string());
}

public static void main(String... args) throws Exception {
new UploadProgress().run();
}

private static class ProgressRequestBody extends RequestBody {

private final ProgressListener progressListener;
private final MediaType contentType;
private final File file;

public ProgressRequestBody(MediaType contentType, File file,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you change this so that it wraps another RequestBody? That way the caller can do whatever they want: upload data being generated, a file, or whatever.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wanted to, but from what I've gathered I can't get the upload data out of the request body, so it would have to look like this, file being passed to the RequestBody and again to the ProgressRequestBody so it can be accessed for contentLength() etc.

File file = new File("website/static/logo-square.png");
RequestBody requestBody = RequestBody.create(MEDIA_TYPE_PNG, file);
Request request = new Request.Builder()
  .header("Authorization", "Client-ID " + IMGUR_CLIENT_ID)
  .url("https://api.imgur.com/3/image")
  .post(new ProgressRequestBody(requestBody, file, progressListener)) // Here file is passed a second time
  .build();

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can, but you need to be a bit sneaky. You'd just need to call delegate.writeTo(buffer(progressSink(sink))) in your own writeTo method.

ProgressListener progressListener) {
this.contentType = contentType;
this.file = file;
this.progressListener = progressListener;
}

@Override public MediaType contentType() {
return contentType;
}

@Override public long contentLength() {
return file.length();
}

@Override public void writeTo(BufferedSink sink) throws IOException {
BufferedSink bufferedSink = Okio.buffer(sink(sink));
Source source = Okio.source(file);
bufferedSink.writeAll(source);
source.close();
bufferedSink.close();
}

public Sink sink(Sink sink) {
return new ForwardingSink(sink) {
long totalBytesWritten = 0L;

@Override public void write(Buffer source, long byteCount) throws IOException {
super.write(source, byteCount);
totalBytesWritten += byteCount;
progressListener.update(totalBytesWritten, contentLength(),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this case is asymmetric with the download case, in that you don't get a call to write() for the end of the stream. I think the fix is to add a close() method.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

... (and the close method calls update().)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know I don't get a -1, which is why I test for totalBytesWritten == contentLength().
Sample output, without overriding close():

2048
3740
false
54% done
3740
3740
true
100% done

It seems to work without it, is there a case where overriding close() is absolutely necessary?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. Sometimes you don't know content length in advance.

totalBytesWritten == contentLength());
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(You can look at the PostStreaming example to see an unknown content length in practice.)

}
};
}
}

interface ProgressListener {
void update(long bytesWritten, long contentLength, boolean done);
}
}