DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports Events Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
Zones
Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones AWS Cloud
by AWS Developer Relations
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones
AWS Cloud
by AWS Developer Relations
The Latest "Software Integration: The Intersection of APIs, Microservices, and Cloud-Based Systems" Trend Report
Get the report
  1. DZone
  2. Data Engineering
  3. Databases
  4. PhoneGap's File API

PhoneGap's File API

Raymond Camden user avatar by
Raymond Camden
·
Mar. 10, 12 · Interview
Like (0)
Save
Tweet
Share
16.73K Views

Join the DZone community and get the full member experience.

Join For Free

This week I had the opportunity to record a few videos on PhoneGap for Adobe TV. One of the videos covered the File API and since it was a bit difficult I figured I'd share my findings, and sample code, with others.

My main struggle with the File API was trying to wrap my head around how it worked. The docs weren't entirely clear to me and were a bit confusing. Turns out there's a good reason for that. (Although I'm working to improve the docs.) PhoneGap's File API is really an implementation of the W3 File API. The PhoneGap docs mention something similar in the database area so it makes sense for the File docs to be updated as well. (And as I said - I'm working on that. I did my first pull request to add just such a mention.)

After I figured that out, I then found an incredibly useful article on the File API over at HTML5 Rocks: Exploring the Filesystem APIs. I encourage everyone to read over Eric Bidelman's article. He's got examples for pretty much every part of the API.

At a high level, working with the File API comes down to a few basic concepts:

  • First, you request a file system. You can ask for either a persistent or temporary file system. On the desktop, these both point to a sandboxed folder. On PhoneGap, your access is a bit broader, essentially the entire storage system.
  • The API supports basic "CRUD" operations for both files and folders.
  • The API supports reading and writing to files, both binary and plain text.
  • Probably the most difficult aspect (well, not difficult, just a bit unwieldy), is that each and every operation is asynchronous. So to get and read a file involves about 3 or 4 levels of callbacks.

For my Adobe TV video, I built a simple application that demonstrates some of these principles. I began with a few simple buttons that would let me test basic file operations:

In order to do anything, I need access to the file system, and this needs to be done after PhoneGap fires the deviceready event:

function onDeviceReady() {

    //request the persistent file system
    window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, onFSSuccess, onError);
    
}

function init() {
    document.addEventListener("deviceready", onDeviceReady, true);
} 

If the file system is loaded, onFSSuccess will handle storing a pointer to it while also setting up my event handlers:

function onFSSuccess(fs) {
    fileSystem = fs;

    getById("#dirListingButton").addEventListener("touchstart",doDirectoryListing);            
    getById("#addFileButton").addEventListener("touchstart",doAppendFile);            
    getById("#readFileButton").addEventListener("touchstart",doReadFile);            
    getById("#metadataFileButton").addEventListener("touchstart",doMetadataFile);            
    getById("#deleteFileButton").addEventListener("touchstart",doDeleteFile);            
    
    logit( "Got the file system: "+fileSystem.name +"<br/>" +
                                    "root entry name is "+fileSystem.root.name + "<p/>")    

    doDirectoryListing();
} 

As a quick aside, getById is simply a wrapper for document.getElementById. (Trying to reduce my dependency on jQuery.) Our fileSystem object has a few properties we can display, like the name for example. It also has a root property which is a pointer to the root directory. (Duh.) The logit function is simply appending to a DIV on the HTML page as a quick debugging technique.

This event handler then fires off doDirectoryListing. This is normally run by the "Show Directory Contents" button but I automatically run it after the file system is opened.

function gotFiles(entries) {
    var s = "";
    for(var i=0,len=entries.length; i<len; i++) {
        //entry objects include: isFile, isDirectory, name, fullPath
        s+= entries[i].fullPath;
        if (entries[i].isFile) {
            s += " [F]";
        }
        else {
            s += " [D]";
        }
        s += "<br/>";
        
    }
    s+="<p/>";
    logit(s);
}

function doDirectoryListing(e) {
    //get a directory reader from our FS
    var dirReader = fileSystem.root.createReader();

    dirReader.readEntries(gotFiles,onError);        
}

Reading bottom to top, the event handler starts off by creating a reader object off the root property of the file system object. To get the files, you simple call readEntries, and use a callback to handle the result. The entries (which can be files or directories) are a simple array of objects. Here's an example of the output:

So what about file reading and writing? Opening a file is simple. You can simply run getFile(name) and the API can (if you want) also create the file if it doesn't exist. This simplifies things a bit. Here's the event handler and call back for clicking "Creating/Append to Test File".

function appendFile(f) {

    f.createWriter(function(writerOb) {
        writerOb.onwrite=function() {
            logit("Done writing to file.<p/>");
        }
        //go to the end of the file...
        writerOb.seek(writerOb.length);
        writerOb.write("Test at "+new Date().toString() + "\n");
    })

}

function doAppendFile(e) {
    fileSystem.root.getFile("test.txt", {create:true}, appendFile, onError);
} 

Again - please read up from bottom to top. You can see the use of getFile here along with the options after it to ensure an error won't be thrown if it doesn't exist. Appending to a file is done by creating a writer object. Do note - and I screwed this up myself - if you don't seek to the end of the file you'll actually overwrite data as opposed to appending. Now let's look at reading:

function readFile(f) {
    reader = new FileReader();
    reader.onloadend = function(e) {
        console.log("go to end");
        logit("<pre>" + e.target.result + "</pre><p/>");
    }
    reader.readAsText(f);
}

function doReadFile(e) {
    fileSystem.root.getFile("test.txt", {create:true}, readFile, onError);
} 

As before, we begin by opening the file, and in the success callback, create a FileReader object. You can read text or binary data depending on your needs. In this example our content is all text so we readAsText and in that callback append it to our div.

Now let's look at metadata. This method doesn't return a lot of data - just the modification date of the file/directory.

function metadataFile(m) {
    logit("File was last modified "+m.modificationTime+"<p/>");    
}

function doMetadataFile(e) {
    fileSystem.root.getFile("test.txt", {create:true}, function(f) {
        f.getMetadata(metadataFile,onError);
    }, onError);
} 

Finally - let's look at the delete operation:

function doDeleteFile(e) {
    fileSystem.root.getFile("test.txt", {create:true}, function(f) {
        f.remove(function() {
            logit("File removed<p/>");
        });
    }, onError);
} 

I hope these examples make sense. If it isn't obvious, I slightly tweaked my style as I built each of the sections. Sometimes I wrote the callbacks within the API calls and sometimes I did it separately. I've included the full code below as well as an APK for those of you who want to test on Android.

<!DOCTYPE HTML>
<html>

<head>
<meta name="viewport" content="width=320; user-scalable=no" />
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<title>Minimal AppLaud App</title>

<script type="text/javascript" charset="utf-8" src="phonegap-1.4.1.js"></script>
<script type="text/javascript" charset="utf-8">
var fileSystem;

//generic getById
function getById(id) {
    return document.querySelector(id);
}
//generic content logger
function logit(s) {
    getById("#content").innerHTML += s;
}

//generic error handler
function onError(e) {
    getById("#content").innerHTML = "<h2>Error</h2>"+e.toString();
}

function doDeleteFile(e) {
    fileSystem.root.getFile("test.txt", {create:true}, function(f) {
        f.remove(function() {
            logit("File removed<p/>");
        });
    }, onError);
}

function metadataFile(m) {
    logit("File was last modified "+m.modificationTime+"<p/>");    
}

function doMetadataFile(e) {
    fileSystem.root.getFile("test.txt", {create:true}, function(f) {
        f.getMetadata(metadataFile,onError);
    }, onError);
}

function readFile(f) {
    reader = new FileReader();
    reader.onloadend = function(e) {
        console.log("go to end");
        logit("<pre>" + e.target.result + "</pre><p/>");
    }
    reader.readAsText(f);
}

function doReadFile(e) {
    fileSystem.root.getFile("test.txt", {create:true}, readFile, onError);
}

function appendFile(f) {

    f.createWriter(function(writerOb) {
        writerOb.onwrite=function() {
            logit("Done writing to file.<p/>");
        }
        //go to the end of the file...
        writerOb.seek(writerOb.length);
        writerOb.write("Test at "+new Date().toString() + "\n");
    })

}

function doAppendFile(e) {
    fileSystem.root.getFile("test.txt", {create:true}, appendFile, onError);
}

function gotFiles(entries) {
    var s = "";
    for(var i=0,len=entries.length; i<len; i++) {
        //entry objects include: isFile, isDirectory, name, fullPath
        s+= entries[i].fullPath;
        if (entries[i].isFile) {
            s += " [F]";
        }
        else {
            s += " [D]";
        }
        s += "<br/>";
        
    }
    s+="<p/>";
    logit(s);
}

function doDirectoryListing(e) {
    //get a directory reader from our FS
    var dirReader = fileSystem.root.createReader();

    dirReader.readEntries(gotFiles,onError);        
}

function onFSSuccess(fs) {
    fileSystem = fs;

    getById("#dirListingButton").addEventListener("touchstart",doDirectoryListing);            
    getById("#addFileButton").addEventListener("touchstart",doAppendFile);            
    getById("#readFileButton").addEventListener("touchstart",doReadFile);            
    getById("#metadataFileButton").addEventListener("touchstart",doMetadataFile);            
    getById("#deleteFileButton").addEventListener("touchstart",doDeleteFile);            
    
    logit( "Got the file system: "+fileSystem.name +"<br/>" +
                                    "root entry name is "+fileSystem.root.name + "<p/>")    

    doDirectoryListing();
}

function onDeviceReady() {

    //request the persistent file system
    window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, onFSSuccess, onError);
    
}

function init() {
    document.addEventListener("deviceready", onDeviceReady, true);
}
</script>

<style>
button { width: 100%; padding: 5px; }
</style>
</head>

<body onload="init();" id="stage" class="theme">

<button id="addFileButton">Create/Append to Test File</button>
<button id="readFileButton">Read Test File</button>
<button id="metadataFileButton">Get Test File Metadata</button>
<button id="deleteFileButton">Delete Test File</button>
<button id="dirListingButton">Show Directory Contents</button>

<div id="content"></div>

</body>
</html>

 

API File system

Published at DZone with permission of Raymond Camden, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Popular on DZone

  • Reliability Is Slowing You Down
  • When Should We Move to Microservices?
  • How We Solved an OOM Issue in TiDB with GOMEMLIMIT
  • OpenVPN With Radius and Multi-Factor Authentication

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com
  • +1 (919) 678-0300

Let's be friends: