How to download files using javascript asynchronously? - javascript

I'm building a Chrome extension to download a series of files from a site. The downloading function is derived from How to save a file from a URL with JavaScript.
The program structure is like:
function download()
{
while(there are still files to download)
{
saveFile(url);
}
}
But I find that all the files are actually written to disk at once after download() returns. And the addresses of those files start with blob: when examine from Chrome's downloads manager.
I wonder if I make the call to saveFile asynchronously, those files could be written one at a time.

Using promises, which are available in Chrome out of the box, you can define the functions like so:
// Download a file form a url.
function saveFile(url) {
return new Promise(function(resolve, reject) {
// Get file name from url.
var xhr = new XMLHttpRequest();
xhr.responseType = 'blob';
xhr.onload = function() {
resolve(xhr);
};
xhr.onerror = reject;
xhr.open('GET', url);
xhr.send();
}).then(function(xhr) {
var filename = url.substring(url.lastIndexOf("/") + 1).split("?")[0];
var a = document.createElement('a');
a.href = window.URL.createObjectURL(xhr.response); // xhr.response is a blob
a.download = filename; // Set the file name.
a.style.display = 'none';
document.body.appendChild(a);
a.click();
return xhr;
});
}
function download(urls) {
return Promise.all(urls.map(saveFile));
}
Using it:
download.then(function() {
alert("all files downloaded");
}).catch(function(e) {
alert("something went wrong: " + e);
});
If you want to wait for 1 file to download before proceeding with next, the download function should be written like:
function download(urls) {
var cur = Promise.resolve();
urls.forEach(function(url) {
cur = cur.then(function() {
return saveFile(url);
});
});
return cur;
}
Usage is same as before.

Related

How correctly store .js files to Localstorage of a browser

Can you please tell me how can I correctly store some js file to localstorage and then run it.
So I have found some code that stores an image, but can I store a js file?
Here is the code:
https://gist.github.com/robnyman/1875344
// Getting a file through XMLHttpRequest as an arraybuffer and creating a Blob
var rhinoStorage = localStorage.getItem("rhino"),
rhino = document.getElementById("rhino");
if (rhinoStorage) {
// Reuse existing Data URL from localStorage
rhino.setAttribute("src", rhinoStorage);
}
else {
// Create XHR and FileReader objects
var xhr = new XMLHttpRequest(),
fileReader = new FileReader();
xhr.open("GET", "rhino.png", true);
// Set the responseType to blob
xhr.responseType = "blob";
xhr.addEventListener("load", function () {
if (xhr.status === 200) {
// onload needed since Google Chrome doesn't support addEventListener for FileReader
fileReader.onload = function (evt) {
// Read out file contents as a Data URL
var result = evt.target.result;
// Set image src to Data URL
rhino.setAttribute("src", result);
// Store Data URL in localStorage
try {
localStorage.setItem("rhino", result);
}
catch (e) {
console.log("Storage failed: " + e);
}
};
// Load blob as Data URL
fileReader.readAsDataURL(xhr.response);
}
}, false);
// Send XHR
xhr.send();
}

Firebase Storage files not downloading. What am I doing wrong?

