How to Add a file to Google Drive via Docs Add On using App Script? [duplicate] - google-apps-script

This question already has answers here:
Uploading Multiple Files to Google Drive with Google App Script
(5 answers)
Closed 6 years ago.
Here is my scenario. I've created an Add-On for Google Docs that acts as a video toolbox.
A feature I'm trying to add is the ability to record a video using the built in web cam (using videojs-recorder) and then link to that video within the doc. I've got the video part working, but not sure how to get the webm JS Blob converted into a Google Blob so I can create a file on the users Google Drive for sharing and linking.
Just to figure out how this might work this is what I've done so far without any luck.
CLIENT SIDE CODE
//event handler for video recording finish
vidrecorder.on('finishRecord', function()
{
// the blob object contains the recorded data that
// can be downloaded by the user, stored on server etc.
console.log('finished recording: ', vidrecorder.recordedData);
google.script.run.withSuccessHandler(function(){
console.log("winning");
}).saveBlob(vidrecorder.recordedData);
});
SERVER SIDE CODE
function saveBlob(blob) {
Logger.log("Uploaded %s of type %s and size %s.",
blob.name,
blob.size,
blob.type);
}
The errors I get seem to be related to serialization of the blob. But really the exceptions aren't very useful - and just point to some minimized code.
EDIT: Note that there is no FORM object involved here, hence no form POST, and no FileUpload objects, as others have indicated that this might be a duplicate, however it's slightly different in that we are getting a Blob object and need to save it to the server.

Thanks goes to Zig Mandel and Steve Webster who provided some insight from the G+ discussion regarding this.
I finally pieced together enough bits to get this working.
CLIENT CODE
vidrecorder.on('finishRecord', function()
{
// the blob object contains the recorded data that
// can be downloaded by the user, stored on server etc.
console.log('finished recording: ', vidrecorder.recordedData.video);
var blob = vidrecorder.recordedData.video;
var reader = new window.FileReader();
reader.readAsDataURL(blob);
reader.onloadend = function() {
b64Blob = reader.result;
google.script.run.withSuccessHandler(function(state){
console.log("winning: ", state);
}).saveB64Blob(b64Blob);
};
});
SERVER CODE
function saveB64Blob(b64Blob) {
var success = { success: false, url: null};
Logger.log("Got blob: %s", b64Blob);
try {
var blob = dataURItoBlob(b64Blob);
Logger.log("GBlob: %s", blob);
var file = DriveApp.createFile(blob);
file.setSharing(DriveApp.Access.ANYONE_WITH_LINK, DriveApp.Permission.COMMENT);
success = { success: true, url: file.getUrl() };
} catch (error) {
Logger.log("Error: %s", error);
}
return success;
}
function dataURItoBlob(dataURI) {
// convert base64/URLEncoded data component to raw binary data held in a string
var byteString;
if (dataURI.split(',')[0].indexOf('base64') >= 0)
byteString = Utilities.base64Decode(dataURI.split(',')[1]);
else
byteString = decodeURI(dataURI.split(',')[1]);
// separate out the mime component
var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];
return Utilities.newBlob(byteString, mimeString, "video.webm");
}

Related

How to deal with Access-Control-Allow-Origin for myDrive files

The question arises from this note:
Someone here suggested using div's. The HTML requirement is very
skeletal. The 3D display is basically canvas, but it requires seven
three.js files, ten js files of my own making to exchange parameters
and other variables with the global variable and .dae collada files
for each of the 3D models you can see. If they could be linked in like
jQuery that might be the solution but I wonder about conflicts.
on Questions on extending GAS spreadsheet usefulness
principally, if they can be linked like jQuery part
The files to be linked are on myDrive. The thinking is that if I can copy the files into GAS editor, it seems as secure and more flexible to bring them into the html directly.
code.gs
function sendUrls(){
var folder = DriveApp.getFoldersByName("___Blazer").next();
var sub = folder.getFoldersByName("assembler").next();
var contents = sub.getFiles();
var file;
var data = []
while(contents.hasNext()) {
file = contents.next();
type = file.getName().split(".")[1];
url = file.getUrl();
data.push([type,url]);
}
return data;
}
html
google.script.run.withSuccessHandler(function (files) {
$.each(files,function(i,v){
if(v[0] === "js"){
$.get(v[1])
}
})
})
.sendUrls();
The first url opens the proper script file but the origin file is not recognisable to me.
I am not sure that this is a proper answer as it relies on cors-anywhere, viz:
function importFile(name){
var myUrl = 'http://glasier.hk/cors/tba.html';
var proxy = 'https://cors-anywhere.herokuapp.com/';
var finalURL = proxy + myUrl;
$.get(finalURL,function(data) {
$("body").append(data);
importNset();
})
}
function importNset(){
google.script.run
.withSuccessHandler(function (code) {
path = "https://api.myjson.com/bins/"+code;
$.get(path)
.done((data, textStatus, jqXHR) => {
nset = data;
cfig = nset.cfig;
start();
})
})
.sendCode();
}
var nset,cfig;
$(document).ready(function(){
importFile();
});
but it works, albeit on my machine, using my own website as the resource.
I used the Gas function in gas Shop to make the eight previously tested js files into the single tba.html script only file. I swapped the workshop specific script files for those needed for google.script.run but otherwise that was it. If I could find out how to cors-enable my site, I think I might be able to demonstrate how scripts might be imported to generate different views from the same TBA and spreadsheet interfaces.

