Mozdev
mozilla/verbosio/core/modules/FileCommon.jsm
author Alex Vincent@SkyfireDEMO1.gateway.2wire.net
Tue Sep 02 22:03:06 2008 -0700 (17 months ago)
changeset 24 64a17b131ade
parent 236da8b043b6d3
child 35906308f72b85
permissions -rw-r--r--
Fix FileCommon.jsm to pass its xpcshell test... and fix the test, too.
     1 const C_i = Components.interfaces;
     2 const C_r = Components.results;
     3 const C_c = Components.classes;
     4 const C_C = Components.Constructor;
     5 const C_E = Components.Exception;
     6 
     7 function dumpJSStack() {
     8   dump((new Error).stack + "\n");
     9 }
    10 
    11 function ERROR_FAILURE() {
    12   dumpJSStack();
    13   throw new C_E("Failure", C_r.NS_ERROR_FAILURE);
    14 }
    15 
    16 function ERROR_ABORT() {
    17   dumpJSStack();
    18   throw new C_E("Aborted", C_r.NS_ERROR_ABORT);
    19 }
    20 
    21 function NOT_AVAILABLE() {
    22   //dumpJSStack();
    23   throw new C_E("Not available", C_r.NS_ERROR_NOT_AVAILABLE);
    24 }
    25 
    26 function ERROR_UNEXPECTED() {
    27   dumpJSStack();
    28   throw new C_E("Assertion failure", C_r.NS_ERROR_UNEXPECTED);
    29 }
    30 
    31 function INVALID_ARG() {
    32   dumpJSStack();
    33   throw new C_E("Invalid argument", C_r.NS_ERROR_INVALID_ARG);
    34 }
    35 
    36 const File = C_C("@mozilla.org/file/local;1",
    37                  C_i.nsILocalFile,
    38                  "initWithPath");
    39 
    40 const InputStream = C_C("@mozilla.org/network/file-input-stream;1",
    41                         C_i.nsIFileInputStream,
    42                         "init");
    43 const SInputStream = C_C("@mozilla.org/scriptableinputstream;1",
    44                          C_i.nsIScriptableInputStream,
    45                          "init");
    46 const OutputStream = C_C("@mozilla.org/network/file-output-stream;1",
    47                          C_i.nsIFileOutputStream,
    48                          "init");
    49 var ZipReader = C_C("@mozilla.org/libjar/zip-reader;1",
    50                     "nsIZipReader",
    51                     "open");
    52 
    53 function readFromStream(aInputStream) {
    54   var sInStream = new SInputStream(aInputStream);
    55 
    56   var contents = "";
    57   try {
    58     while (sInStream.available()) {
    59       contents += sInStream.read(sInStream.available());
    60     }
    61   } catch (e) {
    62     dumpJSStack();
    63     throw e;
    64   }
    65   sInStream.close();
    66   return contents;
    67 }
    68 
    69 function sortEntries(a, b) {
    70   if (a.leafName < b.leafName)
    71     return -1;
    72   if (b.leafName < a.leafName)
    73     return +1;
    74   return 0;
    75 }
    76 
    77 const ZipTypesMap = /.(zip|jar|xpi)$/;
    78 
    79 function ZipEntryKey(args) {
    80   this._keySequence = args;
    81 }
    82 ZipEntryKey.prototype = {
    83   equals: function(other) {
    84     if (other._keySequence.length != this._keySequence.length)
    85       return false;
    86     for (var i = 0; i < this._keySequence.length; i++)
    87       if (this._keySequence[i] != other._keySequence[i])
    88         return false;
    89   
    90     return true;
    91   }
    92 };
    93 
    94 const ZipMap = {
    95   _data: [
    96     /* key = ["local file path", "zip path 1", "zip path 2"...]
    97        value = nsILocalFile for the zip archive.
    98   
    99        We use this to bootstrap in the case of a zip inside a zip (think JAR
   100        files inside XPI files).  We'll hold a reference to a temporary copy of
   101        the inner zip file.
   102     */
   103   ],
   104 
   105   getZip: function getZip(aKeySequence, aLength) {
   106     ShutdownObserver.checkShutdown();
   107     if (aKeySequence.length < 2 || aLength < 2)
   108       ERROR_UNEXPECTED();
   109 
   110     var keyTeeth = [];
   111     for (var i = 0; i < aLength; i++)
   112       keyTeeth[i] = aKeySequence[i];
   113     var key = new ZipEntryKey(keyTeeth);
   114 
   115     for (i = 0; i < this._data.length; i++)
   116       if (key.equals(this._data[i].key))
   117         return this._data.file;
   118 
   119     var temp = C_c["@mozilla.org/file/directory_service;1"]
   120                   .getService(C_i.nsIProperties)
   121                   .get("TmpD", C_i.nsIFile).clone();
   122     temp.append("verbosio");
   123     temp.append("zip-entries.zip");
   124     temp.createUnique(C_i.nsIFile.NORMAL_FILE_TYPE, 0666);
   125 
   126     var lastTooth = keyTeeth.pop();
   127     var parentZip;
   128     if (keyTeeth.length == 1)
   129       parentZip = new File(keyTeeth[0]);
   130     else
   131       parentZip = this.getZip(keyTeeth, keyTeeth.length).file;
   132     if (!parentZip)
   133       ERROR_UNEXPECTED();
   134 
   135     var zipReader = new ZipReader(parentZip);
   136     zipReader.extract(lastTooth, temp);
   137     zipReader.close();
   138 
   139     var dataObj = {
   140       key: key,
   141       file: temp
   142     };
   143 
   144     this._data.unshift(dataObj);
   145     return temp;
   146   },
   147 
   148   shutdown: function shutdown() {
   149     for each (var dataObj in this._data) {
   150       dataObj.file.remove(true);
   151     }
   152     this._data = [];
   153   }
   154 };
   155 
   156 function FlatFile(aFilePath) {
   157   ShutdownObserver.checkShutdown();
   158   this.filePath = aFilePath;
   159   this.fileObj = new File(aFilePath);
   160   this.contents = null;
   161   this.isZip = ZipTypesMap.test(aFilePath);
   162   this.isDirectory = this.fileObj.isDirectory();
   163 }
   164 FlatFile.prototype = {
   165   exists: function() {
   166     ShutdownObserver.checkShutdown();
   167     return this.fileObj.exists();
   168   },
   169   get leafName() {
   170     ShutdownObserver.checkShutdown();
   171     return this.fileObj.leafName;
   172   },
   173 
   174   read: function read()
   175   {
   176     ShutdownObserver.checkShutdown();
   177 
   178     if (!this.exists())
   179       ERROR_ABORT();
   180 
   181     if (this.isZip || this.isDirectory)
   182       ERROR_ABORT();
   183 
   184     var rawInStream = new InputStream(this.fileObj, -1, 0, 0);
   185     return readFromStream(rawInStream);
   186   },
   187 
   188   write: function write(contents)
   189   {
   190     ShutdownObserver.checkShutdown();
   191     if (!this.fileObj)
   192       ERROR_ABORT();
   193 
   194     if (this.isZip || this.isDirectory)
   195       ERROR_ABORT();
   196 
   197     var outStream = new OutputStream(this.fileObj, -1, -1, 0);
   198     outStream.write(contents, contents.length);
   199     outStream.close();
   200   },
   201 
   202   getDirContents: function() {
   203     ShutdownObserver.checkShutdown();
   204     var rv = [];
   205     if (!this.fileObj)
   206       ERROR_ABORT();
   207 
   208     if (!this.fileObj.exists())
   209       ERROR_ABORT();
   210 
   211     if (this.isDirectory)
   212     {
   213       var entries = this.fileObj.directoryEntries;
   214       while (entries.hasMoreElements())
   215       {
   216         var fileObj = entries.getNext().QueryInterface(C_i.nsILocalFile);
   217         rv.push(getFile(fileObj.path));
   218       }
   219     }
   220     else if (this.isZip) {
   221       var zipReader = new ZipReader(this.fileObj);
   222       var iterator = zipReader.findEntries("*");
   223       while (iterator.hasMore()) {
   224         var entryName = iterator.getNext();
   225         var slash = entryName.indexOf("/");
   226         if ((slash == -1) || (slash == entryName.length - 1))
   227           rv.push(getFile(this.filePath, entryName));
   228       }
   229       zipReader.close();
   230     }
   231     else {
   232       NOT_AVAILABLE();
   233     }
   234     rv.sort(sortEntries);
   235     return rv;
   236   },
   237 
   238   toString: function toString() {
   239     return "[object FlatFile(" + this.filePath + ")]";
   240   }
   241 };
   242 
   243 function ZipFile(aFilePath, aZipPath) {
   244   ShutdownObserver.checkShutdown();
   245   this.filePath = aFilePath;
   246   this.zipPath = aZipPath;
   247   this.fileObj = new File(aFilePath);
   248   var zipReader = new ZipReader(this.fileObj);
   249   this.zipWriter = null;
   250   this.contents = null;
   251   this.isZip = ZipTypesMap.test(aZipPath);
   252   
   253   this.isDirectory = zipReader.hasEntry(this.zipPath) ?
   254                      zipReader.getEntry(this.zipPath).isDirectory :
   255                      false;
   256   zipReader.close();
   257 }
   258 ZipFile.prototype = {
   259   exists: function() {
   260     ShutdownObserver.checkShutdown();
   261     var zipReader = new ZipReader(this.fileObj);
   262     var rv = this.fileObj.exists() && zipReader.hasEntry(this.zipPath);
   263     zipReader.close();
   264     return rv;
   265   },
   266 
   267   get leafName() {
   268     ShutdownObserver.checkShutdown();
   269     var index = this.zipPath.lastIndexOf("/");
   270     var str = "";
   271     if (index == this.zipPath.length - 1)
   272       str = this.zipPath.substr(this.zipPath, index);
   273     else
   274       str = this.zipPath;
   275     return str.substr(str.lastIndexOf("/") + 1);
   276   },
   277 
   278   read: function read() {
   279     ShutdownObserver.checkShutdown();
   280     if (!this.exists())
   281       ERROR_ABORT();
   282 
   283     if (this.isZip || this.isDirectory)
   284       ERROR_ABORT();
   285 
   286     var zipReader = new ZipReader(this.fileObj);
   287     if (!zipReader.hasEntry(this.zipPath)) {
   288       zipReader.close();
   289       ERROR_ABORT();
   290     }
   291 
   292     var rawInStream = zipReader.getInputStream(this.zipPath);
   293     var rv = readFromStream(rawInStream);
   294     zipReader.close();
   295     return rv;
   296   },
   297 
   298   write: function write(contents) {
   299     ShutdownObserver.checkShutdown();
   300     if (!this.fileObj)
   301       ERROR_ABORT();
   302 
   303     if (this.isZip || this.isDirectory)
   304       ERROR_ABORT();
   305 
   306     if (!this.zipWriter) {
   307       this.zipWriter = C_c["@mozilla.org/zipwriter;1"]
   308                           .createInstance(C_i.nsIZipWriter);
   309     }
   310 
   311     this.zipWriter.open(this.fileObj, 0x02 | 0x08 | 0x10);
   312 
   313     var stream = C_c["@mozilla.org/io/string-input-stream;1"]
   314                     .createInstance(C_i.nsIStringInputStream);
   315     stream.setData(contents, contents.length);
   316     this.zipWriter.addEntryStream(this.zipPath, 0, 0, stream, false);
   317 
   318     this.zipWriter.close();
   319   },
   320 
   321   getDirContents: function() {
   322     ShutdownObserver.checkShutdown();
   323     var rv = [];
   324     if (!this.fileObj)
   325       ERROR_ABORT();
   326 
   327     if (!this.fileObj.exists())
   328       ERROR_ABORT();
   329 
   330     var zipReader = new ZipReader(this.fileObj);
   331     if (!zipReader.hasEntry(this.zipPath)) {
   332       zipReader.close();
   333       ERROR_ABORT();
   334     }
   335 
   336     if (this.isDirectory)
   337     {
   338       var path = this.zipPath;
   339       if (path.charAt(path.length - 1) != "/")
   340         path += "/";
   341 
   342       var iterator = zipReader.findEntries("*");
   343       while (iterator.hasMore()) {
   344         var entryName = iterator.getNext();
   345         if ((entryName.indexOf(path) != 0) || (entryName == path))
   346           continue;
   347 
   348         entryName = entryName.substr(path.length);
   349         var slash = entryName.indexOf("/");
   350         if ((slash == -1) || (slash == entryName.length - 1))
   351           rv.push(getFile(this.filePath, path + entryName));
   352       }
   353     }
   354 
   355     else if (this.isZip) {
   356       var paths = [this.filePath, this.zipPath];
   357       var zip = ZipMap.getZip(paths, paths.length);
   358 
   359       var zipReader = new ZipReader(zip);
   360       var iterator = zipReader.findEntries("*");
   361       while (iterator.hasMore()) {
   362         var entryName = iterator.getNext();
   363         var slash = entryName.indexOf("/");
   364         if ((slash == -1) || (slash == entryName.length - 1))
   365           rv.push(getFile(this.filePath, this.zipPath, entryName));
   366       }
   367     }
   368     else {
   369       zipReader.close();
   370       NOT_AVAILABLE();
   371     }
   372 
   373     zipReader.close();
   374     return rv.sort(sortEntries);
   375   },
   376 
   377   toString: function toString() {
   378     return "[object ZipFile(" + this.filePath + ", " + this.zipPath + ")]";
   379   }
   380 };
   381 
   382 function ZipZipFile(aZipArchives, aPaths)
   383 {
   384   ShutdownObserver.checkShutdown();
   385   this.fileObj = aZipArchives[0];
   386   this.zipArchives = aZipArchives;
   387   this.paths = aPaths;
   388   this.finalPath = aPaths[aPaths.length - 1];
   389 
   390   this.lastZipArchive = aZipArchives[aZipArchives.length - 1];
   391   var zipReader = new ZipReader(this.lastZipArchive);
   392 
   393   this.zipWriter = null;
   394   this.contents = null;
   395   this.isZip = ZipTypesMap.test(this.finalPath);
   396   this.isDirectory = this.exists() &&
   397                      zipReader.getEntry(this.finalPath).isDirectory;
   398   zipReader.close();
   399 }
   400 ZipZipFile.prototype = {
   401   exists: function exists() {
   402     ShutdownObserver.checkShutdown();
   403     for each (zip in this.zipArchives)
   404       if (!zip.exists())
   405         return false;
   406     var zipReader = new ZipReader(this.lastZipArchive);
   407     var rv = zipReader.hasEntry(this.finalPath);
   408     zipReader.close();
   409     return rv;
   410   },
   411 
   412   get leafName() {
   413     ShutdownObserver.checkShutdown();
   414     var index = this.finalPath.lastIndexOf("/");
   415     var str = "";
   416     if (index == this.finalPath.length - 1)
   417       str = this.finalPath.substr(this.finalPath, index);
   418     else
   419       str = this.finalPath;
   420     return str.substr(str.lastIndexOf("/") + 1);
   421   },
   422 
   423   read: function read() {
   424     ShutdownObserver.checkShutdown();
   425     if (!this.fileObj)
   426       ERROR_ABORT();
   427 
   428     if (typeof this.exists != "function") {
   429       ERROR_UNEXPECTED();
   430     }
   431 
   432     if (!this.exists())
   433       ERROR_ABORT();
   434 
   435     if (this.isZip || this.isDirectory)
   436       ERROR_ABORT();
   437 
   438     var zipReader = new ZipReader(this.lastZipArchive);
   439     if (!zipReader.hasEntry(this.finalPath)) {
   440       zipReader.close();
   441       ERROR_ABORT();
   442     }
   443 
   444     var rawInStream = zipReader.getInputStream(this.finalPath);
   445     var rv = readFromStream(rawInStream);
   446     zipReader.close();
   447     return rv;
   448   },
   449 
   450   writeStack: function writeStack(i) {
   451     ShutdownObserver.checkShutdown();
   452     if (!this.zipWriter)
   453       ERROR_UNEXPECTED();
   454     var parentZip = this.zipArchives[i];
   455     var path = this.paths[i + 1];
   456     var childZip = this.zipArchives[i + 1];
   457 
   458     this.zipWriter.open(parentZip, 0x02 | 0x08 | 0x10);
   459     this.zipWriter.addEntryFile(path, 0, childZip, false);
   460     this.zipWriter.close();
   461 
   462     if (i > 0)
   463       this.writeStack(i - 1);
   464   },
   465 
   466   write: function write(contents) {
   467     ShutdownObserver.checkShutdown();
   468     if (!this.fileObj)
   469       ERROR_ABORT();
   470 
   471     if (this.isZip || this.isDirectory)
   472       ERROR_ABORT();
   473 
   474     var contentWriter;
   475     var index = this.zipArchives.length - 1;
   476     if (!this.zipWriter) {
   477       this.zipWriter = C_c["@mozilla.org/zipwriter;1"]
   478                           .createInstance(C_i.nsIZipWriter);
   479     }
   480 
   481     this.zipWriter.open(this.zipArchives[index], 0x02 | 0x08 | 0x10);
   482 
   483     var stream = C_c["@mozilla.org/io/string-input-stream;1"]
   484                     .createInstance(C_i.nsIStringInputStream);
   485     stream.setData(contents, contents.length);
   486     this.zipWriter.addEntryStream(this.finalPath, 0, 0, stream, false);
   487     this.zipWriter.close();
   488 
   489     this.writeStack(this.zipArchives.length - 2);
   490   },
   491 
   492   getDirContents: function() {
   493     ShutdownObserver.checkShutdown();
   494     var rv = [];
   495     if (!this.fileObj)
   496       ERROR_ABORT();
   497 
   498     if (!this.fileObj.exists())
   499       ERROR_ABORT();
   500 
   501     var zipReader = new ZipReader(this.lastZipArchive);
   502     if (!zipReader.hasEntry(this.finalPath)) {
   503       zipReader.close();
   504       ERROR_ABORT();
   505     }
   506 
   507     var newPaths = [];
   508     for (var i = 0; i < this.paths.length; i++)
   509       newPaths.push(this.paths[i]);
   510 
   511     var path = "";
   512     if (this.isDirectory) {
   513       path = this.finalPath;
   514       if (path.charAt(path.length - 1) != "/")
   515         path += "/";
   516     }
   517     else if (this.isZip) {
   518       newPaths.push(null); // Force a new generation.
   519     }
   520     else {
   521       zipReader.close();
   522       NOT_AVAILABLE();
   523     }
   524 
   525     var iterator = zipReader.findEntries("*");
   526     while (iterator.hasMore()) {
   527       var entryName = iterator.getNext();
   528       if ((entryName.indexOf(path) != 0) || (entryName == path))
   529         continue;
   530 
   531       entryName = entryName.substr(path.length);
   532       var slash = entryName.indexOf("/");
   533       if ((slash == -1) || (slash == entryName.length - 1)) {
   534         newPaths[newPaths.length - 1] = path + entryName;
   535         rv.push(getFile.apply(this, newPaths));
   536       }
   537     }
   538 
   539     zipReader.close();
   540     rv.sort(sortEntries);
   541     return rv;
   542   },
   543 
   544   toString: function toString() {
   545     var rv = "[object ZipZipFile(";
   546     for (var i = 0; i < this.paths.length; i++)
   547       rv += this.paths[i] + ", ";
   548     rv = rv.substr(0, rv.length - 2) + ")]";
   549     return rv;
   550   }
   551 };
   552 
   553 const FileCacheMap = {
   554   _data: [],
   555   getFile: function getFile(key) {
   556     ShutdownObserver.checkShutdown();
   557     for each (var obj in this._data)
   558       if (key.equals(obj.key))
   559         return obj.file;
   560     return null;
   561   },
   562 
   563   addFile: function addFile(key, file) {
   564     ShutdownObserver.checkShutdown();
   565     this._data.push({
   566       key: key,
   567       file: file
   568     });
   569   },
   570 
   571   shutdown: function() {
   572     this._data = [];
   573   }
   574 };
   575 
   576 function getFile() {
   577   ShutdownObserver.checkShutdown();
   578 
   579   var key = new ZipEntryKey(arguments);
   580   var rv = FileCacheMap.getFile(key);
   581   if (rv)
   582     return rv;
   583 
   584   switch (arguments.length) {
   585     case 0:
   586       INVALID_ARG();
   587     case 1:
   588       rv = new FlatFile(arguments[0]);
   589       break;
   590     case 2:
   591       rv = new ZipFile(arguments[0], arguments[1]);
   592       break;
   593 
   594     default: // 3 or more arguments...
   595       var zipArchives = [new File(arguments[0])];
   596     
   597       for (var i = 1; i < arguments.length - 1; i++) {
   598         if (ZipTypesMap.test(arguments[i])) {
   599           zipArchives.push(ZipMap.getZip(arguments, i + 1));
   600         } else {
   601           ERROR.UNEXPECTED();
   602         }
   603       }
   604     
   605       rv = new ZipZipFile(zipArchives, arguments);
   606   }
   607 
   608   FileCacheMap.addFile(key, rv);
   609   return rv;
   610 }
   611 
   612 const ShutdownObserver = {
   613   _shutdown: false,
   614   checkShutdown: function() {
   615     if (this._shutdown)
   616       NOT_AVAILABLE();
   617   },
   618 
   619   observe: function observe(aSubject, aTopic, aData) {
   620     ZipMap.shutdown();
   621     FileCacheMap.shutdown();
   622     this._shutdown = true;
   623   },
   624 
   625   init: function() {
   626     var obs = C_c["@mozilla.org/observer-service;1"]
   627                  .getService(C_i.nsIObserverService);
   628     obs.addObserver(this, "xpcom-shutdown", false);
   629     obs.addObserver(this, "verbosio-filecommon-shutdown", false);
   630   },
   631 
   632   QueryInterface: function QueryInterface(aIID) {
   633     if (aIID.equals(C_i.nsIObserver) ||
   634         aIID.equals(C_i.nsiSupports))
   635       return this;
   636     throw C_r.NS_ERROR_NO_INTERFACE;
   637   }
   638 }
   639 ShutdownObserver.init();
   640 
   641 const FileCommon = {
   642   get getFile() {
   643     return getFile;
   644   }
   645 };
   646 
   647 const EXPORTED_SYMBOLS = ["FileCommon"];