Visit Mozilla.org

Code snippets:File I/O

From MDC

This article describes local file input/output in chrome JavaScript.

You access the file system using Mozilla XPCOM components. The list of components used for local I/O is available at XUL Planet.

Contents

[edit] Available libraries

There are a few JavaScript wrappers for I/O XPCOM components. See JSLib and io.js (original by MonkeeSage). The io.js module is much smaller and very easy to use (simple examples are included in the module).

[edit] Creating a file object ("opening" files)

var file = Components.classes["@mozilla.org/file/local;1"]
                     .createInstance(Components.interfaces.nsILocalFile);
file.initWithPath("/home");

NOTE: the path passed to initWithPath() should be in "native" form (e.g. "C:\\Windows"). If you need to use file:// URIs as initializers, see discussion of nsIFileProtocolHandler below.

NOTE: initWithPath() / initWithFile() functions don't throw an exception if the specified file does not exist. An exception is thrown when methods that require the file existence are called, e.g. isDirectory(), moveTo(), etc.

[edit] Getting special files

// get profile directory
var file = Components.classes["@mozilla.org/file/directory_service;1"]
                     .getService(Components.interfaces.nsIProperties)
                     .get("ProfD", Components.interfaces.nsIFile);

// NOTE: "file" is an object that implements nsIFile. If you want the
// file system path, use file.path

Here are some strings you can put in place of "ProfD" (stolen from MonkeeSage's I/O module comments):

String Meaning
ProfD profile directory
DefProfRt user (e.g., /root/.mozilla)
UChrm %profile%/chrome
DefRt %installation%/defaults
PrfDef %installation%/defaults/pref
ProfDefNoLoc %installation%/defaults/profile
APlugns %installation%/plugins
AChrom %installation%/chrome
ComsD %installation%/components
CurProcD installation (usually)
Home OS root (e.g., /root)
TmpD OS tmp (e.g., /tmp)
ProfLD Local Settings on windows; where the network cache and fastload files are stored
resource:app application directory in a XULRunner app
DeskP Windows desktop (e.g,. C:\Documents and Settings\username\Desktop)
Progs User start menu programs directory (e.g., C:\Documents and Settings\username\Start Menu\Programs)

Look in the Source for other strings available: xpcom/io/nsAppDirectoryServiceDefs.h, xpcom/io/nsDirectoryServiceDefs.h.

[edit] Getting your extension's folder

NOTE: This will only work in Firefox/Thunderbird 1.5+, not 1.0.

To get the directory that an extension is installed in, use an XPCOM component written in JavaScript. The special __LOCATION__ builtin will return an nsIFile of the component file:

extensiondir/component/GetExtensionDirectory.js:

var componentFile = __LOCATION__;
var componentsDir = componentFile.parent;
var extensionDir = componentsDir.parent;

[edit] Creating Folders

To create a folder, use nsIFile.create():

var file = Components.classes["@mozilla.org/file/directory_service;1"]
                     .getService(Components.interfaces.nsIProperties)
                     .get("ProfD", Components.interfaces.nsIFile);
file.append("DIR");
if( !file.exists() || !file.isDirectory() ) {   // if it doesn't exist, create
   file.create(Components.interfaces.nsIFile.DIRECTORY_TYPE, 0777);
}

The above example creates a folder called "DIR" in the Profile folder. For more information, refer to the nsIFile.create reference.

[edit] Creating temporary files

To create a temporary file, use nsIFile.createUnique():

var file = Components.classes["@mozilla.org/file/directory_service;1"]
                     .getService(Components.interfaces.nsIProperties)
                     .get("TmpD", Components.interfaces.nsIFile);
file.append("suggestedName.tmp");
file.createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0666);
// do whatever you need to the created file
alert(file.path);

[edit] User input via nsIFilePicker

The file picker component (nsIFilePicker) can be used to open standard Open / Save dialogs. The components returns user-specified file as nsIFile.

[edit] nsIFile and path strings

You can use nsIFile.path to get platform-specific path string, e.g. "C:\Windows\System32" or "/usr/share".

If you want to get a file:// URL of a file or an nsIFile from file:// URL, you need to use nsIFileProtocolHandler:

// file is nsIFile
var ios = Components.classes["@mozilla.org/network/io-service;1"]
                    .getService(Components.interfaces.nsIIOService);
var fileHandler = ios.getProtocolHandler("file")
                     .QueryInterface(Components.interfaces.nsIFileProtocolHandler);
var URL = fileHandler.getURLSpecFromFile(file);

