HTML Read/Write File [duplicate] - html

I'm playing around a bit with the FileSystem API.
I've found a lot of examples where you generate a download link and let the user download the file the "browser way".
I would like to know two things:
Is there any way to write the ajax result in the fiddle as a file directly to the disk (without any type of prompt). Like to the user's desktop for example.
Is blob the most suitable format for this?
http://jsfiddle.net/FBGDe/
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function(){
if (this.readyState == 4 && this.status == 200){
console.log(this.response, typeof this.response);
var img = document.getElementById('img');
var url = window.URL = window.webkitURL;
img.src = url.createObjectURL(this.response);
}
}
xhr.open('GET', 'http://www.newyorker.com/online/blogs/photobooth
/NASAEarth-01.jpg');
xhr.responseType = 'blob';
xhr.send();

Please note that Filesystem API is no longer part of the standard's specification, as specified at:
http://www.w3.org/TR/file-system-api/
EDIT:
Quoting the specification in case the link changes:
"File API: Directories and System
W3C Working Group Note 24 April 2014
Work on this document has been discontinued and it should not be referenced or used as a basis for implementation."
(This does not relate to the question directly, but it is essential to know not to use the FileSystem API further.)
Another link:
http://www.html5rocks.com/en/tutorials/file/filesystem/
"In April 2014, it was announced on public-webapps that the Filesystem API spec should be considered dead. Other browsers have showed little interest in implementing it."

Unfortunately, writing to regular files is not currently possible (despite the accepted answer Modifying Local Files Using HTML5 and JavaScript).
You can only write to the sandboxed filesystem.
FYI, you can do this in a Chrome Packaged App: http://developer.chrome.com/apps/fileSystem.html But even then the user must at least choose the file first. Writing to any file would be a serious security hole.
What problem are you really trying to solve?

Related

How to programmatically read-write scripts for offline usage in chrome extension?

I need to have predefined scripts, accessible from chrome content_script, that could be updated automatically from given URL.
Exactly what i do:
I have content_script.js. Inside it, i`d like to create iframe for current page from predefined html+css+js.Sometimes html or css or js can be changed. I want to avoid updating extension, instead, each time user have internet, he could load fresh html+css+js for further offline usage.
So, how to read and write some internal files within extension from content script (or delegate this task to background script)?
You can use HTML5 Filesystem to have a read/write place for files, or just store it as strings in chrome.storage (with "unlimitedStorage" permission as needed) for later reuse.
This code can then be executed in a content script using executeScript, or, if you enable 'unsafe-eval' for the extension CSP, in the main script (which is dangerous, and should be avoided in most cases).
Note that this Filesystem API has a warning that's it's only supported in Chrome, but that shouldn't be a problem (Firefox / WebExtensions platform explicitly reject self-update mechanisms).
You can do read extension file contents, but you can't write to extension folder since it is sandboxed.
To read an extension file, you can just send Ajax call using chrome.runtime.getURL("filepath") as url
var xhr = new XMLHttpRequest();
xhr.open('GET', chrome.runtime.getURL('your file path'), true);
xhr.onreadystatechange = function() {
if (chr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
var text = xhr.responseText;
// Do what you want using text
}
};
xhr.send();

Opening specific MimeTypes with google picker

I am working with realtime API and I am making use of realtime-client-utils.
Using existing code, I am creating realtime files with this method:
createRealtimeFile = function(title, callback) {
gapi.client.load('drive', 'v2', function() {
gapi.client.drive.files.insert({
'resource': {
mimeType: rtclient.REALTIME_MIMETYPE,
title: title
}
}).execute(callback);
});
}
and then I am trying to open files with this picker:
var popupOpen = function () {
var token = gapi.auth.getToken().access_token;
var view = new google.picker.View(google.picker.ViewId.DOCS);
view.setMimeTypes(rtclient.REALTIME_MIMETYPE+ "."+realTimeOptions.appId);
var picker = new google.picker.PickerBuilder()
.enableFeature(google.picker.Feature.NAV_HIDDEN)
.setAppId(realTimeOptions.appId)
.setOAuthToken(token)
.addView(view)
.addView(new google.picker.DocsUploadView())
.setCallback(pickerCallback)
.build();
picker.setVisible(true);
};
Although if I use the setMimeTypes, no documents are found. If I remove that filter, my documents appear normally(along with every time of document in the drive). THe mime type I am using is the default one:
rtclient.REALTIME_MIMETYPE = 'application/vnd.google-apps.drive-sdk';
I am adding the appID as this is how its done on realtime-playground. I also tried without the "." or the appID but no documents are found.
Any suggestions about how to fix the filter ?
You should look for mimeType you created with.
You created your file with mimeType rtclient.REALTIME_MIMETYPE and you're looking for files with mimeType rtclient.REALTIME_MIMETYPE+ "."+realTimeOptions.appId That is the reason why you're not getting any files.
Change filepicker code to:
view.setMimeTypes(rtclient.REALTIME_MIMETYPE);
And make sure you change
rtclient.REALTIME_MIMETYPE = 'application/{{YOURE_CUSTOM_MIMETYPE}}';
to avoid collision with other apps.
Found the answer on an android forum. I had to create files with this mimeType:
REALTIME_MIMETYPE = 'application/vnd.google-apps.drive-sdk.'+appID;
and use same mimeType on view :
view.setMimeTypes(REALTIME_MIMETYPE);
Short answer: correct your appID. It is the first part of your CLIENT-ID. That is, if your client-id is 1088706429537-4oqhqr7o826ditbok23sll1rund1jim1.apps.googleusercontent.com, your APP_ID is 1088706429537. I initially confused it with the project-id and had your problem.
I started new answer because all accepted ones are wrong since they disregard the main issue. Might be they are right advising to change the mime-type to something completely custom. Yet, the most popular answer says that
You created your file with mimeType rtclient.REALTIME_MIMETYPE and
you're looking for files with mimeType rtclient.REALTIME_MIMETYPE+
"."+realTimeOptions.appId
This is seems natural but wrong fact. The topic author is right reproducing the demos. Google demos seem to do exactly that: they create application/vnd.google-apps.drive-sdk file and look for appid-expanded ones. This is correct actually because whenever you create a REALTIME_MIMETYPE=application/vnd.google-apps.drive-sdk file, google drive silently fixes file type! It expands it with .APP_ID. This also means that you do not need to customize your mime-type, as other answers propose. You can proceed with application/vnd.google-apps.drive-sdk, I suppose. I have checked: Google will even fix your mime type if some letters are misspelled, which makes the proposed customization even harder. Google API will take another mime type only if it is too different from REALTIME one. But I am not sure that this is a right thing to do, despite this is proposed by all other accepted answers as 'solution' because I do not know about the difference this implies. Nevertheless,
to to tie up, the reasons to use application/vnd.google-apps.drive-sdk instead of proposed custom types are:
This standard type is proposed by Google in realtime playground demos instead of custom type
Google calls this type 'REALTIME_MIMETYPE'. If your use something different, you use Realtime API to create non-realtime files. At least not that much realtime as proposed by google.
If you specify a different mime type, google still corrects it to REALTIME_MIMETYPE. This means that custom mime types are unwelcomed.
Proponents of customization do not understand anything of this. They even dare to laugh at Google official examples as 'ridiculous'. They basically say that we should not trust them.

Using local file for Web Audio API in Javascript

I'm trying to get sound working on my iPhone game using the Web Audio API. The problem is that this app is entirely client side. I want to store my mp3s in a local folder (and without being user input driven) so I can't use XMLHttpRequest to read the data. I was looking into using FileSystem but Safari doesn't support it.
Is there any alternative?
Edit: Thanks for the below responses. Unfortunately the Audio API is horribly slow for games. I had this working and the latency just makes the user experience unacceptable. To clarify, what I need is sounething like -
var request = new XMLHttpRequest();
request.open('GET', 'file:///./../sounds/beep-1.mp3', true);
request.responseType = 'arraybuffer';
request.onload = function() {
context.decodeAudioData(request.response, function(buffer) {
dogBarkingBuffer = buffer;
}, onError);
}
request.send();
But this gives me the errors -
XMLHttpRequest cannot load file:///sounds/beep-1.mp3. Cross origin requests are only supported for HTTP.
Uncaught Error: NETWORK_ERR: XMLHttpRequest Exception 101
I understand the security risks with reading local files but surely within your own domain should be ok?
I had the same problem and I found this very simple solution.
audio_file.onchange = function(){
var files = this.files;
var file = URL.createObjectURL(files[0]);
audio_player.src = file;
audio_player.play();
};
<input id="audio_file" type="file" accept="audio/*" />
<audio id="audio_player" />
You can test here:
http://jsfiddle.net/Tv8Cm/
Ok, it's taken me two days of prototyping different solutions and I've finally figured out how I can do this without storing my resources on a server. There's a few blogs that detail this but I couldn't find the full solution in one place so I'm adding it here. This may be considered a bit hacky by seasoned programmers but it's the only way I can see this working, so if anyone has a more elegent solution I'd love to hear it.
The solution was to store my sound files as a Base64 encoded string. The sound files are relatively small (less than 30kb) so I'm hoping performance won't be too much of an issue. Note that I put 'xxx' in front of some of the hyperlinks as my n00b status means I can't post more than two links.
Step 1: create Base 64 sound font
First I need to convert my mp3 to a Base64 encoded string and store it as JSON. I found a website that does this conversion for me here - xxxhttp://www.mobilefish.com/services/base64/base64.php
You may need to remove return characters using a text editor but for anyone that needs an example I found some piano tones here - xxxhttps://raw.github.com/mudcube/MIDI.js/master/soundfont/acoustic_grand_piano-mp3.js
Note that in order to work with my example you're need to remove the header part data:audio/mpeg;base64,
Step 2: decode sound font to ArrayBuffer
You could implement this yourself but I found an API that does this perfectly (why re-invent the wheel, right?) - https://github.com/danguer/blog-examples/blob/master/js/base64-binary.js
Resource taken from - here
Step 3: Adding the rest of the code
Fairly straightforward
var cNote = acoustic_grand_piano.C2;
var byteArray = Base64Binary.decodeArrayBuffer(cNote);
var context = new webkitAudioContext();
context.decodeAudioData(byteArray, function(buffer) {
var source = context.createBufferSource(); // creates a sound source
source.buffer = buffer;
source.connect(context.destination); // connect the source to the context's destination (the speakers)
source.noteOn(0);
}, function(err) { console.log("err(decodeAudioData): "+err); });
And that's it! I have this working through my desktop version of Chrome and also running on mobile Safari (iOS 6 only of course as Web Audio is not supported in older versions). It takes a couple of seconds to load on mobile Safari (Vs less than 1 second on desktop Chrome) but this might be due to the fact that it spends time downloading the sound fonts. It might also be the fact that iOS prevents any sound playing until a user interaction event has occured. I need to do more work looking at how it performs.
Hope this saves someone else the grief I went through.
Because ios apps are sandboxed, the web view (basically safari wrapped in phonegap) allows you to store your mp3 file locally. I.e, there is no "cross domain" security issue.
This is as of ios6 as previous ios versions didn't support web audio api
Use HTML5 Audio tag for playing audio file in browser.
Ajax request works with http protocol so when you try to get audio file using file://, browser mark this request as cross domain request. Set following code in request header -
header('Access-Control-Allow-Origin: *');

chrome.app.getDetails() returns null

I was trying to get the version of my extension at run-time using chrome.app.getDetails().version and noticed that chrome.app.getDetails() returns null. Surprisingly, there is no talk about this in the online community and the function isn't even documented by the Google folks. Is there a permission I am missing? I do have tabs enabled.
Very old... I know
But in case if someone is looking for this, you can have your extension version reading the manifest file with chrome.runtime API and the getManifest Method.
Ex. in your background script:
var manifest = chrome.runtime.getManifest();
var current_version = manifest.version;
console.info('Current Version: ', current_version);
The object returned is a serialization of the full manifest file, so you can get all the info in the manifest file
So.. if you want for example all and only the matches of your content_scripts... for say something...
for(var i in manifest.content_scripts) {
console.log(manifest.content_scripts[i]['matches']));
}
Note: Stable since Chrome 22
It's undocumented because they might be moving getDetails to a different part of the API -- see this bug. It's currently working on my copy of Chrome (beta channel), but I wouldn't be surprised if they've disabled it in a newer release. In the meantime you can just do an AJAX query to get the manifest.json of your extension -- you can get its URI using chrome.extension.getURL("manifest.json").
Here is what I'm using to pull the current version.
var manifest = new XMLHttpRequest();
manifest.open("get", "/manifest.json", true);
manifest.onreadystatechange = function (e) { if (manifest.readyState == 4) {console.log(JSON.parse(manifest.responseText).version)} };
manifest.send({});

Detecting folders/directories in javascript FileList objects

I have recently contributed some code to Moodle which uses some of the capabilities of HTML5 to allow files to be uploaded in forms via drag and drop from the desktop (the core part of the code is here: https://github.com/moodle/moodle/blob/master/lib/form/dndupload.js for reference).
This is working well, except for when a user drags a folder / directory instead of a real file. Garbage is then uploaded to the server, but with the filename matching the folder.
What I am looking for is an easy and reliable way to detect the presence of a folder in the FileList object, so I can skip it (and probably return a friendly error message as well).
I've looked through the documentation on MDN, as well as a more general web search, but not turned up anything. I've also looked through the data in the Chrome developer tools and it appears that the 'type' of the File object is consistently set to "" for folders. However, I'm not quite convinced this is the most reliable, cross-browser detection method.
Does anyone have any better suggestions?
You cannot rely on file.type. A file without an extension will have a type of "". Save a text file with a .jpg extension and load it into a file control, and its type will display as image/jpeg. And, a folder named "someFolder.jpg" will also have its type as image/jpeg.
Instead, try to read the first byte of the file. If you are able to read the first byte, you have a file. If an error is thrown, you probably have a directory:
try {
await file.slice(0, 1).arrayBuffer();
// it's a file!
}
catch (err) {
// it's a directory!
}
If you are in the unfortunate position of supporting IE11, The file will not have the arrayBuffer method. You have to resort to the FileReader object:
// use this code if you support IE11
var reader = new FileReader();
reader.onload = function (e) {
// it's a file!
};
reader.onerror = function (e) {
// it's a directory!
};
reader.readAsArrayBuffer(file.slice(0, 1));
I also ran into this problem and below is my solution. Basically, I took have a two pronged approach:
(1) check whether the File object's size is large, and consider it to be a genuine file if it is over 1MB (I'm assuming folders themselves are never that large).
(2) If the File object is smaller than 1MB, then I read it using FileReader's 'readAsArrayBuffer' method. Successful reads call 'onload' and I believe this indicates the file object is a genuine file. Failed reads call 'onerror' and I consider it a directory. Here is the code:
var isLikelyFile = null;
if (f.size > 1048576){ isLikelyFile = false; }
else{
var reader = new FileReader();
reader.onload = function (result) { isLikelyFile = true; };
reader.onerror = function(){ isLikelyFile = false; };
reader.readAsArrayBuffer(f);
}
//wait for reader to finish : should be quick as file size is < 1MB ;-)
var interval = setInterval(function() {
if (isLikelyFile != null){
clearInterval(interval);
console.log('finished checking File object. isLikelyFile = ' + isLikelyFile);
}
}, 100);
I tested this in FF 26, Chrome 31, and Safari 6 and three browsers call 'onerror' when attempting to read directories. Let me know if anyone can think of a use case where this fails.
I proposing calling FileReader.readAsBinaryString on the File object. In Firefox, this will raise an Exception when the File is a Directory. I only do this if the File meets the conditions proposed by gilly3.
Please see my blog post at http://hs2n.wordpress.com/2012/08/13/detecting-folders-in-html-drop-area/ for more details.
Also, version 21 of Google Chrome now supports dropping folders. You can easily check if the dropped items are folders, and also read their contents.
Unfortunately, I donĀ“t have any (client-side) solution for older Chrome versions.
One other note is that type is "" for any file that has an unknown extension. Try uploading a file named test.blah and the type will be empty. AND... try dragging and dropping a folder named test.jpg - type will be set to "image/jpeg". To be 100% correct, you can't depend on type solely (or if at all, really).
In my testing, folders have always been of size 0 (on FF and Chrome on 64-bit Windows 7 and under Linux Mint (Ubuntu essentially). So, my folder check is just checking if size is 0 and it seems to work for me in our environment. We also don't want 0-byte files uploaded either so if it's 0 byte the message comes back as "Skipped - 0 bytes (or folder)"
FYI, this post will tell you how to use dataTransfer API in Chrome to detect file type: http://updates.html5rocks.com/2012/07/Drag-and-drop-a-folder-onto-Chrome-now-available
The best option is to use both the 'progress' and 'load' events on a FileReader instance.
var fr = new FileReader();
var type = '';
// Early terminate reading files.
fr.addEventListener('progress', function(e) {
console.log('progress - valid file');
fr.abort();
type = 'file';
});
// The whole file loads before a progress event happens.
fr.addEventListener('load', function(e) {
console.log('load - valid file');
type = 'file';
});
// Not a file. Possibly a directory.
fr.addEventListener('error', function(e) {
console.log('error - not a file or is not readable by the web browser');
});
fr.readAsArrayBuffer(thefile);
This fires the error handler when presented with a directory and most files will fire the progress handler after reading just a few KB. I've seen both events fire. Triggering abort() in the progress handler stops the FileReader from reading more data off disk into RAM. That allows for really large files to be dropped without reading all of the data of such files into RAM just to determine that they are files.
It may be tempting to say that if an error happens that the File is a directory. However, a number of scenarios exist where the File is unreadable by the web browser. It is safest to just report the error to the user and ignore the item.
An easy method is the following:
Check if the file's type is an empty string: type === ""
Check if the file's size is 0, 4096, or a multiple of it: size % 4096 === 0.
if (file.type === "" && file.size % 4096 === 0) {
// The file is a folder
} else {
// The file is not a folder
}
Note: Just by chance, there could be files without a file extension that have the size of some multiple of 4096. Even though this will not happen very often, be aware of it.
For reference, please see the great answer from user Marco Bonelli to a similar topic. This is just a short summary of it.