Enhanced makeCopy() using Google Apps Script

Gists

Overview

This is sample scripts for copying files to a specific folder in Google Drive using Google Apps Script (GAS).

Description

When the files in Google Drive are copied to a specific folder using GAS, most users will use makeCopy(destination). When the files are copied using makeCopy(), you might have already noticed that only the standalone projects cannot be copied to the specific folder. They are copied to the root folder (My Drive). Also this situation can be also seen even when “copy” of Drive API v2 and v3 is used. I think that this might be a bug. So I would like to introduce 2 sample scripts as the workaround. In the workaround, it uses Drive API.

Sample script 1

This sample is very simple.

  1. Retrieve files in source folder.
  2. Copy the files to destination folder.
  3. If the file is the standalone project, the file is moved from the root folder to the destination folder using “update” method of Drive API.

When you use this sample, please enable Drive API at Advanced Google Services and API console.

function makeCopy(srcFolderId, dstFolderId) {
  var srcFolder = DriveApp.getFolderById(srcFolderId);
  var dstFolder = DriveApp.getFolderById(dstFolderId);
  var files = srcFolder.getFiles();
  while (files.hasNext()) {
    var file = files.next();
    var f = file.makeCopy(dstFolder);
    if (file.getMimeType() == MimeType.GOOGLE_APPS_SCRIPT) {
      Drive.Files.update({"parents": [{"id": dstFolderId}]}, f.getId());
    }
  }
}

function run() {
  var srcFolderId = "### folder ID with source files ###";
  var dstFolderId = "### destination folder ID ###";
  makeCopy(srcFolderId, dstFolderId);
}

If you want to use DriveApp instead of Drive API, please modify as follows.

From:

Drive.Files.update({"parents": [{"id": dstFolderId}]}, f.getId());

To:

dstFolder.addFile(file);
f.getParents().next().removeFile(file);

Sample script 2

In the sample script 1, the Drive API is required to be called for the number of the standalone projects. In order to reduce the number of calling Drive API, this sample uses the batch request. The flow is as follows.

  1. Retrieve files in source folder.
  2. Create the request body for copying files to the destination folder using “copy” of Drive API.
  3. Run the request body by the batch request.
  4. Create the request body for moving files (the standalone projects) from the root to the destination folder using “update” of Drive API.
  5. Run the request body by the batch request.

From this flow, when the number of total files is less than 100, the number of API calls is 2. And the batch request is worked by the asynchronous processing. So the copy speed of sample 2 is faster than that of sample 1.

When you use this sample, please enable Drive API at API console. And please install the GAS library (BatchRequest) for using batch request at GAS.

function makeCopy(srcFolderId, dstFolderId) {
  var parseBatchRes = function(res) {
    var splittedRes = res.split("--batch");
    return splittedRes.slice(1, splittedRes.length - 1).map(function(e) {
      return {
        contentId: Number(e.match(/Content-ID: response-(\d+)/)[1]),
        status: Number(e.match(/HTTP\/\d+.\d+ (\d+)/)[1]),
        object: JSON.parse(e.match(/{[\S\s]+}/)[0]),
      };
    });
  };
  var baseUrl = "https://www.googleapis.com/drive/v3/files/";
  var fields = "fields=id%2CmimeType%2Cname%2Cparents";
  var folder = DriveApp.getFolderById(srcFolderId);
  var files = folder.getFiles();
  var req1 = {
    batchPath: "batch/drive/v3",
    requests: [],
  };
  var project = [];
  while (files.hasNext()) {
    var file = files.next();
    req1.requests.push({
      method: "POST",
      endpoint: baseUrl + file.getId() + "/copy?" + fields,
      requestBody: {parents: [dstFolderId], name: file.getName()},
    });
    if (file.getMimeType() == MimeType.GOOGLE_APPS_SCRIPT) {
      project.push(req1.requests.length - 1);
    }
  }
  var res1 = BatchRequest.Do(req1).getContentText();
  var r1 = parseBatchRes(res1);
  var req2 = {
    batchPath: "batch/drive/v3",
    requests: project.map(function(e) {return {
        method: "PATCH",
        endpoint: baseUrl + r1[e].object.id + "?addParents=" + dstFolderId + "&removeParents=" + r1[e].object.parents[0] + "&" + fields,
      },
    }),
  };
  var res2 = BatchRequest.Do(req2).getContentText();
  var r2 = parseBatchRes(res2);
  project.forEach(function(e, i) {
    r1[e] = r2[i];
  });
  return r1.map(function(e){return e.object});
}

function run() {
  var srcFolderId = "### folder ID with source files ###";
  var dstFolderId = "### destination folder ID ###";
  var res = makeCopy(srcFolderId, dstFolderId);
  Logger.log(res)
}

Note

  • At “update” of Drive API v2, the parent ID can be directly overwritten. But at Drive API v3, it cannot do it. So the file is moved using addParents and removeParents.

 Share!