2010/08/08

Compressing a folder with Firefox nsIZipWriter interface

Writing an extension in Firefox is usually done in Javascript, but instead of using just the classic features available to a web page, there are many others that are exclusive to the inner workings.

One of those features introduced in Firefox 3 is nsIZipWriter; with this interface it's possible for an extension to create or modify a zip archive without the need of any external program.

And that looked perfect for some code that I was planning so I looked at it and after reading the docs page and a little testing I created this zipFolder function that takes as arguments a nsFile object that points to the folder that you want to compress, and the second argument is a callback function.

It takes the contents of that folder (including subfolders thanks to the addFolderContentsToZip recursive function) and compress them in a temporary file that you can use afterwards as it's the parameter passed to the callback function.

The only "problem" is that there is no progress notification about the task, I've configured it to do the job asynchronously (that's why it uses a callback to be notified of the end of the task), but that doesn't allow to set any progress listener or use an interval to check for the number of bytes processed.

/* Zipping functions */
const PR_RDONLY      = 0x01;
const PR_WRONLY      = 0x02;
const PR_RDWR        = 0x04;
const PR_CREATE_FILE = 0x08;
const PR_APPEND      = 0x10;
const PR_TRUNCATE    = 0x20;
const PR_SYNC        = 0x40;
const PR_EXCL        = 0x80;

/**
* folder is a nsFile pointing to a folder
* callback is a function that it's called after the zip is created. It has one parameter: the nsFile created
*/
function zipFolder(folder, callback)
{
 // get TMP directory  
 var nsFile = Cc["@mozilla.org/file/directory_service;1"].  
   getService(Ci.nsIProperties).  
   get("TmpD", Ci.nsIFile); 

 // Create a new file
 nsFile.append( folder.leafName  + ".zip");
 nsFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0666);  
 
 var zipWriter = Components.Constructor("@mozilla.org/zipwriter;1", "nsIZipWriter");
 var zipW = new zipWriter();
 
 zipW.open(nsFile, PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE);
 
 addFolderContentsToZip(zipW, folder, "");
 
 // We don't want to block the main thread, so the zipping is done asynchronously
 // and here we get the notification that it has finished
 var observer = {
  onStartRequest: function(request, context) {},
  onStopRequest: function(request, context, status)
  {
   zipW.close();
   // Notify that we're done. 
   callback(nsFile);
  }
 }

 zipW.processQueue(observer, null);
}

/**
* function to add the contents of a folder recursively
* zipW a nsIZipWriter object
* folder a nsFile object pointing to a folder
* root a string defining the relative path for this folder in the zip
*/
function addFolderContentsToZip(zipW, folder, root)
{
 var entries = folder.directoryEntries;  
 while(entries.hasMoreElements())  
 {  
  var entry = entries.getNext();  
  entry.QueryInterface(Ci.nsIFile);  
  zipW.addEntryFile(root + entry.leafName, Ci.nsIZipWriter.COMPRESSION_DEFAULT, entry, true);
  if (entry.isDirectory())
   addFolderContentsToZip(zipW, entry, root + entry.leafName + "/");
 } 
}

Hope you find it useful, it's mostly putting together some sample code and doing some tests, but if you are trying to do it and have some problem it's usually nice to find some code that works as it might be the hint to understand your problem.

3 comments:

myuceel said...

Thats perfect. Thanks

Cancerbero said...

Thank you very much for this code. no more 7za binaries for window sand linux in my firefox extensions!!

Alfonso Martínez de Lizarrondo said...

Yeah, not having to use additional binaries is good.

I'm glad that both of you have found the code useful :-)