Google App Script Timed Out after 30 mins - google-apps-script

I'm running below script but it errors out with a Error Message of "Timed Out" because the folder Id I'm giving have lots of lots folders under.
Can someone please help me out to optimize it or alternative approach.
function listFolderContents() {
var currentFolder = DriveApp.getFolderById("0B89Y-hAfWt_HVkhSbWprOVhPM00");
traverseFolder(currentFolder);
}
function traverseFolder(folder) {
DriveApp.getRootFolder().removeFolder(folder);
var subFolders = folder.getFolders();
while (subFolders.hasNext()) {
traverseFolder(subFolders.next());
}
}

It seems like you are doing a recursive delete of a folder. Try moving the removeFolder call to after the recursive calls. Otherwise, you delete the folder before you call getFolders on it, which is why I think it's timing out on you.
Like this:
function deleteFolder(folder) {
var subFolders = folder.getFolders();
while (subFolders.hasNext()) {
deleteFolder(subFolders.next());
}
DriveApp.getRootFolder().removeFolder(folder);
}
The termination condition for the resursive function is that there are no sub-folders, that is you are in a leaf folder.
See this related answer to a question about recursive deletion of a binary tree

I think that solution in this case could be using of getContinuationToken()
and example of iteration with continuationToken is available in another answer on stackoverflow.

Related

DriveApp.getFolders() not working as expected