Microsoft cognitive services face API call

I've build an application on the Azure (microsoft) emotion API, but that was just merged with their cognitive services face API. I'm using a webcam to send an image (in binary data) to their server for analysis, and used to get an xml in return. (I've already commented out some old code, in this example. Trying to get it fixed).
function saveSnap(data){
// Convert Webcam IMG to BASE64BINARY to send to EmotionAPI
var file = data.substring(23).replace(' ', '+');
var img = Base64Binary.decodeArrayBuffer(file);
var ajax = new XMLHttpRequest();
// On return of data call uploadcomplete function.
ajax.addEventListener("load", function(event) {
uploadcomplete(event);
}, false);
// AJAX POST request
ajax.open("POST", "https://westcentralus.api.cognitive.microsoft.com/face/v1.0/detect?returnFaceId=true&returnFaceLandmarks=false&returnFaceAttributes=emotion","image/jpg");
ajax.setRequestHeader("Content-Type","application/json");
//ajax.setRequestHeader("Accept","text/html,application/xhtml+xml,application/xml");
ajax.setRequestHeader("Ocp-Apim-Subscription-Key","subscription_key");
ajax.send(img);
}
now I understood from their website the call returns a JSON. But I just can't get it to work. I can see there is data coming back, but how do I even get the JSON out of it. I'm probably missing something essential, and hope someone can help me out. :) the program was working when I could still use the Emotion API.
function uploadcomplete(event){
console.log("complete");
console.log(event);
//var xmlDoc = event.target.responseXML;
//var list = xmlDoc.getElementsByTagName("scores");
console.log(JSON.stringify(event));
A few issues to address:
You'll want to wait for the POST response, not just for the upload
completion.
You'll want to set the content type to be application/octet-stream if you are uploading a binary as you are.
You'll want to set the subscription key to the real value (you probably did before pasting your code here.)
.
function saveSnap(data) {
// Convert Webcam IMG to BASE64BINARY to send to EmotionAPI
var file = data.substring(23).replace(' ', '+');
var img = Base64Binary.decodeArrayBuffer(file);
ajax = new XMLHttpRequest();
ajax.onreadystatechange = function() {
if (ajax.readyState == XMLHttpRequest.DONE) {
console.log(JSON.stringify(ajax.response));
}
}
ajax.open('post', 'https://westcentralus.api.cognitive.microsoft.com/face/v1.0/detect?returnFaceId=true&returnFaceLandmarks=false&returnFaceAttributes=emotion');
ajax.setRequestHeader('Content-Type', 'application/octet-stream');
ajax.setRequestHeader('Ocp-Apim-Subscription-Key', key);
ajax.send(img);
}

chrome PDF viewer can't download file

Here is my situation, I got a server running a PDF generator, when I make a request with some params, it will give me back a PDF file, the PDF is not stored in the server it's generated during the runtime.
Everything goes fine, I can get the PDF open in chrome's PDF viewer, but if want to download the file, an error occurred, like the image shows.
Because Chrome go to the origin URL to request the file, but the file is not a static resource on the server.
I don't know if anybody has run into this problem?
Whenever you leave the website you used to create the object URL (window.URL.createObjectURL(...)) that very object will get garbage collected. So you need to keep a reference to that object somehow.
This works for us in Chrome, Firefox, Safari, iOS Safari & Android to first display the PDF in capable browsers in a new tab and allow a download afterwards (in IE it just starts the download):
function openPdfInNewTab(url,
postData,
description = 'Document',
filename = description + '.pdf') {
if (!window.navigator.msSaveOrOpenBlob) {
var tabWindow = window.open('', '_blank');
var a = tabWindow.document.createElement('a');
a.textContent = 'Loading ' + description + '..';
tabWindow.document.body.appendChild(a);
tabWindow.document.body.style.cursor = 'wait';
} else {
spinnerService.show('html5spinner');
}
$http.post(url, postData, {responseType: 'arraybuffer'})
.then(function showDocument(response) {
var file = new Blob([response.data], {type: 'application/pdf'});
if (window.navigator.msSaveOrOpenBlob) {
spinnerService.hide('html5spinner');
window.navigator.msSaveOrOpenBlob(file, filename);
} else {
tabWindow.document.body.style.cursor = 'auto';
var url = a.href = window.URL.createObjectURL(file);
a.click();
a.download = filename;
}
$timeout(function revokeUrl() {
window.URL.revokeObjectURL(url);
}, 3600000);
}, handleDownloadError);
}
We have been opening PDFs in a new browser-tab and had similar issues.
For us it started working again when we use window.URL.createObjectURL instead of tabWindow.URL.createObject which displayed the PDF but didn't allow the download.
Chrome's built in PDF viewer will download the pdf file through the PDF's origin URL. So if the PDF is generated at server runtime and if it's not stored in the sever, the download could fail.
see link here: https://productforums.google.com/forum/#!topic/chrome/YxyVToLN8ho
Just as an additional comment:
We had the same problem on a project, on Chrome only.
Authenticated GET request would fetch the PDF as an attachment from API, and we would forward it via window.createObjectURL(blob) to the browser viewer.
The Network Error was due us invoking window.revokeObjectURL(url); after opening the PDF. When we removed that line, the blob wasn't garbage collected immediately after opening.
fetch(request)
.then(async response => {
if (!response.ok) {
return reject(response.statusText);
}
const blob = await response.blob();
const url = await window.URL.createObjectURL(blob);
window.open(url, '_blank');
URL.revokeObjectURL(url); // This line was causing the problem
resolve();
})
.catch(reject)

Unexpected search results when querying for files based on mimeType - Google Drive API v3 + Javascript

I am trying to search Google Drive for Excel files using the Javascript API (v3). Some Excel files seem to have a mime type of 'application/vnd.ms-excel' and some have a mime type of 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'.
When I use mime type 'application/vnd.ms-excel', the files with that mime type are returned correctly. But, when I use the mime type 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' I get a ton of irrelevant results (with mime types like 'x-javascript', 'x-markdown', and even 'image/png').
Is this a bug or am I doing something wrong?
Any help will be greatly appreciated! Thanks.
Here's my code:
function getItemsByQuery(query, callback) {
var retrievePageOfFiles = function(request, result) {
request.execute(function(resp) {
result = result.concat(resp.files);
var nextPageToken = resp.nextPageToken;
if (nextPageToken) {
request = gapi.client.drive.files.list({ 'pageToken': nextPageToken });
retrievePageOfFiles(request, result);
}
else {
result.length > 0 ? callback(result) : callback([]);
}
});
};
var q = {'q': query };
var initialRequest = gapi.client.drive.files.list(q);
retrievePageOfFiles(initialRequest, []);
}
getItemsByQuery("trashed=false and mimeType='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'", function(results) {
// Handle results - lots of irrelevant files returned here.
});
It looks like you forgot to add the q field when rebuilding the query with a pageToken field.

HTML5 Filesystem API: downloading a sandboxed file?

I have some data in indexeddb files, and I'd like to allow my users to download that data to import into other applications. I can use Blobs to do this easily:
var blob = new Blob(['herp!'], {type: 'application/octet-stream'});
var iframe = document.createElement("iframe");
iframe.id = "myiframe";
iframe.style.display = "none";
iframe.src = window.webkitURL.createObjectURL(blob); // needs a revokeObjectURL
$('#dlplaceholder').append(iframe);
The correct type parameter to Blob is key: if I set it to text/plain, it won't download the file. If I have the iframe a height and width and a display type of block, I can see the data in the iframe.
However, I'll want to build one file out of multiple values from my database, and each of those values can be large, and I can have a lot of entries. I can concatenate those values in the Blob constructor, but I'm worried about having so much data in memory that I blow up.
So I decided to create a file in the sandboxed system and append each value to it, and use the file instead of the blob as the argument to createObjectURL. But when I do that, it
doesn't download; it behaves exactly as if I had used my Blob code with a type of text/plain. Here's the code:
var fname = 'mybadtest'; //UPDATE here's the problem: needs .bin file extension
var init_fs = function ( fs ) {
// first, write to the sandboxed file
fs.root.getFile(fname, {create: true, exclusive: true}, function(fileEntry) {
fileEntry.createWriter(function(fileWriter) {
fileWriter.onwriteend = function(e) {
// write to file completed. now use the file for download
fs.root.getFile(fname, {}, function(fileEntry) {
fileEntry.file(function(file) {
var iframe = document.createElement("iframe");
iframe.id = "myiframe";
iframe.style.display = "none";
iframe.src = window.webkitURL.createObjectURL(file) // needs revokeObjURL
$('#dlplaceholder').append(iframe);
}, errorHandler);
}, errorHandler);
};
var blob = new Blob(['derp!'], {type: 'application/octet-stream'});
fileWriter.write(blob);
}, errorHandler);
}, errorHandler);
};
window.webkitStorageInfo.requestQuota(PERSISTENT, mysz, function(grantedBytes) {
window.webkitRequestFileSystem(PERSISTENT, grantedBytes, init_fs, errorHandler);
}, err_function );
If I give the iframe a size and set the display to block, I can see the data I wrote to the file. So I know I got the file writing part right.
Any ideas on how I can give the file the right type so I can download it? I've been through the MDN docs, the HTML5 Rocks tuts, and Eric Bidelman's "Using the Filesystem API" book, but I don't see anything that addresses this situation. I'm using Chrome version 27.0.1453.110.
UPDATE: problem solved; see comment below.
UPDATED UPDATE: see Rob W's useful comment about an alternate approach below.