GAS Loop goes indefinite - google-apps-script

Problem:-
I need to get Name and ID of the latest created document from some Google Drive folders.
Solution:-
I wasn’t able to find an easy way to get it, hence, created a loop for files within the folder to get max date - then loop again in folder to match the date and Log the name and date – the below code (when commented as mentioned) works properly for the solution.
Instead of rewriting the whole code with just a different folder name, I tried creating a loop for folder name as well.
However app script goes into infinite loop giving out the Maximum execution time limit message.
Any help appreciated.
function Get_lastestdate() {
//Define Spreadsheet and sheet
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName("PEOPLE");
//comment from here to work properly
for (var i =1; i < 4; i++) {
if (i = 1) { var foldername = "Folder1"}
if (i = 2) { var foldername = "Folder2"}
if (i = 3) { var foldername = "Folder3"}
// till here
//Define folder and files
var folder = DocsList.getFolder(foldername); //set folder name in case commenting.
var lastfile = folder.getFiles();
//Make a blank array
var noofdaysarray = []
var yeararray = []
//Loop to get all No of days in a year & the year
for (var i in lastfile) {
noofdaysarray.push(Utilities.formatDate(lastfile[i].getDateCreated(),"GMT","D"));
yeararray.push(Utilities.formatDate(lastfile[i].getDateCreated(),"GMT","y"));
}
//Get the max date from date and year
var largestdate = Math.max.apply(Math, noofdaysarray);
var largestyear = Math.max.apply(Math, yeararray);
//Get maximum available date
var matchcriteria = largestdate + largestyear
//Again loop for matching criteria with the actual date uploaded
for (var i in lastfile) {
var lastdate = Utilities.formatDate(lastfile[i].getDateCreated(),"GMT","D");
var lastyear = Utilities.formatDate(lastfile[i].getDateCreated(),"GMT","y");
var wholedate = parseInt(lastdate) + parseInt(lastyear); //parseInt is for converting text to number
//Get doc name if both dates matches
if (wholedate == matchcriteria) {
Logger.log(lastfile[i].getId());
Logger.log(lastfile[i].getName());
}
}
} //comment this as a part of loop
}
Between:- If there's an easier way to do it, please let me know.

You can use DocsList to retrieve the last modified files. you told that you want the last created but maybe the last modified can be usefull. Check the code bellow:
function findLastModified() {
//Retrieve folder
var folder = DocsList.getFolderById("0B0kQD4hSd4KASUJKb2cya0NET1U");
//Ask for 5 files
var numberOfFiles = 5;
//The first parameter is the search query, in this case we want all files
var filesResult = folder.findForPaging("", 5);
//By Default they will be sorted by last modified date
var files = filesResult.getFiles();
Logger.log("Found "+files.length+" files");
//Iterate
for(var x in files) {
var file = files[x];
Logger.log(file.getLastUpdated());
}
}
Live version here.

Dont use "i" in both inner and outer loops. that will be a problem.
wouldn't it be easier to do something like this so you only do one loop?
var mostRecentDate = new Date(2000,01,01);
var fileName="";
var fileId="";
for(var i = 1; i<5; i++){
var folder = DocsList.getFolder('Folder'+i);
var files = folder.getFiles();
for(var j in files) {
if(files[j].getDateCreated()>mostRecentDate){ //or getLastUpdated
mostRecentDate=files[j].getDateCreated();
fileName=files[j].getName();
fileId=files[j].getId();
}
}
}
Logger.log("File: " + filename + " Id: " + fileId + " Created: " + mostRecentDate);
You may need to do paging if you have a huge number of files & folders to iterate through.

Related

for loop on apps script running very slow, how can i make it run faster?

