One of the major pieces of the eHow Earnings Tracker involves writing data to an XML file and reading it back later. Figuring out how to do file I/O in a Firefox add-on was not straightforward due to Mozilla’s wide array of Chrome APIs and spotty documentation.
Unfortunately, file I/O is not one of Mozilla’s new FUEL APIs. FUEL is a Javascript Library available to Firefox add-ons that is far easier to use than the older XPCOM API. It was introduced in Firefox 3.0 and Mozilla has been slowly adding functionality to it.
Starting with Mozilla’s file I/O code snippets was a quite confusing – the documentation meanders all over the place showing bits and pieces of code without explaining anything in much depth. On top of that, they suggest that you use the io.js wrappers at the beginning of the page but then none of the examples shown use it.
Basically what it comes down to is that file I/O is done by using the XPCOM interfaces nsIFile and nsILocalFile. The io.js wrappers are utility functions used to encapsulate the tedious syntax needed to use them. I don’t fully understand the purpose of everything in io.js but I will show you what I did figure out and ultimately use in the tracker implementation.
The first thing that I needed to do was get a hold of the current Firefox profile directory since that’s where I wanted to store my data. I chose the current profile directory because I wanted to be able to support multiple Firefox profiles using the tracker without overwriting each other’s data.
I wound up with the following function to do it:
var getProfileDir = function()
{
var dir = DirIO.get('ProfD');
if(!dir || !dir.exists())
{
throw Error("Failed to open profile directory");
}
return dir;
};
What this code does is create an nsIFile object that represents the profile directory. ‘ProfD’ is a special string that refers to the profile directory. You can see a list of the supported strings in the file I/O code snippets. If you want to open a specific directory path, use DirIO.open() instead of get().
Once you have an nsIFile object, you can do a bunch of things with it. The following code snippet will try to open a file in the profile directory and create it if it doesn’t already exist:
var openEarningsBackupFile = function(username)
{
var currentFile = getProfileDir();
currentFile.append(username + ".xml");
if(!currentFile.exists())
{
if(!FileIO.create(currentFile))
{
throw Error("Failed to create earnings backup file");
}
}
return currentFile;
}
The append function of the nsIFile object lets you navigate a file hierarchy one level at a time. You could go down multiple levels like so:
currentFile.append("path1");
currentFile.append("path2");
currentFile.append("file.xml");
This would navigate to [currentFile.path]\path1\path2\file.xml. The create() function will create any path segments that don’t already exist.
Keep in mind that the append function modifies the calling object so if you want to open up two different files in a particular directory then the easiest way to do it is to create an nsIFile object that points to the directory and then clone it:
var file1 = getProfileDir();
var file2 = file1.clone();
file1.append("file1.xml");
file2.append("file2.xml");
Once you have an nsIFile object pointed to an existing file you can read from and write to it. The following copy function shows how to do reads and writes with nsIFile objects using the io.js wrappers:
// srcFile and destFile should be nsIFile objects
var copyFile = function(srcFile, destFile)
{
var srcText = FileIO.read(srcFile);
if(!srcText)
{
throw Error("Failed to read " + srcFile.path);
}
if(!FileIO.write(destFile, srcText))
{
throw Error("Failed to copy " + srcFile.path + " to " + destFile.path);
}
};
Another not-so-obvious thing to figure out was how to get the path where the add-on is installed. After quite a bit of searching, I came up with this bit of code:
var getExtensionDir = function()
{
var dir = Components.classes["@mozilla.org/extensions/manager;1"].getService(Components.interfaces.nsIExtensionManager).getInstallLocation(m_extensionId).getItemLocation(m_extensionId);
if(!dir)
{
throw Error("Failed to get extension installation directory");
}
return dir;
};
File I/O in Firefox add-ons is pretty easy to do once you see how all of the pieces fit together.