queue.js 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  1. var util = require('util');
  2. var events = require('events');
  3. var Logger = require('./../util/logger.js');
  4. function AsyncTree() {
  5. events.EventEmitter.call(this);
  6. this.rootNode = {
  7. name : '__root__',
  8. children : [],
  9. started : false,
  10. done : false,
  11. parent : this.rootNode
  12. };
  13. this.currentNode = this.rootNode;
  14. }
  15. util.inherits(AsyncTree, events.EventEmitter);
  16. AsyncTree.prototype.append = function(nodeName, command, context, args, stackTrace) {
  17. var node = {
  18. startTime : null,
  19. name : nodeName,
  20. command : command,
  21. context : context,
  22. args : args || [],
  23. started : false,
  24. done : false,
  25. children : [],
  26. parent : this.currentNode,
  27. stackTrace : stackTrace
  28. };
  29. this.currentNode.children.push(node);
  30. if (this.currentNode.started && !this.currentNode.done) {
  31. this.scheduleTraverse(node);
  32. }
  33. };
  34. AsyncTree.prototype.print = function(node, level) {
  35. process.stdout.write(node.name + '\n');
  36. level = level || 1;
  37. for (var i = 0; i < node.children.length; i++) {
  38. var childNode = node.children[i];
  39. for (var k = 0; k < level; k++) {
  40. process.stdout.write(' |- ');
  41. }
  42. if (childNode.children.length) {
  43. var levelUp = level + 1;
  44. arguments.callee(childNode, levelUp);
  45. } else {
  46. process.stdout.write(childNode.name + '\n');
  47. }
  48. }
  49. process.stdout.write('');
  50. };
  51. AsyncTree.prototype.scheduleTraverse = function(node) {
  52. if (this.scheduled) {
  53. return this;
  54. }
  55. this.scheduled = true;
  56. var self = this;
  57. process.nextTick(function() {
  58. self.scheduled = false;
  59. self.traverse();
  60. });
  61. };
  62. AsyncTree.prototype.traverse = function() {
  63. this.emit('queue:started');
  64. this.currentNode.started = true;
  65. this.walkDown(this.currentNode);
  66. return this;
  67. };
  68. AsyncTree.prototype.walkDown = function walkDown(context) {
  69. var node = this.getNextChild(context);
  70. if (node) {
  71. this.runChildNode(node);
  72. } else {
  73. if (context.instance && !context.done) {
  74. return;
  75. }
  76. this.currentNode.done = true;
  77. if (this.currentNode.name === '__root__') {
  78. this.done();
  79. } else {
  80. this.walkUp(context);
  81. }
  82. }
  83. };
  84. AsyncTree.prototype.walkUp = function(context) {
  85. this.currentNode = context.parent;
  86. this.walkDown(context.parent);
  87. };
  88. AsyncTree.prototype.getNextChild = function(context) {
  89. for (var i = 0; i < context.children.length; i++) {
  90. if (!context.children[i].started) {
  91. var child = context.children[i];
  92. context.children.splice(i, 1);
  93. return child;
  94. }
  95. }
  96. return false;
  97. };
  98. AsyncTree.prototype.runChildNode = function runChildNode(node) {
  99. var self = this;
  100. this.runCommand(node, function onCommandComplete() {
  101. var timems = new Date().getTime() - node.startTime;
  102. // checking if new children have been added while running this command which haven't finished yet
  103. var childrenInProgress = false;
  104. var currentChildNode = null;
  105. for (var i = 0; i < node.children.length; i++) {
  106. currentChildNode = node.children[i];
  107. if (!currentChildNode.done) {
  108. childrenInProgress = true;
  109. break;
  110. }
  111. }
  112. Logger.log(' ' + Logger.colors.green('→') +
  113. ' Completed command ' + Logger.colors.light_green(node.name), '(' + timems, 'ms)');
  114. node.done = true;
  115. if (!childrenInProgress) {
  116. self.traverse();
  117. }
  118. });
  119. };
  120. AsyncTree.prototype.runCommand = function(node, callback) {
  121. this.currentNode = node;
  122. node.started = true;
  123. try {
  124. var commandFn = node.command;
  125. if (typeof node.command !== 'function') {
  126. // backwards compatibility
  127. commandFn = node.command.command;
  128. }
  129. if (typeof commandFn !== 'function') {
  130. throw new Error('Command must be a function');
  131. }
  132. node.startTime = new Date().getTime();
  133. commandFn.prototype.constructor.stackTrace = node.stackTrace;
  134. var instance = commandFn.apply(node.context, node.args);
  135. if (instance instanceof events.EventEmitter) {
  136. node.instance = instance;
  137. instance.once('complete', callback);
  138. }
  139. return node.context;
  140. } catch (err) {
  141. err.stack = node.stackTrace;
  142. err.name = 'Error while running ' + node.name + ' command';
  143. this.emit('error', err);
  144. return this;
  145. }
  146. };
  147. AsyncTree.prototype.done = function() {
  148. this.rootNode.started = false;
  149. this.emit('queue:finished');
  150. return this;
  151. };
  152. AsyncTree.prototype.empty = function() {
  153. this.rootNode.children = [];
  154. return this;
  155. };
  156. AsyncTree.prototype.reset = function() {
  157. this.rootNode.started = false;
  158. this.rootNode.done = false;
  159. this.removeAllListeners();
  160. this.currentNode = this.rootNode;
  161. return this;
  162. };
  163. module.exports = new (function() {
  164. var queue = new AsyncTree();
  165. this.reset = function() {
  166. queue.reset();
  167. };
  168. this.empty = function() {
  169. queue.empty();
  170. };
  171. this.add = function() {
  172. queue.append.apply(queue, arguments);
  173. };
  174. this.run = function queueRunner(callback) {
  175. if (queue.rootNode.started) {
  176. return queue;
  177. }
  178. if (callback) {
  179. queue.once('queue:finished', function() {
  180. callback(null);
  181. })
  182. .on('error', function(err) {
  183. callback(err);
  184. });
  185. }
  186. return queue.traverse();
  187. };
  188. this.done = function() {
  189. queue.done();
  190. };
  191. this.instance = function instance() {
  192. return queue;
  193. };
  194. this.list = function list() {
  195. return queue.rootNode.children.map(function(item) {
  196. return item.name;
  197. });
  198. };
  199. })();