Skip to content

Instantly share code, notes, and snippets.

@tanaikech
Last active March 14, 2024 18:55
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 5 You must be signed in to fork a gist
  • Save tanaikech/ac1b0d50fe1ffaa40e95bbe9faf908b9 to your computer and use it in GitHub Desktop.
Save tanaikech/ac1b0d50fe1ffaa40e95bbe9faf908b9 to your computer and use it in GitHub Desktop.
Enhanced makeCopy() using Google Apps Script

Enhanced makeCopy() using Google Apps Script

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.
@jmm0979
Copy link

jmm0979 commented Jun 25, 2020

Hi Tanaike,

In the second script, can you use the same if I have multiple folders to be copied from MyDrive to Shared Drive? Let's say if I have everything in google sheets and list all the folderids to be copied and loop through it and use your code above.

@tanaikech
Copy link
Author

Thank you for your comment. Unfortunately, I'm not sure whether this sample script can achieve your goal in your comment. I apologize for this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment