3

I need help in writing multipart & resumable upload of large files (>5MB) so far I have only been able to start an multiupload upload but I don't know how to resume it either when the user pauses it or during network failures.

By "Resuming" I mean I don't know how to

1) get the total bytes already uploaded to the drive

2) how to use that value in the Content-Range Header

3) how to even pause this upload by user interaction[executeAsInputStream() Maybe?]

This is what I have done so far. I need the code to resume from where it stopped uploading even if I were to forcefully stop the application and restart it

   Drive service = GDrive.getService(); //Drive Specific Initialization Copied From QuickStart But with DriveScopes.FILES

   File fileMetadata = new File();
   fileMetadata.setName("Video.mp4"); //Video File
   fileMetadata.setMimeType("application/vnd.google-apps.video");

   java.io.File filePath = new java.io.File("E:\\large-file-60MB.mp4");//Large File Of 60 Mega Bytes
   FileContent mediaContent = new FileContent("video/mp4",filePath);

   Drive.Files.Create create=service.files().create(fileMetadata,mediaContent);

   MediaHttpUploader uploader=create.getMediaHttpUploader();
   uploader.setDirectUploadEnabled(false);                       //Use Resumable MultiPart Upload Protocol
   uploader.setChunkSize(2*MediaHttpUploader.MINIMUM_CHUNK_SIZE); //Chunks Of Bytes To Upload With Each Request

  // HttpHeaders headers=new HttpHeaders();
  // headers.put("Content-Range",?);          //This is not actual code which I used here but after reading the drive docs they talk about this header and I am not sure how or when to use it
  // uploader.setInitiationHeaders(headers);

   uploader.setProgressListener((uploading)->
   {
    switch (uploading.getUploadState())
    {
      case INITIATION_STARTED:System.out.println("Initiation has started!");
      break;
      case INITIATION_COMPLETE:System.out.println("Initiation is complete!");
      break;
      case MEDIA_IN_PROGRESS:
      System.out.println("Progress="+uploading.getProgress());
      System.out.println("Bytes="+uploading.getNumBytesUploaded());
      break;
      case MEDIA_COMPLETE:System.out.println("Upload is complete!");
    }
   });

   create.execute(); 
