DriveApp: Get File ID with Parent Folder ID/Name and File Name - google-apps-script

I'm trying to use DriveApp to determine a file ID utilizing:
Either the parent folder name, or the parent folder ID
The file name in question
I thought I'd use this:
https://yagisanatode.com/2018/10/05/google-apps-script-get-file-by-name-with-optional-parent-folder-crosscheck/
But it always returns:
There are multiple files named: (filename). But none of them are in folder, (foldername).
...which is not true. Any ideas or any easier way to do this?
Thanks in advance.
Here's a minimal example:
/*
* ****Get File By Name***
*
*param 1: File Name
*param 2: Parent Folder of File (optional)
*
*returns: Dictionary of file "id" and "error" message {"id": ,"error": }
* -if there is no error, "id" returns file id and "error" returns false
* -if there is an error, "id" returns false and "error" returns type of error as a string for user to display or log.
*/
function getFileByName(fileName, fileInFolder){
var filecount = 0;
var dupFileArray = [];
var folderID = "";
var files = DriveApp.getFilesByName(fileName);
while(files.hasNext()){
var file = files.next();
dupFileArray.push(file.getId());
filecount++;
};
if(filecount > 1){
if(typeof fileInFolder === 'undefined'){
folderID = {"id":false,"error":"More than one file with name: "+fileName+". \nTry adding the file's folder name as a reference in Argument 2 of this function."}
}else{
//iterate through list of files with the same name
for(fl = 0; fl < dupFileArray.length; fl++){
var activeFile = DriveApp.getFileById(dupFileArray[fl]);
var folders = activeFile.getParents();
var folder = ""
var foldercount = 0;
//Get the folder name for each file
while(folders.hasNext()){
folder = folders.next().getName();
foldercount++;
};
if(folder === fileInFolder && foldercount > 1){
folderID = {"id":false,"error":"There is more than one parent folder: "+fileInFolder+" for file "+fileName}
};
if(folder === fileInFolder){
folderID = {"id":dupFileArray[fl],"error":false};
}else{
folderID = {"id":false,"error":"There are multiple files named: "+fileName+". \nBut none of them are in folder, "+fileInFolder}
};
};
};
}else if(filecount === 0){
folderID = {"id":false,"error":"No file in your drive exists with name: "+fileName};
}else{ //IF there is only 1 file with fileName
folderID = {"id":dupFileArray[0],"error":false};
};
return folderID;
};
function test() {
//My arguments for the function
var myFileName = "Invoices";
var myFileParentFolderName = "testing";
//Run the function
var getFileID = getFileByName(myFileName, myFileParentFolderName);
//Check if folder exists
if(getFileID.id === false){ //if file cannot be accurately found.
Logger.log(getFileID.error); //alert or log error. Give option to try another FileName
}else{
// If the file ID exists then proceed with the program.
Logger.log(getFileID.id);
};
}

The solution is much simpler than you think.
In the following script, adjust the folderID and fileName to your
particular case and it will store in an array all the IDs if multiple
files exist in this folder with the selected name.
If the file exists only one, then you will get an array of a single element.
Solution:
function findFilesInfo() {
const folderId = 'folderID';
const fileName = 'fileName';
const folder = DriveApp.getFolderById(folderId);
const files = folder.getFilesByName(fileName);
const fileIDs = [];
while (files.hasNext()) {
var file = files.next();
fileIDs.push(file.getId());
}
Logger.log(fileIDs);
}
fileIDs has the list of IDs of the particular fileName under the folder with ID folderID.

Related

Is it possible to get the names of all the folders and create from each folder a custom script file for that folder

