Browser-Controlled File Storage

Part 1. Quota Management

Introduction


In our first article we outlined the general aspects of file reading operations. Files can be read as plain text, binary string, typed array or Base-64 encoded data: URL. Data format predefines further manipulations: file data can be used to create an object URL, dynamically build new HTML elements, modify the file and download its modified contents, etc. Major browser vendors tacitly agreed on the necessity of File API for rich Web applications and implemented File interface features, though not always uniformly. The next logical step is to introduce APIs allowing file write operations. The step increases security risks considerably: FileSystem interfaces must be carefully designed and securely implemented, otherwise they may become malware authors' best helpers. At present, not all browser vendors have taken risks creating full-fledged FileSystem and FileWriter APIs. The most advanced browser in this sphere is Chrome: Google has always been innovative in implementing interfaces which eliminate the boundaries between Web applications and their desktop counterparts. As for other browsers, developers have to look for alternatives. Among client-side storage solutions are Web Storage API (a lightweight hash table-like storage model), Web SQL Database (a set of interfaces officially abandoned by W3C Consortium but still implemented in some A-grade browsers), and IndexedDB (a more complex database solution favored by W3C). This article will consider FileSystem and FileWriter interfaces as logical expansion of File and FileReader APIs.


FileSystem Request

Every file for itself, and browser for them all
Rewritten proverb

Virtual file system is constructed by the browser and kept within the scope of its sandbox. Web applications cannot reach local files outside the browser-supervised file storage. Further restrictions are imposed by the standard origin isolation and disk quota management. Write operations can be performed asynchronously, thus returning control to the main code without waiting for an operation to complete. Synchronous write is used within WebWorkers. Asynchronous writing will require a chain of success and error callbacks reflecting the flow of operation. We'll demonstrate some basic features of asynchronous file writing by solving a practical task: our application must fetch an image file from a remote server, then save the image locally. After that the application will read the image data and display it to the user.

Let's start. Write operation can be launched by a user's action, e.g. clicking a button:

<button onclick="fetchAndSaveImage()">get image</button>

First of all, our script will define global variables:

var fileName="image.png";
var directoryEntry;
var blob;

The directoryEntry is a new directory, which will be created to store the image. We'll call it 'MyImages'. The blob variable will hold the binary representation of the retrieved image. Now it's time to define the click event handler:

function fetchAndSaveImage(){
 webkitRequestFileSystem(0, 1024*1024, fsRequestCallback, function(fileError){alert("Request failed. Error code: "+fileError.code);});
}

The requestFileSystem function is the starting point in getting access to the virtual file system provided to our application by the browser. Chrome prefixes webkit to the standard function name. Other vendors may use their own prefixes or stick to the canonical designation when they implement FileSystem API in their user agents. The function accepts a number of arguments: file system type (0 denotes webkitStorageInfo.TEMPORARY, 1 is for webkitStorageInfo.PERSISTENT); the size required for the application (we have requested 1024*1024 bytes); success callback which can be created as an anonymous function or a separate named function (for demonstration purposes our callback is a separate fsRequestCallback function; error callback which is implemented here without any named identifier. File system type plays important role for the application logic: temporary storage is granted without asking the user for permission. However, the data placed in that storage can be deleted by the browser in case of necessity: e.g. Chrome's shared pool for temporary storage cannot be more than half of the available disk space, and the browser will delete recent data when the quota is exceeded. Each separate application requesting temporary storage can rely on up to 20% of the shared pool. Applications find out information about available disk space by using queryUsageAndQuota method:

window.webkitStorageInfo.queryUsageAndQuota(
 window.webkitStorageInfo.TEMPORARY,
 function(bytesUsed, currentQuota) { // success callback
  alert("Your application is using " + bytesUsed + " bytes.\nCurrent upper limit of the storage space is " + currentQuota+" bytes.");
 },
 function(storageInfoError) { // anonymous error callback
  alert("Quota information is unavailable");
 }
);

Persistent storage cannot be granted without explicit user permission. By default, the primary quota for persistent storage in Chrome is 0, so the webkitRequestFileSystem function call is not enough to get the actual disk space: application must ask for quota enlargement first.

window.webkitStorageInfo.requestQuota(
 window.webkitStorageInfo.PERSISTENT,
 1024*1024, // requested quota
 quotaEnlargedCallback, // success callback
 function(storageInfoError){ // anonymous error callback
  alert("Quota cannot be enlarged");
 }
);
function quotaEnlargedCallback(bytes){
 alert("Your quota is "+bytes+" bytes");
 window.webkitRequestFileSystem(1, bytes, fsRequestCallback, function(fileError){"Request failed. Error code: "+fileError.code});
}

Persistent data are not deleted by the browser, but can be deleted by the user through the browser UI. Unlike temporary storage, the persistent one can be further enlarged with the user's consent. In addition to temporary and persistent storage model Chrome also introduced a concept of unlimited storage. Unlimited storage is a Google's enrichment of FileSystem API, but this storage type is only available to Chrome extensions and hosted and installed Chrome apps. The unlimitedStorage permission must be included in the extension manifest. If storage preferences are not indicated in the code, Chrome allocates disk space from the shared pool of temporary storage.