downloading files from URL with ashx - google-apps-script

Just started using google scripts and can't seem to find the solution for downloading a file. Here's the issue:
Running a script on 100+ URL's daily
Each URL is .ashx that downloads the file automatically
The script included downloads a file, but not the file type it should be (.xls)
The only workaround is to manually change the file extension in Google Drive to .xls in order to view it correctly
Question: is there anyway to save the file from the .ashx URL as a .xls while running the script? I'm totally fine doing something totally different but need to be able to download these files as .xls' on download.
Thanks for any sugestions,
Jason
function getFileFromURL(fileURL, folder) {
var rc = 404;
var fileName = "";
var fileSize = 0;
try {
var response = UrlFetchApp.fetch("http://www.test.com/InventoryXLS?viewid=450");
var rc = response.getResponseCode();
} catch (e)
{
// fetch() does not handle unresolved DNS or file not found errors
// We'll treat all unhandled errors as "404 Not Found"
// This catch block simply suppresses the error
debugger;
}
if (rc == 200) {
var fileBlob = response.getBlob()
var folder = DocsList.getFolder("MyFolderNameinDrive");
if (folder != null) {
var file = folder.createFile(fileBlob);
fileName = file.getName();
fileSize = file.getSize();
}
}
var retObj = { "rc":rc, "fileName":fileName, "fileSize":fileSize };
debugger; // Stop to observe if in debugger
return retObj
}

Without testing on a .ashx file itself, I have no idea if this will work, but a couple of weeks ago the ability to convert a file with apps script was implemented. See number 15 on this issue.
Then again, you may not wish to convert the file to a Google Sheet. If so, just leave out the optional parameter that contains {convert: true}. Hopefully, this will still be able to set the name and mimeType of your file correctly. Otherwise, I'll delete this answer and we'll start over!
So perhaps something like:
var file = {
title: 'Converted Spreadsheet',
mimeType: 'application/vnd.ms-excel'
};
file = Drive.Files.insert(file, fileBlob, {
convert: true
});
If you could share a .ashx file, I could do some more testing...

Related

unable to copy the gmail file to google sheets. It throws the error as:API call to drive.files.insert failed with error: Invalid mime type provided [duplicate]

