Drive.Files.Copy and "parents" not working - google-apps-script

I'm trying to copy a file in Team Drives to a new folder location, also in Team Drives. I get a "File not found" error on the last line of code. The newFileID has been checked using DriveApp.getFileByID and by testing in Google API Try-It.
I think the "parents" piece is incorrectly formed. When I try Google API Try-It, the file is copied into the correct folder. Yay! So what's wrong with the Google Script code?
https://developers.google.com/drive/api/v3/reference/files/copy#try-it
Google Script code (not working):
function test() {
// find Teacher's Learner Guides folder
var destinationFolderId = "1qQJhDMlHZixBO9KZkkoSNYdMuqg0vBPU";
var newFile = {
"name": "Learner Guide - test",
"description": "New student learner guide",
"parents": [destinationFolderId]
};
// create duplicate document
var newFileID = "1g6cjUn1BWVqRAIhrOyXXsTwTmPZ4QW6qGhUAeTHJSUs";
var newDoc = Drive.Files.copy(newFile, newFileID);
}
The Google API Try-It code works. Here's the javascript (working):
return gapi.client.drive.files.copy({
"fileId": "1g6cjUn1BWVqRAIhrOyXXsTwTmPZ4QW6qGhUAeTHJSUs",
"supportsTeamDrives": true,
"resource": {
"parents": [
"1qQJhDMlHZixBO9KZkkoSNYdMuqg0vBPU"
],
"name": "Learner Test2"
}
})
What would be an efficient and/or correct way of using Drive.Files.Copy in Google Script code to place the copied file into a different folder?

The parents metadata associated with the request expects a ParentReference resource for Drive API v2, which is at minimum an object with an id property and the associated fileId, e.g. {id: "some id"}.
Since you are working with Team Drives, you must tell Google that you (i.e. your code) know how to handle the associated differences between regular & Team Drives, with the supportsTeamDrives optional parameter.
Note:
A parent does not appear in the parents list if the requesting user is a not a member of the Team Drive and does not have access to the parent. In addition, with the exception of the top level folder, the parents list must contain exactly one item if the file is located within a Team Drive.
Assuming the code runner meets the criteria, the most simple code to copy a given file to a given Team Drive folder is:
function duplicate_(newName, sourceId, targetFolderId) {
if (!newName || !sourceId || !targetFolderId)
return;
const options = {
fields: "id,title,parents", // properties sent back to you from the API
supportsTeamDrives: true, // needed for Team Drives
};
const metadata = {
title: newName,
// Team Drives files & folders can have only 1 parent
parents: [ {id: targetFolderId} ],
// other possible fields you can supply:
// https://developers.google.com/drive/api/v2/reference/files/copy#request-body
};
return Drive.Files.copy(metadata, sourceId, options);
}
Additional reading:
Standard Query Parameters (these can always be passed in the optional argument)
Partial Responses (aka "fields")

Here's the solution for copying files in Team Drives. #tehhowch had an important piece about needing the optional parameters (you need to use all three parameters for copy API v2). Then the "parents" argument requires a File object, not a string. The code below works by copying the file and moving it into another Team Drives folder.
function test() {
// find Teacher's Learner Guides folder
var destFolderId = "1qQJhDMlHZixBO9KZkkoSNYdMuqg0vBPU";
var originalDocID = "1g6cjUn1BWVqRAIhrOyXXsTwTmPZ4QW6qGhUAeTHJSUs";
var destFolder = Drive.Files.get(destFolderId, {"supportsTeamDrives": true});
var newFile = {
"fileId": originalDocID,
"parents": [
destFolder // this needed to be an object, not a string
]
};
var args = {
"resource": {
"parents": [
destFolder // this needed to be an object, not a string
],
"title": "new name of document here"
},
"supportsTeamDrives": true
};
// create duplicate Learner Guide Template document
var newTargetDoc = Drive.Files.copy(newFile, originalDocID, args);
}

Related

How to allot dynamic drive access permission from csv data to google drive folders?

