4

I am developing a Firefox addon. I need to save a bunch of data URI images to the disk. How do I approach to this?

I have browsed through the file I/O snippets on MDN, but the snippets don't help me much.

There are async and sync methods.I would like to use async method but how can I write a binary file using async method

Components.utils.import("resource://gre/modules/NetUtil.jsm");
Components.utils.import("resource://gre/modules/FileUtils.jsm");

// file is nsIFile
var file = FileUtils.getFile("Desk", ["test.png"]);

// You can also optionally pass a flags parameter here. It defaults to
// FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE | FileUtils.MODE_TRUNCATE;
var ostream = FileUtils.openSafeFileOutputStream(file);

//base64 image that needs to be saved 
image ="iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==";

// How can I create an inputstream from the image data URI?
var inputstream = createInputstream(image);

// The last argument (the callback) is optional.
NetUtil.asyncCopy(inputstream , ostream, function(status) {
  if (!Components.isSuccessCode(status)) {
    // Handle error!
    return;
  }

  // Data has been written to the file.
});
nmaier
  • 32,336
  • 5
  • 63
  • 78
tinker
  • 855
  • 1
  • 6
  • 13

1 Answers1

5

It sounds like you'd like to write not the data URI but the binary data it "contains", so I'll answer that.

First, lets assume we got some actual data URI, (if not, adding data:application/octet-stream;base64, isn't too hard ;)

// btoa("helloworld") as a placeholder ;)
var imageDataURI = "data:application/octet-stream;base64,aGVsbG93b3JsZA==";

Option 1 - Using OS.File

OS.File has the benefit that it is truly async. On the other hand, NetUtil is only mostly async, in that there will be stat calls on the main thread and the file will be opened and potentially closed on the main thread as well (which can lead to buffer flushes and hence block the main thread while the flush is happening).

After constructing a path (with some constants help), OS.File.writeAtomic is suited for the job.

Components.utils.import("resource://gre/modules/osfile.jsm");

var file = OS.Path.join(OS.Constants.Path.desktopDir, "test.png");

var str = imageDataURI.replace(/^.*?;base64,/, "");
// Decode to a byte string
str = atob(str);
// Decode to an Uint8Array, because OS.File.writeAtomic expects an ArrayBuffer(View).
var data = new Uint8Array(str.length);
for (var i = 0, e = str.length; i < e; ++i) {
  data[i] = str.charCodeAt(i);
}

// To support Firefox 24 and earlier, you'll need to provide a tmpPath. See MDN.
// There is in my opinion no need to support these, as they are end-of-life and
// contain known security issues. Let's not encourage users. ;)
var promised = OS.File.writeAtomic(file, data);
promised.then(
  function() {
    // Success!
  },
  function(ex) {
    // Failed. Error information in ex
  }
);

Option 2 - Using NetUtil

NetUtil has some drawbacks in that is is not fully async, as already stated above.

We can take a shortcut in that we can use NetUtil.asyncFetch to directly fetch the URL, which gives us a stream we can pass along to .asyncCopy.

Components.utils.import("resource://gre/modules/NetUtil.jsm");
Components.utils.import("resource://gre/modules/FileUtils.jsm");

// file is nsIFile
var file = FileUtils.getFile("Desk", ["test.png"]);

NetUtil.asyncFetch(imageDataURI, function(inputstream, status) {
  if (!inputstream || !Components.isSuccessCode(status)) {
    // Failed to read data URI.
    // Handle error!
    return;
  }

  // You can also optionally pass a flags parameter here. It defaults to
  // FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE | FileUtils.MODE_TRUNCATE;
  var ostream = FileUtils.openSafeFileOutputStream(file);

  // The last argument (the callback) is optional.
  NetUtil.asyncCopy(inputstream , ostream, function(status) {
    if (!Components.isSuccessCode(status)) {
      // Handle error!
      return;
    }

    // Data has been written to the file.
  });
});
nmaier
  • 32,336
  • 5
  • 63
  • 78
  • Thx for 2nd soln I relaly love to see how people would do the same thing I would do diffrently. In my soln I used `encoding:'utf-8'` so no need for me to `//Decode to an Uint8Array` right? – Noitidart Aug 06 '14 at 01:58
  • @Noitidart. No our solutions are entirely different in fact. Mine decodes the base64 encoded data in the data URI first, while yours just writes it unmodified. I'm not sure what the asker wanted so I figured I provide an alternative. – nmaier Aug 06 '14 at 05:56
  • Yes you guessed it write.. thanks for the detail answer :) Btw I don't understand why is it this complicated with firefox. For php I could just do file_put_contents('test.png',base64_decode($base64String)) – tinker Aug 06 '14 at 05:57
  • 1
    Ah I got it, its taking data uris and saving them as png files on disk. Cool stuff. thx guys. – Noitidart Aug 06 '14 at 06:03
  • 1
    Converting a string to Uint8Array through charCodeAt is very slow. Instead we can make a `nsIStringInputStream` with the decoded string, set that as the input stream of a `nsIBinaryInputStream`, `readByteArray()` and pass this to the `Uint8Array` constructor. More verbose code, but also about 4-5 times faster conversion (very noticeable with large strings). – paa Aug 06 '14 at 16:05
  • Hi @paa would this work: `var BinaryInputStream = Components.Constructor('@mozilla.org/binaryinputstream;1', 'nsIBinaryInputStream', 'setInputStream'); var StringInputStream = Components.Constructor('@mozilla.org/io/string-input-stream;1', 'nsIStringInputStream', 'setData'); var str = atob('rawr'); var _inputStreamScriptable = new StringInputStream(str, str.length); var _inputStreamBinary = new BinaryInputStream(_inputStreamScriptable); var ua = new Uint8Array(str.length); ua.set(_inputStreamBinary.readByteArray(str.length));` https://gist.github.com/Noitidart/539fcf7cc503ce51d062 – Noitidart Sep 03 '14 at 04:29
  • This person also had some tehcnique to convert string to Uint8Array: http://stackoverflow.com/a/5370394/1828637 posting this here really just for my reference – Noitidart Feb 09 '15 at 02:51
  • This was awesome I just used this. Also in case anyone wants to the data URI to a blob: http://stackoverflow.com/a/11954337/1828637 – Noitidart Jul 27 '15 at 09:26