Question #4: Now doesn’t all this business with streams and buffer make simple file I/O rather complicated?

Answer: yes and no. If you want to work with the low-level details for whatever reason, the API design gives you that capability. If you don’t, then fortunately there are some higher-level APIs that makes it all much simpler.

For the really low-level view using nothing but streams and buffers, here’s a function to write a string to a file in the Temp folder:

var fileContents = “Congratulations, you’re written data to a temp file!”;
writeTempFile1(“data1.txt”, fileContents);

function writeTempFile1(filename, contents) {
    //tempFolder is a StorageFolder
    var tempFolder = Windows.Storage.ApplicationData.current.temporaryFolder;
    var writer;
    var outputStream;

    //All the control you want!
    tempFolder.createFileAsync(filename,
        Windows.Storage.CreationCollisionOption.replaceExisting)
    .then(function (file) {
        //File is a StorageFile
        return file.openAsync(Windows.Storage.FileAccessMode.readWrite);
    }).then(function (stream) {
        //Stream is an RandomAccessStream. To write to it, we need an IOuputStream
        outputStream = stream.getOutputStreamAt(0);

        //Create a buffer with contents
        writer = new Windows.Storage.Streams.DataWriter(outputStream);
        writer.writeString(contents);
        var buffer = writer.detachBuffer();
        return outputStream.writeAsync(buffer);
    }).then(function (bytesWritten) {
        console.log(“Wrote “ + bytesWritten + ” bytes.”);
        return outputStream.flushAsync();
    }).done(function () {
        writer.close(); //Closes the stream too
    });
}

Within this structure, you have the ability to inject any other actions you might need to take. But if you don’t, then we can start making simplifications. For one, the DataWriter class has a method called storeAsync that takes care of the buffer business for us:

writeTempFile2(“data2.txt”, fileContents);

function writeTempFile2(filename, contents) {
var tempFolder = Windows.Storage.ApplicationData.current.temporaryFolder; 
    var writer;

    //Looking better
    tempFolder.createFileAsync(filename,
Windows.Storage.CreationCollisionOption.replaceExisting)
.then(
function (file) {
return file.openAsync(Windows.Storage.FileAccessMode.readWrite);
}).then(
function (stream) {
writer =
new Windows.Storage.Streams.DataWriter(stream.getOutputStreamAt(0));
writer.writeString(contents);
return writer.storeAsync();
}).then(
function () {
return writer.flushAsync();
}).done(
function () {
writer.close();
});

}

If you don’t need to play with the streams directly, then you can use the FileIO class to hide those details:

writeTempFile3(“data3.txt”, fileContents);

function writeTempFile3(filename, contents) {
    var tempFolder = Windows.Storage.ApplicationData.current.temporaryFolder;
    tempFolder.createFileAsync(filename,
        Windows.Storage.CreationCollisionOption.replaceExisting)
.then(
function (file) {
return Windows.Storage.FileIO.writeTextAsync(file, contents);
}).done();

}

And if you have programmatic access to the desired location on the file system, you can even forego using StorageFile at all and just address the location by pathname or URI through the PathIO class, provided that the file already exists (a point that is missing from the docs at present). So if we’ve created a file with createFileAsync as in writeTempFile3 above, then we can subsequently write to the file through an ms-appdata:///temp/ URI like so:

Windows.Storage.PathIO.writeTextAsync(“ms-appdata:///temp/data3.txt” + filename, contents).done();

So yes, simple file I/O can be complicated if you need it to be. For example, if you wanted to insert some kind of encoding or cryptography deep in the first function, you could do that. APIs like CryptographicEngine.encrypt work with buffers, so the fact that you can get to such buffers for file I/O is a good thing. But if you don’t need that level of control, you don’t need to see any of the details. The APIs are designed to give you the control you need when you need it, but to not burden you when you don’t.

In the FileIO class, all of its methods just work on a StorageFile directly, hiding the business with streams. Its methods are read[Buffer | Lines | Text]Async, write[Buffer | Bytes | Lines | Text]Async, and append[Lines | Text]Async. Note that there isn’t a readBytesAsync so you’ll still need to use the DataReader to pull the bytes from the buffer.

And PathIO, for its part, has an identical list of methods that work with pathnames or suitable ms-appdata:// URIs (or ms-appx:// for reading in-package contents) instead of StorageFile objects.

To complete the overall question of file I/O, it’s also helpful to pop over to the StorageFile class and look at its four methods to “open” a file, which is to say, open a stream that provides access to the file contents:

  • openAsync: provides a RandomAccessStream through which you have full read/write access. We’re using this in the code above.
  • openReadAsync: provides an IRandomAccessStreamWithContentType as described earlier for RandomAccessStreamReference.openReadAsync. This is a read-only stream.
  • openSequentialReadAsync: provides an IInputStream, so it’s optimized for reading data quickly with low memory overhead. This is what you want to use when reading and processing a large input file.
  • openTransactedWriteStream: provides a Windows.Storage.StorageStreamTransaction that’s a specialized object built on a RandomAccessStream . It has the usual close/Dispose methods along with a stream property (an IRandomAccessStream) through which you do the usual work, and then a commitAsync method that will then do the final write to the file. That is, all of the writes and flushes you might do in the stream itself will be cached until you call commitAsync, thereby writing all the data at once.

 

For demonstrations of many of these APIs, the File access sample is your friend here.


2 Comments

  1. bill
    Posted April 11, 2013 at 9:40 pm | Permalink

    Yes, it may seem a bit complicated. But it really isn’t, and if it were documented, it wouldn’t be too hard to use. Sadly, it isn’t documented, and tragically, that makes it nearly impossible to do anything.

    • Posted April 12, 2013 at 4:32 pm | Permalink

      Like everything else, the documentation team needs to prioritize their work. In a perfect world everything would be documented exactly as it needs to be, but I don’t think we live in that world! Hence this blog :). But it will improve over time, as getting all the docs written for a new platform is a monumental task. Overall I think the docs are pretty good.