如何使用 Apps 脚本将 Drive 中的现有文件上传到 Drive

5 google-apps-script google-drive-api

介绍

让我先介绍一下我尝试做的目标。

  • 我之前有一个文件分成两部分

  • 这两个文件的总大小可能超过 50 MB(作为长期目标)。由于UrlFetchApp.fetch()对请求的大小有限制,我想分别上传它们,其中每个文件将小于 50 MB,因此合并它们。现在(尝试 Drive API),我使用的是小文件。

  • 第一个文件是(的倍数)。我意识到我之前犯了一个错误,即我使用文件的大小作为 256 的倍数,但它应该是 256 的倍数 640000 bytes256 524288 bytes256*1024

  • 第二个文件是.47626 bytes 163339 bytes

  • 我使用拆分文件curl并将它们上传到我的驱动器(正常的网络上传)。

  • 我的目的是使用from将它们partial files一个一个地上Resumable Upload传到 Google Drive Google Drive APIGoogle Apps Script以便它们可以合并到一个文件中。

到目前为止我尝试过什么?

  • 昨天,我在这里问了一个问题。我试图执行resumable upload使用Drive.Files.insert,用户指出无法使用Drive.Files.insert下面引用的内容。

遗憾的是,现阶段无法使用Drive.Files.insert实现可续传。似乎这是谷歌方面的当前规范

  • 我现在正在尝试的是使用Google Drive API. 下面附上代码。
function myFunction() {
    var token = ScriptApp.getOAuthToken();

    var f1_id = '1HkBDHV1oXXXXXXXXXXXXXXXXXXXXXXXX';
    var f2_id = '1twuaKTCFTXXXXXXXXXXXXXXXXXXXX';
    
    var putUrl = 'https://www.googleapis.com/drive/v3/files?uploadType=resumable';
  
    var fileData = {
        name : 'Merged-file-from-GAS',
        file : DriveApp.getFileById(f1_id).getBlob()
    }
    
    var options = {
      method : 'put',
      contentType:"application/json",
      headers : {
        Authorization: 'Bearer ' + token,
        'X-Upload-Content-Type' : 'application/octet-stream',
        'Content-Type' : 'application/json; charset=UTF-8'
      },
      muteHttpExceptions: true,
      payload : fileData
    };
  
    var response = UrlFetchApp.fetch(putUrl, options);
    Logger.log(response.getResponseCode());
    Logger.log(response.getAllHeaders()); 
}

Run Code Online (Sandbox Code Playgroud)
  • 我也尝试将方法更改为 patch

  • Content-Length : 640000在里面添加headers,在这种情况下,我收到如下提供的错误。

Exception: Attribute provided with invalid value: Header:Content-Length

  • I tried to create a file using Drive.Files.insert(resource) using blank resource. Then I tried to update it using UrlFetchApp(patchUrl,options) while having the variable var patchUrl = 'https://www.googleapis.com/upload/drive/v3/files/' + fileId + '?uploadType=resumable';

Result

  • It does not create any file.
  • Logger logs for the result of the attached code (initial code) are provided below:

[20-05-12 21:05:37:726 IST] 404.0

