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;
7 function dumpJSStack() {
8 dump((new Error).stack + "\n");
11 function ERROR_FAILURE() {
13 throw new C_E("Failure", C_r.NS_ERROR_FAILURE);
16 function ERROR_ABORT() {
18 throw new C_E("Aborted", C_r.NS_ERROR_ABORT);
21 function NOT_AVAILABLE() {
23 throw new C_E("Not available", C_r.NS_ERROR_NOT_AVAILABLE);
26 function ERROR_UNEXPECTED() {
28 throw new C_E("Assertion failure", C_r.NS_ERROR_UNEXPECTED);
31 function INVALID_ARG() {
33 throw new C_E("Invalid argument", C_r.NS_ERROR_INVALID_ARG);
36 const File = C_C("@mozilla.org/file/local;1",
40 const InputStream = C_C("@mozilla.org/network/file-input-stream;1",
41 C_i.nsIFileInputStream,
43 const SInputStream = C_C("@mozilla.org/scriptableinputstream;1",
44 C_i.nsIScriptableInputStream,
46 const OutputStream = C_C("@mozilla.org/network/file-output-stream;1",
47 C_i.nsIFileOutputStream,
49 var ZipReader = C_C("@mozilla.org/libjar/zip-reader;1",
53 function readFromStream(aInputStream) {
54 var sInStream = new SInputStream(aInputStream);
58 while (sInStream.available()) {
59 contents += sInStream.read(sInStream.available());
69 function sortEntries(a, b) {
70 if (a.leafName < b.leafName)
72 if (b.leafName < a.leafName)
77 const ZipTypesMap = /.(zip|jar|xpi)$/;
79 function ZipEntryKey(args) {
80 this._keySequence = args;
82 ZipEntryKey.prototype = {
83 equals: function(other) {
84 if (other._keySequence.length != this._keySequence.length)
86 for (var i = 0; i < this._keySequence.length; i++)
87 if (this._keySequence[i] != other._keySequence[i])
96 /* key = ["local file path", "zip path 1", "zip path 2"...]
97 value = nsILocalFile for the zip archive.
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
105 getZip: function getZip(aKeySequence, aLength) {
106 ShutdownObserver.checkShutdown();
107 if (aKeySequence.length < 2 || aLength < 2)
111 for (var i = 0; i < aLength; i++)
112 keyTeeth[i] = aKeySequence[i];
113 var key = new ZipEntryKey(keyTeeth);
115 for (i = 0; i < this._data.length; i++)
116 if (key.equals(this._data[i].key))
117 return this._data.file;
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);
126 var lastTooth = keyTeeth.pop();
128 if (keyTeeth.length == 1)
129 parentZip = new File(keyTeeth[0]);
131 parentZip = this.getZip(keyTeeth, keyTeeth.length).file;
135 var zipReader = new ZipReader(parentZip);
136 zipReader.extract(lastTooth, temp);
144 this._data.unshift(dataObj);
148 shutdown: function shutdown() {
149 for each (var dataObj in this._data) {
150 dataObj.file.remove(true);
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();
164 FlatFile.prototype = {
166 ShutdownObserver.checkShutdown();
167 return this.fileObj.exists();
170 ShutdownObserver.checkShutdown();
171 return this.fileObj.leafName;
174 read: function read()
176 ShutdownObserver.checkShutdown();
181 if (this.isZip || this.isDirectory)
184 var rawInStream = new InputStream(this.fileObj, -1, 0, 0);
185 return readFromStream(rawInStream);
188 write: function write(contents)
190 ShutdownObserver.checkShutdown();
194 if (this.isZip || this.isDirectory)
197 var outStream = new OutputStream(this.fileObj, -1, -1, 0);
198 outStream.write(contents, contents.length);
202 getDirContents: function() {
203 ShutdownObserver.checkShutdown();
208 if (!this.fileObj.exists())
211 if (this.isDirectory)
213 var entries = this.fileObj.directoryEntries;
214 while (entries.hasMoreElements())
216 var fileObj = entries.getNext().QueryInterface(C_i.nsILocalFile);
217 rv.push(getFile(fileObj.path));
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));
234 rv.sort(sortEntries);
238 toString: function toString() {
239 return "[object FlatFile(" + this.filePath + ")]";
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);
253 this.isDirectory = zipReader.hasEntry(this.zipPath) ?
254 zipReader.getEntry(this.zipPath).isDirectory :
258 ZipFile.prototype = {
260 ShutdownObserver.checkShutdown();
261 var zipReader = new ZipReader(this.fileObj);
262 var rv = this.fileObj.exists() && zipReader.hasEntry(this.zipPath);
268 ShutdownObserver.checkShutdown();
269 var index = this.zipPath.lastIndexOf("/");
271 if (index == this.zipPath.length - 1)
272 str = this.zipPath.substr(this.zipPath, index);
275 return str.substr(str.lastIndexOf("/") + 1);
278 read: function read() {
279 ShutdownObserver.checkShutdown();
283 if (this.isZip || this.isDirectory)
286 var zipReader = new ZipReader(this.fileObj);
287 if (!zipReader.hasEntry(this.zipPath)) {
292 var rawInStream = zipReader.getInputStream(this.zipPath);
293 var rv = readFromStream(rawInStream);
298 write: function write(contents) {
299 ShutdownObserver.checkShutdown();
303 if (this.isZip || this.isDirectory)
306 if (!this.zipWriter) {
307 this.zipWriter = C_c["@mozilla.org/zipwriter;1"]
308 .createInstance(C_i.nsIZipWriter);
311 this.zipWriter.open(this.fileObj, 0x02 | 0x08 | 0x10);
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);
318 this.zipWriter.close();
321 getDirContents: function() {
322 ShutdownObserver.checkShutdown();
327 if (!this.fileObj.exists())
330 var zipReader = new ZipReader(this.fileObj);
331 if (!zipReader.hasEntry(this.zipPath)) {
336 if (this.isDirectory)
338 var path = this.zipPath;
339 if (path.charAt(path.length - 1) != "/")
342 var iterator = zipReader.findEntries("*");
343 while (iterator.hasMore()) {
344 var entryName = iterator.getNext();
345 if ((entryName.indexOf(path) != 0) || (entryName == path))
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));
355 else if (this.isZip) {
356 var paths = [this.filePath, this.zipPath];
357 var zip = ZipMap.getZip(paths, paths.length);
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));
374 return rv.sort(sortEntries);
377 toString: function toString() {
378 return "[object ZipFile(" + this.filePath + ", " + this.zipPath + ")]";
382 function ZipZipFile(aZipArchives, aPaths)
384 ShutdownObserver.checkShutdown();
385 this.fileObj = aZipArchives[0];
386 this.zipArchives = aZipArchives;
388 this.finalPath = aPaths[aPaths.length - 1];
390 this.lastZipArchive = aZipArchives[aZipArchives.length - 1];
391 var zipReader = new ZipReader(this.lastZipArchive);
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;
400 ZipZipFile.prototype = {
401 exists: function exists() {
402 ShutdownObserver.checkShutdown();
403 for each (zip in this.zipArchives)
406 var zipReader = new ZipReader(this.lastZipArchive);
407 var rv = zipReader.hasEntry(this.finalPath);
413 ShutdownObserver.checkShutdown();
414 var index = this.finalPath.lastIndexOf("/");
416 if (index == this.finalPath.length - 1)
417 str = this.finalPath.substr(this.finalPath, index);
419 str = this.finalPath;
420 return str.substr(str.lastIndexOf("/") + 1);
423 read: function read() {
424 ShutdownObserver.checkShutdown();
428 if (typeof this.exists != "function") {
435 if (this.isZip || this.isDirectory)
438 var zipReader = new ZipReader(this.lastZipArchive);
439 if (!zipReader.hasEntry(this.finalPath)) {
444 var rawInStream = zipReader.getInputStream(this.finalPath);
445 var rv = readFromStream(rawInStream);
450 writeStack: function writeStack(i) {
451 ShutdownObserver.checkShutdown();
454 var parentZip = this.zipArchives[i];
455 var path = this.paths[i + 1];
456 var childZip = this.zipArchives[i + 1];
458 this.zipWriter.open(parentZip, 0x02 | 0x08 | 0x10);
459 this.zipWriter.addEntryFile(path, 0, childZip, false);
460 this.zipWriter.close();
463 this.writeStack(i - 1);
466 write: function write(contents) {
467 ShutdownObserver.checkShutdown();
471 if (this.isZip || this.isDirectory)
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);
481 this.zipWriter.open(this.zipArchives[index], 0x02 | 0x08 | 0x10);
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();
489 this.writeStack(this.zipArchives.length - 2);
492 getDirContents: function() {
493 ShutdownObserver.checkShutdown();
498 if (!this.fileObj.exists())
501 var zipReader = new ZipReader(this.lastZipArchive);
502 if (!zipReader.hasEntry(this.finalPath)) {
508 for (var i = 0; i < this.paths.length; i++)
509 newPaths.push(this.paths[i]);
512 if (this.isDirectory) {
513 path = this.finalPath;
514 if (path.charAt(path.length - 1) != "/")
517 else if (this.isZip) {
518 newPaths.push(null); // Force a new generation.
525 var iterator = zipReader.findEntries("*");
526 while (iterator.hasMore()) {
527 var entryName = iterator.getNext();
528 if ((entryName.indexOf(path) != 0) || (entryName == path))
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));
540 rv.sort(sortEntries);
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) + ")]";
553 const FileCacheMap = {
555 getFile: function getFile(key) {
556 ShutdownObserver.checkShutdown();
557 for each (var obj in this._data)
558 if (key.equals(obj.key))
563 addFile: function addFile(key, file) {
564 ShutdownObserver.checkShutdown();
571 shutdown: function() {
577 ShutdownObserver.checkShutdown();
579 var key = new ZipEntryKey(arguments);
580 var rv = FileCacheMap.getFile(key);
584 switch (arguments.length) {
588 rv = new FlatFile(arguments[0]);
591 rv = new ZipFile(arguments[0], arguments[1]);
594 default: // 3 or more arguments...
595 var zipArchives = [new File(arguments[0])];
597 for (var i = 1; i < arguments.length - 1; i++) {
598 if (ZipTypesMap.test(arguments[i])) {
599 zipArchives.push(ZipMap.getZip(arguments, i + 1));
605 rv = new ZipZipFile(zipArchives, arguments);
608 FileCacheMap.addFile(key, rv);
612 const ShutdownObserver = {
614 checkShutdown: function() {
619 observe: function observe(aSubject, aTopic, aData) {
621 FileCacheMap.shutdown();
622 this._shutdown = true;
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);
632 QueryInterface: function QueryInterface(aIID) {
633 if (aIID.equals(C_i.nsIObserver) ||
634 aIID.equals(C_i.nsiSupports))
636 throw C_r.NS_ERROR_NO_INTERFACE;
639 ShutdownObserver.init();
647 const EXPORTED_SYMBOLS = ["FileCommon"];