diff --git a/toolkit/components/osfile/osfile_async_front.jsm b/toolkit/components/osfile/osfile_async_front.jsm index 8bf91cebd23..99ecfb56380 100644 --- a/toolkit/components/osfile/osfile_async_front.jsm +++ b/toolkit/components/osfile/osfile_async_front.jsm @@ -582,6 +582,173 @@ File.Info.fromMsg = function fromMsg(value) { return new File.Info(value); }; +/** + * Iterate asynchronously through a directory + * + * @constructor + */ +let DirectoryIterator = function DirectoryIterator(path, options) { + /** + * Open the iterator on the worker thread + * + * @type {Promise} + * @resolves {*} A message accepted by the methods of DirectoryIterator + * in the worker thread + * @rejects {StopIteration} If all entries have already been visited + * or the iterator has been closed. + */ + this._itmsg = Scheduler.post( + "new_DirectoryIterator", [Type.path.toMsg(path), options], + path + ); + this._isClosed = false; +}; +DirectoryIterator.prototype = { + /** + * Get the next entry in the directory. + * + * @return {Promise} + * @resolves {OS.File.Entry} + * @rejects {StopIteration} If all entries have already been visited. + */ + next: function next() { + let self = this; + let promise = this._itmsg; + + // Get the iterator, call _next + promise = promise.then( + function withIterator(iterator) { + return self._next(iterator); + }); + + return promise; + }, + /** + * Get several entries at once. + * + * @param {number=} length If specified, the number of entries + * to return. If unspecified, return all remaining entries. + * @return {Promise} + * @resolves {Array} An array containing the |length| next entries. + */ + nextBatch: function nextBatch(size) { + if (this._isClosed) { + return Promise.resolve([]); + } + let promise = this._itmsg; + promise = promise.then( + function withIterator(iterator) { + return Scheduler.post("DirectoryIterator_prototype_nextBatch", [iterator, size]); + }); + promise = promise.then( + function withEntries(array) { + return array.map(DirectoryIterator.Entry.fromMsg); + }); + return promise; + }, + /** + * Apply a function to all elements of the directory sequentially. + * + * @param {Function} cb This function will be applied to all entries + * of the directory. It receives as arguments + * - the OS.File.Entry corresponding to the entry; + * - the index of the entry in the enumeration; + * - the iterator itself - return |iterator.close()| to stop the loop. + * + * If the callback returns a promise, iteration waits until the + * promise is resolved before proceeding. + * + * @return {Promise} A promise resolved once the loop has reached + * its end. + */ + forEach: function forEach(cb, options) { + if (this._isClosed) { + return Promise.resolve(); + } + + let self = this; + let position = 0; + let iterator; + + // Grab iterator + let promise = this._itmsg.then( + function(aIterator) { + iterator = aIterator; + } + ); + + // Then iterate + let loop = function loop() { + if (self._isClosed) { + return Promise.resolve(); + } + return self._next(iterator).then( + function onSuccess(value) { + return Promise.resolve(cb(value, position++, self)).then(loop); + }, + function onFailure(reason) { + if (reason == StopIteration) { + return; + } + throw reason; + } + ); + }; + + return promise.then(loop); + }, + /** + * Auxiliary method: fetch the next item + * + * @rejects {StopIteration} If all entries have already been visited + * or the iterator has been closed. + */ + _next: function _next(iterator) { + if (this._isClosed) { + LOG("DirectoryIterator._next", "closed"); + return this._itmsg; + } + let self = this; + let promise = Scheduler.post("DirectoryIterator_prototype_next", [iterator]); + promise = promise.then( + DirectoryIterator.Entry.fromMsg, + function onReject(reason) { + // If the exception is |StopIteration| (which we may determine only + // from its message...) we need to stop the iteration. + if (!(reason instanceof WorkerErrorEvent && reason.message == "uncaught exception: [object StopIteration]")) { + // Any exception other than StopIteration should be propagated as such + throw reason; + } + self.close(); + throw StopIteration; + }); + return promise; + }, + /** + * Close the iterator + */ + close: function close() { + if (this._isClosed) { + return; + } + this._isClosed = true; + let self = this; + this._itmsg.then( + function withIterator(iterator) { + self._itmsg = Promise.reject(StopIteration); + return Scheduler.post("DirectoryIterator_prototype_close", [iterator]); + } + ); + } +}; + +DirectoryIterator.Entry = function Entry(value) { + return value; +}; +DirectoryIterator.Entry.fromMsg = function fromMsg(value) { + return new DirectoryIterator.Entry(value); +}; + // Constants Object.defineProperty(File, "POS_START", {value: OS.Shared.POS_START}); Object.defineProperty(File, "POS_CURRENT", {value: OS.Shared.POS_CURRENT}); @@ -589,4 +756,4 @@ Object.defineProperty(File, "POS_END", {value: OS.Shared.POS_END}); OS.File = File; OS.File.Error = OSError; - +OS.File.DirectoryIterator = DirectoryIterator;