Browser-Controlled File Storage
Part 2. Directory and File Entries
FileWriter
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 |