I need to download a file that has been uploaded to Firebase Storage. I used to get the CORS error but I used gsutil as per the docs and now nothing really happens. The download doesn't start. What am I doing wrong?
//Create reference of Storage
let storage = firebase.storage();
//Create reference to file in Storage
let pathSubmission = 'Uploads/' + this.place1+ '/' + this.place2+ '/' + this.place3+ '/' + this.downloadSubmissionUID;
let storageRef = storage.ref(pathSubmission);
storageRef.getDownloadURL().then((url) => {
console.log('downloadURL arg: ' + url);
//Download file (no idea how this works)
var xhr = new XMLHttpRequest();
xhr.responseType = 'blob';
xhr.onload = function(event) {
var blob = xhr.response;
};
xhr.open('GET', url);
xhr.send();
})
Figured it out using Kai's blog, here's my solution:
//Create reference of Storage
let storage = firebase.storage();
//Create reference to file in Storage
let pathSubmission = 'Uploads/' + this.place1 + '/' + this.place2+ '/' + this.place3 + '/' + this.downloadSubmissionUID;
//Assign Storage reference the path reference
let storageRef = storage.ref(pathSubmission);
//Download file by creating XMLHttpRequest
storageRef.getDownloadURL().then((url) => {
var xhr = new XMLHttpRequest();
xhr.responseType = 'blob';
xhr.onload = function(event) {
//Create an `a` tag (since it has an `href` and a `download` attribute)
var a = document.createElement('a');
a.href = window.URL.createObjectURL(xhr.response);
a.download = 'someFileName';
a.style.display = 'none';
document.body.appendChild(a);
a.click(); //Simulates a click event
var blob = xhr.response;
};
xhr.open('GET', url);
xhr.send();
})
Actually, the file is probably getting downloaded. The problem is in this code here:
//Download file (no idea how this works)
var xhr = new XMLHttpRequest();
xhr.responseType = 'blob';
xhr.onload = function(event) {
var blob = xhr.response;
};
xhr.open('GET', url);
xhr.send();
The browser is doing exactly what it's told. It downloads the file as a blob. Then discards the result, because you aren't doing anything with the data in the onload handler!
First, I'd recommend using fetch, which provides a much more intuitive API than the XMLHttpRequest (in my opinion).
Here's how you might do that:
// Create reference of Storage
let storage = firebase.storage();
// Create reference to file in Storage
let pathSubmission = 'Uploads/' + this.place1+ '/' + this.place2+ '/' + this.place3+ '/' + this.downloadSubmissionUID;
let storageRef = storage.ref(pathSubmission);
storageRef.getDownloadURL()
.then(url => {
console.log('Download URL: ' + url)
return fetch(url)
})
.then(response => response.blob())
.then(blob => {
console.log('File downloaded as blob')
// Do something with the blob...
})
.catch(error => {
console.error('Something went wrong while downloading the file:', error)
})

JS FileSaver non interactive

I have an array of file to save by using a loop and i generate the name of each file. I want to save file directly (in non interactive way) without asking me to confirm. How can i do ?
Here is my code for saving file
var url = img.src.replace(/^data:image\/[^;]/, 'data:application/octet-stream');
var xhr = new XMLHttpRequest();
xhr.responseType = 'blob'; //Set the response type to blob so xhr.response returns a blob
xhr.open('GET', url , true);
xhr.onreadystatechange = function () {
if (xhr.readyState == xhr.DONE) {
var filesaver = require('file-saver');
filesaver.saveAs(xhr.response, nameFile); // nameFile the name of file to be saved
}
};
xhr.send(); //Request is sent
Finally, i find a solution, instead of saving file, i write it by creating a new one.
for (var i = 0; i < tabForm.length; i++) {
var imgData = $('#affichageqr')[0].childNodes[1].childNodes[0].src;
var data = imgData.replace(/^data:image\/\w+;base64,/, '');
fs.writeFile(qrcode.getNomQRCode()+'.jpeg', data, {encoding: 'base64'}, function(err){
if (err) {
console.log('err', err);
}
console.log('success');
});
}

Web Audio load both .mp3 and .ogg

