In my script I am trying to create a folder, create a date-stamped-document in said folder, create a sub folder, and copy some documents into that sub folder.
All of this works great. When I try to zip the parent folder via either of the methods found here: Creating a zip file inside google drive with apps script - it creates a zip file with a sole PDF file that has the same name as the date-stamped-document. The zipped PDF is blank, and the subfolder isn't there.
Any insight about why this is happening would be great.
var folder = DocsList.createFolder(folderTitle);
var subFolder = folder.createFolder('Attachments');
subfolder.createFile(attachments[]); //In a loop that creates a file from every
//attachment from messages in thread
var doc = DocumentApp.create(docTitle); //Google Doc
var docLocation = DocsList.getFileById(doc.getId());
docLocation.addToFolder(folder);
docLocation.removeFromFolder(DocsList.getRootFolder());
//Everything works fine, I can view file and subfolder, and subfolder's documents
//This is where the problem is:
var zippedFolder = DocsList.getFolder(folder.getName());
zippedFolder.createFile(Utilities.zip(zippedFolder.getFiles(), 'newFiles.zip'));
//this results in a zipped folder containing one blank pdf that has the same title as doc
The DocsList service has been deprecated so Phil Bozak's previous solution no longer works. However, refer to another SO question for solution that works with DriveApp class.
This is a great question. It does not seem that the zip function has the ability to zip sub folders. The reason that your script doesn't work is because you only select the files from the folder.
A solution would be to zip each of the subfolders and store that in the one zipped file. You can use the function that I wrote for this.
function zipFolder(folder) {
var zipped_folders = [];
var folders = folder.getFolders();
for(var i in folders)
zipped_folders.push(zipFolder(folders[i]));
return Utilities.zip(folder.getFiles().concat(zipped_folders),folder.getName()+".zip");
}
This recursively zips all subfolders. Then you need to create the file with something like
DocsList.getFolder("path/to/folder/to/store/zip/file").createFile(zipFolder(folderToZip));
I will put in a feature request to allow subfolders to be zipped.
Related
I am writing an automator workflow to work with files and folders. I’m writing it in JavaScript as I’m more familiar with it.
I would like to receive a folder, and get the folder’s name as well as the files inside.
Here is roughly what I have tried:
Window receives current folders in Finder (I’m only interested in the first and only folder)
Get Folder Contents
JavaScript:
function run(input,parameters) {
var files = [];
for(let file of input) files.push(file.toString().replace(/.*\//,''));
// etc
}
This works, but I don’t have the folder name. Using this, I get the full path name of each file, which is why I run it through the replace() method.
If I omit step 2 above, I get the folder, but I don’t know how to access the contents of the folder.
I can fake the folder by getting the first file and stripping off the file name, but I wonder whether there is a more direct approach to getting both the folder and its contents.
I’ve got it working. In case anybody has a similar question:
// Window receives current folders in Finder
var app = Application.currentApplication()
app.includeStandardAdditions = true
function run(input, parameters) {
let directory = input.toString();
var directoryItems = app.listFolder(directory, { invisibles: false })
var files = [];
for(let file of directoryItems) files.push(file.toString().replace(/.*\//,'')) ;
// etc
}
I don’t include the Get Folder Contents step, but iterate through the folder using app.listFolder() instead. The replace() method is to trim off everything up to the last slash, giving the file’s base name.
EDIT
I have logged an issue with Google's Issue Tracker about the Folder.moveTo(destination) function not working, as it appears to be a bug. Feel free to star the issue to give it a bump!
https://issuetracker.google.com/issues/163080678
Background
I am trying to provide a "one click" way of moving folders in Google Drive via an add-on (which appears in both Drive and Gmail). For context, each "folder" in this case is a matter, and the "one click" will move the folder (containing all the relevant documents to that matter) from the "open" folder to the "closed" folder. However, I am getting confusing and unhelpful errors every way I try to achieve this.
Note: all of these folders are in a Shared Team Drive. I'm aware that this places some limitations on what the DriveApp Service and Drive API can do, but I'm crossing all my fingers and toes that it's not just an inherent limitation and that there is a workaround!
I have already read the following:
How can I use Google Apps Script to move files between personal Google Drives and Team Drives?.
Google Script Move Folders
Move a Google Drive folder to a new location using GAS
https://tanaikech.github.io/2019/11/20/moving-file-to-specific-folder-using-google-apps-script/
https://developers.google.com/apps-script/reference/drive/folder#movetodestination
https://developers.google.com/drive/api/v2/reference/files/update
https://developers.google.com/drive/api/v2/reference/parents/insert
But they did not solve my problem (noting that addFile(), addFolder(), removeFile() and removeFolder() have been deprecated recently and so are no longer available as the answers above would suggest).
Expected Result
My expectation is that when the user hits the "Close" button in the add-on, it will move the relevant folder (selected at a previous stage in the add-on) from the "open" folder, to the "closed" folder (which sit next to each other at the same level in the folder hierarchy). To be clear, I'm hoping that it will move the folder, not duplicate it in the target folder and then delete the original from the original parent folder.
Actual Result
See specific error messages below, but generally speaking, when the user clicks the "Close" button, I get an error right away, and nothing happens to the folder.
Attempt 1 - Folder.moveTo(destination)
Taking the Google documentation at face-value, I first attempted to use Folder.moveTo(destination) as below:
function moveFolder() {
var folderToMove = DriveApp.getFolderById(MOVE_FOLDER_ID);
var destinationFolder = DriveApp.getFolderById(DESTINATION_FOLDER_ID);
folderToMove.moveTo(destinationFolder);
}
However, on running this, I get the error message: Exception: Unexpected error while getting the method or property moveTo on object DriveApp.Folder.
Attempt 2 - Drive.Files.update()
Failing to fix the above, I tried the Advanced Drive feature of Google Apps Script, and attempted to use Drive.Files.update() (documentation here). My code looks like this:
function moveFolder() {
Drive.Files.update({parents: [{id: DESTINATION_FOLDER_ID}]}, MOVE_FOLDER_ID);
}
I also tried a variation including the optional paramter supportsAllDrives, as below:
function moveFolder() {
Drive.Files.update({supportsAllDrives: true, parents: [{id: DESTINATION_FOLDER_ID}]}, MOVE_FOLDER_ID);
}
However, both variations give the error: GoogleJsonResponseException: API call to drive.files.update failed with error: File not found: [fileId], where [fileId] is the actual Google Drive ID of the folder being moved.
I have confirmed in both attempts 1 and 2, by using various other functions I know do work (like adding customer properties with Drive.Properties and renaming the folder using DriveApp), that the folders themselves are definitely being passed through correctly and are able to be manipulated programmatically. Ie, the folder definitely exists, and using the URL in the browser, can definitely be found using that Drive ID.
Rather than moving a folder directly you could a) create an "equivalent" in the new location in your shared drive, b) move its files to the new folder from a using moveTo() and c) delete the original folder after completely migrated.
This will of course change the id of your folders, but it works for shared drives and also with folders from MyDrive that should be migrated to a shared drive
Please note this makes use of Advanced Drive Service v2 (enable it in Apps Script):
// function to copy a folder e.g. to a shared drive
function copyFolderToSharedDrive(folderTitle,folderId,targetParentId) {
var optionalArgs={supportsAllDrives: true};
var resource = {
title: folderTitle + '_copy', // adjust according to your needs
mimeType: 'application/vnd.google-apps.folder',
parents:[{
"id": targetParentId, // some folder in a shared drive
}],
description: folderId // optional, to back reference the source Folder
}
var migratedFolder = Drive.Files.insert(resource, null, optionalArgs);
Logger.log(migratedFolder.id);
// Optional: post-process original folder, e.g. trash it
// ...
}
Drawback when moving from My Drive until Single Parent Model has been enforced in My Drive: You need to check for multiple 'addings' of your subfolders so as to avoid creating duplicate folders.
Using DriveApp Methods only, you can also migrate a folder by
creating the folder in shared drive
move files to the created folders
recurse for any sub-folders
trash original folder that is moved.
hint: If multi-parenting applies to the subfolders within the folder that is migrated, you need to include a check in the code above in order to avoid creating duplicate folders.
// execute this function
function run_me() {
var folderId = 'ID_OF_FOLDER_TO_BE_MIGRATED'; // some folder (located in My Drive or Shared Drive)
var targetParentId = 'ID_OF_TARGET_PARENT_FOLDER'; // a folder in a Shared Drive
var folder = DriveApp.getFolderById(folderId);
var parent = DriveApp.getFolderById(targetParentId);
migrateFolderToSharedDrive(folder,parent); // copy folders and move files
folder.setTrashed(true); // trash original folder
}
// main function (recursive)
function migrateFolderToSharedDrive(folder,parent) {
var copiedFolder = parent.createFolder(folder.getName()).setDescription('previous Id: '+folder.getId());
Logger.log('Folder created with id %s in parent %s',copiedFolder.getId(),parent.getId());
// move files
var files = folder.getFiles();
while (files.hasNext()) {
Logger.log('File with id %s moved to %s', files.next().moveTo(copiedFolder).getId(), copiedFolder.getId())
}
// recurse any subfolders
var childfolders = folder.getFolders();
while (childfolders.hasNext()) {
var child = childfolders.next();
Logger.log('processing subfolder %s',child.getId());
migrateFolderToSharedDrive(child,copiedFolder); // recursive call
}
}
I am working on a project which sends PDFs from a Wordpress website to a Google Drive. The files are PDFs of people who registered for certain events, which are held in certain cities. These PDFs are all sent to a parent folder "registrations". What I need to achieve is to automatically send these PDFs to subfolders like Amsterdam, Rotterdam, Woerden, Westland etc.
The PDFs are all sent to a parent folder "registrations". What I need to achieve is to automatically send these PDFs to subfolders like Amsterdam, Rotterdam. The names of the PDFs are the city names (e.g. Amsterdam).
In conclusion, the question basically is: how could I achieve to send a file with the name Amsterdam to a subfolder with the name Amsterdam, and a file with the name Rotterdam to a folder with the name Rotterdam?
EDIT: I am working on the script, but it is not complete yet. The script below searches for files in a parent folder that contains "woerden" in its name. Next, it moves these files to a folder called "woerden". This works, but the problem is that I will have to create about 300 different scripts to be able to automate the entire process. I am therefore wondering if it is possible to make this script more dynamic. For example, if a file with a name that contains -woerden- it is sent to a folder with the name woerden, and if a file with a name that contains -westland- it will be sent to a folder with the name westland.
function SearchFiles() {
// Parent folder
var parentFolder = DriveApp.getFolderById('15azR1-Fic_F_wPZHzXWKblIyxBB_GVsW'); //parent folder which will be scanned for duplicates
// Log the name of every file in the user's Drive whose name contains "westland".
var files = DriveApp.searchFiles(
'title contains "westland"');
while (files.hasNext()) {
var file = files.next();
// Remove the file from all parent folders
var parents = file.getParents();
while (parents.hasNext()) {
var parent = parents.next();
parent.removeFile(file);
}
DriveApp.getFolderById('1mSW3-uqv_F1m-TfLa_JyGRVK9-sGJYTj').addFile(file);
}
}
I hope that I asked my question clearly. Please let me know if you need some more information.
Cheers,
I have a standalone script that I need to run on Multiple Google Spreadsheets. I am able to assign a script to 1 spreadsheet using the following code:
function filter() {
var ss = SpreadsheetApp.openById('ID');
How to assign this to multiple spreadsheets?
There are more than a single way to do it.
1) You can manually get the id's of the various spreadsheets and hard code the id's as an array in the stand alone script.
2) You can move all the spreadsheets required to a single folder and automate opening the folder and opening the files in the particular folder. For this, say, the folder containing the required spreadsheets is "All spreadsheets", then try out the following code.
function myfunction()
{
var root = DriveApp.getFoldersByName("All spreadsheets");
while (root.hasNext())
{
var folder = root.next(); //If the folder is available, get files in the folder
var files = folder.getFiles();
while(files.hasNext()) //For each file,
{
var spreadsheet = SpreadsheetApp.open(files.next());
required_function(spreadsheet); //Call the required function
}
}
}
Hope it helps :)
My add-on, Color Source, needs to iterate files in a directory within my add-on (e.g., the content subdirectory) when the add-on is not unpacked (i.e., is kept zipped as an XPI).
We are using code like this to get the Addon object:
Cu.import("resource://gre/modules/AddonManager.jsm", null)
.AddonManager
.getAddonByID(
"color_source#brett.zamir",
function(addon) {
var uri = addon.getResourceURI();
}
);
In order to pass a path to new OS.File.DirectoryIterator, we have tried:
OS.Path.join(uri.path, 'content'); which apparently works in *nix but not Windows
Getting and then cloning uri.QueryInterface(Ci.nsIFileURL).file, and then calling append('content') on the clone and this works, but only when the add-on has the <em:unpack> directive set to true in install.rdf.
How do we get a path that works even when the add-on is zipped up?
(Note: I have added a Firefox add-on tag to this post because the issue should be similar there)
The .xpi file is a zip file and should be accessed using nsIZipReader.
The following should create a nsIUTF8StringEnumerator over which you can iterate. The contents of the zip file which match a pattern are obtained using findEntries()
Components.utils.import("resource://gre/modules/FileUtils.jsm");
var zipReader = Components.classes["#mozilla.org/libjar/zip-reader;1"]
.createInstance(Components.interfaces.nsIZipReader);
var addonFile = new FileUtils.File(uri.path);
zipReader.open(addonFile);
var contentEnumerator = zipReader.findEntries("content/*");
This should provide you with all the files and directories which are contained within the content directory. However, you are probably only interested in the files which are directly within the content directory:
for(contentFile of contentEnumerator) {
if(/content\/.*\//.test(contentFile) {
continue;
}
//If you get here contentFile should contain only direct descendants of
// the content directory.
}