diff --git a/.gitignore b/.gitignore index 5d5649f..a820c68 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ node_modules/ coverage/ .project _site/ +.idea/ diff --git a/Gruntfile.js b/Gruntfile.js index ff7381c..a76d850 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -48,13 +48,18 @@ module.exports = function(grunt) { "glMatrixAddon" : { "deps" : ["glMatrix"], "exports" : "glMatrixAddon" + }, + "async": { + "deps": [], + "exports": "async" } }, "paths" : { "typedefs" : "typedefs", "glMatrix" : "vendor/glMatrix", - "glMatrixAddon" : "glMatrixAddon" + "glMatrixAddon" : "glMatrixAddon", + "async": "vendor/async" } } } @@ -68,7 +73,7 @@ module.exports = function(grunt) { grunt.loadNpmTasks('grunt-requirejs'); grunt.loadNpmTasks('grunt-karma'); // Default task(s). - grunt.registerTask('default', ['jshint', 'requirejs', 'uglify']); + grunt.registerTask('default', ['jshint', 'requirejs']); grunt.registerTask('test', ['karma']); }; diff --git a/README.md b/README.md index 29ad8f0..fc18f52 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ quaggaJS ======== +- [Changelog](#changelog) (2015-01-21) + QuaggaJS is a barcode-scanner entirely written in JavaScript supporting real-time localization and decoding of various types of barcodes such as __EAN__ and __CODE128__. The library is also capable of using `getUserMedia` to get direct access to the user's camera stream. Although the code relies on heavy image-processing even recent smartphones are @@ -46,24 +48,23 @@ by simply typing: You can check out the [examples](http://serratus.github.io/quaggaJS/examples) to get an idea of how to use QuaggaJS. Basically the library exposes the following API: -### Quagga.init(config) +### Quagga.init(config, callback) -This method initializes the library for a given configuration (see below) which also includes a callback function (`readyFunc`) which is called when Quagga is ready to start. The initialization process also requests for camera access if real-time detection is configured. +This method initializes the library for a given configuration `config` (see below) and invokes the `callback` when Quagga is ready to start. The initialization process also requests for camera access if real-time detection is configured. ```javascript Quagga.init({ - inputStream : { - name : "Live", - type : "LiveStream" - }, - decoder : { - readers : ["code_128_reader"] - }, - readyFunc : function() { - console.log("Initialization finished. Ready to start"); - Quagga.start(); - } -}); + inputStream : { + name : "Live", + type : "LiveStream" + }, + decoder : { + readers : ["code_128_reader"] + } + }, function() { + console.log("Initialization finished. Ready to start"); + Quagga.start(); + }); ``` ### Quagga.start() @@ -73,18 +74,91 @@ the images. ### Quagga.stop() -If the decoder is currently running, after calling `stop()` the decoder does not process any more images. +If the decoder is currently running, after calling `stop()` the decoder does not process any more images. Additionally, if a camera-stream was requested upon initialization, this operation also disconnects the camera. + +### Quagga.onProcessed(callback) + +This method registers a `callback(data)` function that is called for each frame after the processing is done. The `data` object contains detailed information about the success/failure of the operation. The output varies, depending whether the detection and/or decoding were successful or not. ### Quagga.onDetected(callback) -Registers a callback function which is triggered whenever a barcode-pattern has been located and decoded -successfully. The callback function is called with the decoded data as the first parameter. +Registers a `callback(data)` function which is triggered whenever a barcode-pattern has been located and decoded +successfully. The passed `data` object contains information about the decoding process including the detected code which can be obtained by calling `data.codeResult.code`. ### Quagga.decodeSingle(config, callback) In contrast to the calls described above, this method does not rely on `getUserMedia` and operates on a -single image instead. The provided callback is the same as in `onDetected` and contains the decoded data -as first parameter. +single image instead. The provided callback is the same as in `onDetected` and contains the result `data` +object. + +## The result object + +The callbacks passed into `onProcessed`, `onDetected` and `decodeSingle` receive a `data` object upon execution. The `data` object contains the following information. Depending on the success, some fields may be `undefined` or just empty. + +```javascript +{ + "codeResult": { + "code": "FANAVF1461710", + "start": 355, + "end": 26, + "codeset": 100, + "startInfo": { + "error": 1.0000000000000002, + "code": 104, + "start": 21, + "end": 41 + }, + "decodedCodes": [{ + "code": 104, + "start": 21, + "end": 41 + }, + // stripped for brevity + { + "error": 0.8888888888888893, + "code": 106, + "start": 328, + "end": 350 + }], + "endInfo": { + "error": 0.8888888888888893, + "code": 106, + "start": 328, + "end": 350 + }, + "direction": -1 + }, + "line": [{ + "x": 25.97278706156836, + "y": 360.5616435369468 + }, { + "x": 401.9220519377024, + "y": 70.87524989906444 + }], + "angle": -0.6565217179979483, + "pattern": [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, /* ... */ 1], + "box": [ + [77.4074243622672, 410.9288668804402], + [0.050203235235130705, 310.53619724086366], + [360.15706727788256, 33.05711026051813], + [437.5142884049146, 133.44977990009465] + ], + "boxes": [ + [ + [77.4074243622672, 410.9288668804402], + [0.050203235235130705, 310.53619724086366], + [360.15706727788256, 33.05711026051813], + [437.5142884049146, 133.44977990009465] + ], + [ + [248.90769330706507, 415.2041489551161], + [198.9532321622869, 352.62160512937635], + [339.546160777576, 240.3979259789976], + [389.5006219223542, 302.98046980473737] + ] + ] +} +``` ## Config @@ -99,11 +173,13 @@ The default `config` object is set as followed: debug: false, controls: false, locate: true, + numOfWorkers: 4, + scriptName: 'quagga.js', visual: { show: true }, decoder:{ - drawBoundingBox: true, + drawBoundingBox: false, showFrequency: false, drawScanline: true, showPattern: false, @@ -112,6 +188,7 @@ The default `config` object is set as followed: ] }, locator: { + halfSample: true, showCanvas: false, showPatches: false, showFoundPatches: false, @@ -151,7 +228,24 @@ Unit Tests can be run with [Karma][karmaUrl] and written using [Mocha][mochaUrl] > npm install > grunt test ``` - +## Image Debugging + +In case you want to take a deeper dive into the inner workings of Quagga, get to know the _debugging_ capabilities of the current implementation. The various flags exposed through the `config` object give you the abilily to visualize almost every step in the processing. Because of the introduction of the web-workers, and their restriction not to have access to the DOM, the configuration must be explicitly set to `config.numOfWorkers = 0` in order to work. + +## Changelog + +### 2015-01-21 +- Features + - Added support for web-worker (using 4 workers as default, can be changed through `config.numOfWorkers`) + - Due to the way how web-workers are created, the name of the script file (`config.scriptName`) should be kept in sync with your actual filename + - Removed canvas-overlay for decoding (boxes & scanline) which can now be easily implemented using the existing API (see example) +- API Changes +In the course of implementing web-workers some breaking changes were introduced to the API. + - The `Quagga.init` function no longer receives the callback as part of the config but rather as a second argument: `Quagga.init(config, cb)` + - The callback to `Quagga.onDetected` now receives an object containing much more information in addition to the decoded code. (see [data](#resultobject)) + - Added `Quagga.onProcessed(callback)` which provides a way to get information for each image processed. + The callback receives the same `data` object as `Quagga.onDetected` does. Depending on the success of the process the `data` object + might not contain any `resultCode` and/or `box` properties. [zxing_github]: https://github.com/zxing/zxing [teaser_left]: https://github.com/serratus/quaggaJS/blob/master/doc/img/mobile-located.png diff --git a/dist/quagga.js b/dist/quagga.js index 79bcabd..76a435e 100644 --- a/dist/quagga.js +++ b/dist/quagga.js @@ -1621,21 +1621,22 @@ define('input_stream',["image_loader"], function(ImageLoader) { */ glMatrixArrayType = Float32Array; +if (typeof window !== 'undefined') { + window.requestAnimFrame = (function () { + return window.requestAnimationFrame || + window.webkitRequestAnimationFrame || + window.mozRequestAnimationFrame || + window.oRequestAnimationFrame || + window.msRequestAnimationFrame || + function (/* function FrameRequestCallback */ callback, /* DOMElement Element */ element) { + window.setTimeout(callback, 1000 / 60); + }; + })(); -window.requestAnimFrame = (function() { - return window.requestAnimationFrame || - window.webkitRequestAnimationFrame || - window.mozRequestAnimationFrame || - window.oRequestAnimationFrame || - window.msRequestAnimationFrame || - function(/* function FrameRequestCallback */ callback, /* DOMElement Element */ element) { - window.setTimeout(callback, 1000/60); - }; -})(); - + navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia; + window.URL = window.URL || window.webkitURL || window.mozURL || window.msURL; +} -navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia; -window.URL = window.URL || window.webkitURL || window.mozURL || window.msURL; define("typedefs", (function (global) { return function () { var ret, fn; @@ -5708,7 +5709,6 @@ function(ImageWrapper, CVUtils, Rasterizer, Tracer, skeletonizer, ArrayHelper, I _patchLabelGrid, _imageToPatchGrid, _binaryImageWrapper, - _halfSample = true, _patchSize, _canvasContainer = { ctx : { @@ -5720,12 +5720,13 @@ function(ImageWrapper, CVUtils, Rasterizer, Tracer, skeletonizer, ArrayHelper, I }, _numPatches = {x: 0, y: 0}, _inputImageWrapper, - _skeletonizer; + _skeletonizer, + self = this; function initBuffers() { var skeletonImageData; - if (_halfSample) { + if (_config.halfSample) { _currentImageWrapper = new ImageWrapper({ x : _inputImageWrapper.size.x / 2 | 0, y : _inputImageWrapper.size.y / 2 | 0 @@ -5735,8 +5736,8 @@ function(ImageWrapper, CVUtils, Rasterizer, Tracer, skeletonizer, ArrayHelper, I } _patchSize = { - x : 16 * ( _halfSample ? 1 : 2), - y : 16 * ( _halfSample ? 1 : 2) + x : 16 * ( _config.halfSample ? 1 : 2), + y : 16 * ( _config.halfSample ? 1 : 2) }; _numPatches.x = _currentImageWrapper.size.x / _patchSize.x | 0; @@ -5746,10 +5747,10 @@ function(ImageWrapper, CVUtils, Rasterizer, Tracer, skeletonizer, ArrayHelper, I _labelImageWrapper = new ImageWrapper(_patchSize, undefined, Array, true); - skeletonImageData = new ArrayBuffer(_patchSize.x * _patchSize.y * 16); + skeletonImageData = new ArrayBuffer(64*1024); _subImageWrapper = new ImageWrapper(_patchSize, new Uint8Array(skeletonImageData, 0, _patchSize.x * _patchSize.y)); _skelImageWrapper = new ImageWrapper(_patchSize, new Uint8Array(skeletonImageData, _patchSize.x * _patchSize.y * 3, _patchSize.x * _patchSize.y), undefined, true); - _skeletonizer = skeletonizer(window, { + _skeletonizer = skeletonizer(self, { size : _patchSize.x }, skeletonImageData); @@ -5762,6 +5763,9 @@ function(ImageWrapper, CVUtils, Rasterizer, Tracer, skeletonizer, ArrayHelper, I } function initCanvas() { + if (_config.useWorker || typeof document === 'undefined') { + return; + } _canvasContainer.dom.binary = document.createElement("canvas"); _canvasContainer.dom.binary.className = "binaryBuffer"; if (_config.showCanvas === true) { @@ -5836,7 +5840,7 @@ function(ImageWrapper, CVUtils, Rasterizer, Tracer, skeletonizer, ArrayHelper, I ImageDebug.drawPath(box, {x: 0, y: 1}, _canvasContainer.ctx.binary, {color: '#ff0000', lineWidth: 2}); } - scale = _halfSample ? 2 : 1; + scale = _config.halfSample ? 2 : 1; // reverse rotation; transMat = mat2.inverse(transMat); for ( j = 0; j < 4; j++) { @@ -6166,9 +6170,10 @@ function(ImageWrapper, CVUtils, Rasterizer, Tracer, skeletonizer, ArrayHelper, I } return { - init : function(config, data) { + init : function(inputImageWrapper, config) { _config = config; - _inputImageWrapper = data.inputImageWrapper; + _inputImageWrapper = inputImageWrapper; + initBuffers(); initCanvas(); }, @@ -6177,31 +6182,30 @@ function(ImageWrapper, CVUtils, Rasterizer, Tracer, skeletonizer, ArrayHelper, I topLabels = [], boxes = []; - if (_halfSample) { + if (_config.halfSample) { CVUtils.halfSample(_inputImageWrapper, _currentImageWrapper); } - + binarizeImage(); patchesFound = findPatches(); // return unless 5% or more patches are found if (patchesFound.length < _numPatches.x * _numPatches.y * 0.05) { - return; + return null; } - + // rasterrize area by comparing angular similarity; var maxLabel = rasterizeAngularSimilarity(patchesFound); if (maxLabel <= 1) { return null; } - + // search for area with the most patches (biggest connected area) topLabels = findBiggestConnectedAreas(maxLabel); if (topLabels.length === 0) { return null; } - + boxes = findBoxes(topLabels, maxLabel); - return boxes; } }; @@ -6441,30 +6445,32 @@ define('barcode_decoder',["bresenham", "image_debug", 'code_128_reader', 'ean_re initConfig(); function initCanvas() { - var $debug = document.querySelector("#debug.detection"); - _canvas.dom.frequency = document.querySelector("canvas.frequency"); - if (!_canvas.dom.frequency) { - _canvas.dom.frequency = document.createElement("canvas"); - _canvas.dom.frequency.className = "frequency"; - if($debug) { - $debug.appendChild(_canvas.dom.frequency); + if (typeof document !== 'undefined') { + var $debug = document.querySelector("#debug.detection"); + _canvas.dom.frequency = document.querySelector("canvas.frequency"); + if (!_canvas.dom.frequency) { + _canvas.dom.frequency = document.createElement("canvas"); + _canvas.dom.frequency.className = "frequency"; + if($debug) { + $debug.appendChild(_canvas.dom.frequency); + } } - } - _canvas.ctx.frequency = _canvas.dom.frequency.getContext("2d"); - - _canvas.dom.pattern = document.querySelector("canvas.patternBuffer"); - if (!_canvas.dom.pattern) { - _canvas.dom.pattern = document.createElement("canvas"); - _canvas.dom.pattern.className = "patternBuffer"; - if($debug) { - $debug.appendChild(_canvas.dom.pattern); + _canvas.ctx.frequency = _canvas.dom.frequency.getContext("2d"); + + _canvas.dom.pattern = document.querySelector("canvas.patternBuffer"); + if (!_canvas.dom.pattern) { + _canvas.dom.pattern = document.createElement("canvas"); + _canvas.dom.pattern.className = "patternBuffer"; + if($debug) { + $debug.appendChild(_canvas.dom.pattern); + } } - } - _canvas.ctx.pattern = _canvas.dom.pattern.getContext("2d"); + _canvas.ctx.pattern = _canvas.dom.pattern.getContext("2d"); - _canvas.dom.overlay = document.querySelector("canvas.drawingBuffer"); - if (_canvas.dom.overlay) { - _canvas.ctx.overlay = _canvas.dom.overlay.getContext("2d"); + _canvas.dom.overlay = document.querySelector("canvas.drawingBuffer"); + if (_canvas.dom.overlay) { + _canvas.ctx.overlay = _canvas.dom.overlay.getContext("2d"); + } } } @@ -6477,20 +6483,22 @@ define('barcode_decoder',["bresenham", "image_debug", 'code_128_reader', 'ean_re } function initConfig() { - var i, - vis = [{ - node : _canvas.dom.frequency, - prop : config.showFrequency - }, { - node : _canvas.dom.pattern, - prop : config.showPattern - }]; - - for (i = 0; i < vis.length; i++) { - if (vis[i].prop === true) { - vis[i].node.style.display = "block"; - } else { - vis[i].node.style.display = "none"; + if (typeof document !== 'undefined') { + var i, + vis = [{ + node : _canvas.dom.frequency, + prop : config.showFrequency + }, { + node : _canvas.dom.pattern, + prop : config.showPattern + }]; + + for (i = 0; i < vis.length; i++) { + if (vis[i].prop === true) { + vis[i].node.style.display = "block"; + } else { + vis[i].node.style.display = "none"; + } } } } @@ -6646,6 +6654,7 @@ define('barcode_decoder',["bresenham", "image_debug", 'code_128_reader', 'ean_re for ( i = 0; i < boxes.length; i++) { result = decodeFromBoundingBox(boxes[i]); if (result && result.codeResult) { + result.box = boxes[i]; return result; } } @@ -6807,19 +6816,22 @@ define('config',[],function(){ debug: false, controls: false, locate: true, + numOfWorkers: 4, + scriptName: 'quagga.js', visual: { show: true }, decoder:{ - drawBoundingBox: true, + drawBoundingBox: false, showFrequency: false, - drawScanline: true, + drawScanline: false, showPattern: false, readers: [ 'code_128_reader' ] }, locator: { + halfSample: true, showCanvas: false, showPatches: false, showFoundPatches: false, @@ -6862,10 +6874,10 @@ define('events',[],function() { function publishSubscription(subscription, data) { if (subscription.async) { setTimeout(function() { - subscription.callback.call(null, data); + subscription.callback(data); }, 4); } else { - subscription.callback.call(null, data); + subscription.callback(data); } } @@ -7031,11 +7043,1135 @@ define('camera_access',[],function() { } }; }); +/*! + * async + * https://github.com/caolan/async + * + * Copyright 2010-2014 Caolan McMahon + * Released under the MIT license + */ +/*jshint onevar: false, indent:4 */ +/*global setImmediate: false, setTimeout: false, console: false */ +(function () { + + var async = {}; + + // global on the server, window in the browser + var root, previous_async; + + root = this; + if (root != null) { + previous_async = root.async; + } + + async.noConflict = function () { + root.async = previous_async; + return async; + }; + + function only_once(fn) { + var called = false; + return function() { + if (called) throw new Error("Callback was already called."); + called = true; + fn.apply(root, arguments); + } + } + + //// cross-browser compatiblity functions //// + + var _toString = Object.prototype.toString; + + var _isArray = Array.isArray || function (obj) { + return _toString.call(obj) === '[object Array]'; + }; + + var _each = function (arr, iterator) { + for (var i = 0; i < arr.length; i += 1) { + iterator(arr[i], i, arr); + } + }; + + var _map = function (arr, iterator) { + if (arr.map) { + return arr.map(iterator); + } + var results = []; + _each(arr, function (x, i, a) { + results.push(iterator(x, i, a)); + }); + return results; + }; + + var _reduce = function (arr, iterator, memo) { + if (arr.reduce) { + return arr.reduce(iterator, memo); + } + _each(arr, function (x, i, a) { + memo = iterator(memo, x, i, a); + }); + return memo; + }; + + var _keys = function (obj) { + if (Object.keys) { + return Object.keys(obj); + } + var keys = []; + for (var k in obj) { + if (obj.hasOwnProperty(k)) { + keys.push(k); + } + } + return keys; + }; + + //// exported async module functions //// + + //// nextTick implementation with browser-compatible fallback //// + if (typeof process === 'undefined' || !(process.nextTick)) { + if (typeof setImmediate === 'function') { + async.nextTick = function (fn) { + // not a direct alias for IE10 compatibility + setImmediate(fn); + }; + async.setImmediate = async.nextTick; + } + else { + async.nextTick = function (fn) { + setTimeout(fn, 0); + }; + async.setImmediate = async.nextTick; + } + } + else { + async.nextTick = process.nextTick; + if (typeof setImmediate !== 'undefined') { + async.setImmediate = function (fn) { + // not a direct alias for IE10 compatibility + setImmediate(fn); + }; + } + else { + async.setImmediate = async.nextTick; + } + } + + async.each = function (arr, iterator, callback) { + callback = callback || function () {}; + if (!arr.length) { + return callback(); + } + var completed = 0; + _each(arr, function (x) { + iterator(x, only_once(done) ); + }); + function done(err) { + if (err) { + callback(err); + callback = function () {}; + } + else { + completed += 1; + if (completed >= arr.length) { + callback(); + } + } + } + }; + async.forEach = async.each; + + async.eachSeries = function (arr, iterator, callback) { + callback = callback || function () {}; + if (!arr.length) { + return callback(); + } + var completed = 0; + var iterate = function () { + iterator(arr[completed], function (err) { + if (err) { + callback(err); + callback = function () {}; + } + else { + completed += 1; + if (completed >= arr.length) { + callback(); + } + else { + iterate(); + } + } + }); + }; + iterate(); + }; + async.forEachSeries = async.eachSeries; + + async.eachLimit = function (arr, limit, iterator, callback) { + var fn = _eachLimit(limit); + fn.apply(null, [arr, iterator, callback]); + }; + async.forEachLimit = async.eachLimit; + + var _eachLimit = function (limit) { + + return function (arr, iterator, callback) { + callback = callback || function () {}; + if (!arr.length || limit <= 0) { + return callback(); + } + var completed = 0; + var started = 0; + var running = 0; + + (function replenish () { + if (completed >= arr.length) { + return callback(); + } + + while (running < limit && started < arr.length) { + started += 1; + running += 1; + iterator(arr[started - 1], function (err) { + if (err) { + callback(err); + callback = function () {}; + } + else { + completed += 1; + running -= 1; + if (completed >= arr.length) { + callback(); + } + else { + replenish(); + } + } + }); + } + })(); + }; + }; + + + var doParallel = function (fn) { + return function () { + var args = Array.prototype.slice.call(arguments); + return fn.apply(null, [async.each].concat(args)); + }; + }; + var doParallelLimit = function(limit, fn) { + return function () { + var args = Array.prototype.slice.call(arguments); + return fn.apply(null, [_eachLimit(limit)].concat(args)); + }; + }; + var doSeries = function (fn) { + return function () { + var args = Array.prototype.slice.call(arguments); + return fn.apply(null, [async.eachSeries].concat(args)); + }; + }; + + + var _asyncMap = function (eachfn, arr, iterator, callback) { + arr = _map(arr, function (x, i) { + return {index: i, value: x}; + }); + if (!callback) { + eachfn(arr, function (x, callback) { + iterator(x.value, function (err) { + callback(err); + }); + }); + } else { + var results = []; + eachfn(arr, function (x, callback) { + iterator(x.value, function (err, v) { + results[x.index] = v; + callback(err); + }); + }, function (err) { + callback(err, results); + }); + } + }; + async.map = doParallel(_asyncMap); + async.mapSeries = doSeries(_asyncMap); + async.mapLimit = function (arr, limit, iterator, callback) { + return _mapLimit(limit)(arr, iterator, callback); + }; + + var _mapLimit = function(limit) { + return doParallelLimit(limit, _asyncMap); + }; + + // reduce only has a series version, as doing reduce in parallel won't + // work in many situations. + async.reduce = function (arr, memo, iterator, callback) { + async.eachSeries(arr, function (x, callback) { + iterator(memo, x, function (err, v) { + memo = v; + callback(err); + }); + }, function (err) { + callback(err, memo); + }); + }; + // inject alias + async.inject = async.reduce; + // foldl alias + async.foldl = async.reduce; + + async.reduceRight = function (arr, memo, iterator, callback) { + var reversed = _map(arr, function (x) { + return x; + }).reverse(); + async.reduce(reversed, memo, iterator, callback); + }; + // foldr alias + async.foldr = async.reduceRight; + + var _filter = function (eachfn, arr, iterator, callback) { + var results = []; + arr = _map(arr, function (x, i) { + return {index: i, value: x}; + }); + eachfn(arr, function (x, callback) { + iterator(x.value, function (v) { + if (v) { + results.push(x); + } + callback(); + }); + }, function (err) { + callback(_map(results.sort(function (a, b) { + return a.index - b.index; + }), function (x) { + return x.value; + })); + }); + }; + async.filter = doParallel(_filter); + async.filterSeries = doSeries(_filter); + // select alias + async.select = async.filter; + async.selectSeries = async.filterSeries; + + var _reject = function (eachfn, arr, iterator, callback) { + var results = []; + arr = _map(arr, function (x, i) { + return {index: i, value: x}; + }); + eachfn(arr, function (x, callback) { + iterator(x.value, function (v) { + if (!v) { + results.push(x); + } + callback(); + }); + }, function (err) { + callback(_map(results.sort(function (a, b) { + return a.index - b.index; + }), function (x) { + return x.value; + })); + }); + }; + async.reject = doParallel(_reject); + async.rejectSeries = doSeries(_reject); + + var _detect = function (eachfn, arr, iterator, main_callback) { + eachfn(arr, function (x, callback) { + iterator(x, function (result) { + if (result) { + main_callback(x); + main_callback = function () {}; + } + else { + callback(); + } + }); + }, function (err) { + main_callback(); + }); + }; + async.detect = doParallel(_detect); + async.detectSeries = doSeries(_detect); + + async.some = function (arr, iterator, main_callback) { + async.each(arr, function (x, callback) { + iterator(x, function (v) { + if (v) { + main_callback(true); + main_callback = function () {}; + } + callback(); + }); + }, function (err) { + main_callback(false); + }); + }; + // any alias + async.any = async.some; + + async.every = function (arr, iterator, main_callback) { + async.each(arr, function (x, callback) { + iterator(x, function (v) { + if (!v) { + main_callback(false); + main_callback = function () {}; + } + callback(); + }); + }, function (err) { + main_callback(true); + }); + }; + // all alias + async.all = async.every; + + async.sortBy = function (arr, iterator, callback) { + async.map(arr, function (x, callback) { + iterator(x, function (err, criteria) { + if (err) { + callback(err); + } + else { + callback(null, {value: x, criteria: criteria}); + } + }); + }, function (err, results) { + if (err) { + return callback(err); + } + else { + var fn = function (left, right) { + var a = left.criteria, b = right.criteria; + return a < b ? -1 : a > b ? 1 : 0; + }; + callback(null, _map(results.sort(fn), function (x) { + return x.value; + })); + } + }); + }; + + async.auto = function (tasks, callback) { + callback = callback || function () {}; + var keys = _keys(tasks); + var remainingTasks = keys.length + if (!remainingTasks) { + return callback(); + } + + var results = {}; + + var listeners = []; + var addListener = function (fn) { + listeners.unshift(fn); + }; + var removeListener = function (fn) { + for (var i = 0; i < listeners.length; i += 1) { + if (listeners[i] === fn) { + listeners.splice(i, 1); + return; + } + } + }; + var taskComplete = function () { + remainingTasks-- + _each(listeners.slice(0), function (fn) { + fn(); + }); + }; + + addListener(function () { + if (!remainingTasks) { + var theCallback = callback; + // prevent final callback from calling itself if it errors + callback = function () {}; + + theCallback(null, results); + } + }); + + _each(keys, function (k) { + var task = _isArray(tasks[k]) ? tasks[k]: [tasks[k]]; + var taskCallback = function (err) { + var args = Array.prototype.slice.call(arguments, 1); + if (args.length <= 1) { + args = args[0]; + } + if (err) { + var safeResults = {}; + _each(_keys(results), function(rkey) { + safeResults[rkey] = results[rkey]; + }); + safeResults[k] = args; + callback(err, safeResults); + // stop subsequent errors hitting callback multiple times + callback = function () {}; + } + else { + results[k] = args; + async.setImmediate(taskComplete); + } + }; + var requires = task.slice(0, Math.abs(task.length - 1)) || []; + var ready = function () { + return _reduce(requires, function (a, x) { + return (a && results.hasOwnProperty(x)); + }, true) && !results.hasOwnProperty(k); + }; + if (ready()) { + task[task.length - 1](taskCallback, results); + } + else { + var listener = function () { + if (ready()) { + removeListener(listener); + task[task.length - 1](taskCallback, results); + } + }; + addListener(listener); + } + }); + }; + + async.retry = function(times, task, callback) { + var DEFAULT_TIMES = 5; + var attempts = []; + // Use defaults if times not passed + if (typeof times === 'function') { + callback = task; + task = times; + times = DEFAULT_TIMES; + } + // Make sure times is a number + times = parseInt(times, 10) || DEFAULT_TIMES; + var wrappedTask = function(wrappedCallback, wrappedResults) { + var retryAttempt = function(task, finalAttempt) { + return function(seriesCallback) { + task(function(err, result){ + seriesCallback(!err || finalAttempt, {err: err, result: result}); + }, wrappedResults); + }; + }; + while (times) { + attempts.push(retryAttempt(task, !(times-=1))); + } + async.series(attempts, function(done, data){ + data = data[data.length - 1]; + (wrappedCallback || callback)(data.err, data.result); + }); + } + // If a callback is passed, run this as a controll flow + return callback ? wrappedTask() : wrappedTask + }; + + async.waterfall = function (tasks, callback) { + callback = callback || function () {}; + if (!_isArray(tasks)) { + var err = new Error('First argument to waterfall must be an array of functions'); + return callback(err); + } + if (!tasks.length) { + return callback(); + } + var wrapIterator = function (iterator) { + return function (err) { + if (err) { + callback.apply(null, arguments); + callback = function () {}; + } + else { + var args = Array.prototype.slice.call(arguments, 1); + var next = iterator.next(); + if (next) { + args.push(wrapIterator(next)); + } + else { + args.push(callback); + } + async.setImmediate(function () { + iterator.apply(null, args); + }); + } + }; + }; + wrapIterator(async.iterator(tasks))(); + }; + + var _parallel = function(eachfn, tasks, callback) { + callback = callback || function () {}; + if (_isArray(tasks)) { + eachfn.map(tasks, function (fn, callback) { + if (fn) { + fn(function (err) { + var args = Array.prototype.slice.call(arguments, 1); + if (args.length <= 1) { + args = args[0]; + } + callback.call(null, err, args); + }); + } + }, callback); + } + else { + var results = {}; + eachfn.each(_keys(tasks), function (k, callback) { + tasks[k](function (err) { + var args = Array.prototype.slice.call(arguments, 1); + if (args.length <= 1) { + args = args[0]; + } + results[k] = args; + callback(err); + }); + }, function (err) { + callback(err, results); + }); + } + }; + + async.parallel = function (tasks, callback) { + _parallel({ map: async.map, each: async.each }, tasks, callback); + }; + + async.parallelLimit = function(tasks, limit, callback) { + _parallel({ map: _mapLimit(limit), each: _eachLimit(limit) }, tasks, callback); + }; + + async.series = function (tasks, callback) { + callback = callback || function () {}; + if (_isArray(tasks)) { + async.mapSeries(tasks, function (fn, callback) { + if (fn) { + fn(function (err) { + var args = Array.prototype.slice.call(arguments, 1); + if (args.length <= 1) { + args = args[0]; + } + callback.call(null, err, args); + }); + } + }, callback); + } + else { + var results = {}; + async.eachSeries(_keys(tasks), function (k, callback) { + tasks[k](function (err) { + var args = Array.prototype.slice.call(arguments, 1); + if (args.length <= 1) { + args = args[0]; + } + results[k] = args; + callback(err); + }); + }, function (err) { + callback(err, results); + }); + } + }; + + async.iterator = function (tasks) { + var makeCallback = function (index) { + var fn = function () { + if (tasks.length) { + tasks[index].apply(null, arguments); + } + return fn.next(); + }; + fn.next = function () { + return (index < tasks.length - 1) ? makeCallback(index + 1): null; + }; + return fn; + }; + return makeCallback(0); + }; + + async.apply = function (fn) { + var args = Array.prototype.slice.call(arguments, 1); + return function () { + return fn.apply( + null, args.concat(Array.prototype.slice.call(arguments)) + ); + }; + }; + + var _concat = function (eachfn, arr, fn, callback) { + var r = []; + eachfn(arr, function (x, cb) { + fn(x, function (err, y) { + r = r.concat(y || []); + cb(err); + }); + }, function (err) { + callback(err, r); + }); + }; + async.concat = doParallel(_concat); + async.concatSeries = doSeries(_concat); + + async.whilst = function (test, iterator, callback) { + if (test()) { + iterator(function (err) { + if (err) { + return callback(err); + } + async.whilst(test, iterator, callback); + }); + } + else { + callback(); + } + }; + + async.doWhilst = function (iterator, test, callback) { + iterator(function (err) { + if (err) { + return callback(err); + } + var args = Array.prototype.slice.call(arguments, 1); + if (test.apply(null, args)) { + async.doWhilst(iterator, test, callback); + } + else { + callback(); + } + }); + }; + + async.until = function (test, iterator, callback) { + if (!test()) { + iterator(function (err) { + if (err) { + return callback(err); + } + async.until(test, iterator, callback); + }); + } + else { + callback(); + } + }; + + async.doUntil = function (iterator, test, callback) { + iterator(function (err) { + if (err) { + return callback(err); + } + var args = Array.prototype.slice.call(arguments, 1); + if (!test.apply(null, args)) { + async.doUntil(iterator, test, callback); + } + else { + callback(); + } + }); + }; + + async.queue = function (worker, concurrency) { + if (concurrency === undefined) { + concurrency = 1; + } + function _insert(q, data, pos, callback) { + if (!q.started){ + q.started = true; + } + if (!_isArray(data)) { + data = [data]; + } + if(data.length == 0) { + // call drain immediately if there are no tasks + return async.setImmediate(function() { + if (q.drain) { + q.drain(); + } + }); + } + _each(data, function(task) { + var item = { + data: task, + callback: typeof callback === 'function' ? callback : null + }; + + if (pos) { + q.tasks.unshift(item); + } else { + q.tasks.push(item); + } + + if (q.saturated && q.tasks.length === q.concurrency) { + q.saturated(); + } + async.setImmediate(q.process); + }); + } + + var workers = 0; + var q = { + tasks: [], + concurrency: concurrency, + saturated: null, + empty: null, + drain: null, + started: false, + paused: false, + push: function (data, callback) { + _insert(q, data, false, callback); + }, + kill: function () { + q.drain = null; + q.tasks = []; + }, + unshift: function (data, callback) { + _insert(q, data, true, callback); + }, + process: function () { + if (!q.paused && workers < q.concurrency && q.tasks.length) { + var task = q.tasks.shift(); + if (q.empty && q.tasks.length === 0) { + q.empty(); + } + workers += 1; + var next = function () { + workers -= 1; + if (task.callback) { + task.callback.apply(task, arguments); + } + if (q.drain && q.tasks.length + workers === 0) { + q.drain(); + } + q.process(); + }; + var cb = only_once(next); + worker(task.data, cb); + } + }, + length: function () { + return q.tasks.length; + }, + running: function () { + return workers; + }, + idle: function() { + return q.tasks.length + workers === 0; + }, + pause: function () { + if (q.paused === true) { return; } + q.paused = true; + }, + resume: function () { + if (q.paused === false) { return; } + q.paused = false; + // Need to call q.process once per concurrent + // worker to preserve full concurrency after pause + for (var w = 1; w <= q.concurrency; w++) { + async.setImmediate(q.process); + } + } + }; + return q; + }; + + async.priorityQueue = function (worker, concurrency) { + + function _compareTasks(a, b){ + return a.priority - b.priority; + }; + + function _binarySearch(sequence, item, compare) { + var beg = -1, + end = sequence.length - 1; + while (beg < end) { + var mid = beg + ((end - beg + 1) >>> 1); + if (compare(item, sequence[mid]) >= 0) { + beg = mid; + } else { + end = mid - 1; + } + } + return beg; + } + + function _insert(q, data, priority, callback) { + if (!q.started){ + q.started = true; + } + if (!_isArray(data)) { + data = [data]; + } + if(data.length == 0) { + // call drain immediately if there are no tasks + return async.setImmediate(function() { + if (q.drain) { + q.drain(); + } + }); + } + _each(data, function(task) { + var item = { + data: task, + priority: priority, + callback: typeof callback === 'function' ? callback : null + }; + + q.tasks.splice(_binarySearch(q.tasks, item, _compareTasks) + 1, 0, item); + + if (q.saturated && q.tasks.length === q.concurrency) { + q.saturated(); + } + async.setImmediate(q.process); + }); + } + + // Start with a normal queue + var q = async.queue(worker, concurrency); + + // Override push to accept second parameter representing priority + q.push = function (data, priority, callback) { + _insert(q, data, priority, callback); + }; + + // Remove unshift function + delete q.unshift; + + return q; + }; + + async.cargo = function (worker, payload) { + var working = false, + tasks = []; + + var cargo = { + tasks: tasks, + payload: payload, + saturated: null, + empty: null, + drain: null, + drained: true, + push: function (data, callback) { + if (!_isArray(data)) { + data = [data]; + } + _each(data, function(task) { + tasks.push({ + data: task, + callback: typeof callback === 'function' ? callback : null + }); + cargo.drained = false; + if (cargo.saturated && tasks.length === payload) { + cargo.saturated(); + } + }); + async.setImmediate(cargo.process); + }, + process: function process() { + if (working) return; + if (tasks.length === 0) { + if(cargo.drain && !cargo.drained) cargo.drain(); + cargo.drained = true; + return; + } + + var ts = typeof payload === 'number' + ? tasks.splice(0, payload) + : tasks.splice(0, tasks.length); + + var ds = _map(ts, function (task) { + return task.data; + }); + + if(cargo.empty) cargo.empty(); + working = true; + worker(ds, function () { + working = false; + + var args = arguments; + _each(ts, function (data) { + if (data.callback) { + data.callback.apply(null, args); + } + }); + + process(); + }); + }, + length: function () { + return tasks.length; + }, + running: function () { + return working; + } + }; + return cargo; + }; + + var _console_fn = function (name) { + return function (fn) { + var args = Array.prototype.slice.call(arguments, 1); + fn.apply(null, args.concat([function (err) { + var args = Array.prototype.slice.call(arguments, 1); + if (typeof console !== 'undefined') { + if (err) { + if (console.error) { + console.error(err); + } + } + else if (console[name]) { + _each(args, function (x) { + console[name](x); + }); + } + } + }])); + }; + }; + async.log = _console_fn('log'); + async.dir = _console_fn('dir'); + /*async.info = _console_fn('info'); + async.warn = _console_fn('warn'); + async.error = _console_fn('error');*/ + + async.memoize = function (fn, hasher) { + var memo = {}; + var queues = {}; + hasher = hasher || function (x) { + return x; + }; + var memoized = function () { + var args = Array.prototype.slice.call(arguments); + var callback = args.pop(); + var key = hasher.apply(null, args); + if (key in memo) { + async.nextTick(function () { + callback.apply(null, memo[key]); + }); + } + else if (key in queues) { + queues[key].push(callback); + } + else { + queues[key] = [callback]; + fn.apply(null, args.concat([function () { + memo[key] = arguments; + var q = queues[key]; + delete queues[key]; + for (var i = 0, l = q.length; i < l; i++) { + q[i].apply(null, arguments); + } + }])); + } + }; + memoized.memo = memo; + memoized.unmemoized = fn; + return memoized; + }; + + async.unmemoize = function (fn) { + return function () { + return (fn.unmemoized || fn).apply(null, arguments); + }; + }; + + async.times = function (count, iterator, callback) { + var counter = []; + for (var i = 0; i < count; i++) { + counter.push(i); + } + return async.map(counter, iterator, callback); + }; + + async.timesSeries = function (count, iterator, callback) { + var counter = []; + for (var i = 0; i < count; i++) { + counter.push(i); + } + return async.mapSeries(counter, iterator, callback); + }; + + async.seq = function (/* functions... */) { + var fns = arguments; + return function () { + var that = this; + var args = Array.prototype.slice.call(arguments); + var callback = args.pop(); + async.reduce(fns, args, function (newargs, fn, cb) { + fn.apply(that, newargs.concat([function () { + var err = arguments[0]; + var nextargs = Array.prototype.slice.call(arguments, 1); + cb(err, nextargs); + }])) + }, + function (err, results) { + callback.apply(that, [err].concat(results)); + }); + }; + }; + + async.compose = function (/* functions... */) { + return async.seq.apply(null, Array.prototype.reverse.call(arguments)); + }; + + var _applyEach = function (eachfn, fns /*args...*/) { + var go = function () { + var that = this; + var args = Array.prototype.slice.call(arguments); + var callback = args.pop(); + return eachfn(fns, function (fn, cb) { + fn.apply(that, args.concat([cb])); + }, + callback); + }; + if (arguments.length > 2) { + var args = Array.prototype.slice.call(arguments, 2); + return go.apply(this, args); + } + else { + return go; + } + }; + async.applyEach = doParallel(_applyEach); + async.applyEachSeries = doSeries(_applyEach); + + async.forever = function (fn, callback) { + function next(err) { + if (err) { + if (callback) { + return callback(err); + } + throw err; + } + fn(next); + } + next(); + }; + + // Node.js + if (typeof module !== 'undefined' && module.exports) { + module.exports = async; + } + // AMD / RequireJS + else if (typeof define !== 'undefined' && define.amd) { + define('async',[], function () { + return async; + }); + } + // included directly via