Browser-Controlled File Storage

Part 2. Directory and File Entries

FileWriter


...Do you know what writer is? Pure abstraction, I dare say.
Neo Tolstoy

If a bid to gain access to the file system is successful, application can use an appropriate callback to view or create file and directory entries. Let's have a closer look at our FileSystem success callback now:

function fsRequestCallback(fileSystem){
 alert("File System request is satisfied.");
 var rootDirectory=fileSystem.root;
 rootDirectory.getDirectory(
   "MyImages", // new directory name
   {create:true}, // Flags
   directoryCreationCallback, // directory creation success callback
   function(fileError){ // anonymous error callback
     alert("New directory cannot be created. Error code: "+fileError.code);
    }
 );
}

The callback accepts a FileSystem instance as its single argument. FileSystem's root property gives us access to the root directory. We want to create a new directory named MyImages, so our code uses getDirectory method with the following arguments: new directory name; Flags object hinting at the nature of the requested operation ({create:true}); directory creation success callback; anonymous error callback. Success callback is represented as a named function:

function directoryCreationCallback(entry){
 directoryEntry=entry;
 var directoryName=directoryEntry.name;
 var directoryPath=directoryEntry.fullPath;
 var directoryURL=directoryEntry.toURL();
 var description="New directory is created.\nName : "+directoryName+"\nPath : "+directoryPath+"\nURL : "+directoryURL;
 alert(description);
 directoryEntry.getMetadata(
  function(metadata){ // anonymous metadata callback
   alert("Directory was last modified : \n"+metadata.modificationTime);
  },
  function(fileError){ // anonymous error callback
   alert("Metadata cannot be obtained. Error code: "+fileError.code);
  }
 );
  fetchImage();
}

The callback's argument is the created directory which implements DirectoryEntry interface. DirectoryEntry is a subinterface of more general Entry API. Both interfaces expose a number of properties and methods we can use to display directory attributes: name will return MyImages; fullPath is the full absolute path from the root to our directory (/MyImages); toURL method will give us the value of filesystem:http://example.com/temporary/MyImages. Modification date is obtained via getMetadata method with accompanying callbacks. Other directory manipulations can be performed though moveTo, copyTo, remove and removeRecursively methods. The filesystem property and getParent method allows script navigation along the files hierarchy.

Now the application calls fetchImage method to retrieve a remote image resource resided on a Web server:

function fetchImage(){
 var xhr=new XMLHttpRequest();
 xhr.onreadystatechange=imageLoader;
 xhr.open("GET","http://example.com/resources/image.png",true);
 xhr.responseType="blob";
 xhr.send();
}
function imageLoader(event){
 if((event.target.readyState==4)&&(event.target.status==200)){
  alert("Image is loaded.");
  blob=event.target.response;
  createImageFile();
 }
}

XMLHttpRequest has a single readyState change event handler which first checks the completion of the asynchronous operation and its HTTP status code, then returns the resulting Blob in the response property of the XHR. The Blob will be used subsequently by FileWriter to save the image. Before creating a FileWriter, we need to obtain a FileEntry handle:

function createImageFile(){
 directoryEntry.getFile(
  fileName, // this is a global variable
  {create:true}, // Flags
  fileCreationCallback, // FileEntry creation success callback
  function(fileError){ // anonymous error callback
   alert("FileEntry cannot be created");
  }
 );
}

The getFile method of the directory entry allows us to get an instance of a FileEntry with the supplied parameters. As FileEntry is a subinterface of Entry, it exposes the properties and methods mentioned above (name, fullPath, filesystem, getMetadata, moveTo, copyTo, remove, getParent and toURL). Some of them can be used in the FileEntry creation success callback:

function fileCreationCallback(fileEntry){
 var fileName=fileEntry.name; // image.png
 var filePath=fileEntry.fullPath; // /MyImages/image.png
 var fileURL=fileEntry.toURL(); // filesystem:http://example.com/temporary/MyImages/image.png
 var description="New FileEntry is created.\nName : "+fileName+"\nPath : "+filePath+"\nURL : "+fileURL;
 alert(description);
 fileEntry.createWriter(
  fileWriterCreationCallback,
  function(fileError){ // anonymous error callback
   alert("FileWriter cannot be created.Code: "+fileError.code);
  }
 );
}

We could view the received image immediately (window.open (fileURL);) thus allowing the user to save it by means of the browser UI (Save image as ... context menu option). To persist data locally without user interaction, our application creates an instance of FileWriter and writes image Blob in the creation callback:

function fileWriterCreationCallback(fileWriter){
 alert("FileWriter is created");
 fileWriter.onwrite=function(event){
  alert("Image is saved");
 };
 fileWriter.write(blob);
}

Like FileReader, FileWriter employs events-based model for its operations. FileWriter generates a sequence of events as it writes data to a file. First writestart event is fired, then FileWriter dispatches progress events each time it writes a chunk of data. If no error occurs, write event is fired. The user can abort the write: in this case abort event is thrown. Errors are handled within error event handlers. The writeend event designates the end of write, even it was unsuccessful.

writestart progress write
abort
error
writeend

Let's summarize. Our application writing files asynchronously has undergone the following transformations:

requesting FileSystem access
+
specifying desired parameters
(size, type)
error callback
success callback

using getDirectory method
of root DirectoryEntry
+
creating new DirectoryEntry
error callback
success callback

fetching a remote file
+
creating new FileEntry
via getFile method
of the new DirectoryEntry
error callback
success callback

using createWriter
method of the new FileEntry
error callback
success callback

using FileWriter to
save data to file
error callback