How read a file in chunks correctly using Okio?

1.3k Views Asked by At

I'm trying to consume a file upload API that requires the file to be uploaded in chunks of a size in bytes that it specifies from a previous API call. So.... for example if the file is small it will say upload it in chunks of 1MB (in bytes) but it it is big it might say upload it in 10MB chunks and if it is really small it might simply return me the file size indicating that I should upload it all at once. The point is I don't know ahead of time. It also needs me to specify the order of the chunks as a query parameter so I need to keep track of what chunk number I am attempting to upload as I cycle through the chunks of the file.

Anyways I am using Retrofit / OkHttp in my project anyway and thought that this should be a good usecase for Okio. This is what I have at the moment and it seems to work. But I am new to Okio and don't know if I am missing something obvious or if this could be done in a better way.

private void chunkedUpload(int offset, int chunkSize, int blockNumber, @Nonnull CompletableFuture<Foo> future) {
  File file = ...
  String url = ...

  try (final BufferedSource bufferedSource = Okio.buffer(Okio.source(file))) {
    bufferedSource.skip(offset);
    final Buffer buffer = new Buffer();
    final boolean isFinal = !bufferedSource.request(chunkSize);
    bufferedSource.read(buffer, chunkSize);
    final RequestBody requestFile = RequestBody.create(buffer.readByteArray(), null);
    upload(offset, chunkSize, url, requestFile, blockNumber, future, isFinal);
  } catch (IOException e) {
    future.completeExceptionally(e);
  }
}

private void upload(int offset, int chunkSize, @Nonnull String url, @Nonnull RequestBody requestFile, int blockNumber, @Nonnull CompletableFuture<Foo> future, boolean isFinal) {
        fileUloadService.uploadApp(url + "&block_number=" + blockNumber, requestFile)
            .whenComplete((responseBody, throwable) -> {
                if (throwable != null) {
                    future.completeExceptionally(throwable);
                } else {
                    if (isFinal) {
                        future.complete();
                        return;
                    }

                    chunkedUpload(offset + chunkSize, chunkSize, blockNumber + 1, future);
                }
            });
    }

You can see that I am tying upload a chunk, wait for the success response (from the Java 8 CompletableFuture) and then call the chunkedUpload method again with updated offset and blockNumber. Like I said, it works but I'm new to Okio and was wondering if I have missed something obvious?

Why I want to use Okio is pretty much because I'm going to be uploading files typically between 30MB and 1 GB so don't fancy loading all of that into memory at once :).

1

There are 1 best solutions below

0
On

This is my code using Okio and OkHttp to read an image file from the assets folder and send it in the request body as a multipart/form-data.

You basic need to:

  1. Create an OkHttpClient
  2. Create a RequestBody object
  3. Add your RequestBody as a multipart/form-data
  4. Create a Request object to add your RequestBody and Headers to it
  5. Use OkHttpClient to call the request
  6. Add a Callback to your enqueue so you can get back the Response bject

    // Read image file from the assets folder
    BufferedSource img = Okio.buffer(Okio.source(getAssets().open("image.jpg")));
    byte[] image = img.readByteArray();

    // Create the OkHttpClient
    OkHttpClient client = new OkHttpClient().newBuilder().build();

    // Create the request body
    RequestBody rq =  RequestBody.create(MediaType.parse("image/jpeg"),image);

    RequestBody body = new MultipartBody.Builder().setType(MultipartBody.FORM)
            .addFormDataPart("image", "image.jpg", rq)
            .build();

    // Create the request and add the RequestBody to it
    Request request = new Request.Builder()
            .url(YOUR_URL)
            .method("POST", body)
            .addHeader("Authorization", "Bearer " + BEARER_TOKEN)
            .build();

    // Use OkHttpClient to call the request
    client.newCall(request).enqueue(new okhttp3.Callback() {
        @Override
        public void onFailure(okhttp3.Call call, IOException e) {
            Snackbar.make(upload, "Error", Snackbar.LENGTH_SHORT).show();

        }

        @Override
        public void onResponse(okhttp3.Call call, okhttp3.Response response) throws IOException {
            Snackbar.make(upload, "Success", Snackbar.LENGTH_SHORT).show();

        }
    });