I Have a csv file with first name, ID number and email id data of a certain set of people. I want to take this information and with it, in a Google Drive parent folder I want to create subfolders with the name firstname_ID_number and set the access permission of each folder to the corresponding email only.
In other words each person in the list should only be able to access their own folder in that drive and not others'.
My first thought was to use Google Colab and Google Drive API to get this done. But I do not know how or where to start with. I would appreciate some kind guidance.
You could start by importing your csv file in Google Sheets and later use Apps Script for your task.
Therefore, assuming your sheet will end up looking something similar to this:
First Name
ID Number
Email ID
Name1
N1
E1
Name2
N2
E2
Name3
N3
E3
You can make use of the Drive advanced service in order to create the folders and assign the permissions. You can take a look at the below script in order to achieve this:
Code
function mainFunction() {
let spreadsheet = SpreadsheetApp.openById("SPREADSHEET_ID");
let sheet = spreadsheet.getSheetByName("SHEET_NAME");
let nameCol = sheet.getRange(START_ROW, START_COL, NO_ROWS, NO_COLS).getValues();
let idCol = sheet.getRange(START_ROW, START_COL, NO_ROWS, NO_COLS).getValues();
let emailCol = sheet.getRange(START_ROW, START_COL, NO_ROWS, NO_COLS).getValues();
for (let i = 0; i < nameCol.length; i++) {
let fileName = nameCol[i + 1][0] + "_" + idCol[i + 1][0];
let folderId = createFolder(fileName);
createPermission(emailCol[i + 1][0], folderId);
}
}
function createFolder(fileName) {
let optionalArgs = { supportsAllDrives: true };
let resource = {
title: fileName,
mimeType: 'application/vnd.google-apps.folder',
parents: [{
"id": "SHARED_DRIVE_ID"
}]
}
let folder = Drive.Files.insert(resource, null, optionalArgs);
return folder.id;
}
function createPermission(emailAddress, folderId) {
let optionalArgs = { supportsAllDrives: true };
let resource = {
role: "ROLE",
type: "TYPE",
emailAddress: emailAddress
}
Drive.Permissions.insert(resource, folderId);
}
Explanation
The script is composed of three functions: mainFunction, createFolder and createPermission. The mainFunction is used to gather all the values from the sheet while createFolder and createPermission are used to create the folder and assign the necessary permission to it. Since you are working with shared drives, the right approach here is to use the Drive advanced service.
Reference
Range Class getValues();
Drive Advanced Service;
Drive API v2 Files:insert;
Drive API v2 Permissions:insert.

copy file in google drive using pydrive or google drive api

function copyFolderContents_(source, target) {
// Iterate files in source folder
const filesIterator = source.getFiles()
while (filesIterator.hasNext()) {
const file = filesIterator.next()
// Make a copy of the file keeping the same name
file.makeCopy(file.getName(), target) // need to find python function replacing THIS LINE!!!!!!!!!!!!!!!!!!
}
}
const toCopy = DriveApp.getFolderById('')
const copyInto = DriveApp.getFolderById('')
const newFolder = copyInto.createFolder(toCopy.getName())
copyFolderContents_(toCopy, newFolder)
I trying to rewrite this app script into python, which only copies the file but not the folder into another locatioN
is there any pydrive or vanilla HTTP restful API that could replace file.makeCopy(file.getName(), target) ?
After visiting ref , seems this restful API doesn't have a target that I could specified.
The parent[] parameter in the request body of Files:copy is the same as the destination in makeCopy(name, destination).
Example:
Here I have a folder named 68759008 which has the sample document:
and here is the destination folder I want my sample document to be copied.
Using API Explorer:
Request Parameter:
In the request parameter, I only inserted the file ID of the sample document
Request Body:
{
"parents": [
"Insert Destination folder ID here"
],
"name": "68759008_TEST_DOC_COPY"
}
Response:
Output:
There are two options to obtain the parent ID or the destination folder ID:
You can go to the destination folder and copy the string after the https://drive.google.com/drive/folders/
Use Files:list, find the file name and the mimeType should be application/vnd.google-apps.folder
Files:list Example:

Can I list all the times I saved a file?