I made a code that renames multiple files in Google Drive (taken from Here) and here is the content
function renamejpgs() {
/*
// A given Goiogle Drive folder contains jpg files.
// The files have a consistent naming structure:
// "AA123_y.jpg, where y is a single or multi-digit sequence number.
//
// The function renames the files by removing the third, fourth, fifth and sixth characters of the file name and substituting a single"underscore"
// For example, "AA123_1.jpg" -> "AA_1.jpg"
*/
// set the mime type/file type to be renamed
var mimetype = 'image/jpeg';
// get the folder ID; note the id is a string
var folderid = "<<insert your folder ID>>"
// getFoldersById = Gets a specific folders in the user's Drive
var folder = DriveApp.getFolderById(folderid)
// get files in this folder
// myfiles is a File Iterator
var myfiles = folder.getFiles();
// loop through files in this folder
while (myfiles.hasNext()) {
var myfile = myfiles.next();
var myname = myfile.getName();
var ftype = myfile.getMimeType();
// find the next file that matches the mime type
var indexOfFirst = ftype.indexOf(mimetype);
if (indexOfFirst != -1){
// the next file was an image
// edit the file name
var fname = myname.replace("123_", "_");
// update the file name
myfile.setName(fname);
} //end if
} // end while loop through main folder
return false;
}
My problem is that it only works on the root folder and not on subfolders
I tried to ask in this place if subfolders could be included and they helped me change the name there. But then there was a problem of a longer time than the Google limit.
My question is Is it possible to get the names of all the folders and create from each folder a custom script file for that folder in Google Drive
function renamejpgs() {
/*
// A given Goiogle Drive folder contains jpg files.
// The files have a consistent naming structure:
// "AA123_y.jpg, where y is a single or multi-digit sequence number.
//
// The function renames the files by removing the third, fourth, fifth and sixth characters of the file name and substituting a single"underscore"
// For example, "AA123_1.jpg" -> "AA_1.jpg"
*/
// set the mime type/file type to be renamed
var mimetype = 'image/jpeg';
// get the folder ID; note the id is a string
var folderid = "<<insert your folder ID>>"
// getFoldersById = Gets a specific folders in the user's Drive
var folder = DriveApp.getFolderById(folderid)
// get files in this folder
// myfiles is a File Iterator
var myfiles = folder.getFiles();
// loop through files in this folder
while (myfiles.hasNext()) {
var myfile = myfiles.next();
var myname = myfile.getName();
var ftype = myfile.getMimeType();
// find the next file that matches the mime type
var indexOfFirst = ftype.indexOf(mimetype);
Utilities.sleep(10);
if (indexOfFirst != -1){
// the next file was an image
// edit the file name
var fname = myname.replace("123_", "_");
// update the file name
myfile.setName(fname);
} //end if
} // end while loop through main folder
return false;
}
I believe this will help stop the code from timing out.
I added a sleep function to pause it and stop it from exhaustion.
This is my basis of research for using the sleep function: https://webapps.stackexchange.com/questions/116041/how-to-set-up-delays-in-google-apps-script
In this implementation, I've decided to use Drive API service on Apps Scripts in order to use the filter functionality (q parameter) of Drive API queries.
Given that, make sure to add Drive API on the service list on your Apps Scripts project.
The code below will "scan" for a file name that includes MATCH_PATTERN set on a given starting folder ID including sub-folders.
Make sure to customize the constants in the beginning to adapt to your use case.
The function main() is a sample start point for this script.
Sample Code:
const MATCH_PATTERN = "[replace]"; //file name pattern to be renamed
const REPLACE_PATTERN = "[123456789]"; //pattern to replace MATCH_PATTERN from file name
const MIMETYPE_TARGET_FILES = "image/jpeg"; //mimetype of target files
function listFoldersIn(parentId = "root") {
/**
* Lists sub-folders in the user's Drive for a given folder ID.
* Slightly modified script from https://developers.google.com/apps-script/advanced/drive#listing_folders
*/
var query = '"'+ parentId +'" in parents and trashed = false and ' +
'mimeType = "application/vnd.google-apps.folder"';
var folders;
var pageToken;
var folderIdList = [];
do {
folders = Drive.Files.list({
q: query,
maxResults: 100,
pageToken: pageToken
});
if (folders.items && folders.items.length > 0) {
for (var i = 0; i < folders.items.length; i++) {
var folder = folders.items[i];
folderIdList.push(folder.id);
}
}
pageToken = folders.nextPageToken;
} while (pageToken);
return folderIdList;
};
function listTargetFilesForParent(parentId = "root", mime = MIMETYPE_TARGET_FILES) {
/**
* Lists files on a given folder ID matching the MIME type set.
* Slightly modified script from https://developers.google.com/apps-script/advanced/drive#listing_folders
*/
var query = '"'+ parentId +'" in parents and trashed = false and ' +
'mimeType = "'+ mime +'"';
var files;
var pageToken;
var fileIdList = [];
do {
files = Drive.Files.list({
q: query,
maxResults: 100,
pageToken: pageToken
});
if (files.items && files.items.length > 0) {
for (var i = 0; i < files.items.length; i++) {
var file = files.items[i];
fileIdList.push({title: file.title, id: file.id});
}
}
pageToken = files.nextPageToken;
} while (pageToken);
return fileIdList;
};
function renameFilesInFolders(folderList) {
if (folderList.length > 0) { //if folder ID list provided is empty, exit method;
for (folder of folderList) { //loop for folder ID list provided.
//Fetch target files in current folder
var filesList = listTargetFilesForParent(parentId = folder);
renameTargetFiles(filesList); // Rename files if applicable;
//Check for sub folders
var subFolders = listFoldersIn(folder);
if (subFolders.length > 0) { //It has subfolders?
renameFilesInFolders(subFolders); //call this same method recursively
} else { //if current folder does not have sub-folders, then do nothing and continue loop;
continue;
}
}
} else {
return;
}
}
function renameTargetFiles(filesList){
for (file of filesList) { //loop through file list provided
if (file.title.includes(MATCH_PATTERN)) { //is current file applicable to be renamed? In other words, does the current file name contain the MATCH_PATTERN?
var newName = file.title.replace(MATCH_PATTERN, REPLACE_PATTERN); //replace MATCH_PATTERN from file name for REPLACE_PATTERN
Drive.Files.patch({title: newName}, file.id); //Call Drive API patch to modify file name.
}
}
}
function main() {
/**
* Make sure to set up constants in the beginning of this file to adapt to your use case
* When calling renameFilesInFolders() without passing parameters, it will scan starting from the root of My Drive.
* Passing an array of a single Drive Folder ID, sets the root level for the scan
*/
renameFilesInFolders(["<STARTING_FOLDER_ID>"]);
}

