In this tutorial I will introduce you to APIs using which you can work with the device filesystem. Filesystem APIs will help you create audio, images, cloud etc applications.
Cordova File API
There is actually no Intel XDK plugin/API to work with file system. But there is a cordova plugin called as File API plugin using which we can access the filesystem.
Before we continue exploring this API you need to included this plugin in your project. You can do this by select the File checkbox in you Intel XDK project dashboard.
Application Data Storage Directory
When an application is installed, mobile OS assigns an unique directory to the application where the app can store files. This directory is called application storage directory for that app.
Inside the application storage directory there are many other directories such as directory for storing code files, for storing permanent data, for storing cache data, for storing temporary data, data to be cloud synced etc. The name and path of these sub directories differ depending on the operating system.
Some operating systems also allow the app to store data outside the application storage directory i.e., SD card, shared directory etc.
DirectoryEntry and FileEntry
A DirectoryEntry object is a pointer to a directory. Similarly FileEntry is a pointer to a file.
A DirectoryEntry object can be used to create files/subdirectories. A DirectoryEntry object cannot access files/directories which is inside of an another directory i.e., it has access to its children but not children of children.
A FileEntry object can be used to write or read a file. We can get a FileEntry object for a file using its first parent’s DirectoryEntry object.
A DirectoryEntry object for a directory can retrieved using its first parent’s(i.e., nearest parent in the tree) DirectoryEntry object. DirectoryEntry for permanent or temporary directories in the application storage directory can be retrieved using FileSystem object.
function onDeviceReady()
{
//PERSISTENT indicates that any change we make to the file system now will be permanent.
requestFileSystem(LocalFileSystem.PERSISTENT, 0, onSuccess, onError);
}
//fileSystem object points to the complete mobile file system.
function onSuccess(fileSystem)
{
//fileSystem.root points to the application storage directory
var directoryEntry = fileSystem.root;
}
function onError(evt)
{
console.log("Error occurred during request to file system pointer. Error code is: " + evt.code);
}
LocalFileSystem.PERSISTENT indicates that any change we make to the application storage directory using application storage’s DirectoryEntry and its sub DirectoryEntry’s will be permanent. If we pass LocalFileSystem.TEMPORARY than any change we make to the application storage directory using application storage’s DirectoryEntry and its sub DirectoryEntry’s will be temporary i.e., will be reset once application is terminated.
Actually LocalFileSystem.PERSISTENT gets directoryEntry object pointing to the directory which stores permanent data. Similarly LocalFileSystem.TEMPORARY gets directoryEntry object pointing to the directory which stores temporary data. As I said earlier there are various different directories in the application storage and outside it. Here is an guide which shows how to get directoryEntry objects for other different directories and also explains how they behave.
Path of a File or Directory
There are two kinds of path to a file/directory i.e., filesytem path and URL. They both can be used alternative to each other.
DirectoryEntry object can return both types of paths of the directory its pointing to. Similarly FileEntry can return both types of paths of the file its pointing to.
Here is an example of retrieving path of application storage directory:
function onDeviceReady()
{
requestFileSystem(LocalFileSystem.PERSISTENT, 0, onSuccess, onError);
}
function onSuccess(fileSystem)
{
var directoryEntry = fileSystem.root;
//absolute fileSystem path to the application storage directory
console.log(directoryEntry.fullPath);
//web path(or URL) to the application storage directory
console.log(directoryEntry.toURL());
}
function onError(evt)
{
console.log("Error occurred during request to file system pointer. Error code is: " + evt.code);
}
Creating a File and Writing Data to it
We can create a file using DirectoryEntry object of its first parent directory. He can use the DirectoryEntry object retrieved above to create a file inside the application storage directory.
Once we create a file we will have a FileEntry object for that file passed as callback parameter. Using it we can write data to the file.
Here is the code to create a file:
function onDeviceReady()
{
requestFileSystem(LocalFileSystem.PERSISTENT, 0, onSuccess, onError);
}
function onSuccess(fileSystem)
{
var directoryEntry = fileSystem.root;
//lets create a file named readme.txt. getFile method actually creates a file and returns a pointer(FileEntry) if it doesn't exist otherwise just returns a pointer to it. It returns the file pointer as callback parameter.
directoryEntry.getFile("readme.txt", {create: true, exclusive: false}, function(fileEntry){
//lets write something into the file
fileEntry.createWriter(function(writer){
writer.write("This is the text inside readme file");
}, function(error){
console.log("Error occurred while writing to file. Error code is: " + error.code);
});
}, function(error){
console.log("Error occurred while getting a pointer to file. Error code is: " + error.code);
});
}
function onError(evt)
{
console.log("Error occurred during request to file system pointer. Error code is: " + evt.code);
}
You can write text or binary data to a file. In the above code we wrote textual data to a file. Here is a sample code on how you would write binary data to a file
var data = new ArrayBuffer(5),
dataView = new Int8Array(data);
for (i=0; i < 5; i++)
{
dataView[i] = i;
}
writer.write(data);
};
Read Contents of a File
Using FileEntry object we can read contents of a file. In the above code we saw how to get FileEntry object using DirectoryEntry object.
Here is the code to read content of the file we created
function onDeviceReady()
{
requestFileSystem(LocalFileSystem.PERSISTENT, 0, onSuccess, onError);
}
function onSuccess(fileSystem)
{
var directoryEntry = fileSystem.root;
directoryEntry.getFile("readme.txt", {create: true, exclusive: false}, function(fileEntry){
//lets read the content of the file.
fileEntry.file(function(file){
//FileReader object streams the content of the file from storage disk to app
var reader = new FileReader();
reader.onloadend = function (evt) {
//result property is string type if you read data as string. If you read data as array buffer then its assigned to a array buffer object.
console.log(evt.target.result);
};
//to read the content as binary use readAsArrayBuffer function.
reader.readAsText(file);
}, function(error){
console.log("Error occurred while readline file. Error code is: " + error.code);
});
}, function(error){
console.log("Error occurred while getting a pointer to file. Error code is: " + error.code);
});
}
function onError(evt)
{
console.log("Error occurred during request to file system pointer. Error code is: " + evt.code);
}
We can read file data as text or binary. In the above code we read textual data from file. Here is a sample code on how you would read binary data from a file
var reader = new FileReader();
reader.onloadend = function (evt) {
console.log(new Uint8Array(evt.target.result));
};
reader.readAsArrayBuffer(file);
};
Path of a File
We already saw a code example of getting path of a directory. Let’s see how to get the path of a file using FileEntry object.
function onDeviceReady()
{
requestFileSystem(LocalFileSystem.PERSISTENT, 0, onSuccess, onError);
}
function onSuccess(fileSystem)
{
var directoryEntry = fileSystem.root;
directoryEntry.getFile("readme.txt", {create: true, exclusive: false}, function(fileEntry){
//absolute fileSystem path to the readme.txt file
console.log(fileEntry.fullPath);
//web path(or URL) to the readme.txt file
console.log(fileEntry.toURL());
}, function(error){
console.log("Error occurred while getting a pointer to file. Error code is: " + error.code);
});
}
function onError(evt)
{
console.log("Error occurred during request to file system pointer. Error code is: " + evt.code);
}
Creating a Directory
We can create a directory using DirectoryEntry object. It can only create as a sub directory but not a directory as sub-sub-directory or at the same level as itself.
Here is the code to create a sub directory inside application storage directory
function onDeviceReady()
{
requestFileSystem(LocalFileSystem.PERSISTENT, 0, onSuccess, onError);
}
function onSuccess(fileSystem)
{
var directoryEntry = fileSystem.root;
//create a directory using getDirectory. If already exists it returns a pointer only.
directoryEntry.getDirectory("new_directory", {create: true, exclusive: false}, function(directoryEntry_1){
//for any operation inside this directory use directoryEntry_1 object.
}, function(error){
console.log("Error occurred while getting pointer to new directory. Error code is: " + error.code);
});
}
function onError(evt)
{
console.log("Error occurred during request to file system pointer. Error code is: " + evt.code);
}
Here DirectoryEntry to our newly created directory is passed as callback. This is how you can point to different directories using their parent DirectoryEntry to make changes in them.
Find Contents in a Directory
You can find the contents in a directory using its DirectoryEntry object.
Here is the code to find contents inside the application storage directory
function onDeviceReady()
{
requestFileSystem(LocalFileSystem.PERSISTENT, 0, onSuccess, onError);
}
function onSuccess(fileSystem)
{
var directoryEntry = fileSystem.root;
//object to read the contents of the directory
var directoryReader = directoryEntry.createReader();
//now read the contents using the readEntries function.
directoryReader.readEntries(function(entries){
var i;
for (i=0; i<entries.length; i++)
{
console.log(entries[i].name);
}
},function(error){
console.log("Failed to list directory contents. Error code is: " + error.code);
});
}
function onError(evt)
{
console.log("Error occurred during request to file system pointer. Error code is: " + evt.code);
}
Final Code
Here I have mixed all code into one snippet to show a workflow.
function onDeviceReady()
{
//PERSISTENT indicates that any change we make to the file system now will be permanent.
requestFileSystem(LocalFileSystem.PERSISTENT, 0, onSuccess, onError);
}
//fileSystem object points to the hard disk.
function onSuccess(fileSystem)
{
//fileSystem.root points to the application storage directory
var directoryEntry = fileSystem.root;
//absolute fileSystem path to the application storage directory
console.log(directoryEntry.fullPath);
//web path(or URL) to the application storage directory
console.log(directoryEntry.toURL());
//lets create a file named readme.txt. getFile method actually creates a file and returns a pointer(FileEntry) if it doesn't exist otherwise just returns a pointer to it. It returns the file pointer as callback parameter.
directoryEntry.getFile("readme.txt", {create: true, exclusive: false}, function(fileEntry){
//lets write something into the file
fileEntry.createWriter(function(writer){
writer.write("This is the text inside readme file");
}, function(error){
console.log("Error occurred while writing to file. Error code is: " + error.code);
});
//lets read the content of the file.
fileEntry.file(function(file){
var reader = new FileReader();
reader.onloadend = function (evt) {
//result property is string type if you read data as string. If you read data as array buffer then its assigned to a array buffer object.
console.log(evt.target.result);
};
//to read the content as binary use readAsArrayBuffer function.
reader.readAsText(file);
}, function(error){
console.log("Error occurred while readline file. Error code is: " + error.code);
});
}, function(error){
console.log("Error occurred while getting a pointer to file. Error code is: " + error.code);
});
//create a directory using getDirectory. If already exists it returns a pointer only.
directoryEntry.getDirectory("new_directory", {create: true, exclusive: false}, function(directoryEntry_1){
//for any operation inside this directory use directoryEntry_1 object.
}, function(error){
console.log("Error occurred while getting pointer to new directory. Error code is: " + error.code);
});
//object to read the contents of the directory
var directoryReader = directoryEntry.createReader();
//now read the contents using the readEntries function.
directoryReader.readEntries(function(entries){
var i;
for (i=0; i<entries.length; i++)
{
console.log(entries[i].name);
}
},function(error){
console.log("Failed to list directory contents. Error code is: " + error.code);
});
}
function onError(evt)
{
console.log("Error occurred during request to file system pointer. Error code is: " + evt.code);
}
Path of WWW Directory
WWW directory is also stored inside application storage directory. But the name of this directory after build is different. Therefore its difficult to find path to it. Instead you can use the below code to find the path of the www directory.
function getAbsolutePath() {
"use strict" ;
var path = window.location.pathname ;
path = path.substring( 0, path.lastIndexOf('/') ) ;
return path ;
}
//returns URL pointing to the www directory.
function getWebRoot() {
"use strict" ;
var path = window.location.href ;
path = path.substring( 0, path.lastIndexOf('/') ) ;
return path ;
}
Now you can create a file anywhere in the application storage and then move it to the www directory using FileEntry’s moveTo function.
Final Thoughts
You can refer to Cordova File API documentation to learn in depth about all methods and properties of the objects I mentioned. I tried to simplify everything so that you can get started with the API.