I have a Google Apps Script for Sheets (running as a script under Sheets) that is supposed to return all the folders in my Google drive, however, the following code is stuck in an endless loop and only shows 1 of 3 folders in the drive - over and over again.
//this doesn't work -only shows 1 folder and repeats indefinitely
function getMyFolders() {
var f;
while (DriveApp.getFolders().hasNext()) {
f = DriveApp.getFolders().next();
console.log("f: " +f.getName());
}
}
The code returns the name of only one of my folders (hasNext() is clearly not working or there is a bug in Google Apps Scripts - since the while condition is never false (I ran it for several minutes and it never stopped!)
Could this be a security issue? This is just one of the problems I've run into. The other is that the 3 folders are subfolders of the same parent folder. No sure why getFolders() is not returning just the parent (that would make more sense).
Get All My Files
function getAllMyFiles(folder=DriveApp.getRootFolder()) {
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName('Sheet0');
let files = folder.getFiles();
while(files.hasNext()) {
let file = files.next()
sh.appendRow([file.getName(),file.getId()]);
}
let sfldrs = folder.getFolders();
while(sfldrs.hasNext()) {
let sfldr = sfldrs.next();
getAllMyFiles(sfldr)
}
}
Try this as your function instead to return just the parent folders!
function getMyFolders() {
let folders = DriveApp.getFolders();
while (folders.hasNext()) {
console.log("f: " +folders.next().getName());
}
}
If you want child folders as well, you would need to use recursion. Are you familiar with the concept? Happy to update my answer with that information if it's helpful.
Explanation:
You were calling DriveApp.getFolders(); on every iteration, so hasNext() was always retrieving the same item (the first folder in the iterator). Hence, you had an infinite loop.
I rewrote Cooper's answer to get all folders underneath a given parent.
In my case the parent folder is called "Gill".
function getAllMyFolders(folder=DriveApp.getFoldersByName("Gill").next()) {
let sfldrs = folder.getFolders();
while(sfldrs.hasNext()) {
let sfldr = sfldrs.next();
console.log("folder name: " +sfldr.getName());
getAllMyFolders(sfldr)
}
}
Ok - I played around with this code and discovered recursion is completely unnecessary, so here's code that will return all folders at all levels, then you can test the level with getParents()
function getMyFolders() {
let folders = DriveApp.getFolders();
while (folders.hasNext()) {
let folder = folders.next();
Logger.log(folder.getName() + ", Parent Folder: " + folder.getParents().next().getName());
}
}
I had a hunch that using a recursive function was the wrong way to go (and it looks terrible plus adds unnecessary overhead) but was confused why, my first attempt in this question did not work. The answer was the way my code was written - apparently, you need to assign DriveApp.getFolders() to a variable only once. Simply putting it in more than once, seems to reset it. That is, checking for DriveApp.getFolders().hasNext() and followed by DriveApp.getFolders().next() will cause the endless loop! Lesson here: assign it to a variable and then check for hasNext as calling it changes the state of the iterator which is reset again if getFolders is called again.
That was my actual bug here.

Copying multiple files in Apps Script

I have a script of which finds all files by name then copies and renames them, I want it to make a copy of multiple files (all of which have the same name) then rename them to the same original name. Currently, it mostly functions as it finds the files by name but it only copies the first file and ignores the rest. My current script is as follows
function copyDocs() {
var file = DriveApp.getFilesByName('PlaceHolder').next();
file.makeCopy('PlaceHolder');
}
I myself am not the most educated on this type of language or any coding language, to be honest, so I'm sorry if this is an easy solution or rookie mistake.
DriveApp.getFilesByName(...) returns a FileIterator. You need to iterate over all of the found files.
function copyDocs()
{
var files = DriveApp.getFilesByName('PlaceHolder');
while(files.hasNext())
{
files.next().makeCopy("PlaceHolder");
}
}
My issue was solved with help from #Cooper and #IMTheNachoMan. I simply needed to use the while(files.hasNext) iterator before placing makeCopy("PlaceHolder"). The end script is as follows
function copyDocs() {
var files = DriveApp.getFilesByName('PlaceHolder');
while (files.hasNext()) {
files.next().makeCopy('PlaceHolder');
}
}

folder.revokePermissions fails to revoke permissions

Can anyone help me figure out why this revokeAdminPermissions function is not working? It executes, and the Execution Transcript states that it's successful, but the permissions are not revoked; the user still has access to the folders in question.
AddAdminPermissions and findFolder included for reference only, those are working fine.
Thanks in advance to anyone who can help!
function findFolder(folderName){
var folders = DriveApp.getFolders();
while (folders.hasNext()) {
var folder = folders.next();
if(folder.getName()==folderName){
return folder
}
}
}
function addAdminPermissions(email){
findFolder("Admin").addEditor(email)
findFolder("Project Sheets").addEditor(email)
findFolder("Team Members").addEditor(email)
}
function revokeAdminPermissions(email){
var admin = findFolder("Admin")
admin.revokePermissions(email)
var projects = findFolder("Project Sheets")
projects.revokePermissions(email)
var tmFolder = findFolder("Team Members")
tmFolder.revokePermissions(email)
}
Looking at the documentation from revokePermissions(user), it says that:
This method does not block users from accessing the File if they
belong to a class of users who have general access — for example, if
the File is shared with the user's entire domain.
I was able to work around this by calling folder.removeEditor(email) and folder.removeViewer(email). Doesn't explain the failure, but at least it works

error - "Method Range.getValue is heavily used by the script"

I posted this question previously but did not tag it properly (and hence why I likely did not get an answer) so I thought I would give it another shot as I haven't been able to find the answer in the meantime.
The below script is giving me the message in the title. I have another function which is using the same getValue method but it is running fine. What can I change in my script to avoid this issue?
function trashOldFiles() {
var ffile = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("CtrlSht").getRange("B3:B3").getValue();
var files = DriveApp.getFilesByName(ffile);
while (files.hasNext()) {
var file = files.next();
var latestfile = DriveApp.getFileById(listLatestFile());
if(file.getId() ==! latestfile){
file.setTrashed(true);
}
}
};
Is it an error or an execution hint(the light bulb in the menu)?
are you using that method on other part of your code? probably in listLatestFile()?
I got the same execution hint by calling getRange().getValue() in listLatestFile() (using a loop)
and the hint always mentioned that the problem was when calling
var ffile = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("CtrlSht").getRange("B3:B3").getValue();
in the function trashOldFiles() even when the actual problem was in other function.
Check if you are calling it in other place in your code, probably inside a loop.
OK, so Gerardo's comment about loops started to get me thinking again. I checked some other posts about how to re-use a variable and decided to put the listLatestFile() value in my spreadsheet -
var id = result[0][1];
SpreadsheetApp.getActiveSpreadsheet().getSheetByName("CtrlSht").getRange("B5:B5").setValue(id);
//Logger.log(id);
return id;
and then retrieved the latest file ID from the spreadsheet to use as a comparison value for the trashOldFiles() function which worked a treat.
function trashOldFiles() {
var tfile = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("CtrlSht").getRange("B3:B3").getValue();
var tfiles = DriveApp.getFilesByName(tfile);
var lfile = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("CtrlSht").getRange("B5:B5").getValue();
while (tfiles.hasNext()) {
var tfile = tfiles.next();
if(tfile.getId() !== lfile){
tfile.setTrashed(true);
}
}
};
Not sure if that approach was best practice but it did work for me. If anyone has suggestions for achieving this in a more elegant way, I'm all ears.

Migrating from DocsList to DriveApp?

I've been using DocsList for a big project and it was working perfectly. Lately, bugs have been popping up and they mostly have roots with getting a folder or file. When I did research, I found that DriveApp had been updated. The problem is that DriveApp doesn't have search parameters like DocsList had.
For example, if I had a folder structure like this:
Root
-Main Folder 1
--Folder 1
--Folder 2
-Main Folder 2
--Folder 1
--Folder 2
To get folder "Folder 1" in "Main Folder 2," I could put in the search parameter like so: DocsList.getFolder('Main Folder 2/Folder 1')
With DriveApp, I just can't understand how to work with it. From what I understand, I have to do something like this for DriveApp:
var mainFolders = DriveApp.getFoldersByName('Main Folder 2');
while (mainFolders.hasNext()) {
var mainFolder = termFolders.next();
var subFolders = termFolder.getFoldersByName('Folder 1');
// Something like this...
}
So if I had a folder that is more "deep" I would have to expand this even further..?
I feel like instead of making things easier, they made it more complicated with all the FileIterators and FolderIterators. And just making it hard to "get" a file or folder in code terms.
So basically, the point of this thread is to find out how a person who is use to DocsList to navigate and edit Drive files/folders can migrate to DriveApp and achieve the same things.
Small/Discrete examples of different scenarios would be really helpful. I can take it from there. I'll edit this more, if you guys think I'm not being clear about what I need help on.
The discussions from wchiquito's comment are an interesting read, but following all the links is time-consuming.
Bottom line: There will not be DriveApp version of getFolderByPath(), so you will need to roll your own. In the Google+ group, Faustino proposed a work-around and Eric improved it. Here it is, with an added check to allow paths that start with "/".
function getFolderByPath(path) {
var parts = path.split("/");
if (parts[0] == '') parts.shift(); // Did path start at root, '/'?
var folder = DriveApp.getRootFolder();
for (var i = 0; i < parts.length; i++) {
var result = folder.getFoldersByName(parts[i]);
if (result.hasNext()) {
folder = result.next();
} else {
return null;
}
}
return folder;
}
With that, you can simply do myFolder = getFolderByPath('Main Folder 2/Folder 1');. You will end up with a DriveApp Folder instance.
The code below works at mine.
It is based on having the same Id
function convertFileFromDocsListToDriveApp(file)
{ // Because of difference between DocsList and DriveApp
return (file === null) ? null : DriveApp.getFileById(file.getId());
}
function convertFolderFromDocsListToDriveApp(folder)
{ // Because of difference between DocsList and DriveApp
return (folder === null) ? null : DriveApp.getFolderById(folder.getId());
}
I call this in a few 'strategic' positions in my code.
I didn't test conversion from DriveApp to DocsList, but I expect this to work as well.