How to iterate through the files in a folder - Part II? - google-apps-script

I wrote this code to iterate from the files of a folder:
function showList() {
var folder = DocsList.getFolderById('0B9HEC6UUJ_rsYWNPYko0MsrBRU0');
var files = folder.getFiles();
Logger.log("files = " + files);
arrayList = [];
for (var file in files) {
Logger.log("file = " + file);
var fileName = file.getName();
var fileId = file.getId();
var newArray = [fileName, "some info", fileId];
arrayList.push(newArray);
}
But in this line var fileName = file.getName();, I got this error: TypeError: Cannot find function getName in object 0.
The logs show this:
It seems there are files, but not the file that should get in the for loop. How to fix that?

Many problems in your js code:
1) thats not how you use 'in' in js. File will be an index so you need to do files[file]
2) even then its still wrong because iterating an array with 'in' will give you other things like the 'length' property.
Look up in the web how to iterate a js array.

Related

How to search a Specific File Name and File Type together in Google Drive folder from multiple sub folders using Google Script

In Google Drive, I'm searching for a specific file that contain words "Verification Visit" and "completed" where the file type need to be a spreadsheet(.xlsx) type file. The searching will go through a parent folder and then into sub folders. So the particular files will be stored in some of the sub folders (Google Drive -> Parent folder -> Sub folders).
Here is the code:
function getChildFolders(parentName, parent, sheet, voidFolder, excluded) {
var childFolders = parent.getFolders();
var folder = childFolders.next();
var failIter = folder.searchFiles('title contains "completed"');
while (failIter.hasNext()) {
var fail = failIter.next();
var failWithTitle = fail.getName();
var files = folder.getFilesByType(MimeType.MICROSOFT_EXCEL);
var output = [];
var path;
var Url;
var fileID;
while (files.hasNext()) {
var childFile = files.next();
var fileName = childFile.getName();
path = parentName + ' |--> ' + fileName;
fileID = childFile.getId();
Url = 'https://drive.google.com/open?id=' + fileID;
output.push([fileID, fileName, path, Url]);
}
if (output.length) {
var last_row = sheet.getLastRow();
sheet.getRange(last_row + 1, 1, output.length, 4).setValues(output);
}
getChildFolders(
parentName + ' |--> ' + fileName,
folder,
sheet,
voidFolder,
excluded
);
}
}
I have successfully implemented the two conditions (file that contains specific name and also the file type) in this function. The problem here is the searching process of the file in the sub folder only does on the first sub folder and it doesn't proceed searching into the next sub folder. It only list the files from the first sub folder but not from the other sub folders.
I am getting an error message telling "Exception: Cannot retrieve the next object: iterator has reached the end."
Issues:
You are not checking whether parent.getFolders() returns any folder, so when you reach the end of the hiearchy and there are no further subfolders on that level, childFolders.next(); fails with the error you are getting.
You are not iterating through the FolderIterator (for example, with while (folderIter.hasNext()) {), so you are only getting the first folder in the iterator.
You are iterating through the files that contain completed, but inside each file iteration you are iterating again with folder.getFilesByType to check the MIME type (check the available search query terms). That is to say, you are iterating through the files in that folder twice, which will most likely return duplicate results.
Suggested workflow:
For each folder, look for your desired files with Folder.searchFiles with the two conditions you want (title contains completed and mime type corresponds to MS Excel) and iterate through the results with FileIterator.hasNext() in a while loop.
For each folder, look for the corresponding subfolders via Folder.getFolders(), and loop through the results with FolderIterator.hasNext() and a while loop. For each of these subfolders, call your function recursively.
Use SpreadsheetApp.flush to make sure the sheet gets updated every time setValues is used, so that previous data is not overwritten.
Code sample:
function getChildFiles(folderName, folder, sheet) {
var fileIter = folder.searchFiles("title contains 'completed' and mimeType='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'");
var folderId = folder.getId();
var output = [];
while (fileIter.hasNext()) { // Iterate through files in folder
var file = fileIter.next();
var fileName = file.getName();
var path = folderName + ' |--> ' + fileName;
var fileID = file.getId();
var Url = 'https://drive.google.com/open?id=' + fileID;
output.push([fileID, fileName, path, Url, folderId]);
}
if (output.length) {
var last_row = sheet.getLastRow();
sheet.getRange(last_row + 1, 1, output.length, output[0].length).setValues(output);
SpreadsheetApp.flush();
}
var childFolders = folder.getFolders();
while (childFolders.hasNext()) { // Iterate through folders in folder
var childFolder = childFolders.next();
var childFolderName = childFolder.getName();
getChildFiles(
folderName + ' |--> ' + childFolderName,
childFolder,
sheet
);
}
}

List all files and folder in google drive

I've been trying to figure this out for a while now. I hope I can get some guidance on this. The purpose of the following script is to get a full list of folders and files with subfolders and their files included.
Here is what I currently have:
var counter = 0
var files = folder.getFiles();
var subfolders = folder.getFolders();
var folderPath = folder.getName();
while (subfolders.hasNext()){
subfolder = subfolders.next();
var row = [];
//row.push(subfolder.getName(),'',subfolder.getId(),subfolder.getUrl(),subfolder.getSize(),subfolder.getDateCreated(),subfolder.getLastUpdated());
//list.push(row);
if(counter > 0){
var files = subfolder.getFiles();
}
while (files.hasNext()){
file = files.next();
var vals = file.getUrl();
var row = [];
if(counter == 0){
row.push(folder.getName(),file.getName(),file.getId(),file.getUrl(),file.getSize(),file.getDateCreated(),file.getLastUpdated())
}else{
row.push(folderPath + '/' + subfolder.getName(),file.getName(),file.getId(),file.getUrl(),file.getSize(),file.getDateCreated(),file.getLastUpdated())
}
list.push(row);
}
counter = counter + 1
}
It currently gets the folder names and file names for the current folder and it's subfolder. It doesn't go any further than that. I'm stuck trying to figure out how to get a loop going to continue until there are no more sub-folders.
It isn't a very big drive. There are less than 10 levels but would like the flexibility to go further if needed.
Recursion is beneficial in this case. The code below calls the recursive method recurseFolder() which takes a Folder and Array as a parameter. It adds all the files in the folder to a list, then calls itself on any subfolders it finds.
function test(){
var root = DriveApp.getRootFolder();
var list = [];
var list = recurseFolder(root, list);
Logger.log(JSON.stringify(list));
//This is just how I am testing the outputed list. You can do what you need.
var sheet = SpreadsheetApp.getActiveSheet();
list.forEach(function (row){
sheet.appendRow(row);
});
}
function recurseFolder(folder, list){
var files = folder.getFiles();
var subfolders = folder.getFolders();
while (files.hasNext()){ //add all the files to our list first.
var file = files.next();
var row = [];
Logger.log("File: " + folder.getName());
row.push(folder.getName(),file.getName(),file.getId(),file.getUrl(),file.getSize(),file.getDateCreated(),file.getLastUpdated())
list.push(row);
}
while (subfolders.hasNext()){ //Recurse through child folders.
subfolder = subfolders.next();
Logger.log("Folder: " + subfolder.getName());
list = recurseFolder(subfolder, list); //Past the original list in so it stays a 2D Array suitible for inserting into a range.
}
return list;
}
I'm not sure if the output is formatted how you intended so you might need to play with it a little. Note: It will easily time out if run on a larger Drive.
You need a function that will navigate the folder structure recursively, meaning that if it runs into a subfolder within a folder, it will call itself again passing that folder as a new parent.
function listFolders(parentFolderId) {
var sourceFolder = DriveApp.getFolderById(parentFolderId) || DriveApp.getRootFolder();
var folders = sourceFolder.getFolders();
var files = sourceFolder.getFiles();
while (files.hasNext()) {
var file = files.next();
//Do something
}
while (folders.hasNext()) {
var folder = folders.next();
listFolders(folder.getId());
}
}
Note that this function will still time out if you have lots of files in your Drive, in which case you need to store the state of your app using PropertiesService and schedule the function to run again using triggers via the ScriptApp. You can achieve this by saving the continuation token for your Files Iterator between script executions
More on ContinuationToken

Skip processing .xls to Google Sheets script if the file already exists in Google Drive

I am currently using this code to automatically convert all uploaded .xls files in Google Drive to Google Sheets.
function importXLS(){
var files = DriveApp.searchFiles('title contains ".xls"');
while(files.hasNext()){
var xFile = files.next();
var name = xFile.getName();
if (name.indexOf('.xls')>-1){
var ID = xFile.getId();
var xBlob = xFile.getBlob();
var newFile = { title : name,
key : ID,
'parents':[{"id":"12FcKokB-ppW7rSBtAIG96uoBOJtTlNDT"}]
}
file = Drive.Files.insert(newFile, xBlob, {
convert: true
});
}
}
}
It works perfectly, but fails if there is already a file in the output folder with the same name. Even though I never technically get to see this error below (since it runs on a schedule and not fired manually like in the screenshot), I would prefer to simply skip the conversion process if the file already exists.
If possible, I would also like to avoid overwriting it each time, as I feel that would be a waste of processing time. How would I edit this code to say that if the file name already exists in that folder, skip the entire code completely?
Thanks!
Two things you can try:
Get the files names that are already in the destination folder and check if the file exists before you try copying.
Wrap the section of your code that does the copying in a try..catch statement.
Both of these should work independently, but using the try..catch statement will catch all errors, so it would be best to combine them. (You can review the error logs in the Developer Console.) Doing this you'll be able to skip files that have the same name as those already in your destination folder and any other error that might come up will not terminate your script from completing.
function importXLS(){
var files = DriveApp.searchFiles('title contains ".xls"');
var destinationFolderId = "12FcKokB-ppW7rSBtAIG96uoBOJtTlNDT";
var existingFileNames = getFilesInFolder(destinationFolderId);
while(files.hasNext()){
var xFile = files.next();
var name = xFile.getName();
try {
if (!existingFileNames[name] && (name.indexOf('.xls')>-1)) {
var ID = xFile.getId();
var xBlob = xFile.getBlob();
var newFile = { title : name,
key : ID,
'parents':[{"id": destinationFolderId}]
}
file = Drive.Files.insert(newFile, xBlob, {
convert: true
});
}
} catch (error) {
console.error("Error with file " + name + ": " + error);
}
}
}
/**
* Get an object of all file names in the specified folder.
* #param {string} folderId
* #returns {Object} files - {filename: true}
*/
function getFilesInFolder(folderId) {
var folder = DriveApp.getFolderById(folderId);
var filesIterator = folder.getFiles();
var files = {};
while (filesIterator.hasNext()) {
var file = filesIterator.next();
files[file.getName()] = true;
}
return files;
}

Add multiple dynamic attachments to email

I'm listing all PDF files of a folder and send them into an email.
The issue is that I don't know how attach multiple files.
This is what I've tried so far: put files into blob then pass it.
Other try was to put blob[0] and blob1 if there is 2 pdf files but it doesn't work.
var folders = DriveApp.getFoldersByName(folderToScan);
var folder = folders.next();
var contents = folder.getFiles();
var blob = [];
var filesTextList = "";
// foreach file
for(var counter = 0;contents.hasNext();counter++)
{
file = contents.next();
var MimeType = file.getMimeType();
// filter PDF
if(file.getMimeType() == "application/pdf")
{
blob[counter] = file.getBlob();
// add file name to text
filesTextList += "\n" + file.getName();
}
}
MailApp.sendEmail(sender, subject, message,
{attachments: blob}
);
If I only have 1 blob file, {attachments: blob[0]} is working but it's not dynamic
Here is the debugger at the line of the mail:
The attachments parameter of sendEmail method takes "an array of files to send with the email". They can be File objects, you don't have to get blobs from them.
More importantly, your loop will create an array with undefined elements because blob[counter] only gets assigned when the file is a PDF, but the value of counter increases regardless. I don't think sendEmail will be happy about that.
Use while loop with iterators, and push method to add elements to an array. A complete example.
function emailatt() {
var contents = DriveApp.getFolderById("id here").getFiles();
var attachments = [];
while (contents.hasNext()) {
var file = contents.next();
if (file.getMimeType() == "application/pdf") {
attachments.push(file);
}
}
MailApp.sendEmail("user#example.com", "subject", "body", {attachments: attachments});
}
Aside: getting folder by Id is best when you know what folder you want. Using getFoldersByName and then picking whatever folder with that name came up first is a less robust approach.

Creating a zip file inside Google Drive with Apps Script

I have a folder in Google Drive folder containing few files. I want to make a Google Apps Script that will zip all files in that folder and create the zip file inside same folder.
I found a video that has Utilities.zip() function, but there is no API reference for that. How do I use it? Thanks in advance.
Actually it's even easier than that. Files are already Blobs (anything that has getBlob() can be passed in to any function that expects Blobs). So the code looks like this:
var folder = DocsList.getFolder('path/to/folder');
folder.createFile(Utilities.zip(folder.getFiles(), 'newFiles.zip'));
Additionally, it won't work if you have multiple files with the same name in the Folder... Google Drive folders support that, but Zip files do not.
To make this work with multiple files that have the same name:
var folder = DocsList.getFolder('path/to/folder');
var names = {};
folder.createFile(Utilities.zip(folder.getFiles().map(function(f){
var n = f.getName();
while (names[n]) { n = '_' + n }
names[n] = true;
return f.getBlob().setName(n);
}), 'newFiles.zip'));
As DocsList has been deprecated, You can use the following code to zip an entire folder containing files and sub-folders and also keep its structure:
var folder = DriveApp.getFolderById('<YOUR FOLDER ID>');
var zipped = Utilities.zip(getBlobs(folder, ''), folder.getName()+'.zip');
folder.getParents().next().createFile(zipped);
function getBlobs(rootFolder, path) {
var blobs = [];
var files = rootFolder.getFiles();
while (files.hasNext()) {
var file = files.next().getBlob();
file.setName(path+file.getName());
blobs.push(file);
}
var folders = rootFolder.getFolders();
while (folders.hasNext()) {
var folder = folders.next();
var fPath = path+folder.getName()+'/';
blobs.push(Utilities.newBlob([]).setName(fPath)); //comment/uncomment this line to skip/include empty folders
blobs = blobs.concat(getBlobs(folder, fPath));
}
return blobs;
}
getBlobs function makes an array of all files in the folder and changes each file name to it's relative path to keep structure when became zipped.
To zip a folder containing multiple items with the same name use this getBlob function:
function getBlobs(rootFolder, path) {
var blobs = [];
var names = {};
var files = rootFolder.getFiles();
while (files.hasNext()) {
var file = files.next().getBlob();
var n = file.getName();
while(names[n]) { n = '_' + n }
names[n] = true;
blobs.push(file.setName(path+n));
}
names = {};
var folders = rootFolder.getFolders();
while (folders.hasNext()) {
var folder = folders.next();
var n = folder.getName();
while(names[n]) { n = '_' + n }
names[n] = true;
var fPath = path+n+'/';
blobs.push(Utilities.newBlob([]).setName(fPath)); //comment/uncomment this line to skip/include empty folders
blobs = blobs.concat(getBlobs(folder, fPath));
}
return blobs;
}
I was able to use the code that #Hafez posted but I needed to modify it because It was not working for me. I added the first three lines because I needed the folder ID which is a string value and is not the name of the folder.
var folderName = DriveApp.getFoldersByName("<folderName>");
var theFolder = folderName.next();
var folderID =theFolder.getId();
var folder = DriveApp.getFolderById(folderID);
var zipped = Utilities.zip(getBlobs(folder, ''), folder.getName()+'.zip');
folder.getParents().next().createFile(zipped);
function getBlobs(rootFolder, path) {
var blobs = [];
var files = rootFolder.getFiles();
while (files.hasNext()) {
var file = files.next().getBlob();
file.setName(path+file.getName());
blobs.push(file);
}
var folders = rootFolder.getFolders();
while (folders.hasNext()) {
var folder = folders.next();
var fPath = path+folder.getName()+'/';
blobs.push(Utilities.newBlob([]).setName(fPath)); //comment/uncomment this line to skip/include empty folders
blobs = blobs.concat(getBlobs(folder, fPath));
}
return blobs;
}
The only weird thing that I'm experiencing is that when I run the script it says TypeError: Cannot call method "getFiles" of undefined. (line 10, file "Code"). When I happened to look at the place where this script lives there was also 5 zip files that were complete. It works but I still get that error. Weird...but this code works for me. Thanks to everyone on this thread. Cheers!
There's no API reference indeed. You could open an issue request regarding this on Apps Script issue tracker. But deducing from what the code-completion shows, here is my understanding:
var folder = DocsList.getFolder('path/to/folder');
var files = folder.getFiles();
var blobs = [];
for( var i in files )
blobs.push(files[i].getBlob());
var zip = Utilities.zip(blobs, 'newFiles.zip');
folder.createFile(zip);
But I have not tested this code, so I don't know if it will work. Also, it may work only for files not converted to Google's format, or maybe only for those or a subset of it. Well, if you try it out and find something, please share here with us. One limit that you'll sure face is the filesize, it will probably not work if the zip file gets "too" big... yeah, you'll have to test this limit too.
If Hafez solution didn't worked out, and you get this error
TypeError: Cannot read property 'getFiles' of undefined
Try doing this
/**
* Creates a zipFile of the mentioned document ID and store it in Drive. You can search the zip by folderName.zip
*/
function createAndSendDocument() {
var files = DriveApp.getFolderById("DOCUMENT ID CAN BE FIND IN THE URL WHEN A DOCUMENT IS OPENED");
var folder = files;
var zipped = Utilities.zip(getBlobs(folder, ''), folder.getName() + '.zip');
folder.getParents().next().createFile(zipped);
}
function getBlobs(rootFolder, path) {
var blobs = [];
var files = rootFolder.getFiles();
while (files.hasNext()) {
var file = files.next().getBlob();
file.setName(path+file.getName());
blobs.push(file);
}
var folders = rootFolder.getFolders();
while (folders.hasNext()) {
var folder = folders.next();
var fPath = path+folder.getName() + '/';
blobs.push(Utilities.newBlob([]).setName(fPath)); //comment/uncomment this line to skip/include empty folders
blobs = blobs.concat(getBlobs(folder, fPath));
}
return blobs;
}