My company uses Google Drive and we are still mainly using Microsoft Office documents.
Is it possible to see my activity or each time I saved a document in the Shared Drive even though it is not Google Documents I want to see the activity of?
I found this link https://developers.google.com/apps-script/advanced/drive-activity , but the code only returns Google Docs activity. Not non-Google documents like Word and Excel.
You might want to check if using Revisions.list will fit your needs.
Revisions.list
Lists the current file's revisions.
Path parameters:
fileId - The ID of the file.
Optional query parameters:
fields - The paths of the fields you want included in the response. If not specified, the response includes a default set of fields specific to this method. For development you can use the special value * to return all fields, but you'll achieve greater performance by only selecting the fields you need. For more information, see Return specific fields for a file.
pageSize - The maximum number of revisions to return per page. Acceptable values are 1 to 1000, inclusive. (Default: 200)
pageToken - The token for continuing a previous list request on the next page. This should be set to the value of 'nextPageToken' from the previous response.
Response Body:
{
"kind": "drive#revisionList",
"nextPageToken": string,
"revisions": [
revisions Resource
]
}
Revisions Resource Representation:
You can obtain useful information related to the revision done to the file such as the modified time and last modifying user.
{
"kind": "drive#revision",
"id": string,
"mimeType": string,
"modifiedTime": datetime,
"keepForever": boolean,
"published": boolean,
"publishedLink": string,
"publishAuto": boolean,
"publishedOutsideDomain": boolean,
"lastModifyingUser": {
"kind": "drive#user",
"displayName": string,
"photoLink": string,
"me": boolean,
"permissionId": string,
"emailAddress": string
},
"originalFilename": string,
"md5Checksum": string,
"size": long,
"exportLinks": {
(key): string
}
}
You can specify specific fields in your request under fields parameter so that only necessary information can be shown in the response body:
Sample Fields Parameter:
nextPageToken, revisions/id, revisions/modifiedTime, revisions/lastModifyingUser/displayName, revisions/lastModifyingUser/emailAddress
Sample Response Body:
{
"revisions": [
{
"id": "1898",
"modifiedTime": "2020-12-16T22:29:02.971Z",
"lastModifyingUser": {
"displayName": "User1 Test",
"emailAddress": "user1#example.com"
}
}
]
}
Play with DriveApps file.getLastUpdated(). This is not the same as ALL the times you've updated it but it should get the last time the file was changed. https://developers.google.com/apps-script/reference/drive/file#getLastUpdated()
Or do you really need a list of all the edit times not just the most recent one? In that case you could run a script once a day that records the lastUpdated for all the files you care about or all the files in a folder and record if they've changed. What is the use case?
Thanks for that Ron. Revisions did the trick.
It took me a while to wrap my head around it, but these are the 2 functions I used. I will try to put them together at some point when I have Drive API v3.
function listFilesInFolder() {
// get the id's of files in a folder
var sheet = SpreadsheetApp.getActiveSheet();
sheet.appendRow(["Name", "File-Id"]);
var folder = DriveApp.getFolderById(" ID STRING ");
var contents = folder.getFiles();
var counter = 0;
var file;
while (contents.hasNext()) {
var file = contents.next();
counter++;
data = [
file.getName(),
file.getId(),
];
sheet.getRange("C2").setValue = counter
sheet.appendRow(data);
};
};
function fileactivity() {
var revs = Drive.Revisions.list(" FileID String ");
var savedList = [];
for(var i=0; i<revs.items.length; i++) {
var revision = revs.items[i];
// modifiedByMeTime requires Drive API v3
savedList.push([revision.kind, revision.modifiedDate]);
};
var sheet = SpreadsheetApp.getActiveSheet();
Logger.log(savedList);
}

Google Apps Script - Impossible operation with an element inside a shared drive

I need your help on something.
I did a function which goal is to make a copy of a template file and put it in a folder in a shared drive. The problem is that the program returns :
"Exception: This operation cannot be performed on an item in a shared Drive"
Yet, I have all the permissions in that shared drive so I don't understand.
I did several tweaks and I've founded that removeFile and addFile are parts of the problem. If I don't run them, the folder is created and the copy is made. But I still need the file to be moved.
I hope someone can help me with this.
PS : You can fin my code below.
function makeCopyFile(folderID, fileID, newName) {
var getFolder = DriveApp.getFolderById(folderID);
var file = DriveApp.getFileById(fileID);
var copyFile = file.makeCopy();
var newFile = copyFile.setName(newName);
// Remove the file from all parent folders
var parents = newFile.getParents();
while (parents.hasNext()) {
var parent = parents.next();
parent.removeFile(newFile);
}
getFolder.addFile(newFile);
};
The problem is that you are trying to delete a file on a shared drive with DriveApp
It will work if you do it instead with the Advanced Drive service where you can specify "supportsAllDrives": true
So, after enabling the Advanced Service: replace
var parents = newFile.getParents();
while (parents.hasNext()) {
var parent = parents.next();
parent.removeFile(newFile);
}
through
var id = copyFile.getId();
// or alternatively `var id = newFile.getId();` - because it is the same
Drive.Files.remove(id,{"supportsAllDrives": true})
As for getFolder.addFile(newFile);
Once you remove a file, you cannot add it back anymore. Also, I do not understand your motivation for it - copyFile.setName(newName); is enough to rename the file - you do not need to delete the file with the old name and insert the one with the new name.
UPDATE
If your goal is to copy a file to a team drive folder, you can do it easily as following with the Drive API:
function makeCopyFile(folderID, fileID, newName){
var resource = {
"parents": [
{
"id": folderID
}
],
"title": newName
}
Drive.Files.copy(resource, fileID, {"supportsAllDrives": true})
}