I have a code that I have been using for 2 years and it was working perfectly until recently. It started to give a timeout warning a couple of weeks ago and now it's not really running anymore.
The code pulls every picture from 13 different folders on google drive and then adds them to a sheet, it was giving the timeout warning after row 200. I modified it a bit but i'm still getting the exceeded maximum execution time warning (after 360 rows now)
I would really appreciate it if somebody had a solution for this. The script used to run pretty fast (around 2-3 rows per second) and now it's taking around 3-4secs per row.
Thank you so much in advance!
Here's the script:
function Iteration() {
var lista_carpetas = SpreadsheetApp.getActive().getSheetByName("carpetas").getRange("A2:A").getValues(); //event list
var lista_carpetas_ok = lista_carpetas.reduce(function(ar, e) {
if (e[0]) ar.push(e[0])
return ar;
}, []);
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("data");
sheet.clear()
sheet.appendRow(["Name", "Date", "Size", "URL", "Download", "Description", "Image","Folder Id"]);
for (var i = 5; i <8; i++) {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("data");
var folderId = lista_carpetas_ok[i];
var season1 = SpreadsheetApp.getActive().getSheetByName("carpetas").getRange("B2:B").getValues(); //event list
var season = season1.reduce(function(ar, e) {
if (e[0]) ar.push(e[0])
return ar;
}, []);
var folder = DriveApp.getFolderById(folderId);
var contents = folder.getFiles();
var cnt = 0;
var file;
while (contents.hasNext()) {
var file = contents.next();
cnt++;
Logger.log(file);
Logger.log(cnt);
// writes the various chunks to the spreadsheet- just delete anything you don't want
data = [
file.getName(),
file.getDateCreated(),
file.getSize(),
file.getUrl(),
"https://docs.google.com/uc?export=download&confirm=no_antivirus&id=" + file.getId(),
file.getDescription(),
"=image(\"https://docs.google.com/uc?export=download&id=" + file.getId() +"\")",
folderId,
];
sheet.appendRow(data);
};
}
}
From your explanations in your reply,
I didn't copy the whole code because the rest is just 4 more loops just like the one that i copied but for the other folders. So 4 loops in total pull the pictures from 13 different folders on google drive, and each loop pulls the pictures from 3-4 folders.
We have 13 folders on google drive which hold pictures from 2019-2022, and I made a loop that pulls the pictures from each folder that holds pictures for that year. Therefore the script has 4 for loops (only showing the first one above, which pulls the pictures from folders in rows 6 to 8, which are the folders for year 2018) that pull pictures from the folders on google drive.
I believe your current situation and your goal are as follows.
You have 13 folder IDs in the column "A" of "carpetas" sheet.
In your script, you want to use the values from "A6:A8" of "carpetas" sheet.
By retrieving the file metadata from the folders of folder IDs retrieved from "A7:A9" of "carpetas" sheet, you want to put the retrieved file metadata to "data" sheet.
You want to reduce the process cost of the script.
Modification points:
When you want to retrieve the folder IDs from the cells "A6:A8", when I saw your script, it seems that your script retrieves the values from the cells "A7:A9".
In your script, appendRow is used in the loop. In this case, the process cost becomes high. Ref
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("data") can be put out of the loop.
season is not used. So var season1 = SpreadsheetApp.getActive().getSheetByName("carpetas").getRange("B2:B").getValues() can be removed.
When these points are reflected in your script, it becomes as follows.
Modified script:
function Iteration() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var carpetasSheet = ss.getSheetByName("carpetas");
var lista_carpetas = carpetasSheet.getRange("A2:A" + carpetasSheet.getLastRow()).getValues();
var lista_carpetas_ok = lista_carpetas.filter(([a]) => a);
var sheet = ss.getSheetByName("data");
sheet.clear();
var files = [["Name", "Date", "Size", "URL", "Download", "Description", "Image", "Folder Id"]];
for (var i = 4; i < 7; i++) {
console.log(lista_carpetas_ok[i][0]) // Here, you can see the folder ID in the log.
var folderId = lista_carpetas_ok[i][0];
try {
var folder = DriveApp.getFolderById(folderId);
var contents = folder.getFiles();
while (contents.hasNext()) {
var file = contents.next();
var id = file.getId();
data = [
file.getName(),
file.getDateCreated(),
file.getSize(),
file.getUrl(),
"https://docs.google.com/uc?export=download&confirm=no_antivirus&id=" + id,
file.getDescription(),
"=image(\"https://docs.google.com/uc?export=download&id=" + id + "\")",
folderId,
];
files.push(data);
};
} catch (e) {
// In this modification, when your folder ID cannot be used, that folder ID is skipped. At that time, an error message can be seen in the log.
console.log(e.message);
}
}
if (files.length == 0) return;
sheet.getRange(1, 1, files.length, files[0].length).setValues(files);
}
Note:
This modified script retrieves the folder IDs from the cells "A6:A8" because of your reply of only showing the first one above, which pulls the pictures from folders in rows 6 to 8, which are the folders for year 2018. But if you want to retrieved the folder IDs from "A7:A9", please modify for (var i = 4; i < 7; i++) { to for (var i = 5; i < 8; i++) {.
References:
Benchmark: Reading and Writing Spreadsheet using Google Apps Script
setValues(values)
Try this:
function Iteration() {
const ss = SpreadsheetApp.getActive();
const sh0 = ss.getSheetByName("carpetas");
const evlist = sh0.getRange("A6:A9").getValues().flat();
const sh1 = ss.getSheetByName("data");
sh1.clear();
sh1.appendRow(["Name", "Date", "Size", "URL", "Download", "Description", "Image", "Folder Id"]);
for (let i = 0; i < 3; i++) {
let folder = DriveApp.getFolderById(evlist[i]);
let contents = folder.getFiles();
while (contents.hasNext()) {
let file = contents.next();
sh1.appendRow([file.getName(),file.getDateCreated(),file.getSize(),file.getUrl(),"https://docs.google.com/uc?export=download&confirm=no_antivirus&id=" + file.getId(),file.getDescription(),"=image(\"https://docs.google.com/uc?export=download&id=" + file.getId() + "\")",evlist[i]]);
};
}
}
If you must use ranges like "A2:A" then always use the form "A2:A" + sheet.getLastRow() otherwise you always have to remove the nulls inserted in the range from getLastRow() to getMaxRows() which is a big waste of time. In this case just using a fixed range actually works better since you're only using three rows.
You could be storing all of your image in Google Image Library and removing this with their API.

Grab the folders of different parent folderids and display output in google sheets

I would like to grab all the folders under several parent folderids. I am using the code below but think it is missing something.
function searchFolders() {
var sheet1 = SpreadsheetApp.getActive().getSheetByName('Search Folder');
var range = [];
range.push([ "FolderId", "Folder Name", "Owner", "Date Created", "URL" ]);
var parentId = sheet1.getRange(2,1,sheet1.getLastRow(),sheet1.getLastColumn()).getValues(); // list of parent folderids
for (i=0; i<parentId.length; i++) {
var parfolder = DriveApp.getFolderById(parentId[i][0]).getFolders(); //retrieves the folders
while(parfolder.hasNext()){
var pchild = parfolder.next();
var row = [];
row.push(pchild.getId(),
pchild.getName(),
pchild.getOwner().getEmail(),
pchild.getDateCreated(),
pchild.getUrl())
range.push(row);
sheet1.getRange(i+2,2,range.length,range[0].length).setValues(range);
}
}
}
I was hoping for the code to grab the parent folderid then list the results in cols B-F.
parent folderid -- list the folders (Cols B-F), if it has 2 then
grab the next parent folderid -- list the next folders in 4th row...etc. And if the folder is blank to write "none" then continue with the next parent folderid. I haven't added this scenario in my code (just thought about just now).
Currently, the results are as follows:
The headers are printed 3x in rows 2-4 (cols B-F) then it lists the folders in row 5.
Your previous one was repeating the header lines because you were essentially writing the whole row variable every time, and it always had the header in it. I rewrote the function, now it organizes it as in your screenshot. Try the following code:
UPDATED CODE
function newSearch(){
var ss = SpreadsheetApp.getActiveSheet();
var i = ss.getLastRow() + 1; // Next empty cell in A
var j = ss.getRange("B1:B").getValues(); // Same as i, but for columns B-F
var bLast = j.filter(String).length + 1;
var ids = ss.getRange("A2:A"+i).getValues(); // gets the list of folderIDs
Logger.log(i + " " + bLast);
var currFolder;
var children;
var childFolders;
var folderInfo;
for (var x = 0; x < ids.length-1; x++){
Logger.log("Length: " + ids.length);
children = DriveApp.getFolderById(ids[x][0]);
childFolders = children.getFolders(); //get the children of the main folders
while(childFolders.hasNext()){
currFolder = childFolders.next(); // subfolder information
Logger.log(bLast);
folderInfo = [[currFolder.getId(), currFolder.getName(), currFolder.getOwner().getEmail(), currFolder.getDateCreated(), currFolder.getUrl()]];
ss.getRange("B" + bLast + ":F" + bLast).setValues(folderInfo);
bLast++;
}
}
ss.appendRow(["","//"]);
}
I put the ss.appendRow(["//"]) command to create a separator between the executions of the code.

Import new (latest) .csv file into existing Google spreadsheet automatically

I receive a new CSV file every hour in my Google Drive.
I need my spreadsheet updated with the data in the latest CSV file after it has been received in the Google Drive folder.
The files coming into the folder has a unique name for each new one according to date and time.
For example: FileName_date_24hourtime.csv
FileName_20190524_1800.csv then FileName_20190524_1900.csv etc.
Firstly I'm not sure what the best approach is:
simply with a formula (probably not possible with not knowing the exact filename?) like =IMPORTDATA
a google script to find latest .csv file and automatically import as soon as file was added to Google Drive folder
Any assistance will be great!
The .csv file:
.csv file contains 28 rows and data should be split by ;
.csv file looks like this:
NAME;-63.06;-58.08;50.62;-66.67;-80.00
NAME;-61.82;-56.83;-50.55;-77.78;-70.00
NAME;-57.77;-50.21;52.88;-77.78;-70.00
NAME1;-57.69;-61.48;-55.59;-55.56;-60.00
NAME2;-61.62;-53.79;50.34;-66.67;-70.00
NAME3;-54.62;-54.57;-52.22;55.56;-60.00
... with total of 28 rows
Data should go to "Import_Stats" sheet.
The best approach here would be a script with a trigger that runs a function that performs data import to a spreadsheet.
Create a time-based trigger with 1-hour offset:
function trigger() {
var trg = ScriptApp.newTrigger('checkFiles');
trg.timeBased().everyHours(1).create();
}
Create function that checks files in a folder (e.g. "checkFiles").
function checkFiles(alreadyWaited) {
//get spreadsheet and sheet;
var id = 'yourSpreadsheetId';
var ss = SpreadsheetApp.openById(id);
var sh = ss.getSheetByName('Import_Stats');
var folderId = 'yourIdHere'; //folder by id is the simplest way;
//get folder and files in it;
var folder = DriveApp.getFolderById(folderId);
var files = folder.getFilesByType('text/csv');
var filesImport = folder.getFilesByType('text/csv'); //fetch files again;
//try to fetch number of files;
var scriptProps = PropertiesService.getScriptProperties();
var numFiles = scriptProps.getProperty('numFiles');
//count current number of files;
var numCurr = 0;
while(files.hasNext()) {
var f = files.next();
numCurr++;
}
//if this is the first time, save current number;
if(numFiles===null) {
scriptProps.setProperty('numFiles',numCurr);
}else {
numFiles = parseInt(numFiles);
}
if(numFiles===null||numFiles===(numCurr-1)) {
//get today and reset everything except hours;
var today = new Date();
today.setMinutes(0);
today.setSeconds(0);
today.setMilliseconds(0);
//iterate over files;
while(files.hasNext()) {
var file = files.next();
//get file creation date and reset;
var created = file.getDateCreated();
created.setMinutes(0);
created.setSeconds(0);
created.setMilliseconds(0);
//calculate offset, equals 0 for each file created this hour;
var offset = today.valueOf()-created.valueOf();
if(offset===0) {
//perform data import here;
var data = file.getBlob().getDataAsString();
//ignore empty files;
if(data!=='') {
//split data in rows;
var arr = data.split('\r\n');
//resplit array if only one row;
if(arr.length===1) {
arr = data.split('\n');
}
//append rows with data to sheet;
arr.forEach(function(el){
el = el.split(';');
sh.appendRow(el);
});
}
}
}
}else {
//if never waited, set minute to wait, else add minute;
if(!alreadyWaited) {
alreadyWaited = 60000;
}else {
alreadyWaited += alreadyWaited;
}
//if waited for 10 minutes -> end recursion;
if(alreadyWaited===600000) {
Logger.log('Waited 10 minutes but recieved no files!');
return;
}
//wait a minute and recheck;
Utilities.sleep(60000);
return checkFiles(alreadyWaited);
}
}
And this is what should happen:

Google Drive API - Bulk Uploader, File Renaming and Timeouts

I recently found a great google script which allows one to use Google Sheets to list lots of downloads (Jpegs in my case) and set titles. The script transloads(?)... moves the files from a remote place to your Google Drive. So no pointless downloading, uploading in-between.
function SaveToGoogleDrive(){
var folderID = 'FOLDER_HERE'; // put id of the Google Drive folder
var folder = DriveApp.getFolderById(folderID)// get the folder
var sheet = SpreadsheetApp.getActiveSheet();
var data = sheet.getDataRange().getValues();
for (var i = 1; i < data.length; i++) {
var pdfURL = data[i][2];
var myFileName = data[i][1] + '.pdf';
var file = UrlFetchApp.fetch(pdfURL);
folder.createFile(myFileName,file);
}
}
(code comes via - http://unexpectedweb.blogspot.com.es/2017/11/directly-save-file-to-google-drive-by.html )
The script should allow me to set a name for each upload, which will be applied to the file on adding to Google Drive, but this doesn't work for me.
Is there something obvious in the code which doesn't look good to you as renaming doesn't work. Perhaps there's a script that will allow me to rename once the files are all in my Google Drive?
Also- I'm transloading(?) about 500 files and Google's Scripts can only run for 6mins. How would I incorporate something like the script demonstrated here:
That code...
/* Based on https://gist.github.com/erickoledadevrel/91d3795949e158ab9830 */
function isTimeUp_(start) {
var now = new Date();
return now.getTime() - start.getTime() > 300000; // 5 minutes
}
function SaveToGoogleDrive(){
var folderID = 'FOLDER_HERE'; // put id of the Google Drive folder
var folder = DriveApp.getFolderById(folderID)// get the folder
var sheet = SpreadsheetApp.getActiveSheet();
var data = sheet.getDataRange().getValues();
var threads = GmailApp.getInboxThreads(0, 50);
var start = new Date();
for (var i in threads) {
if (isTimeUp_(start)) {
Logger.log("Time up");
break;
}
// Process the thread otherwise
for (var i = 1; i < data.length; i++) {
var pdfURL = data[i][2];
var myFileName = data[i][1] + '.pdf';
var file = UrlFetchApp.fetch(pdfURL);
folder.createFile(myFileName, file);
}
}
}
Thanks for your thoughts. Having so much trouble marrying the two together with my limited knowledge.
Change
folder.createFile(myFileName,file);
to
folder.createFile(file).setName(myFileName);

Parsing XML Data that I receive from UrlFetch

I want to parse the data I get from UrlFetch into a spreadsheet, but all I'm getting is undefined can someone show me what i'm doing wrong
The xml is at the address https://dl.dropbox.com/u/11787731/Minecraft/bans.xml
function runevery15mins() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("MC Bans");
sheet.clearContents();
var banURL = "https://dl.dropbox.com/u/11787731/Minecraft/bans.xml";
var banXML = UrlFetchApp.fetch(banURL).getContentText();
var banDOC = Xml.parse(banXML, false);
var mcuser = banDOC.bans;
var x = 0;
for(var c=0; c>mcuser.length;c++){
var name = mcuser.getElement("username")[c].getText();
var date = mcuser.getElement("date")[c].getText();
var reason = mcuser.getElement("reason")[c].getText();
var duration = mcuser.getElement("duration")[c].getText();
}
sheet.appendRow([name, date, reason, duration]);
}
You have some small errors in your code.
For example, the second argument in the for loop needs to be c<mcuser.length.
Using the Xml service documentation, this worked for me
function runevery15mins() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("MC Bans");
sheet.clearContents();
var banURL = "https://dl.dropbox.com/u/11787731/Minecraft/bans.xml";
var banXML = UrlFetchApp.fetch(banURL).getContentText();
var banDOC = Xml.parse(banXML, false);
// Get all the child nodes from the document element that are 'user' nodes
var mcusers = banDOC.getElement().getElements('user');
for(var c=0; c<mcusers.length;c++){
var user = mcusers[c];
var name = user.getElement('username').getText();
var date = user.getElement('date').getText();
var reason = user.getElement('reason').getText();
var duration = user.getElement('duration').getText();
sheet.appendRow([name, date, reason, duration]);
}
}
Note for example that the sheet.appendRow line is INSIDE the loop, not outside as you had it before. I also deleted the X variable, since I didn't see any purpose for it.
I also created a user variable, which is an XmlElement, to make it easier to understand how to get the different contents of each node.
You were almost there.
Looks like there was another array you needed to drill down into. Also, your call back to the spreadsheet should be in the loop. Try this:
...
var mcuser = banDOC.bans.user;
for(var i in mcuser){
var name = mcuser[i].getElement("username").getText();
var date = mcuser[i].getElement("date").getText();
var reason = mcuser[i].getElement("reason").getText();
var duration = mcuser[i].getElement("duration").getText();
sheet.appendRow([name, date, reason, duration])
}