This is a continuation from How to Use Advanced Drive Service to Upload Files.
My Webapp consists of an upload form for data files, which are then stored on Google Drive. (Full code in snippet below.)
I'm having a problem with the following line of code:
var file = Drive.Files.insert(resource, mediaData); // create file using Drive API
try {
//Get root folder and pull all existing folders, plus setup variables pulled from form
var dropbox = form.Country;
var timeStamp = new Date();
//Set file name slightly differently for Weekly Member Report (do not want to overright based on name just keep each extract so add timestamp to name)
if (form.reportType == "Member Weekly"){
var filename = form.reportType + timeStamp + ".xls";
}
else
{
var filename = form.reportType+".xls";
}
var rootfolder = DriveApp.getFolderById("0Byvtwn42HsoxfnVoSjB2NWprYnRiQ2VWUDZEendNOWwwM1FOZk1EVnJOU3BxQXhwU0pDSE0");
//Note root folder is Live Uploads Folder in Flatworld App folder structure
var folder, folders = rootfolder.getFoldersByName(dropbox);
//Check if folder exists and if not create
if (folders.hasNext()) {
folder = folders.next();
} else {
folder = rootfolder.createFolder(dropbox);
}
var FolderURL = folder.getUrl(); // Retain URL of folder for final end message to user
//Check if file already exists and delete if it does
var file, files = folder.getFilesByName(filename);
while( files.hasNext()){
file = files.next();
file.setTrashed(true);
}
//New Code from Stackover Flow:
//Upload file and set various properties
var mediaData = form.myFile;
var resource = {
title: filename,
mimetype: 'application/vnd.ms-excel',
description: "Uploaded via BNI Upload Form by " + form.myName + " on: " + timeStamp
};
var file = Drive.Files.insert(resource, mediaData); // create file using Drive API
var fileId = file.id;
var DriveAppFile = DriveApp.getFileById(fileId); // retrieve file in DriveApp scope.
var FileURL = DriveAppFile.getUrl(); //Retain URL of file for final end message to user
DriveApp.removeFile(DriveAppFile); // remove new file from Users root My Drive
folder.addFile(DriveAppFile); // puts file in selected folder
//End of New code from Stackover Flow
//Success message displayed to user
return "Thanks! File uploaded successfully to: <br><br><b>Folder Location:</b> " + FolderURL + "<br>" + "<b>File:</b> " + FileURL + ". <br><br>For any queries please email user#example.com copying the URLs displayed here in your email. You can close this browser window now or use the link below to upload another file.<br><br>";
} catch (error) {
//Catch error return it to user and email with error details
Its throwing the error message "Empty Response" on the line of code above when we try and upload a large file (15MB) Do you have any suggestions. This is well inside the Files insert limit of 5120GB and the code works fine on smaller files.
I've now tried to add in a loop in function to try the upload a couple of times, still throwing the same error sadly:
//setup function that will return null if file is not uploaded correctly
function createDriveFile(resource_f, mediaData_f){
try{
var file = Drive.Files.insert(resource_f, mediaData_f); // create file using Drive API
return file;
} catch (e) {return null;}
}
//try upload and loop if null
var maxTries = 3;
var tries = 0;
do {
tries = tries + 1;
if (tries > 0) {
var file = createDriveFile(resource, mediaData);
Logger.log("I'm trying to upload, try number: " + tries);
}
} while ((file == null) && (tries < maxTries));
if (file == null) {
var file = Drive.Files.insert(resource, mediaData); // try one laste time to create file using Drive API - outside loop function so if error is thrown script stops
}
The error only seems to occur on a larger file, even if we reduce the size of the same file that solves error so do we need to adjust the upload process to account for a larger file. Is there a Google Apps Script equivalent of making the API upload request resumable?
Your file size is the determinant factor here. Referencing the documentation suggests the simple upload method used here is good for up to 5MB only.
https://developers.google.com/drive/web/manage-uploads
Your comments seem to confirm this is what is happening for you.
As you hinted, use the resumable upload method. Use the uploadType: resumable parameter flag – API docs on the insert method describes how.
You can also check the naming of the file, for me I had a slash in the name which is why it would not upload. So take away any special characters before uploading and it should work.

Automatic Convert and Delete Filetype Server Error (Timing Out?)

I am trying to create a script that will convert xls documents in a folder to sheets versions, then delete the xls versions. The script works, but because I have plenty of xls files in this folder, I believe it's timing out because it is throwing up, "We're sorry, a server error occurred. Please wait a bit and try again. (line 6, file "Convert XLS")" error a little while after I start running it. Is there a way to execute this without the server timing out or something?
function convertXLS() {
var folder = DriveApp.getFolderById('File_ID');
var files = folder.getFilesByType(MimeType.MICROSOFT_EXCEL_LEGACY);
while (files.hasNext()) {
var file = files.next();
Drive.Files.copy({}, file.getId(), {convert: true});
}
// while (files.hasNext()) {
// Drive.Files.remove( file.getId());
//}
}
Update:
So I reconfigured the code to this:
function importXLS(){
var folder = DriveApp.getFolderById('File_ID');// you can also use a folder as starting point and get the files in that folder... use only DriveApp method here.
var files = folder.getFilesByType(MimeType.MICROSOFT_EXCEL_LEGACY);
while(files.hasNext()){
var xFile = files.next();
var name = xFile.getName();
if (name.indexOf('.xls')>-1){ // this check is not necessaey here because I get the files with a search but I left it in case you get the files differently...
var ID = xFile.getId();
var xBlob = xFile.getBlob();
var newFile = { title : name+'_converted',
key : ID,
'parents':[{"id":"File_ID"}]
}
file = Drive.Files.insert(newFile, xBlob, {
convert: true,
});
}
}
}
But now it's giving me this error, "API call to drive.files.insert failed with error: Internal Error (line 14, file "Convert XLS")" after about two minutes of it running. I don't know what to do :(
What you are facing is a time-out due to the execution limit for Apps Script.
To solve this, create a function that converts and deletes a single file, then run it multiple times.
To do that, you will use getFilesByType in conjunction with Advanced Drive Services with the Drive.files.insert function with options as {convert:true}.
Running it multiple times can be done with a time-based trigger and/or manually.
Since you will be getting files only of a specific MIME Type, when you run out of XLS files you should have no problems.
Hope this helps!

Google Script - How to use unzip

I am downloading a .zip from a website. It contains one .txt file. I would like to access the data in the txt and write it to a spreadsheet. I'm open to either accessing it directly and not extracting the zip OR extracting the zip, saving the txt to a Google Drive Folder, and accessing it once it is saved.
When I use Utilities.unzip(), I can never get it to unzip the file and usually end up with an "Invalid argument" error. In the code below, the last section before else contains the unzip command. It successfully saves the file to the correct Google Folder but then I can't extract it.
function myFunction() {
// define where to gather data from
var url = '<insert url here>';
var filename = "ReportUploadTesting05.zip";
var response = UrlFetchApp.fetch(url, {
// muteHttpExceptions: true,
// validateHttpsCertificates: false,
followRedirects: true // Default is true anyway.
});
// get spreadsheet for follow up info
var Sp = SpreadsheetApp.getActiveSpreadsheet();
if (response.getResponseCode() === 200) {
// get folder details of spreadsheet for saving future files
var folderURL = getParentFolder(Sp);
var folderID = getIdFromUrl(folderURL);
var folder = DriveApp.getFolderById(folderID);
// save zip file
var blob = response.getBlob();
var file = folder.createFile(blob);
file.setName(filename);
file.setDescription("Downloaded from " + url);
var fileID = file.getId();
Logger.log(fileID);
Logger.log(blob)
// extract zip (not working)
file.setContent('application/zip')
var fileUnzippedBlob = Utilities.unzip(file); // invalid argument error occurs here
var filename = 'unzipped file'
var fileUnzipped = folder.createFile(fileUnzippedBlob)
fileUnzipped.setName(filename)
}
else {
Logger.log(response.getResponseCode());
}
}
I've followed the instructions on the Utilities page. I can get their exact example to work. I've tried creating a .zip on my computer, uploading it to Google Drive and attempted to open it unsuccessfully. Obviously there are some subtleties of using the unzip that I'm missing.
Could you help me understand this?
I was running into the same "Invalid arguments" error in my testing, so instead of using:
file.setContent('application/zip')
I used:
file.setContentTypeFromExtension();
And, that solved the problem for me. Also, as #tukusejssirs mentioned, a zip file can contain multiple files, so unzip() returns an array of blobs (as documented here). That means you either need to loop through the files, or if you know you only have one, explicitly reference it's position in the array, like this:
var fileUnzipped = folder.createFile(fileUnzippedBlob[0])
Here's my entire script, which covers both of these issues:
/**
* Fetches a zip file from a URL, unzips it, then uploads a new file to the user's Drive.
*/
function uploadFile() {
var url = '<url goes here>';
var zip = UrlFetchApp.fetch('url').getBlob();
zip.setContentTypeFromExtension();
var unzippedFile = Utilities.unzip(zip);
var filename = unzippedFile[0].getName();
var contentType = unzippedFile[0].getContentType();
var csv = unzippedFile[0];
var file = {
title: filename,
mimeType: contentType
};
file = Drive.Files.insert(file, csv);
Logger.log('ID: %s, File size (bytes): %s', file.id, file.fileSize);
var fileId = file.id;
// Move the file to a specific folder within Drive (Link: https://drive.google.com/drive/folders/<folderId>)
var folderId = '<folderId>';
var folder = DriveApp.getFolderById(folderId);
var driveFile = DriveApp.getFileById(fileId);
folder.addFile(driveFile);
}
I think the answer to your question may be found here. Is there a size limit to a blob for Utilities.unzip(blob) in Google Apps Script?
If the download is over 100 mb the full file cannot be downloaded. Due to that it will not be in the proper zip format. Throwing the cannot unzip file error.
I believe that the creation of the blob from a file (in this case the .zip file) requires the .next(); otherwise it did not work for me.
Also note that the .zip file might contain more than one file, therefore I included a for cycle.
Anyway, my working/tested solution/script is the following:
function unzip(folderName, fileZipName){
// Variables
// var folderName = "folder_name";
// var fileZipName = "file_name.zip";
var folderId = getFolderId(folderName);
var folder = DriveApp.getFolderById(folderId);
var fileZip = folder.getFilesByName(fileZipName);
var fileExtractedBlob, fileZipBlob, i;
// Decompression
fileZipBlob = fileZip.next().getBlob();
fileZipBlob.setContentType("application/zip");
fileExtractedBlob = Utilities.unzip(fileZipBlob);
for (i=0; i < fileExtractedBlob.length; i++){
folder.createFile(fileExtractedBlob[i]);
}
}

Google Apps Script: Downloading files from Drive (same user)

I'm trying to write a Google Apps Script to download all files in a particular Drive folder (likely .csv files). I have found the getDownloadUrl() method but I haven't been able to figure out what to do with it. I'm currently trying the following code, where files is the list of the files in the folder:
while(files.hasNext()) {
var response = UrlFetchApp.fetch(files.next().getDownloadUrl());
Logger.log(response.getContentText());
}
When I try to run the code, however, I get a 401 error which I guess means I lack the proper authorization? But I was under the impression that I wouldn't need to go through all of the OAuth2 steps if everything was taking place within my one Google account. The Google guide to connecting to external APIs makes it look like I should be able to just fetch the url. I've already gotten access to my Drive files, because the download URL does exist when I run that method. What am I missing here? I'm really new to all of this so maybe it's something basic.
Thanks!
EDIT:
I managed to fix the 401 error by modifying the code as follows:
while(files.hasNext()) {
var response = UrlFetchApp.fetch(files.next().getDownloadUrl(),{headers: {Authorization: "Bearer " + ScriptApp.getOAuthToken()}});
Logger.log(response.getContentText());
}
But the issue remains that this only returns the contents to me, rather than downloading the file. How can I initiate a download from the results of this fetch call?
Besides listing all download links, I guess original poster also wants to download files to user's computer (according to earlier discussion).
To do this, encode blob with base 64 in server side (e.g. Google App Script) and download with data URI in client's browser. Below are code for this, with help of this answer.
Google App Script
...
function getBlobInBase64(fileId){
// omit authorization code if any
var file = DriveApp.getFileById(fileId);
var blob = file .getBlob();
return {
file_name: file.getName(),
mime: file.getMimeType(),
b64: Utilities.base64Encode(blob.getBytes());
}
...
Javascript that serve with index.html
...
function getFile(fileId){
google.script.run.withSuccessHandler((data) => {
var uri = 'data:' + data.mime + ';charset=ISO-8859-1;base64,' + encodeURIComponent(data.b64);
downloadURI(uri, data.file_name);
}).withFailureHandler((err) => {
console.log(err);
}).getBlobInBase64();
}
...
NOTE: I haven't run this code but the method should work as used in my other project.
This will log the file names & URLS for any files available for downloading (first 100 in root drive):
function myFunction() {
var files = DriveApp.getFiles();
var c = 0;
while (files.hasNext() && c<100) {
var file = files.next();
Logger.log("File Name: " + file.getName());
Logger.log(" Download URL: " + file.getDownloadUrl());
c++;
}
}
My answer might be a bit off but I think you have a better chance downloading files from Google Drive using the webContentLink as it is the method I commonly use. I obtain webContentLink by using Files.list and ask for webContentLink in the fields parameter. I run that link through the browser and it downloads the file.
If you are trying to download Google Drive files to local computer using Google Apps Script, Then please understand that Google Apps Script is a server side scripting language. It can't download and save files to your local drive.
Here is a webapp that may be helpful for you. It does not do exactly what you are looking for but you may be able to edit it and get a result. Hope it helps!
CODE:
function doGet(e) { // main function
var template = HtmlService.createTemplateFromFile('index.html'); // filename always!
return template.evaluate().setTitle('Search Drive').setSandboxMode(HtmlService.SandboxMode.IFRAME);
}
// Process the form
function processForm(searchTerm) {
var resultToReturn;
Logger.log('processForm was called! ' + searchTerm);
resultToReturn = SearchFiles(searchTerm); // Call to the search files function to search files on google drive
Logger.log('resultToReturn: ' + resultToReturn);
return resultToReturn; // return the results
}
function SearchFiles(searchTerm) {
var searchFor ="title contains '" + searchTerm + "'"; //single quotes are needed around searchterm
var owneris ="and 'YOUREmail#email.com' in Owners"; //email address to search for
var names = [];
Logger.log(searchFor + " " + owneris);
var files = DriveApp.searchFiles(searchFor + " " + owneris);
while (files.hasNext()) {
var file = files.next();
var fileId = file.getId();// To get FileId of the file
var lm = file.getLastUpdated();
var name = file.getName()+"|~|"+fileId+"|~|"+lm; // Im concatenating the filename with file id separated by |~|
names.push(name); // adding to the array
}
return names; // return results
}
INDEX.html
<html>
<head>
<base target="_top">
<script>
function displayMessage() {
var searchTerm;
searchTerm = document.getElementById('idSrchTerm').value;
console.log('searchTerm: ' + searchTerm );
// Below call means: call to processForm passing the searchTerm value (previously escaped) and after finish call the handleResults function
google.script.run.withSuccessHandler(handleResults).processForm(searchTerm.replace("'","\'"));
}
function handleResults(results){
console.log('Handle Results was called! ');
document.writeln('BACK<br/><br/>');
var length=results.length; // total elements of results
for(var i=0;i<length;i++)
{
var item=results[i];
item=item.split("|~|"); // split the line |~|, position 0 has the filename and 1 the file id
document.writeln("<b><a href='https://docs.google.com/document/d/"+item[1]+"' target='_blank'>"+item[0]+"</b></a> (Last modified: "+item[2]+")<br/><br/>"); // write result
}
document.writeln("End of results...");
}
</script>
</head>
<body><center><br/>
Search: <input type="text" id="idSrchTerm" name="search">
<input type="button" value="search files on Google Drive" name="submitButton" onclick="displayMessage()"/>
</center>
</body>
</html>

Advanced Drive Service returning Empty Response Error when inserting file

This is a continuation from How to Use Advanced Drive Service to Upload Files.
My Webapp consists of an upload form for data files, which are then stored on Google Drive. (Full code in snippet below.)
I'm having a problem with the following line of code:
var file = Drive.Files.insert(resource, mediaData); // create file using Drive API
try {
//Get root folder and pull all existing folders, plus setup variables pulled from form
var dropbox = form.Country;
var timeStamp = new Date();
//Set file name slightly differently for Weekly Member Report (do not want to overright based on name just keep each extract so add timestamp to name)
if (form.reportType == "Member Weekly"){
var filename = form.reportType + timeStamp + ".xls";
}
else
{
var filename = form.reportType+".xls";
}
var rootfolder = DriveApp.getFolderById("0Byvtwn42HsoxfnVoSjB2NWprYnRiQ2VWUDZEendNOWwwM1FOZk1EVnJOU3BxQXhwU0pDSE0");
//Note root folder is Live Uploads Folder in Flatworld App folder structure
var folder, folders = rootfolder.getFoldersByName(dropbox);
//Check if folder exists and if not create
if (folders.hasNext()) {
folder = folders.next();
} else {
folder = rootfolder.createFolder(dropbox);
}
var FolderURL = folder.getUrl(); // Retain URL of folder for final end message to user
//Check if file already exists and delete if it does
var file, files = folder.getFilesByName(filename);
while( files.hasNext()){
file = files.next();
file.setTrashed(true);
}
//New Code from Stackover Flow:
//Upload file and set various properties
var mediaData = form.myFile;
var resource = {
title: filename,
mimetype: 'application/vnd.ms-excel',
description: "Uploaded via BNI Upload Form by " + form.myName + " on: " + timeStamp
};
var file = Drive.Files.insert(resource, mediaData); // create file using Drive API
var fileId = file.id;
var DriveAppFile = DriveApp.getFileById(fileId); // retrieve file in DriveApp scope.
var FileURL = DriveAppFile.getUrl(); //Retain URL of file for final end message to user
DriveApp.removeFile(DriveAppFile); // remove new file from Users root My Drive
folder.addFile(DriveAppFile); // puts file in selected folder
//End of New code from Stackover Flow
//Success message displayed to user
return "Thanks! File uploaded successfully to: <br><br><b>Folder Location:</b> " + FolderURL + "<br>" + "<b>File:</b> " + FileURL + ". <br><br>For any queries please email user#example.com copying the URLs displayed here in your email. You can close this browser window now or use the link below to upload another file.<br><br>";
} catch (error) {
//Catch error return it to user and email with error details
Its throwing the error message "Empty Response" on the line of code above when we try and upload a large file (15MB) Do you have any suggestions. This is well inside the Files insert limit of 5120GB and the code works fine on smaller files.
I've now tried to add in a loop in function to try the upload a couple of times, still throwing the same error sadly:
//setup function that will return null if file is not uploaded correctly
function createDriveFile(resource_f, mediaData_f){
try{
var file = Drive.Files.insert(resource_f, mediaData_f); // create file using Drive API
return file;
} catch (e) {return null;}
}
//try upload and loop if null
var maxTries = 3;
var tries = 0;
do {
tries = tries + 1;
if (tries > 0) {
var file = createDriveFile(resource, mediaData);
Logger.log("I'm trying to upload, try number: " + tries);
}
} while ((file == null) && (tries < maxTries));
if (file == null) {
var file = Drive.Files.insert(resource, mediaData); // try one laste time to create file using Drive API - outside loop function so if error is thrown script stops
}
The error only seems to occur on a larger file, even if we reduce the size of the same file that solves error so do we need to adjust the upload process to account for a larger file. Is there a Google Apps Script equivalent of making the API upload request resumable?
Your file size is the determinant factor here. Referencing the documentation suggests the simple upload method used here is good for up to 5MB only.
https://developers.google.com/drive/web/manage-uploads
Your comments seem to confirm this is what is happening for you.
As you hinted, use the resumable upload method. Use the uploadType: resumable parameter flag – API docs on the insert method describes how.
You can also check the naming of the file, for me I had a slash in the name which is why it would not upload. So take away any special characters before uploading and it should work.