java谷歌云端硬盘API V3多部分和可恢复上传

huangapple go评论73阅读模式
英文:

java google drive api V3 MultiPart & Resumable Upload

问题

我需要帮助编写大文件(>5MB)的多部分和可续传上传,到目前为止,我只能开始一个多部分上传,但我不知道如何在用户暂停它或在网络故障期间恢复它。

通过“续传”,我指的是我不知道如何:

1)获取已上传到驱动器的总字节数

2)如何在Content-Range标头中使用该值

3)如何通过用户交互甚至暂停此上传[executeAsInputStream() Maybe?]

到目前为止,我已经做了以下工作。我需要代码从停止上传的地方继续上传,即使我强制停止应用程序并重新启动它。

Drive service = GDrive.getService(); // 从QuickStart复制的Drive特定初始化,但使用DriveScopes.FILES

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

java.io.File filePath = new java.io.File("E:\\large-file-60MB.mp4"); // 大文件,60兆字节
FileContent mediaContent = new FileContent("video/mp4", filePath);

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

MediaHttpUploader uploader = create.getMediaHttpUploader();
uploader.setDirectUploadEnabled(false); // 使用可续传的多部分上传协议
uploader.setChunkSize(2 * MediaHttpUploader.MINIMUM_CHUNK_SIZE); // 每个请求上传的字节块

// HttpHeaders headers = new HttpHeaders();
// headers.put("Content-Range", ?); // 这不是我在此处使用的实际代码,但在阅读驱动器文档后,它们提到了这个标头,我不确定如何或何时使用它
// uploader.setInitiationHeaders(headers);

uploader.setProgressListener((uploading) -> {
    switch (uploading.getUploadState()) {
        case INITIATION_STARTED:
            System.out.println("初始化已开始!");
            break;
        case INITIATION_COMPLETE:
            System.out.println("初始化已完成!");
            break;
        case MEDIA_IN_PROGRESS:
            System.out.println("进度=" + uploading.getProgress());
            System.out.println("已上传字节=" + uploading.getNumBytesUploaded());
            break;
        case MEDIA_COMPLETE:
            System.out.println("上传已完成!");
    }
});

create.execute();

希望这能帮助你在上传大文件时实现暂停和续传功能。

英文:

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(); 

答案1

得分: 4

虽然在Stack Overflow上一次回答多个问题通常是不合适的,但这些问题似乎都密切相关,所以我将概述可续传上传,并在此过程中尝试解决您的三个问题:

  • 如何获取已上传到Drive的总字节数
  • 如何使用Content-Range标头中的值
  • 如何暂停可续传上传

来自Google关于Direct and Resumable Media Uploads的文档,详细实现细节如下:

主要感兴趣的类是MediaHttpUploaderMediaHttpProgressListener

如果服务特定的生成库中的方法在Discovery文档中包含mediaUpload参数,则会为这些方法创建一个便捷方法,该方法以InputStreamContent作为参数。

例如,Drive API的insert方法支持mediaUpload,您可以使用以下代码上传文件:

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();

然而,这些类抽象了诸如在创建可续传上传时返回的位置URI等内容,因此,如果您希望能够执行此操作,则需要按照此处记录的可续传上传启动步骤进行操作。但是,这完全是手动完成的,而不是直接使用Google Drive API客户端库。

为了回答第一个问题,您如何存储已上传的字节数取决于您。与其思考“已经在Drive上有多少”,不如思考“我已经上传了多少”。

如果您希望,可以在本地存储此值,因为它将是您的分块大小(在您的情况下为2 * MediaHttpUploader.MINIMUM_CHUNK_SIZE)的倍数,应该很容易跟踪。

但事实是,实际上不需要这个值。您可以只使用通配符表示文件的当前位置是未知的,如文档中所述(重点是我的):

如果上传请求在收到响应之前终止,或者收到503 Service Unavailable响应,那么您需要恢复中断的上传。

要请求上传状态,请创建一个空的PUT请求到可续传会话URI。

添加Content-Range标头,以指示文件中的当前位置是未知的。例如,如果您的文件总长度为2,000,000字节,则将Content-Range设置为*/2000000。如果您不知道文件的完整大小,请将Content-Range设置为*/*

如果您确实想跟踪字节数,可以在Content-Range标头中指定它:

Content-Range: bytes_so_far/total_bytes

步骤:

要初始化可续传上传,您需要向Drive API的/upload端点发出POST请求。您不需要使用Drive API客户端库进行此操作(实际上,如果您想获取可续传会话URI,您不能使用客户端库,因为客户端库不会提供这个)。

假设您有来自以下位置的凭据定义:

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

然后进行包含文件元数据的POST请求:

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();

会话URI - 需要在需要时调用以恢复 - 在来自API的响应标头中返回。在连接后,您可以从响应中获取此URI:

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

现在您有了会话URI - 使用此URI,您可以根据需要将文件块上传到Drive。现在您需要将此URI用作连续上传的上传点。

但请记住:可续传会话URI在一周后过期。

如何暂停可续传上传:

实际上,这取决于您如何实现它。例如,您可以中断循环,或者在GUI中有一个巨大的“暂停此上传”按钮,切换下一个上传部分是否继续。

需要记住的是,在上传文件内容时,所进行的请求必

英文:

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:


huangapple
  • 本文由 发表于 2020年5月30日 20:54:08
  • 转载请务必保留本文链接:https://go.coder-hub.com/62102721.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定