[20-05-12 21:05:37:736 IST] {X-Frame-Options=SAMEORIGIN, Content-Security-Policy=frame-ancestors 'self', Transfer-Encoding=chunked, alt-svc=h3-27=":443"; ma=2592000,h3-25=":443"; ma=2592000,h3-Q050=":443"; ma=2592000,h3-Q049=":443"; ma=2592000,h3-Q048=":443"; ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443"; ma=2592000,quic=":443"; ma=2592000; v="46,43", X-Content-Type-Options=nosniff, Date=Tue, 12 May 2020 15:35:37 GMT, Expires=Mon, 01 Jan 1990 00:00:00 GMT, X-XSS-Protection=1; mode=block, Content-Encoding=gzip, Pragma=no-cache, Cache-Control=no-cache, no-store, max-age=0, must-revalidate, Vary=[Origin, X-Origin], Server=GSE, Content-Type=text/html; charset=UTF-8}

Question

  • What is the proper way of initiating a upload of a file in Drive to Drive using Drive API from Apps Script while keeping the upload type as resumable?

  • What should subsequent requests be like? So that files above 50 MB can be subsequently uploaded to the merged file?

Edit 1

Tried it again using corrected file chunks sizes. Same problem persists.

Edit 2

To understand the code in the answer, I used the code in // 2 of Tanaike's code alone to understand how Location is retrieved.

function understanding() {
  var token = ScriptApp.getOAuthToken();
  const filename = 'understanding.pdf';
  const mimeType = MimeType.PDF;

  const url = 'https://www.googleapis.com/drive/v3/files?uploadType=resumable';
  
  const res1 = UrlFetchApp.fetch(url, {
    method: "post",
    contentType: "application/json",
    payload: JSON.stringify({name: filename, mimeType: mimeType}),
    headers: {authorization: "Bearer " + ScriptApp.getOAuthToken()
  }});
  const location = res1.getHeaders().Location;
  Logger.log(location);
}
Run Code Online (Sandbox Code Playgroud)

This creates a file understanding.pdf of size 0 bytes. However, Logger.log(location) logs null.

Why is it so?

The mistake was in the end point. Setting it to https://www.googleapis.com/upload/drive/v3/files?uploadType=resumable works. It retrieves the location.

Tan*_*ike 7

从你的问题和回复中,我可以理解你的情况和目标,如下所示。

  • 在您的示例文件中,“文件 A”和“文件 B”分别为 524,288 字节和 163,339 字节。
  • 您测试的脚本是您的问题中显示的脚本。
  • 您想使用可恢复上传合并文件。
  • 合并文件的 mimeType 为 PDF。

为此,这个答案怎么样?

改装要点:

  • 不幸的是,您的脚本不完整,无法实现可恢复上传。Google Drive API 的可恢复上传流程如下。参考

    1. 请求检索用作上传数据端点的位置。
      • 在您的情况下,将创建新文件。所以需要使用POST方法。
    2. 通过包含数据来请求检索到的位置(在您的情况下,它是每个文件。)。
      • 在这种情况下,需要使用循环上传数据。并且使用了 PUT 方法。
      • 在这里,每个文件大小都是最重要的。如果除最后一个文件外的文件大小不是262,144字节的倍数,则无法运行可恢复上传,错误。请注意这一点。

对于上面的流程,当准备好示例脚本时,它变成如下。

用法:

1. 启用驱动 API。

在这种情况下,使用 Drive API。因此,请在高级 Google 服务中启用 Drive API。这样,Drive API 会在 API 控制台自动启用。

示例脚本的流程如下。

  1. 创建一个在可恢复上传时使用的对象。
  2. 检索“位置”以开始可恢复上传。
  3. 上传每个文件并合并它们。

2. 示例脚本。

请复制并粘贴以下脚本。并请设置文件 ID。在这种情况下,请设置它们以便合并。请注意这一点。

function myFunction() {
  const fileIds = ["###", "###"];  // Please set the file IDs of the file "A" and "B" in order.
  const filename = "sample.pdf";
  const mimeType = MimeType.PDF;

  // 1. Create an object for using at the resumable upload.
  const unitSize = 262144;
  const fileObj = fileIds.reduce((o, id, i, a) => {
    const file = DriveApp.getFileById(id);
    const size = file.getSize();
    if (i != a.length - 1 && (size % unitSize != 0 || size > 52428800)) {
      throw new Error("Size of each file is required to be the multiples of 262,144 bytes and less than 52,428,800 bytes.");
    }
    o.files.push({data: file.getBlob().getBytes(), range: `bytes ${o.size}-${o.size + size - 1}\/`, size: size.toString()});
    o.size += size;
    return o;
  }, {size: 0, files: []});

  // 2. Retrieve "location" for starting the resumable upload.
  const url = "https://www.googleapis.com/upload/drive/v3/files?uploadType=resumable";
  const res1 = UrlFetchApp.fetch(url, {
    method: "post",
    contentType: "application/json",
    payload: JSON.stringify({name: filename, mimeType: mimeType}),
    headers: {authorization: "Bearer " + ScriptApp.getOAuthToken()
  }});
  const location = res1.getHeaders().Location;

  // 3. Upload each file and merge them.
  fileObj.files.forEach((e, i) => {
    const params = {
      method: "put",
      headers: {"Content-Range": e.range + fileObj.size},
      payload: e.data,
      muteHttpExceptions: true,
    };
    const res = UrlFetchApp.fetch(location, params);
    const status = res.getResponseCode();
    if (status != 308 && status != 200) {
      throw new Error(res.getContentText());
    }
    if (status == 200) {
      console.log(res.getContentText())
    }
  });

  // DriveApp.createFile()  // This comment line is used for automatically detecting the scope of "https://www.googleapis.com/auth/drive" by the script editor. So please don't remove this line.
}
Run Code Online (Sandbox Code Playgroud)

结果:

可续传上传完成后,在日志中可以看到如下结果。您可以在根文件夹中看到合并后的文件。

{
 "kind": "drive#file",
 "id": "###",
 "name": "sample.pdf",
 "mimeType": "application/pdf"
}
Run Code Online (Sandbox Code Playgroud)

笔记:

  • 这是一个简单的示例脚本。所以请根据您的实际情况修改它。
  • 我针对您的示例情况测试了上述脚本,即“文件 A”和“文件 B”分别为 524,288 字节和 163,339 字节。因此,当使用此脚本合并大小约为 50 MB 的多个文件时,会发生错误。
  • 如果在使用大文件时出现内存错误,在现阶段,这似乎是谷歌方面的规范。所以请注意这一点。

参考: