google script: File class is not defined - google-apps-script

I'm trying to make my google docs script create a backup copy of the file each time I open it.
To make a copy I write
var name = File.getName();
var filecopy = File.makeCopy(name + " backup");
But it won't recognize the File class. Although it knows DocsList. How do I make it work or make a copy of the file another way?

GAS permits to call class methods or instance only native classes (Object, String, etc), own classes or Google Services (DocList, SpreadsheetApp, etc). Other classes like File, Folder, Spreadsheet, Range, etc are accessible and instanceable only via calling the services functions, for example, DocsList.getFileById("..."); returns the File class instance.
The following function copies a file having the srcFileID ID to a new file with the name stored in the dstFileName parameter.
function testCopy(srcFileID, dstFileName) {
var srcFile = DocsList.getFileById(srcFileID);
srcFile.makeCopy(dstFileName);
}

You cannot use the File class that way. Use something on these lines
var file = DocsList.getFileById(ID) ; // you can use DocsList.find or DocsList.create
var filecopy = file.makeCopy();

Related

Run a Google Apps Script from another file within the same project

I would like to run the script "xGen_FilterRemove" from my "Generic Macros.gs" file in my "Test" Script located in "Macro.gs" as per below, but am know sure how to correctly reference it?
function Test() {
var aCurrentBook = SpreadsheetApp.getActive();
'Generic Macros.gs'.xGen_FilterRemove()
}
};
Just run it like this
function Test() {
var aCurrentBook = SpreadsheetApp.getActive();
xGen_FilterRemove()
}
All of the functions in a project are accessible universally no matter what file they are in. That's why all functions must have a unique name. The files are provided to make it easier to organize them. You can have as many functions in a file as you wish

Overwrite an Image File with Google Apps Script

Can I overwrite an image file with Google Apps Script? I've tried:
file.setContent(newBlobImage);
file.replace(newBlobImage);
Neither of those work. .setContent() will delete whatever data was in the file, and it looks like maybe it just writes the variable name as text, or something like that. I'm assuming that both .setContent() and .replace() are meant for text documents, and maybe that's why they don't work.
If it were a text file, or a spreadsheet, I might be able to clear it, then append new content.
I can trash the file, then create a new one, but I'd rather not if there is some other way.
If I write a file with the same name, it won't overwrite the existing file, it creates a another file with the same name.
The only way I've been able to trash the file is with DocsList and the only success I've had with creating an image file is with DriveApp. So I have to trash the file with DocsList, then create another file with DriveApp.
Well, I've figured out how to delete the file without sending it to the trash, so I won't need to clean out the trash later. The Google Drive SDK inside of Apps Script has a remove method that didn't send the file to trash, it's just gone.
var myFolder = DriveApp.getFolderById('3Bg2dKau456ySkhNBWB98W5sSTM');
thisFile = myFolder.getFilesByName(myFileName);
while (thisFile.hasNext()) {
var eachFile = thisFile.next();
var idToDLET = eachFile.getId();
Logger.log('idToDLET: ' + idToDLET);
var rtrnFromDLET = Drive.Files.remove(idToDLET);
};
So, I'm combining the DriveApp service and the DriveAPI to delete the file without sending it to the trash. The DriveAPI .remove needs the file ID, but I don't have the file ID, so the file gets looked up by name, then the file ID is retrieved, then the ID is used to delete the file. So, if I can't find a way to overwrite the file, I can at least delete the old file without it going to the trash.
I just noticed that the DriveAPI service has a Patch and an Update option.
.patch(resource, fileId, optionalArgs)
Google Documentation Patch Updates file metadata.
The resource arg is probably the metadata. The fileId is self explanatory. I'm guessing that the optionalArgs are parameters that follow the HTTP Request Patch semantics? I don't know.
It looks like both Patch and Update will update data. Update is a PUT request that will
clears previously set data if you don't supply optional parameters.
According to the documentation. So it's safer to use a Patch request because any parameters that are missing are simply ignored. I haven't tried it yet, but maybe this is the answer.
I'm getting an error with Patch, so I'll try Update:
.update(resource, fileId, mediaData)
That has a arg for mediaData in the form of a blob. And I think that is what I need. But I'm not sure what the resource parameter needs. So I'm stuck there.
An image file can be overwritten with Google Apps Script and the DriveAPI using the update() method:
.update(File resource, String fileId, Blob mediaData)
Where file resource is:
var myFileName = 'fileName' + '.jpg';
var file = {
title: myFileName,
mimeType: 'image/jpeg'
};
I'm getting the file ID with the DriveApp service, and the Blob is what was uploaded by the user.
In order to use DriveAPI, you need to add it through the Resources, Advanced Google Services menu. Set the Drive API to ON.
var allFilesByName,file,myFolder,myVar,theFileID,thisFile;
//Define var names without assigning a value
file = {
title: myFileName,
mimeType: 'image/jpeg'
};
myFolder = DriveApp.getFolderById('Folder ID');
allFilesByName = myFolder.getFilesByName(myFileName);
while (allFilesByName.hasNext()) {
thisFile = allFilesByName.next();
theFileID = thisFile.getId();
//Logger.log('theFileID: ' + theFileID);
myVar = Drive.Files.update(file, theFileID, uploadedBlob);
};
Thank you for this track !
This allowed me to find a solution to my problem : move a bound form after copying and moved his spreadsheet.
The Drive app advanced service must be activated in the "Resource Script Editor" to run this script.
function spreadsheetCopy() {
// Below is the file to be copied with a bound form.
var fileToCopy = DriveApp.getFileById("file_key"); // key is fileId
var saveFolder = DriveApp.getFolderById("folder_key"); // key is folderId
var currentFolder = "";
( fileToCopy.getParents().next() ) ? currentFolder = fileToCopy.getParents().next() : currentFolder = DriveApp.getRootFolder();
Logger.log(currentFolder)
var copyFile = fileToCopy.makeCopy(saveFolder),
copyName = copyFile.getName();
Utilities.sleep(30000);
moveFormCopy(currentFolder, saveFolder, copyName);
}
function moveFormCopy(currentFolder, saveFolder, copyName) {
var formsInFolder = currentFolder.getFilesByType(MimeType.GOOGLE_FORMS);
var form, copyForm, copyFormMimeType, copyFormName, copyFormId;
while ( formsInFolder.hasNext() ) {
form = formsInFolder.next();
if ( copyName === form.getName() ) {
copyForm = form;
copyFormMimeType = copyForm.getMimeType();
copyFormName = copyForm.getName();
copyFormId = copyForm.getId();
break;
}
};
var resource = {title: copyName, mimeType: copyFormMimeType};
Drive.Files.patch(resource, copyFormId, {addParents: saveFolder.getId(), removeParents: currentFolder.getId()})
}

Create new file in a folder with Apps Script using Google Advanced Drive service