To load from file://, http://, chrome://, resource:// and other URLs directly, use XMLHttpRequest or nsIChannel (example).

Also note that generally you don't need to use nsIFile::path. Use nsIFile directly wherever possible. An example below shows how you should save a path in user prefs.

[edit] Storing nsILocalFile in preferences

The following two snippets show the right way to store a file path in user preferences (more about preferences in Mozilla):

[edit] Absolute path (nsILocalFile)

To store arbitrary path in user preferences, use this code.

// |file| is nsILocalFile
// 1. Write path to prefs
var prefs = Components.classes["@mozilla.org/preferences-service;1"]
                      .getService(Components.interfaces.nsIPrefService)
                      .getBranch("extensions.myext.");
prefs.setComplexValue("filename", Components.interfaces.nsILocalFile, file);

// 2. Read path from prefs
var file = prefs.getComplexValue("filename", Components.interfaces.nsILocalFile);

[edit] Relative path (nsIRelativeFilePref)

To store paths relative to one of the predefined folders listed above, for example file relative to profile folder, use the following code:

// 1. Write to prefs
var relFile = Components.classes["@mozilla.org/pref-relativefile;1"]
                        .createInstance(Components.interfaces.nsIRelativeFilePref);
relFile.relativeToKey = "ProfD"; // or any other string listed above
relFile.file = file;             // |file| is nsILocalFile
prefs.setComplexValue("filename", 
     Components.interfaces.nsIRelativeFilePref, relFile);

// 2. Read from prefs
var value = prefs.getComplexValue("filename", 
     Components.interfaces.nsIRelativeFilePref);
// |value.file| is the file.

[edit] Navigating with nsIFile

[edit] Get a file in given directory

Assume, file is an nsIFile pointing to some directory (f.e. a user profile directory). You can use file.append("myfile.txt"); to make file point to myfile.txt inside that directory.

NOTE: avoid using dir.path+"\\"+"myfile.txt", as it is not cross-platform at all. Using something like ((path.search(/\\/) != -1) ? path + "\\" : path + "/") + "myfile.txt"; is possible, but nsIFile.append() is much easier to read and is guaranteed to work on all platforms Mozilla itself works.

[edit] Enumerating files in given directory

The snippet below makes an array of nsIFiles corresponding to sub-directories/"sub-files" of given directory. You can tell files from folders by calling nsIFile.isDirectory() and nsIFile.isFile() methods on each entry.

// file is the given directory (nsIFile)
var entries = file.directoryEntries;
var array = [];
while(entries.hasMoreElements())
{
  var entry = entries.getNext();
  entry.QueryInterface(Components.interfaces.nsIFile);
  array.push(entry);
}

[edit] Reading from a file

NOTE: The sample code below does not handle text with non-ASCII characters. See Reading textual data details on how to read text from other character sets.

[edit] Simple

// |file| is nsIFile
var data = "";
var fstream = Components.classes["@mozilla.org/network/file-input-stream;1"]
                        .createInstance(Components.interfaces.nsIFileInputStream);
var sstream = Components.classes["@mozilla.org/scriptableinputstream;1"]
                        .createInstance(Components.interfaces.nsIScriptableInputStream);
fstream.init(file, -1, 0, 0);
sstream.init(fstream); 

var str = sstream.read(4096);
while (str.length > 0) {
  data += str;
  str = sstream.read(4096);
}

sstream.close();
fstream.close();
alert(data);

[edit] Line by line

// open an input stream from file
var istream = Components.classes["@mozilla.org/network/file-input-stream;1"]
                        .createInstance(Components.interfaces.nsIFileInputStream);
istream.init(file, 0x01, 0444, 0);
istream.QueryInterface(Components.interfaces.nsILineInputStream);

// read lines into array
var line = {}, lines = [], hasmore;
do {
  hasmore = istream.readLine(line);
  lines.push(line.value); 
} while(hasmore);

istream.close();

// do something with read data
alert(lines);

[edit] Asynchronously

This will allow you to read a file without locking up the UI thread.

// |file| is nsIFile
var ios = Components.classes["@mozilla.org/network/io-service;1"]
                    .getService(Components.interfaces.nsIIOService);
var fileURI = ios.newFileURI(file);
var channel = ios.newChannelFromURI(fileURI);
var observer = {
  onStreamComplete : function(aLoader, aContext, aStatus, aLength, aResult)
  {
    alert(aResult);
  }
};
var sl = Components.classes["@mozilla.org/network/stream-loader;1"]
                   .createInstance(Components.interfaces.nsIStreamLoader);
sl.init(channel, observer, null);

