I tried for most of the week, and I finally got renewable downloads to run. This does not work as I expected, but it will work.
Do not use the REST API for everyone.
What I found out is that the Google Drive REST API is, as far as I know, really not capable of doing pieces of downloads. It could be a mistake, or it could be by design. I can be too stupid too.
But I thought that I could not see the code examples anywhere. Everyone was talking about Http headers all the time. So this is what we will do below. We will only use headers.
So, how do you do renewable, hosted downloads using the Google Drive and Android REST APIs:
0) Initialization
String accountName = "account_name"; GoogleAccountCredential credential = GoogleAccountCredential.usingOAuth2(context, Arrays.asList(SCOPES)).setBackOff(new ExponentialBackOff()).setSelectedAccountName(accountName);
1) Start a renewable session
Follow the guidelines set forth by Google in this document :
POST /upload/drive/v3/files?uploadType=resumable HTTP/1.1 Host: www.googleapis.com Authorization: Bearer your_auth_token Content-Length: 38 Content-Type: application/json; charset=UTF-8 X-Upload-Content-Type: image/jpeg X-Upload-Content-Length: 2000000 { "name": "My File" }
Set all header fields exactly as in the Google example. Send it as a POST request. Use the credential variable to get the authorization token. The mime type for X-Upload-Content-Type not so important, it works without it ( this SO answer provides a nice function to extract it from the path). Set X-Upload-Content-Length to the total file length. Set Content-Type to JSON, as our body will provide metadata for Google in JSON format.
Now create the body of your metadata. I entered the file name and parent. Set the Content-Length to the length of your body in bytes. Then write your body to the request.getOutputStream() output stream.
URL url = new URL("https://www.googleapis.com/upload/drive/v3/files?uploadType=resumable"); HttpURLConnection request = (HttpURLConnection) url.openConnection(); request.setRequestMethod("POST"); request.setDoInput(true); request.setDoOutput(true); request.setRequestProperty("Authorization", "Bearer " + credential.getToken()); request.setRequestProperty("X-Upload-Content-Type", getMimeType(file.getPath())); request.setRequestProperty("X-Upload-Content-Length", String.format(Locale.ENGLISH, "%d", file.length())); request.setRequestProperty("Content-Type", "application/json; charset=UTF-8"); String body = "{\"name\": \"" + file.getName() + "\", \"parents\": [\"" + parentId + "\"]}"; request.setRequestProperty("Content-Length", String.format(Locale.ENGLISH, "%d", body.getBytes().length)); OutputStream outputStream = request.getOutputStream(); outputStream.write(body.getBytes()); outputStream.close(); request.connect();
2) Save the renewable session URI
Finally, connect() and wait for an answer. If the response code is 200 , you have successfully initiated a restarted, resumed download. Now save the location header URI somewhere (database, text file, whatever). You will need this later.
if (request.getResponseCode() == HttpURLConnection.HTTP_OK) { String sessionUri = request.getHeaderField("location"); }
3) Download the file
PUT {session_uri} HTTP/1.1 Host: www.googleapis.com Content-Length: 524288 Content-Type: image/jpeg Content-Range: bytes 0-524287/2000000 bytes 0-524288
Put the following code in a loop until the entire file is loaded. After each fragment, you will receive a response with the code 308 and the header range . From this range header, you can read the next run of the piece (see (4)).
Content-Type will again be the mime type. Content-Length - the number of bytes loaded into this fragment. Content-Range should be of the form bytes startByte-EndByte/BytesTotal . You put this in a PUT request.
Then you create a FileInputStream and set the position to the start byte (which you got from the last header of the range response) and read the other piece into your buffer. Then this buffer is written to the output stream of the connection. Finally, connect() .
URL url = new URL(sessionUri); HttpURLConnection request = (HttpURLConnection) url.openConnection(); request.setRequestMethod("PUT"); request.setDoOutput(true); request.setConnectTimeout(10000); request.setRequestProperty("Content-Type", getMimeType(file.getPath())); long uploadedBytes = chunkSizeInMb * 1024 * 1024; if (chunkStart + uploadedBytes > file.length()) { uploadedBytes = (int) file.length() - chunkStart; } request.setRequestProperty("Content-Length", String.format(Locale.ENGLISH, "%d", uploadedBytes)); request.setRequestProperty("Content-Range", "bytes " + chunkStart + "-" + (chunkStart + uploadedBytes - 1) + "/" + file.length()); byte[] buffer = new byte[(int) uploadedBytes]; FileInputStream fileInputStream = new FileInputStream(file); fileInputStream.getChannel().position(chunkStart); if (fileInputStream.read(buffer, 0, (int) uploadedBytes) == -1) { } fileInputStream.close(); OutputStream outputStream = request.getOutputStream(); outputStream.write(buffer); outputStream.close(); request.connect();
4) Reply to appeal
After that, you will receive a response with the code 308 (if successful). This answer contains the range header (mentioned).
HTTP/1.1 308 Resume Incomplete Content-Length: 0 Range: bytes=0-524287
You split it and got a new start byte.
String range = chunkUploadConnection.getHeaderField("range"); int chunkPosition = Long.parseLong(range.substring(range.lastIndexOf("-") + 1, range.length())) + 1;
5) The response code is not 308 ?!
It may happen that you get a 5xx response. Your internet connection may fail, the file may be deleted / renamed during download, etc. Etc. Do not worry. As long as you save your session URI and start byte, you can resume downloading at any time.
To do this, submit the header of the following form:
PUT {session_uri} HTTP/1.1 Content-Length: 0 Content-Range: bytes */TotalFileLength URL url = new URL(sessionUri); HttpURLConnection request = (HttpURLConnection) url.openConnection(); request.setRequestMethod("PUT"); request.setDoOutput(true); request.setConnectTimeout(10000); request.setRequestProperty("Content-Length", "0"); request.setRequestProperty("Content-Range", "bytes */" + file.length()); request.connect();
Then you get 308 with the range header, from which you can read the last byte loaded (as we did above). Take this number and start the cycle again.
I hope I can help some of you. If you have more questions, just ask in the comments and I will edit the answer.