Bumping gaia.json for 1 gaia revision(s) a=gaia-bump
[gecko.git] / toolkit / modules / ZipUtils.jsm
blob7979443c5cdd55c66db97cbb050e91d94647e68c
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 this.EXPORTED_SYMBOLS = [ "ZipUtils" ];
7 const Cc = Components.classes;
8 const Ci = Components.interfaces;
9 const Cr = Components.results;
10 const Cu = Components.utils;
12 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
13 Cu.import("resource://gre/modules/Services.jsm");
15 XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
16                                   "resource://gre/modules/FileUtils.jsm");
17 XPCOMUtils.defineLazyModuleGetter(this, "OS",
18                                   "resource://gre/modules/osfile.jsm");
19 XPCOMUtils.defineLazyModuleGetter(this, "Promise",
20                                   "resource://gre/modules/Promise.jsm");
21 XPCOMUtils.defineLazyModuleGetter(this, "Task",
22                                   "resource://gre/modules/Task.jsm");
25 // The maximum amount of file data to buffer at a time during file extraction
26 const EXTRACTION_BUFFER               = 1024 * 512;
29 /**
30  * Asynchronously writes data from an nsIInputStream to an OS.File instance.
31  * The source stream and OS.File are closed regardless of whether the operation
32  * succeeds or fails.
33  * Returns a promise that will be resolved when complete.
34  *
35  * @param  aPath
36  *         The name of the file being extracted for logging purposes.
37  * @param  aStream
38  *         The source nsIInputStream.
39  * @param  aFile
40  *         The open OS.File instance to write to.
41  */
42 function saveStreamAsync(aPath, aStream, aFile) {
43   let deferred = Promise.defer();
45   // Read the input stream on a background thread
46   let sts = Cc["@mozilla.org/network/stream-transport-service;1"].
47             getService(Ci.nsIStreamTransportService);
48   let transport = sts.createInputTransport(aStream, -1, -1, true);
49   let input = transport.openInputStream(0, 0, 0)
50                        .QueryInterface(Ci.nsIAsyncInputStream);
51   let source = Cc["@mozilla.org/binaryinputstream;1"].
52                createInstance(Ci.nsIBinaryInputStream);
53   source.setInputStream(input);
55   let data = new Uint8Array(EXTRACTION_BUFFER);
57   function readFailed(error) {
58     try {
59       aStream.close();
60     }
61     catch (e) {
62       logger.error("Failed to close JAR stream for " + aPath);
63     }
65     aFile.close().then(function() {
66       deferred.reject(error);
67     }, function(e) {
68       logger.error("Failed to close file for " + aPath);
69       deferred.reject(error);
70     });
71   }
73   function readData() {
74     try {
75       let count = Math.min(source.available(), data.byteLength);
76       source.readArrayBuffer(count, data.buffer);
78       aFile.write(data, { bytes: count }).then(function() {
79         input.asyncWait(readData, 0, 0, Services.tm.currentThread);
80       }, readFailed);
81     }
82     catch (e if e.result == Cr.NS_BASE_STREAM_CLOSED) {
83       deferred.resolve(aFile.close());
84     }
85     catch (e) {
86       readFailed(e);
87     }
88   }
90   input.asyncWait(readData, 0, 0, Services.tm.currentThread);
92   return deferred.promise;
96 this.ZipUtils = {
98   /**
99    * Asynchronously extracts files from a ZIP file into a directory.
100    * Returns a promise that will be resolved when the extraction is complete.
101    *
102    * @param  aZipFile
103    *         The source ZIP file that contains the add-on.
104    * @param  aDir
105    *         The nsIFile to extract to.
106    */
107   extractFilesAsync: function ZipUtils_extractFilesAsync(aZipFile, aDir) {
108     let zipReader = Cc["@mozilla.org/libjar/zip-reader;1"].
109                     createInstance(Ci.nsIZipReader);
111     try {
112       zipReader.open(aZipFile);
113     }
114     catch (e) {
115       return Promise.reject(e);
116     }
118     return Task.spawn(function() {
119       // Get all of the entries in the zip and sort them so we create directories
120       // before files
121       let entries = zipReader.findEntries(null);
122       let names = [];
123       while (entries.hasMore())
124         names.push(entries.getNext());
125       names.sort();
127       for (let name of names) {
128         let entryName = name;
129         let zipentry = zipReader.getEntry(name);
130         let path = OS.Path.join(aDir.path, ...name.split("/"));
132         if (zipentry.isDirectory) {
133           try {
134             yield OS.File.makeDir(path);
135           }
136           catch (e) {
137             dump("extractFilesAsync: failed to create directory " + path + "\n");
138             throw e;
139           }
140         }
141         else {
142           let options = { unixMode: zipentry.permissions | FileUtils.PERMS_FILE };
143           try {
144             let file = yield OS.File.open(path, { truncate: true }, options);
145             if (zipentry.realSize == 0)
146               yield file.close();
147             else
148               yield saveStreamAsync(path, zipReader.getInputStream(entryName), file);
149           }
150           catch (e) {
151             dump("extractFilesAsync: failed to extract file " + path + "\n");
152             throw e;
153           }
154         }
155       }
157       zipReader.close();
158     }).then(null, (e) => {
159       zipReader.close();
160       throw e;
161     });
162   },
164   /**
165    * Extracts files from a ZIP file into a directory.
166    *
167    * @param  aZipFile
168    *         The source ZIP file that contains the add-on.
169    * @param  aDir
170    *         The nsIFile to extract to.
171    */
172   extractFiles: function ZipUtils_extractFiles(aZipFile, aDir) {
173     function getTargetFile(aDir, entry) {
174       let target = aDir.clone();
175       entry.split("/").forEach(function(aPart) {
176         target.append(aPart);
177       });
178       return target;
179     }
181     let zipReader = Cc["@mozilla.org/libjar/zip-reader;1"].
182                     createInstance(Ci.nsIZipReader);
183     zipReader.open(aZipFile);
185     try {
186       // create directories first
187       let entries = zipReader.findEntries("*/");
188       while (entries.hasMore()) {
189         let entryName = entries.getNext();
190         let target = getTargetFile(aDir, entryName);
191         if (!target.exists()) {
192           try {
193             target.create(Ci.nsIFile.DIRECTORY_TYPE,
194                           FileUtils.PERMS_DIRECTORY);
195           }
196           catch (e) {
197             dump("extractFiles: failed to create target directory for extraction file = " + target.path + "\n");
198           }
199         }
200       }
202       entries = zipReader.findEntries(null);
203       while (entries.hasMore()) {
204         let entryName = entries.getNext();
205         let target = getTargetFile(aDir, entryName);
206         if (target.exists())
207           continue;
209         zipReader.extract(entryName, target);
210         try {
211           target.permissions |= FileUtils.PERMS_FILE;
212         }
213         catch (e) {
214           dump("Failed to set permissions " + aPermissions.toString(8) + " on " + target.path + "\n");
215         }
216       }
217     }
218     finally {
219       zipReader.close();
220     }
221   }