[edit] Binary File

For instance, to get the data in a PNG file:

var ios = Components.classes["@mozilla.org/network/io-service;1"]
                    .getService(Components.interfaces.nsIIOService);
var url = ios.newURI(aFileURL, null, null);

if (!url || !url.schemeIs("file")) throw "Expected a file URL.";

var pngFile = Components.classes["@mozilla.org/file/local;1"]
                        .createInstance(Components.interfaces.nsILocalFile);
pngFile.initWithPath(url.path);

var istream = Components.classes["@mozilla.org/network/file-input-stream;1"]
                        .createInstance(Components.interfaces.nsIFileInputStream);
istream.init(pngFile, -1, -1, false);

var bstream = Components.classes["@mozilla.org/binaryinputstream;1"]
                        .createInstance(Components.interfaces.nsIBinaryInputStream);
bstream.setInputStream(istream);

var bytes = bstream.readBytes(bstream.available());

[edit] Writing to a file

NOTE: This code is not intl-aware. Writing non-ASCII characters will not work correctly. See Writing textual data for intl-aware code.

// file is nsIFile, data is a string
var foStream = Components.classes["@mozilla.org/network/file-output-stream;1"]
                         .createInstance(Components.interfaces.nsIFileOutputStream);

// use 0x02 | 0x10 to open file for appending.
foStream.init(file, 0x02 | 0x08 | 0x20, 0666, 0); 
// write, create, truncate
// In a c file operation, we have no need to set file mode with or operation,
// directly using "r" or "w" usually.
foStream.write(data, data.length);
foStream.close();

Flags parameter to the nsIFileOutputStream::init() function (For more information refer to nsprpub/pr/include/prio.h).

flags: The file status flags. It is a bitwise OR of the following bit flags (only one of the first three flags below may be used):

Name Value Description
PR_RDONLY 0x01 Open for reading only.
PR_WRONLY 0x02 Open for writing only.
PR_RDWR 0x04 Open for reading and writing.
PR_CREATE_FILE 0x08

If the file does not exist, the file is created. If the file exists, this flag has no effect.

PR_APPEND 0x10

The file pointer is set to the end of the file prior to each write.

PR_TRUNCATE 0x20

If the file exists, its length is truncated to 0.

PR_SYNC 0x40

If set, each write will wait for both the file data and file status to be physically updated.

PR_EXCL 0x80

With PR_CREATE_FILE, if the file does not exist, the file is created. If the file already exists, no action and NULL is returned.

Note that the strings in the Name column of this table cannot be used in Javascript code. You have to use the octal constants.

[edit] Writing a Binary File

For example, here we can write PNG data to a file.

// pngBinary already exists
var aFile = Components.classes["@mozilla.org/file/local;1"]
                      .createInstance(Components.interfaces.nsILocalFile);

aFile.initWithPath( "/tmp/mypicture.png" );
aFile.createUnique( Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 600);
            
var stream = Components.classes["@mozilla.org/network/safe-file-output-stream;1"]
                       .createInstance(Components.interfaces.nsIFileOutputStream);
stream.init(aFile, 0x04 | 0x08 | 0x20, 0600, 0); // write, create, truncate
            
stream.write(pngBinary, pngBinary.length);
if (stream instanceof Components.interfaces.nsISafeOutputStream) {
    stream.finish();
} else {
    stream.close();
}

[edit] Copy a stream to a file

This function returns a basic stream listener that copies all of the data from an input stream to a file. Takes an nsIFile as an argument, which is assumed to be open and created; depending on what you're doing, you might prefer to create the file in onStartRequest and close it in onStopRequest.

function StreamToFile(file)
{
    var output = Components.classes["@mozilla.org/network/file-output-stream;1"]
                           .createInstance(Components.interfaces.nsIFileOutputStream);
    var buffer = Components.classes["@mozilla.org/network/buffered-output-stream;1"]
                           .createInstance(Components.interfaces.nsIBufferedOutputStream);
    output.init(file, 0x02 | 0x08 | 0x20, 0664, null);
    buffer.init(output, 8192);

    return {
        onStartRequest: function(request, context) { },

        onDataAvailable: function onDataAvailable(request, context, stream, offset, count)
        {
            while(count > 0)
                count -= buffer.writeFrom(stream, count);
        },

        onStopRequest: function(request, context) { }
    };
}

[edit] More

There are more methods and properties on nsIFile and nsILocalFile interfaces, please refer to documentation on XUL Planet. Those methods/properties are mostly self-explanatory, so we haven't included examples of using them here.