I'm building a canvas game using javascript and web audio. To get it to work in Firefox I have a copy of all the audio files in .ogg format. The code to load these files is below. To get the desired audio file I use ' playSound(samplebb[3], channel1); ', I have done it like this as in my app it is useful to choose the sample based on number, for example sounds can be chosen using probability and randomness.
I read on a forum "the loader will accept both [mp3 and ogg] for the same sound, just pass both paths in an array rather than a single string."
The 4th line of code is me trying this but it does not work.
Is it possible to load an alternate ogg file for each mp3 like this? (In one bufferlist) Or will I have to detect the browser and build a bufferlist of oggs if the browser is Firefox?
Thanks
function loadSounds() {
bufferLoader = new BufferLoader(audioContext,
[
['sounds/1-KICK.mp3', 'sounds/1-KICK.ogg'], //0 // Not found
'sounds/2-BASS.mp3', //1
'sounds/3-BASS2.mp3', //2
'sounds/4-BASS4.mp3' //3
// ... ... ...
],
finishedLoading
);
bufferLoader.load();
}
function finishedLoading(bufferList) {
for (var i = 0, l = bufferList.length; i < l; i += 1) {
var source = audioContext.createBufferSource();
source.buffer = bufferList[i];
source.connect(audioContext.destination);
var note = {
note: source,
ready: true
};
samplebb.push(note);
}
setTimeout(play, 1000);
}
Are you using BufferLoader from html5rocks? If so, the JS file clearly shows that it only expects strings (not arrays) as url arguments. However you can modify the class so that it works like you want. Use the following BufferLoader.loadBuffer() function instead:
BufferLoader.prototype.loadBuffer = function(url, index) {
// Load buffer asynchronously
var request = new XMLHttpRequest(),
mult = typeof url != 'string',
srcInd = 0;
request.open("GET", mult ? url[srcInd++] : url, true);
request.responseType = "arraybuffer";
var loader = this;
request.onload = function() {
// Asynchronously decode the audio file data in request.response
loader.context.decodeAudioData(
request.response,
function(buffer) {
if (!buffer) {
if(!mult || srcInd == url.length) {
console.error('error decoding file data:', url);
return;
} else {
console.info('error decoding file data, trying next source');
request.open("GET", url[srcInd++], true);
return request.send();
}
}
loader.bufferList[index] = buffer;
if (++loader.loadCount == loader.urlList.length)
loader.onload(loader.bufferList);
},
function(error) {
if(!mult || srcInd == url.length) {
console.error('decodeAudioData error:', url);
return;
} else {
console.info('decodeAudioData error, trying next source');
request.open("GET", url[srcInd++], true);
return request.send();
}
}
);
}
request.onerror = function() {
if(!mult || srcInd == url.length) {
console.error('BufferLoader XHR error:', url);
return;
} else {
console.info('BufferLoader XHR error, trying next source');
request.open("GET", url[srcInd++], true);
return request.send();
}
}
request.send();
}

How to create a Blob from a local file in phonegap on android

I am trying to upload a file to amazon S3 from a phonegap app on android. I already have the code working for iOS. But I've got a trouble making it work on android. The problem is that I do not know how to create the Blob object properly. On iOS I just do this:
blob = new Blob([evt.target.result], {type: "image/png"});
It is uploaded just fine. On android one can not use the Blob constructor (see here), but I could not manage to get the file data correctly into the Blob object using WebKitBlobBuilder.
Here is how I retrieve the file data, there are two approaches and both pass without errors, but the resulting file on the S3 is empty:
window.resolveLocalFileSystemURI(url, function(e){
e.file(function(f){
var reader = new FileReader();
reader.onloadend = function(evt) {
// This way also did not work:
// var builder = new WebKitBlobBuilder();
// builder.append(evt.target.result);
// blob = builder.getBlob("image/png");
var blob = null;
var builder = new WebKitBlobBuilder();
for(var i = 0; i < evt.target.result.length; i++){
builder.append(evt.target.result[i]);
}
blob = builder.getBlob("image/png");
uploadToS3(filename, s3url, blob);
};
reader.readAsArrayBuffer(f);
});
}, function(e){
console.log("error getting file");
error();
});
also, here is the uploadToS3 function:
var uploadToS3 = function(filename, s3url, fileData) {
var xhr = createCORSRequest('PUT', s3url);
if(!xhr) {
console.log('CORS not supported');
error();
return;
}
xhr.onload = function () {
if(xhr.status == 200) {
console.log('100% - Upload completed.');
callback(filename); //callback defined in outer context
}
else {
console.log('0% - Upload error: ' + xhr.status);
console.log(xhr.responseText);
error();
}
};
xhr.onerror = function () {
console.log(0, 'XHR error.');
error();
};
xhr.upload.onprogress = function (e) {
if(e.lengthComputable) {
var percentLoaded = Math.round((e.loaded / e.total) * 100);
var label = (percentLoaded == 100) ? 'Finalizing.' : 'Uploading.';
console.log(percentLoaded + "% - " + label);
}
};
xhr.setRequestHeader('Content-Type', "image/png");
//xhr.setRequestHeader('x-amz-acl', 'public-read');
xhr.send(fileData);
};
EDIT:
I checked the filesize by logging evt.target.result.byteLength and it was ok, so evt.target.result contains image data. Still there is a problem with the upload - I checked the s3 storage and file size is 0, so I am not constructing the Blob correctly.
So this is an android bug after all, which was not fixed since at least Nov 2012.
Here is an article I found which is directly related to my problem: https://ghinda.net/article/jpeg-blob-ajax-android/
It also provides a workaround for the bug, which is to send ArrayBuffer directly, without creating a Blob. In my case I had to send evt.target.result directly.

Categories