Google Apps Script - XLSX from E-mail to specific Google Sheet

I'm getting an e-mail daily with an XLSX file that I need to then export over to a specific Google Sheet.
The code simply searches the GMAIL for a specific query, where it then gets the body (which contains a URL), which it then downloads with the doGet function, that uses the Url.FetchApp.fetch(url).getBlob()
After that, using the variable fileInfo, it defines, the title, mimeType of the file and the "parents" which is the ID of the folder where I need to deposit the converted file.
Then, I insert the file with the Drive.Files.insert(fileInfo, file, {convert: true}) which should convert my Excel file and deposit it in my folder.
The export works (a file is exported). If I append a .xlsx extension manually and try to open in Excel,it opens perfectly with all the required information.
But, the issue is that I don't need to open in an Excel file, so I'm trying to export it to a Google Sheet or even parse it as a CSV, but it doesn't work.
When trying to open this exported file by ID, it says that it is missing or don't have permissions, but it is there and I do have permissions.
When checking the mime type of the file, it says "application/x-zip".
Is there anyway for me to upload the file data to my Google Sheet with the ID "1HMXgJuuRFaGK11sfR38mKh6rk4ta_Qgtlljk6HBjLkE"?
function CostDSP() {
var sheetId="1HMXgJuuRFaGK11sfR38mKh6rk4ta_Qgtlljk6HBjLkE";
var threads = GmailApp.search("from:no-reply#amazon.com subject:Cost - DSP in:inbox newer_than:1d");
var message = threads[0].getMessages()[0];
var attachment = message.getPlainBody();
var regex= new RegExp("\<(.*)\>");
var url=regex.exec(attachment)[1];
Logger.log(url);
var file=doGet(url);
var fileInfo = {
title: "Cost - DSP",
mimeType: "MICROSOFT_EXCEL",
"parents": [{'id': "19jrt0DyfvsDz5WAdKhkekHkJ_wP7qP7f"}],
};
Drive.Files.insert(fileInfo, file, {convert: true});
var sourceFile = DriveApp.getFilesByName("Cost - DSP").next();
Logger.log(sourceFile.getMimeType());
Logger.log(sourceFile);
var source=SpreadsheetApp.openById("1lIVb9YM9IK7f8dKuC1RpXjP7nLnMNXm1");
var sheet = sourceFile.getSheets()[0];
var destination = SpreadsheetApp.openById("1HMXgJuuRFaGK11sfR38mKh6rk4ta_Qgtlljk6HBjLkE").getSheetByName('Data Intake');
sheet.copyTo(destination);
}
function doGet(e) {
// e.parameter["file"] //returns ?file=filelink.com/file.xlsx
var file = UrlFetchApp.fetch(e).getBlob();
return file;
}
It seems you need to use the correct mime type for import
mimeType:
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
The next code works fine for me
var file = UrlFetchApp.fetch(
'https://ec.europa.eu/eurostat/statistics-explained/images/9/9f/Country_Codes_and_Names.xlsx'
);
var fileInfo = {
title: 'Cost - DSP',
mimeType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
parents: [{
id: 'root'
}],
};
Drive.Files.insert(fileInfo, file, {
convert: true
})
As #Raserhin said it works without the mimeType parameter.
var fileInfo = {
title: 'Cost - DSP',
// mimeType:
// 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
parents: [{ id: 'root' }],
}
;
Also you can't use doGet as you're using now. doGet is a reserved function.
Turns out I also had to set the mimetype in the doGet function to the xlsx mimetype for it to work.
As an optimization I also removed the mimetype specification in the file info variable.
I'm managing to use this doGet function despite it being reserved, so I guess I'll stick to it for now?
As for calling the drive api directly, I need to use the advanced service to be able to convert the xlsx file.