/*
* Copyright 2014 Loic Merckel
* Copyright 2014 Dirk Boye
*
* 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.
*/
/*
*
* The original version of this file (i.e., the one that is copyrighted 2014 Dirk Boye)
* can be found here:
*
* https://github.com/dirkboye/GDriveUpload
*
* Massive changes have been made
*
*/
package io.uploader.drive.drive.largefile;
import io.uploader.drive.config.proxy.HasProxySettings;
import io.uploader.drive.drive.DriveUtils;
import io.uploader.drive.drive.DriveUtils.HasDescription;
import io.uploader.drive.drive.DriveUtils.HasId;
import io.uploader.drive.drive.DriveUtils.HasMimeType;
import io.uploader.drive.util.FileUtils.InputStreamProgressFilter;
import java.net.URI;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.client.utils.URIUtils;
import org.apache.http.entity.BufferedHttpEntity;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicHttpRequest;
import org.apache.http.protocol.HTTP;
import org.apache.http.util.EntityUtils;
import org.json.JSONObject;
import org.json.XML;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Preconditions;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URISyntaxException;
public class DriveResumableUpload {
private static final Logger logger = LoggerFactory.getLogger(DriveResumableUpload.class);
private final DriveAuth auth;
private final long fileSize;
private final String location;
private final URI uri;
private final boolean useOldApi;
private final HasProxySettings proxySetting ;
public DriveResumableUpload(HasProxySettings proxySetting, DriveAuth auth,
String uploadLocation, String title, HasDescription description,
HasId parentId, HasMimeType mimeType, String filename,
long fileSize,
InputStreamProgressFilter.StreamProgressCallback progressCallback)
throws IOException, URISyntaxException {
this.auth = auth ;
this.fileSize = fileSize;
this.useOldApi = true;
this.proxySetting = proxySetting ;
if (org.apache.commons.lang3.StringUtils.isEmpty(uploadLocation)) {
this.location = createResumableUpload(title, description, parentId, mimeType);
} else {
this.location = uploadLocation;
}
Preconditions.checkState(StringUtils.isNotEmpty(this.location));
URIBuilder urib = new URIBuilder(location);
uri = urib.build();
//logger.info("URI: " + uri.toASCIIString());
}
public DriveResumableUpload(HasProxySettings proxySetting, DriveAuth auth, String uploadLocation, DriveUtils.HasId fileId, HasMimeType mimeType, String filename,
long fileSize,
InputStreamProgressFilter.StreamProgressCallback progressCallback)
throws IOException, URISyntaxException {
this.auth = auth;
this.useOldApi = true;
this.fileSize = fileSize;
this.proxySetting = proxySetting ;
if (org.apache.commons.lang3.StringUtils.isEmpty(uploadLocation)) {
this.location = createResumableUploadUpdate(fileId, mimeType);
} else {
this.location = uploadLocation;
}
Preconditions.checkState(StringUtils.isNotEmpty(this.location));
URIBuilder urib = new URIBuilder(location);
uri = urib.build();
//logger.info("URI: " + uri.toASCIIString());
}
public String getFileId() throws IOException {
logger.info("Querying file id of completed upload...");
CloseableHttpClient httpclient = null ;
CloseableHttpResponse response = null ;
try {
httpclient = getHttpClient () ;
BasicHttpRequest httpreq = new BasicHttpRequest("PUT", location);
httpreq.addHeader("Authorization", auth.getAuthHeader());
httpreq.addHeader("Content-Length", "0");
httpreq.addHeader("Content-Range", "bytes */" + getFileSizeString());
response = httpclient.execute(URIUtils.extractHost(uri), httpreq);
BufferedHttpEntity entity = new BufferedHttpEntity(response.getEntity());
EntityUtils.consume(response.getEntity());
String retSrc = EntityUtils.toString(entity);
if (useOldApi) {
// Old API will return XML!
JSONObject result = XML.toJSONObject(retSrc);
return result.getJSONObject("entry").getString("gd:resourceId").replace("file:", "") ;
} else {
JSONObject result = new JSONObject(retSrc);
return result.getString("id") ;
}
} finally {
if (response != null) {
response.close();
}
if (httpclient != null) {
httpclient.close();
}
}
}
public boolean checkMD5(String md5) throws IOException {
logger.info("Querying metadata of completed upload...");
Preconditions.checkState(org.apache.commons.lang3.StringUtils.isNotEmpty(md5)) ;
CloseableHttpClient httpclient = null ;
CloseableHttpResponse response = null ;
try {
httpclient = getHttpClient () ;
BasicHttpRequest httpreq = new BasicHttpRequest("PUT", location);
httpreq.addHeader("Authorization", auth.getAuthHeader());
httpreq.addHeader("Content-Length", "0");
httpreq.addHeader("Content-Range", "bytes */" + getFileSizeString());
response = httpclient.execute(URIUtils.extractHost(uri), httpreq);
BufferedHttpEntity entity = new BufferedHttpEntity(response.getEntity());
EntityUtils.consume(response.getEntity());
String retSrc = EntityUtils.toString(entity);
String driveMd5 = null ;
if (useOldApi) {
// Old API will return XML!
JSONObject result = XML.toJSONObject(retSrc);
logger.info("id : " + result.getJSONObject("entry").getString("gd:resourceId").replace("file:", ""));
logger.info("title : " + result.getJSONObject("entry").getString("title"));
logger.info("link : " + result.getJSONObject("entry").getJSONArray("link").getJSONObject(0).getString("href"));
logger.info("md5Checksum : " + result.getJSONObject("entry").getString("docs:md5Checksum"));
driveMd5 = result.getJSONObject("entry").getString("docs:md5Checksum") ;
} else {
JSONObject result = new JSONObject(retSrc);
logger.info("id : " + result.getString("id"));
logger.info("title : " + result.getString("title"));
logger.info("link : " + result.getString("webContentLink"));
logger.info("md5Checksum : " + result.getString("md5Checksum"));
driveMd5 = result.getString("md5Checksum") ;
}
// verify the consistency of the md5 values
return md5.equals(driveMd5) ;
} finally {
if (response != null) {
response.close();
}
if (httpclient != null) {
httpclient.close();
}
}
}
public boolean updateAccessToken() throws UnsupportedEncodingException, IOException {
return auth.updateAccessToken();
}
private String getFileSizeString() {
return Long.toString(fileSize);
}
public String getLocation() {
return location;
}
private CloseableHttpClient getHttpClient () {
return HttpClientUtils.getHttpClient(proxySetting) ;
}
public long getCurrentByte() throws IOException {
logger.info("Querying status of resumable upload...");
CloseableHttpClient httpclient = null ;
CloseableHttpResponse response = null ;
long lastbyte = -1;
try {
httpclient = getHttpClient () ;
BasicHttpRequest httpreq = new BasicHttpRequest("PUT", location);
httpreq.addHeader("Authorization", auth.getAuthHeader());
httpreq.addHeader("Content-Length", "0");
httpreq.addHeader("Content-Range", "bytes */" + getFileSizeString());
//logger.info(httpreq.toString());
response = httpclient.execute(URIUtils.extractHost(uri), httpreq);
@SuppressWarnings("unused")
BufferedHttpEntity entity = new BufferedHttpEntity(response.getEntity());
EntityUtils.consume(response.getEntity());
if (response.getStatusLine().getStatusCode() == 200 || response.getStatusLine().getStatusCode() == 201) {
lastbyte = fileSize;
}
if (response.getStatusLine().getStatusCode() == 308) {
if (response.getHeaders("Range").length > 0) {
String range = response.getHeaders("Range")[0].getValue();
String[] parts = range.split("-");
lastbyte = Long.parseLong(parts[1]) + 1;
} else {
// nothing uploaded, but file is there to start upload!
lastbyte = 0;
}
}
return lastbyte;
} finally {
if (response != null) {
response.close();
}
if (httpclient != null) {
httpclient.close();
}
}
}
public int uploadChunk(byte[] bytecontent, long start_range, int bytes_in_array) throws IOException {
logger.info(String.format("% 5.1f%% complete. Uploading next chunk.", start_range*100.0/fileSize));
String byterange = "bytes " + Long.toString(start_range) + "-" +
Long.toString(start_range+bytes_in_array-1) + "/" + getFileSizeString();
if (start_range+bytes_in_array-1 >= fileSize) {
logger.info("Trying to push more than remaining bytes. Aborting.");
throw new RuntimeException () ;
}
CloseableHttpClient httpclient = null ;
CloseableHttpResponse response = null ;
int status_code = 420 ;
try {
logger.info("Uploading " + byterange);
httpclient = getHttpClient () ;
HttpPut httpPut = new HttpPut(location);
httpPut.addHeader("Authorization", auth.getAuthHeader());
httpPut.addHeader("Content-Range", byterange);
if (bytes_in_array != bytecontent.length) {
logger.info("Seems to be the last part of the file.");
byte[] contentpart = new byte[bytes_in_array];
for (int i=0; i<bytes_in_array;++i) {
contentpart[i] = bytecontent[i];
}
httpPut.setEntity(new ByteArrayEntity(contentpart));
} else {
httpPut.setEntity(new ByteArrayEntity(bytecontent));
}
response = httpclient.execute(httpPut);
@SuppressWarnings("unused")
BufferedHttpEntity entity = new BufferedHttpEntity(response.getEntity());
EntityUtils.consume(response.getEntity());
status_code = response.getStatusLine().getStatusCode();
return status_code;
} finally {
if (response != null) {
response.close();
}
if (httpclient != null) {
httpclient.close();
}
}
}
// https://developers.google.com/gdata/docs/resumable_upload
private String createResumableUpload(String title, HasDescription description,
HasId parentId, HasMimeType mimeType) throws IOException {
logger.info("Creating resumable upload...");
String postUri = "https://www.googleapis.com/upload/drive/v2/files?uploadType=resumable";
if (useOldApi) {
postUri = "https://docs.google.com/feeds/upload/create-session/default/private/full?convert=false";
if (parentId != null
&& org.apache.commons.lang3.StringUtils.isNotEmpty(parentId.getId())) {
// https://developers.google.com/google-apps/documents-list/
postUri = "https://docs.google.com/feeds/upload/create-session/default/private/full"
+ "/folder%3A"
+ parentId.getId()
+ "/contents"
+ "?convert=false";
}
} else {
// TODO: new api
// ...
throw new IllegalStateException ("Not implemented") ;
}
CloseableHttpClient httpclient = null ;
CloseableHttpResponse response = null ;
try {
httpclient = getHttpClient () ;
HttpPost httpPost = new HttpPost(postUri);
httpPost.addHeader("Authorization", auth.getAuthHeader());
httpPost.addHeader("X-Upload-Content-Type", mimeType.getMimeType());
httpPost.addHeader("X-Upload-Content-Length", getFileSizeString());
String entityString = new JSONObject().put("title", title).toString();
BasicHeader entityHeader = new BasicHeader(HTTP.CONTENT_TYPE, "application/json");
if (useOldApi) {
StringBuilder sb = new StringBuilder () ;
sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>") ;
sb.append("<entry xmlns=\"http://www.w3.org/2005/Atom\" xmlns:docs=\"http://schemas.google.com/docs/2007\">") ;
sb.append ("<category scheme=\"http://schemas.google.com/g/2005#kind\" term=\"http://schemas.google.com/docs/2007#document\"/>") ;
// title
sb.append ("<title>").append(title).append("</title>") ;
// description
if (description != null
&& org.apache.commons.lang3.StringUtils.isNotEmpty(description.getDescription())) {
sb.append ("<docs:description>").append (description.getDescription()).append ("</docs:description>") ;
}
sb.append ("</entry>") ;
entityString = sb.toString() ;
httpPost.addHeader("GData-Version","3");
entityHeader = new BasicHeader(HTTP.CONTENT_TYPE, "application/atom+xml");
} else {
// TODO: new api
// ...
throw new IllegalStateException ("Not implemented") ;
}
StringEntity se = new StringEntity( entityString );
se.setContentType(entityHeader);
httpPost.setEntity(se);
//logger.info("Create Resumable: " + httpPost.toString());
response = httpclient.execute(httpPost);
@SuppressWarnings("unused")
BufferedHttpEntity entity = new BufferedHttpEntity(response.getEntity());
EntityUtils.consume(response.getEntity());
String location = "";
if (response.getStatusLine().getStatusCode() == 200) {
location = response.getHeaders("Location")[0].getValue();
//logger.info("Location: " + location);
}
return location;
} finally {
if (response != null) {
response.close();
}
if (httpclient != null) {
httpclient.close();
}
}
}
private String getResumableUploadUpdateUri (DriveUtils.HasId fileId) throws IOException {
String getUri = "https://www.googleapis.com/upload/drive/v2/files";
if (useOldApi) {
StringBuilder sb = new StringBuilder () ;
sb.append ("https://docs.google.com/feeds/default/private/full/") ;
sb.append (fileId.getId()) ;
getUri = sb.toString() ;
} else {
// TODO: new api
// ...
throw new IllegalStateException ("Not implemented") ;
}
CloseableHttpClient httpclient = null ;
CloseableHttpResponse response = null ;
String putUri = null ;
try {
httpclient = getHttpClient () ;
HttpGet httpGet = new HttpGet(getUri);
httpGet.addHeader("Authorization", auth.getAuthHeader());
httpGet.addHeader("GData-Version","3");
response = httpclient.execute(httpGet);
BufferedHttpEntity entity = new BufferedHttpEntity(response.getEntity());
String contents = IOUtils.toString(entity.getContent(), "UTF8") ;
String strBegin = "#resumable-edit-media" ;
String uriBegin = "href='" ;
String uriEnd = "'/>" ;
int index = contents.indexOf(strBegin) ;
if (index < 0) {
return null ;
}
contents = contents.substring(index + strBegin.length()) ;
index = contents.indexOf(uriBegin) ;
if (index < 0) {
return null ;
}
contents = contents.substring(index + uriBegin.length()) ;
index = contents.indexOf(uriEnd) ;
if (index < 0) {
return null ;
}
contents = contents.substring(0, index) ;
EntityUtils.consume(response.getEntity());
putUri = contents ;
//logger.info("Upload update uri: " + putUri);
return putUri ;
} finally {
if (response != null) {
response.close();
}
if (httpclient != null) {
httpclient.close();
}
}
}
private String createResumableUploadUpdate(DriveUtils.HasId fileId, HasMimeType mimeType) throws IOException {
logger.info("Creating update resumable upload...");
Preconditions.checkArgument(fileId != null);
Preconditions.checkArgument(StringUtils.isNotEmpty(fileId.getId()));
CloseableHttpClient httpclient = null ;
CloseableHttpResponse response = null ;
// https://developers.google.com/google-apps/documents-list/#updatingchanging_documents_and_files
try {
String putUri = "https://www.googleapis.com/upload/drive/v2/files?uploadType=resumable";
if (useOldApi) {
putUri = getResumableUploadUpdateUri (fileId) ;
//putUri = "https://docs.google.com/feeds/upload/create-session/default/private/full/file%3A";
//putUri = putUri + fileId.getId() ;
} else {
// TODO: new api
// ...
throw new IllegalStateException ("Not implemented") ;
}
httpclient = getHttpClient () ;
HttpPut httpPut = new HttpPut(putUri);
httpPut.addHeader("Authorization", auth.getAuthHeader());
httpPut.addHeader("If-Match", "*");
httpPut.addHeader("X-Upload-Content-Type", mimeType.getMimeType());
httpPut.addHeader("X-Upload-Content-Length", getFileSizeString());
httpPut.addHeader("GData-Version","3");
//logger.info("Create Update Resumable Upload: " + httpPut.toString());
response = httpclient.execute(httpPut);
@SuppressWarnings("unused")
BufferedHttpEntity entity = new BufferedHttpEntity(response.getEntity());
EntityUtils.consume(response.getEntity());
String location = "";
if (response.getStatusLine().getStatusCode() == 200) {
location = response.getHeaders("Location")[0].getValue();
//logger.info("Location: " + location);
}
return location;
} finally {
if (response != null) {
response.close();
}
if (httpclient != null) {
httpclient.close();
}
}
}
}