There are four ways to create a new file:
DocsList - Shown as DocsList in the Main List. Built in to Apps Script.
DriveApp - Shown as Drive in the Main List. Built in to Apps Script.
Drive API - Also shown as Drive in the Main List. Must be added to Apps Script.
DocumentApp - Shown as Document in the Main List. Built in, but only creates a document file.
They are all called services. Drive API is called an advanced service. So, which one should you use? I don't know, it depends. This question is about the Drive API Advanced Service.
I don't want to use 2 or 3 of the services to get the job done. I'd like to just use one of them. But to decide which one to use, I need to know the capabilities and options of all of them. If the simplest and easiest one to use will do everything I want, then I'll use that.
If I can create a new file with Drive API, but then I need to use the DriveApp service to move the file I created with Drive API, to the folder I want it in, then using Drive API in that particular situation is pointless.
I can create a new file in my Google Drive from a Google Apps Script .gs code, but the file gets written to the main 'My Drive'. I want to write the file directly to a sub-folder. My current code is:
var fileNameSetA = 'someFile.jpg';
var uploadedBlobA = an image uploaded with a file picker;
var fileTestDrive = {
title: fileNameSetA,
mimeType: 'image/jpeg'
};
fileTestDrive = Drive.Files.insert(fileTestDrive, uploadedBlobA);
Even though the code works, I have no idea why the syntax is the way it is, and I can't find documentation that tells me why. I can find a list of properties:
The title: and mimeType: are Optional Properties as part of the Request Body. From the example, the Optional Properties are obviously put in a key:value paired object. So, is the syntax:
Drive.Files.insert(optional properties, content);
There are also Required query parameters of:
uploadType --> media, multipart, resumable
But I don't see any required uploadType parameter designated anywhere in the example code. So, I don't understand Google's documentation.
Google Documentation Insert
Is it possible to write directly to a specific drive with Google Advanced Drive service in a Apps Script .gs code file? How do I do it?
The easiest way to create a new file is to use DriveApp which comes with pure Google Apps Script:
var dir = DriveApp.getFolderById("{dir_id}");
var file = dir.createFile(name, content);
If you do not know exact directory's id you can get the folder by its name:
var dir = DriveApp.getFoldersByName(name).next();
The next() is there because getFoldersByName() returns collection of all directories whose names match given value.
Also check DriveApp docs: https://developers.google.com/apps-script/reference/drive/drive-app
Maybe this is a bit late, but by looking at the REST API docs, it shows that you can use Drive.Files.insert to insert into any folder. You simply have to add the folder's ID in the properties of the file you are inserting as such:
var file = {
title: 'myFile',
"parents": [{'id':folder.getId()}], //<--By setting this parent ID to the folder's ID, it creates this file in the correct folder.
mimeType: 'image/png'
};
Folder ID can be obtained from the shareable link using the Google Drive GUI or as shown here. (e.g. Use the Execute function on the right.)
Alternatively, you can access the folder by name by replacing the folder.getID() with Drive.getFoldersByName('name of folder').
This is helpful because Drive.Files.insert() accepts arguments while Drive.createFile() and Drive.createFolder() do not.
The documentation for INSERT for the Drive API is found at this link:
Drive API for INSERT
There is a section for Request body. One of the Optional Properties for Insert is parents[]. The brackets [] indicate that a list of parents can be designated. The documentation for parents[] states this:
Collection of parent folders which contain this file. Setting this
field will put the file in all of the provided folders. On insert, if
no folders are provided, the file will be placed in the default root
folder.
So, . . . using Insert in Drive API, . . . . CAN write a new file directly to a subfolder. It's possible.
Now, the nomenclature and syntax for the Google Drive SDK, HTTP request is different than what is inside of Apps Script.
The syntax for invoking the Drive API HTTP Request inside of a .gs file is one of the following three:
Drive.Files.insert(FILE resource)
Drive.Files.insert(FILE resource, BLOB mediaData)
Drive.Files.insert(FILE resource, BLOB mediaData, OBJECT optionalArgs)
The syntax shown in the list above is from the auto-complete drop down list inside the Apps Script code editor. If you type Drive.Files. a list of possible methods will appear. I can't find information about the syntax anywhere in the online documentation.
So, where does the parents[] optional property go? Well, it's not a Blob, so we can rule that out. It's either FILE resource, or OBJECT optionalArgs. optionalArgs indicates that it's an object, but FILE resource is actually also an object.
In the examples, the FILE resource is constructed as key:value pair object.
Uploading Files - Advanced Drive Service - Google Documentation
Direct Answer to Question
This summary from https://developers.google.com/apps-script/advanced/drive sums things up pretty well:
The advanced Drive service allows you to use the Google Drive web API
in Apps Script. Much like Apps Script's built-in Drive service, this
API allows scripts to create, find, and modify files and folders in
Google Drive. In most cases, the built-in service is easier to
use, but this advanced service provides a few extra features,
including access to custom file properties as well as revisions for
files and folders.
Like all advanced services in Apps Script, the advanced Drive
service uses the same objects, methods, and parameters as the public
API.
Essentially DriveApp is easier to use than Drive, but Drive gives you more functionality since it shares the same functionality of the public API. I was not able to see how to save a file to a Shared/Team drive using DriveApp, so I ended up using Drive. The pain came around lack of documentation for the Google Apps Script implementation of Drive.
Explanation of My Solution and Code Sample:
A specific implementation of saving a file to Google drive, but this will likely be useful for others. It took me a whole day to figure this out since the documentation and code examples for Google Apps scripts is severely lacking. My use case was for saving a JSON file to a shared Google Drive (Team Drive).
There are three parameters that I did not have at first and my files were not uploading. I am not sure if all are necessary. One was the "kind": "drive#parentReference" part of the parents metadata. The next was "teamDriveId": teamDriveId which is also in the metadata. The last parameter was "supportsAllDrives": true which I passed in the optional parameter location of Drive.Files.insert().
I found the API explorer on https://developers.google.com/drive/api/v2/reference/files/insert to be very useful in figuring out which parameters were needed and how they needed to be formatted. I basically edited values in the explorer till I got a network request that worked. I then pulled the parameters I used into my Google Apps script.
/**
* Creates a JSON file in the designated Google Drive location
* #param {String} jsonString - A JS string from the result of a JSON.stringify(jsObject)
* #param {String} filename - The filename. Be sure to include the .json extension
* #param {String} folderId - The ID of the Google Drive folder where the file will be created
* #param {String} teamDriveId - The ID of the team drive
* #return {void}
*/
function createJSONFileInDriveFolder(jsonString, filename, folderId, teamDriveId) {
var metadata = {
"title": filename,
"mimeType": "application/json",
"parents": [
{
"id": folderId,
"kind": "drive#parentReference"
}
],
"teamDriveId": teamDriveId
};
var optionalParams = {
"supportsAllDrives": true
};
try {
var jsonBlob = Utilities.newBlob(jsonString, 'application/vnd.google-apps.script+json');
Drive.Files.insert(metadata, jsonBlob, optionalParams);
} catch (error) {
Logger.log(error);
}
}
var searchthreads = GmailApp.search('in:inbox AND after:2020/11/30 AND has:attachment');//"in:all -in:trash category:social older_than:15d
Logger.log("GMAIL thread 0:"+ searchthreads[0].getId());
Logger.log("GMAIL thread 1:"+ searchthreads[1].getId());
Logger.log("GMAIL thread 2:"+ searchthreads[2].getId());
Logger.log("Active User: " + me);
Logger.log("Search Thread: " + searchthreads.length);
Logger.log("Gmail lenght" + gmailthread.length);
//Logger.log("Gmail lenght" + gmailMessages.length);
for (var i in searchthreads){
var messageCOunt = searchthreads[i].getMessageCount();
Logger.log("messageCOunt :" + messageCOunt);
var messages = searchthreads[i].getMessages();
for (var m in messages){
var messagesender = messages[m].getFrom();
var messageDate = messages[m].getDate();
var messageReplyTo = messages[m].getReplyTo();
var messagesubject = messages[m].getSubject();
var messagebody = messages[m].getSubject();
var messagephoneNo = messages[m].getSubject();
//messages[m].isInInbox();
var messageid = messages[m].getId();
var messageplainbody = messages[m].getSubject();//messages[0].getPlainBody();
var EmailStatus ='N';
var ApptStatus = "CVReceived";// Tracking till candidate offer and payout
var messageattachement = messages[m].getAttachments();
//var png=UrlFetchApp.fetch(messageattachement).getBlob();
//https://drive.google.com/drive/folders/1RY4i6FwUvfy5OxrJ1pZTxJAOxjFFXbhz?usp=sharing
var folder = DriveApp.getFolderById("1RY4i6FwUvfy5OxrJ1pZTxJAOxjFFXbhz");
// DriveApp.getFolderById("1RY4i6FwUvfy5OxrJ1pZTxJAOxjFFXbhz").createFile(png);
//DriveApp.createFile();
for (var k in messageattachement){
var filename = messageattachement[k].getName();
var filesize = messageattachement[k].getSize();
var filecontent = messageattachement[k].getContentType();
var fileBlob = messageattachement[k].getAs(filecontent);
var filecpblob = messageattachement[k].copyBlob();
//folder.createFile(filename, messageattachement);
var file = {
title: filename,
"parents": [{'id':folder.getId()}],
mimeType: filecontent
};
file = Drive.Files.insert(file, filecpblob);
//DataStudioApp
Logger.log('ID: %s, File size (bytes): %s', file.id, file.fileSize);
//folder.createFile(filecpblob);
}
var processeddate = new Date();
I know it's been a while since this question was posted. But here is the solution to help other readers. When using Drive.Files.insert() method, in order to specify a location for the inserted file, you must specify the parents[] property within the FILE resource. So expanding on #Alan Wells response here is the syntax for writing a blob as Goggle Spreadsheet format in a specific folder.
let newFile = {title: 'Title goes here', parents: [{id: targetFolderId}]};
let savedFile = Drive.Files.insert(
newFile,
blobGoesHere,
{mimeType: MimeType.GOOGLE_SHEETS, convert: true});
Please note that parents: takes an array of objects. You can specify multiple locations for a single file (it will be created in all the specified folders). Even if you want to use a single location you still have to provide this object in a list.
I was able to use the DriveApp to create a file in a specified folder this way.
var driveFolder = DriveApp.getFolderByName("MyDriveFolder");
var file = driveFolder.createFile(formObject.txtReceipt);
file.setName("MyFile");
PS: formObject.txtReceipt is coming from a file upload control on a form in the html and this returns a blob

How to delete/overwrite CSV file using google apps script?

My google apps script imports a csv file [test.csv] into a new tab, manipulates some values, then saves the manipulated tab as a csv file [test.csv]. When it saves the new version, it simply makes another copy [test(1).csv]. I wish instead to overwrite the previous file (or delete the old one then export/write the new version.) Help please?
I am using reference code from the Interacting With Docs List Tutorial
I know this is an old question, but much of the information in the accepted answer has been deprecated by Google since then. DocsList is gone, as are the clear() and append() methods on a file object.
I use the following function to create or overwrite a file:
// Creates or replaces an existing file
function updateFile (folder, filename, data) {
try {
// filename is unique, so we can get first element of iterator
var file = folder.getFilesByName(filename).next()
file.setContent(data)
} catch(e) {
folder.createFile(filename, data)
}
}
For reference, here's some code for doing the same for a folder. It assumes we're operating in the parent folder of the current sheet and want a folder
object for a new or existing folder there.
// Get folder under current folder. Create it if it does not exist
function getOrCreateFolder(csvFolderName) {
var thisFileId = SpreadsheetApp.getActive().getId();
var thisFile = DriveApp.getFileById(thisFileId);
var parentFolder = thisFile.getParents().next();
try {
// csvFolderName is unique, so we can get first element of iterator
var folder = parentFolder.getFoldersByName(csvFolderName).next();
// asking for the folder's name ensures we get an error if the
// folder doesn't exist. I've found I don't necessarily catch
// an error from getFoldersByName if csvFolderName doesn't exist.
fname = folder.getName()
} catch(e) {
var folder = parentFolder.createFolder(csvFolderName);
}
return folder
}
You could do DocsList.find(fileName) which gives you a list of files that have that name. If file names are unique, then you can just do var file = DocsList.find(fileName)[0].
If you are a Google Apps user, you can use file.clear() to remove all the contents of the old file, and then file.append() to insert all of the new contents.
Otherwise, you will have to file.setTrashed(true) and then DocsList.createFile() to make the new one.

I can file.addToFolder but cant file.removeFromFolder

I have a script that reads files from a folder and catalogs the contents to a spreadsheet. To avoid duplicates I want to move the files to a different folder (file.addToFolder(newFolder)) which works, however when I try to file.removeFromFolder(formerFolder) I get "cannot find method" which would suggest I am not giving it the right class of object even though removeFromFolder is listed as a member of the File class.
function moveFileToFolder(fileId, targetFolderId,formerFolderId) {
var targetFolder = DocsList.getFolderById(targetFolderId);
var file = DocsList.getFileById(fileId);
file.addToFolder(targetFolder);
file.removeFromFolder(formerFolderId);
};
What am I doing wrong?
You should get the formerFolder like you get the targetfolder and pass it to the method instead of the formerFolderId.