Sync it
  • 1,180
  • 2
  • 11
  • 29
  • Does this answer your question? [Google Drive api Java - is my large multipart upload complete?](https://stackoverflow.com/questions/59794970/google-drive-api-java-is-my-large-multipart-upload-complete) – Rafa Guillermo Jun 01 '20 at 09:15
  • @ Rafa Guillermo the code in my question was taken from your link. It explains how to do multipart upload but it dosen't explain Two Things. 1) How to pause/cancel the upload with user interaction 2) How to resume upload from where my app stopped if I were to forcefully terminate the app or cancel the upload(or even how to cancel?) – Sync it Jun 01 '20 at 13:04
  • You should break your question down into two questions: how to resume an upload and how to pauce the upload via user interaction. These are two distinct questions and would be better answered separately. Refer to the [how to ask](https://stackoverflow.com/help/how-to-ask) guidelines when posting questions. – Rafa Guillermo Jun 01 '20 at 13:17
  • Though, it is worth taking a look at the [upload file data](https://developers.google.com/drive/api/v3/manage-uploads#uploading) documentation where it specifys how to use the `Content-Range` header. (Add a `Content-Range` header to indicate that the current position in the file is unknown. For example, set the `Content-Range` to `*/2000000` if your total file length is 2,000,000 bytes. If you don't know the full size of the file, set the `Content-Range` to `*/*`.) – Rafa Guillermo Jun 01 '20 at 13:18
  • Yes you are right about breaking my question into 2 parts but maybe the answer's could be very short and easily answered in code. About Content-Range header that's what I have posted in my code . I have read the documentation about it and am not sure how to pass these values into my upload they just explain it using their HttpClient but not using V3 library. – Sync it Jun 01 '20 at 14:00
  • What is the issue with using the HttpClient? The documentation is pretty extensive explaining [how to do this](https://developers.google.com/drive/api/v3/manage-uploads#send_the_initial_request) and it says [here](https://developers.google.com/api-client-library/java/google-api-java-client/media-upload#implementation) that you can use the resumable media upload feature without the service-specific generated libraries. – Rafa Guillermo Jun 02 '20 at 10:11

1 Answers1

4

While answering multiple questions in one answer isn't normally appropriate for Stack Overflow, it seems that these are all closely linked and so will give an overview of resumable uploads and in doing so attempt to address your three points:

  • How does one get total bytes already uploaded to Drive
  • How to use the value in the Content-Range Header
  • How to pause a resumable upload

From Google's documentation on Direct and Resumable Media Uploads on ther Java API Client Library documentation:

Implementation details

The main classes of interest are MediaHttpUploader and MediaHttpProgressListener.

If methods in the service-specific generated libraries contain the mediaUpload parameter in the Discovery document, then a convenience method is created for these methods that takes an InputStreamContent as a parameter.

For example, the insert method of the Drive API supports mediaUpload, and you can use the following code to upload a file:

class CustomProgressListener implements MediaHttpUploaderProgressListener {
  public void progressChanged(MediaHttpUploader uploader) throws IOException {
    switch (uploader.getUploadState()) {
      case INITIATION_STARTED:
        System.out.println("Initiation has started!");
        break;
      case INITIATION_COMPLETE:
        System.out.println("Initiation is complete!");
        break;
      case MEDIA_IN_PROGRESS:
        System.out.println(uploader.getProgress());
        break;
      case MEDIA_COMPLETE:
        System.out.println("Upload is complete!");
    }
  }
}

File mediaFile = new File("/tmp/driveFile.jpg");
InputStreamContent mediaContent =
    new InputStreamContent("image/jpeg",
        new BufferedInputStream(new FileInputStream(mediaFile)));
mediaContent.setLength(mediaFile.length());

Drive.Files.Insert request = drive.files().insert(fileMetadata, mediaContent);
request.getMediaHttpUploader().setProgressListener(new CustomProgressListener());
request.execute();

These classes however abstract away things like the location URI which is returned when creating the resumable upload, so if you want to be able to do this then you will need to follow the resumable upload initiation steps as documented here. This is all manually done however rather than directly using the Google Drive API Client Library.

To answer the first point, how you store how many bytes have been uploaded is up to you. Rather than thinking "how much is already on Drive", think "how much have I already uploaded?".

You can store this as a vairable locally if you so desire, as it will be a multiple of your chunk size (2 * MediaHttpUploader.MINIMUM_CHUNK_SIZE in your case) and should be easy to track.

The thing is, this isn't actually needed. You can just use a wildcard to indicate that the current position of your file is unknown, as per the documentation (emphasis my own):

If an upload request is terminated before a response, or if you receive a 503 Service Unavailable response, then you need to resume the interrupted upload.

To request the upload status, create an empty PUT request to the resumable session URI.

Add a Content-Range header to indicate that the current position in the file is unknown. For example, set the Content-Range to */2000000 if your total file length is 2,000,000 bytes. If you don't know the full size of the file, set the Content-Range to */*.

If you do want to keep track of the bytes, you can specify it in your Content-Range header as

Content-Range: bytes_so_far/total_bytes

Steps:

To initialise the resumable upload, you need to make a POST request to the /upload endpoint of the Drive API. You do not need to use the Drive API client library for this (and actually if you want to get the resumable session URI, you can't as the client library doesn't give you this).

Assuming you have your credential definition from:

GoogleAccountCredential credential = GoogleAccountCredential.usingOAuth2(...);

Then make the POST request containing the file metadata:

URL requestUrl = new URL("https://www.googleapis.com/upload/drive/v3/files?uploadType=resumable");

String requestBody = "{\"name\": \"fileName\"}";

HttpURLConnection request = (HttpURLConnection) requestUrl.openConnection();

request.setRequestMethod("POST");
request.setDoInput(true);
request.setDoOutput(true);
request.setRequestProperty("Authorization", "Bearer " + credential.getToken());
request.setRequestProperty("X-Upload-Content-Type", "file/mimetype");
request.setRequestProperty("X-Upload-Content-Length", number_of_bytes_of_your_file);
request.setRequestProperty("Content-Type", "application/json; charset=UTF-8");
request.setRequestProperty("Content-Length", String.format(Locale.ENGLISH, "%d", requestBody.getBytes().length));

OutputStream outputStream = request.getOutputStream();
outputStream.write(requestBody.getBytes());
outputStream.close();

request.connect();

The session URI - where to call to resume in case you need to - is returned in the headers of the response from the API. After connecting you can get this URI from the response:

if (request.getResponseCode() == HttpURLConnection.HTTP_OK) {
    URL sessionUri = new URL(request.getHeaderField("location"));
}

Now you have the Session URI - with this you can upload chunks of file to Drive as you please. You now need to use this URI as the upload point for successive uploads.

Remember though: A resumable session URI expires after one week.

How to Pause a Resumable Upload:

This in reality is down to how you wish to implement this. You could break out a loop for example or have a giant PAUSE THIS UPLOAD button in a GUI which toggles whether the next section of the upload continues or not.

The thing to remember is when uploading the file contents, the request made has to be done with HTTP PUT rather than POST. Following on from the previous section:

// set these variables:
long beginningOfChunk = 0;
long chunkSize = 2 * MediaHttpUploader.MINIMUM_CHUNK_SIZE;
int chunksUploaded = 0;

// Here starts the upload chunk code:
HttpURLConnection request = (HttpURLConnection) sessionUri.openConnection();

request.setRequestMethod("PUT");
request.setDoOutput(true);
// change your timeout as you desire here:
request.setConnectTimeout(30000); 
request.setRequestProperty("Content-Type", "file/mimetype");

long bytesUploadedSoFar = chunksUploaded * chunkSize;

if (beginningOfChunk + chunkSize > number_of_bytes_of_your_file) {
    chunkSize = (int) number_of_bytes_of_your_file - beginningOfChunk;
}

request.setRequestProperty("Content-Length", String.format(Locale.ENGLISH, "%d", chunkSize));
request.setRequestProperty("Content-Range", "bytes " + beginningOfChunk + "-" + (beginningOfChunk + chunkSize - 1) + "/" + number_of_bytes_of_your_file);

byte[] buffer = new byte[(int) chunksize];
FileInputStream fileInputStream = new FileInputStream(yourFile);
fileInputStream.getChannel().position(beginningOfChunk);
fileInputStream.close();

OutputStream outputStream = request.getOutputStream();
outputStream.write(buffer);
outputStream.close();
request.connect();

chunksUploaded += 1;

// End of upload chunk section

You can then call the upload chunk code on repeat; in a loop, as a function; how you like. As it is a distinct code block, you can call it as you like and therefore implement some way of pausing the upload (through breaks, sleeps, waits, etc).

Just remember: you will need to save the session URI in order to resume.


Update:

It seems that using the Drive V3 API directly to make resumable uploads isn't something yet possible. The Java Client Library documentation alludes to this when discussing when to use Drive: create vs the non service-specific libraries:

...the insert method of the Drive API supports mediaUpload, and you can use the following code to upload a file:

code block

You can also use the resumable media upload feature without the service-specific generated libraries.

Feature Request:

You can however let Google know that this is a feature that is important for the Drive API directly rather than the requirement to use the non-service specific library. Google's Issue Tracker is a place for developers to report issues and make feature requests for their development services. The page to file a Feature Request for the Drive API is here.

Note of discrepancy: what was called Drive.Files.Insert in Drive API V2 was changed to Drive.Files.Create in Drive API V3.

References:


Related Questions:

Nimantha
  • 6,405
  • 6
  • 28
  • 69
Rafa Guillermo
  • 14,474
  • 3
  • 18
  • 54
  • I have already implemented your answer a while back using Http and it works perfectly. I had hoped that the V2/V3 library would have reduce this large amount of code by now. The api provides resumability for downloads using the MediaHttpDownloader and using the executeAsInputStream() I can even cancel/pause the downloads. But options are limited for the uploader – Sync it Jun 02 '20 at 19:01
  • I also heard that V3 uploader has the uploadTo(GenericUri) method and the drive has an httpRequestFactory() and httpheaders class. Is there as example using these classes for upload anywhere? – Sync it Jun 02 '20 at 19:03
  • @RVISHAL AFAIK neither [MediaHttpUploader](https://googleapis.dev/java/google-api-client/latest/com/google/api/client/googleapis/media/MediaHttpUploader.html) nor [Drive API](https://developers.google.com/s/results/drive/api/v3/about-sdk?q=%22uploadTo%22) have a direct `uploadTo(genericUri)` method, do you have a source for this? – Rafa Guillermo Jun 03 '20 at 08:03
  • As far as I am aware this is the method of resumable uploading supported by Google (as it says [here](https://developers.google.com/api-client-library/java/google-api-java-client/media-upload#implementation) `the insert method of the Drive API supports mediaUpload... You can also use the resumable media upload feature without the service-specific generated libraries` – Rafa Guillermo Jun 03 '20 at 08:03
  • @ Rafa Guillermo https://googleapis.dev/java/google-api-client/latest/com/google/api/client/googleapis/media/MediaHttpUploader.html#upload-com.google.api.client.http.GenericUrl- – Sync it Jun 03 '20 at 09:58
  • Thanks for your answer I would have marked it as the accepted one but as you have already provided related posts linking to the same answer we know it's atleast an half a decade old answer. what I am looking for is the same approach but using the v2/v3 library – Sync it Jun 03 '20 at 10:03
  • @RVISHAL The `upload(generic URI)` method of `MediaHttpUploader`isn't a method of Drive V3, it's of the [Java client library](https://github.com/googleapis/google-api-java-client). The reason that there haven't been any other ways of doing this for the last decade is because there aren't any; the documentation even alludes to this with the wording on the upload media page: "the insert method of the Drive API supports mediaUpload". – Rafa Guillermo Jun 03 '20 at 10:23
  • Sorry I've been unable to be more helpful. I suggest making a feature request on Google's Issue Tracker for the [Drive API](https://issuetracker.google.com/issues/new?component=191650&template=824106) to make this directly usable via V3. – Rafa Guillermo Jun 03 '20 at 10:24
  • Ahh I see had my hopes high oh well no loss in asking :). Files.Insert was deprecated and replaced with Files.Create(https://stackoverflow.com/questions/48085032/google-drive-api-drive-files-class-has-no-insert-method) but still doesn't serve my purpose. thank you for the response though – Sync it Jun 03 '20 at 10:32
  • @RVISHAL No worries, sorry that it's not what you were hoping for! I compiled this information and updated the answer for completion's sake. – Rafa Guillermo Jun 03 '20 at 10:39
  • I will commend you for the details and related links you have put in your answer really paints the whole picture for anyone googling this question for the first time. Hopefully someone in the dev team will see this post and make improvements :). U get an upvote from me – Sync it Jun 03 '20 at 11:04
  • @RVISHAL I would really urge you to make a feature request rather than hope a dev sees the question and decides to implement it on their own accord - I think the devs of the API are the least likely people to search for how to use them ;) – Rafa Guillermo Jun 03 '20 at 11:07
  • Haa can't be to hopeful my friend. There were hundreds of people before me who made the same feature request ages ago and I too have made one as you have requested but 10 years later still nothing :D – Sync it Jun 03 '20 at 11:41
  • You forgot a line: "fileInputStream.read(buffer);" needs to go right before "fileInputStream.close()". If you followed this post and you're wondering why all the files you upload are corrupt, well, they're actually just empty because you've been writing a steady stream of 0s to the outputStream! Nonetheless, infinite thanks to Rafa Guillermo for seemingly being the only person in the world to give a full example of how to do this! – Gavin Wright Sep 22 '21 at 20:24