Get File by name at specific folder

Trying to replicate this code to fine a specific file by name, by I got error at var files = DriveApp.getFilesByName(fileName); as Exception: Invalid argument at fileName, but if I entered the fileNamemanually as string "10504-China-ReflectiveTape-NA.jpg"` it is running correctly!
Below the full code:
// ****EXAMPLE****
//My arguments for the function
var myFileName = "10504-China-ReflectiveTape-NA.jpg";
var myFileParentFolderName = "Catalog";
//Run the function
var getFileID = getFileByName(myFileName, myFileParentFolderName);
//Check if folder exists
if(getFileID.id === false){ //if file cannot be accurately found.
Logger.log(getFileID.error); //alert or log error. Give option to try another FileName
}else{
// If the file ID exists then proceed with the program.
Logger.log(getFileID.id);
};
/*
* ****Get File By Name***
*
*param 1: File Name
*param 2: Parent Folder of File (optional)
*
*returns: Dictionary of file "id" and "error" message {"id": ,"error": }
* -if there is no error, "id" returns file id and "error" returns false
* -if there is an error, "id" returns false and "error" returns type of error as a string for user to display or log.
*/
function getFileByName(fileName, fileInFolder){
var filecount = 0;
var dupFileArray = [];
var folderID = "";
var files = DriveApp.getFilesByName(fileName);
while(files.hasNext()){
var file = files.next();
dupFileArray.push(file.getId());
filecount++;
};
if(filecount > 1){
if(typeof fileInFolder === 'undefined'){
folderID = {"id":false,"error":"More than one file with name: "+fileName+". \nTry adding the file's folder name as a reference in Argument 2 of this function."}
}else{
//iterate through list of files with the same name
for(fl = 0; fl < dupFileArray.length; fl++){
var activeFile = DriveApp.getFileById(dupFileArray[fl]);
var folders = activeFile.getParents();
var folder = ""
var foldercount = 0;
//Get the folder name for each file
while(folders.hasNext()){
folder = folders.next().getName();
foldercount++;
};
if(folder === fileInFolder && foldercount > 1){
folderID = {"id":false,"error":"There is more than one parent folder: "+fileInFolder+" for file "+fileName}
};
if(folder === fileInFolder){
folderID = {"id":dupFileArray[fl],"error":false};
}else{
folderID = {"id":false,"error":"There are multiple files named: "+fileName+". \nBut none of them are in folder, "+fileInFolder}
};
};
};
}else if(filecount === 0){
folderID = {"id":false,"error":"No file in your drive exists with name: "+fileName};
}else{ //IF there is only 1 file with fileName
folderID = {"id":dupFileArray[0],"error":false};
};
return folderID;
};
I wasn't able to replicate your issue where you passed a parameter and it fails vs you passed a string and it works.
But it seems that you are running getFileByName() directly by default thus it errors out like that.
Place everything else inside a main function and run that function instead.
I'm getting the expected output when I do that using your code.
It should look like this:
make sure that the chosen function is your main function that calls the getFileByName and just run the code.

Checking a folder for a filename, and exporting as PDF if it is not found

I am quite new to scripting and has attempted this for embarrassingly many hours now, so I hope you can help me.
I have a dashfolder that contains Google Sheets called "X", and I have a pdffolder containing pdfs that are called "X.pdf". I am trying to loop through the names of my dashfiles + ".pdf" to find those which are missing, and finally create its pdf in that same folder. My script, however, loops too many times. I want it to skip the dashfile if a file with the name+".pdf" are already in the pdffolder. Here is my code
function createPdf() {
var pdfFolder = DriveApp.getFolderById("ID")
var pdfFiles = pdfFolder.getFiles();
var dashFolder = DriveApp.getFolderById('ID');
var dashFiles = dashFolder.getFiles();
var pdfNames = [];
var dashNames = [];
while (pdfFiles.hasNext()) {
var currentFile2 = pdfFiles.next();
var fileName2 = currentFile2.getName();
pdfNames.push(fileName2);
}
while (dashFiles.hasNext()) {
var currentFile = dashFiles.next();
var fileName = currentFile.getName();
dashNames.push(fileName);
for (p in pdfNames) {
if((fileName + ".pdf") == pdfNames[p]) {
Logger.log("YES");
}
else {
var xlsBlob = currentFile.getBlob(); // Blob source of Excel file for conversion
var xlsFilename = currentFile.getName(); // File name to give to converted file; defaults to same as source file
pdfFolder.createFile(currentFile.getAs(MimeType.PDF));
Logger.log("pdf Created");
}
}
}
}
My real problem stems from the fact that I will have 100+ sheets that needs to be converted to pdf's and that will exceed the 6 minutes limit. So I am trying to build a script that can trigger itself and continue where it left off, skipping sheets that are already in the pdffolder and creating those that are missing.
I might be way over my head here, so I hope someone can give me some hints :-)
You wrong use loops. Currently you create more pdfs of same sheet, because when you traversating over dashFiles, you create PDF one more pdf for each exists PDF.
Change part of code like this:
var pdfNames = {}; //Object instead of array
while (pdfFiles.hasNext()) {
var currentFile2 = pdfFiles.next();
var fileName2 = currentFile2.getName();
pdfNames[fileName2] = true; // use PDF name as key for faster searching
}
while (dashFiles.hasNext()) {
var currentFile = dashFiles.next();
var fileName = currentFile.getName();
if(pdfNames[fileName + ".pdf"]) { // is exists pdf?
Logger.log("YES");
}
else {
var xlsBlob = currentFile.getBlob(); // Blob source of Excel file for conversion
var xlsFilename = currentFile.getName(); // File name to give to converted file; defaults to same as source file
pdfFolder.createFile(currentFile.getAs(MimeType.PDF));
Logger.log("pdf Created");
}
}
You can create an object of file names with a value of true, and then check for the existence of the file name in the object. If the file name exists, then continue to loop.
var pdfNames = {};//Create an object - not an array
pdfNames[fileName2] = true;//Put the file name into the object
if (pdfNames[fileName]) {//Test for file name in the object
Code:
function createPdf() {
var currentFile,fileName,xlsBlob,xlsFilename;
var pdfFolder = DriveApp.getFolderById("ID")
var pdfFiles = pdfFolder.getFiles();
var dashFolder = DriveApp.getFolderById('ID');
var dashFiles = dashFolder.getFiles();
var pdfNames = {};
var dashNames = [];
while (pdfFiles.hasNext()) {
var currentFile2 = pdfFiles.next();
var fileName2 = currentFile2.getName();
pdfNames[fileName2] = true;//Put the file name into the object
}
while (dashFiles.hasNext()) {
currentFile = dashFiles.next();
fileName = currentFile.getName();
dashNames.push(fileName);
if (pdfNames[fileName]) {//The file name was found in the object of pdf files
Logger.log("YES");
continue;
}
xlsBlob = currentFile.getBlob(); // Blob source of Excel file for conversion
xlsFilename = currentFile.getName(); // File name to give to converted file; defaults to same as source file
pdfFolder.createFile(currentFile.getAs(MimeType.PDF));
Logger.log("pdf Created");
}
}
Original Answer:
Test for the existence of the file name in the pdf array in a different way.
pdfNames.indexOf(fileName + ".pdf") !== -1
If a value is not found in an array, then indexOf() returns minus one.
So, if the return value is not minus one, then a file name was found. If a file name was found, you don't want a new file created, so continue.
function createPdf() {
var currentFile,fileName,xlsBlob,xlsFilename;
var pdfFolder = DriveApp.getFolderById("ID")
var pdfFiles = pdfFolder.getFiles();
var dashFolder = DriveApp.getFolderById('ID');
var dashFiles = dashFolder.getFiles();
var pdfNames = [];
var dashNames = [];
while (pdfFiles.hasNext()) {
var currentFile2 = pdfFiles.next();
var fileName2 = currentFile2.getName();
pdfNames.push(fileName2);
}
while (dashFiles.hasNext()) {
currentFile = dashFiles.next();
fileName = currentFile.getName();
dashNames.push(fileName);
if (pdfNames.indexOf(fileName + ".pdf") !== -1) {//The file name was found in the array of pdf files
Logger.log("YES");
continue;
}
xlsBlob = currentFile.getBlob(); // Blob source of Excel file for conversion
xlsFilename = currentFile.getName(); // File name to give to converted file; defaults to same as source file
pdfFolder.createFile(currentFile.getAs(MimeType.PDF));
Logger.log("pdf Created");
}
}

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;
}

unable to access folder just created Google Drive

I created a script that generates a folder in Google Drive and gets the folder ID of that folder.
Upon trying to access that folder in a unique function I get the error:
no item with the given ID could be found, or you do not have permission to access it on Folder
Please see my script below (called independently from html):
var childFolderIdA;
function doGet() {
return HtmlService.createHtmlOutputFromFile('multifile').setTitle('test – multi upload').setSandboxMode(HtmlService.SandboxMode.IFRAME);
}
function test(parent,child){
createSharedSubFolder(parent,child);
}
function createSharedSubFolder(parent,child) { // folder names as string parameters
var folders = DriveApp.getFolders();
var exist = false
while (folders.hasNext()) {
var folder = folders.next();
if(folder.getName()==parent){exist = true ; var folderId = folder.getId(); break};// find the existing parent folder
}
if(exist){ //parent folder exists
var child = DriveApp.getFolderById(folderId).createFolder(child).setSharing(DriveApp.Access.ANYONE, DriveApp.Permission.EDIT);
var childFolderId = child.getId();
childFolderIdA = childFolderId;
}else{
var childFolder = DriveApp.createFolder(parent).createFolder(child); //create parent and child folders
childFolder.setSharing(DriveApp.Access.ANYONE, DriveApp.Permission.EDIT);
}
}
function saveFile(data,name,folderName) {
var contentType = data.substring(5,data.indexOf(';'));
var file = Utilities.newBlob(Utilities.base64Decode(data.substr(data.indexOf('base64,')+7)), contentType, name); //does the uploading of the files
DriveApp.getFolderById(childFolderIdA).createFile(file);
}
You can't depend on global variables to save state between calls. Each time you call a script a new script instance is spawned. Each one will maintain its own state.
For example:
google.script.run.createSharedSubFolder(...) --> Script Instance 1..var childFolderIdA=folderId;
google.script.run.saveFile(...) --> Script Instance 2..var childFolderIdA=null;
You can save the the folderId to the users property store:
PropertiesService.getUserProperties().setProperty("childFolderId", childFolderId);
You can retrieve the folder Id:
var folderId = PropertiesService.getUserProperties().getProperty("childFolderId");
Your code with this change:
function doGet() {
return HtmlService.createHtmlOutputFromFile('multifile').setTitle('test – multi upload').setSandboxMode(HtmlService.SandboxMode.IFRAME);
}
function test(parent,child){
createSharedSubFolder(parent,child);
}
function createSharedSubFolder(parent,child) { // folder names as string parameters
var folders = DriveApp.getFolders();
var exist = false
while (folders.hasNext()) {
var folder = folders.next();
if(folder.getName()==parent){exist = true ; var folderId = folder.getId(); break};// find the existing parent folder
}
if(exist){ //parent folder exists
var child = DriveApp.getFolderById(folderId).createFolder(child).setSharing(DriveApp.Access.ANYONE, DriveApp.Permission.EDIT);
var childFolderId = child.getId();
PropertiesService.getUserProperties().setProperty("childFolderId", childFolderId);
}else{
var childFolder = DriveApp.createFolder(parent).createFolder(child); //create parent and child folders
childFolder.setSharing(DriveApp.Access.ANYONE, DriveApp.Permission.EDIT);
}
}
function saveFile(data,name,folderName) {
var contentType = data.substring(5,data.indexOf(';'));
var file = Utilities.newBlob(Utilities.base64Decode(data.substr(data.indexOf('base64,')+7)), contentType, name); //does the uploading of the files
var childFolderId = PropertiesService.getUserProperties().getProperty("childFolderId");
DriveApp.getFolderById(childFolderId).createFile(file);
}