[Service] Integrate DeviceHome and SignalingServer
[platform/framework/web/wrtjs.git] / device_home / node_modules / jake / lib / task / task.js
1 let EventEmitter = require('events').EventEmitter;
2 let async = require('async');
3 let chalk = require('chalk');
4 // 'rule' module is required at the bottom because circular deps
5
6 // Used for task value, so better not to use
7 // null, since value should be unset/uninitialized
8 let UNDEFINED_VALUE;
9
10 const ROOT_TASK_NAME = '__rootTask__';
11 const POLLING_INTERVAL = 100;
12
13 // Parse any positional args attached to the task-name
14 function parsePrereqName(name) {
15   let taskArr = name.split('[');
16   let taskName = taskArr[0];
17   let taskArgs = [];
18   if (taskArr[1]) {
19     taskArgs = taskArr[1].replace(/\]$/, '');
20     taskArgs = taskArgs.split(',');
21   }
22   return {
23     name: taskName,
24     args: taskArgs
25   };
26 }
27
28 /**
29   @name jake.Task
30   @class
31   @extends EventEmitter
32   @description A Jake Task
33
34   @param {String} name The name of the Task
35   @param {Array} [prereqs] Prerequisites to be run before this task
36   @param {Function} [action] The action to perform for this task
37   @param {Object} [opts]
38     @param {Array} [opts.asyc=false] Perform this task asynchronously.
39     If you flag a task with this option, you must call the global
40     `complete` method inside the task's action, for execution to proceed
41     to the next task.
42  */
43 class Task extends EventEmitter {
44
45   constructor(name, prereqs, action, options) {
46     // EventEmitter ctor takes no args
47     super();
48
49     if (name.indexOf(':') > -1) {
50       throw new Error('Task name cannot include a colon. It is used internally as namespace delimiter.');
51     }
52     let opts = options || {};
53
54     this._currentPrereqIndex = 0;
55     this._internal = false;
56     this._skipped = false;
57
58     this.name = name;
59     this.prereqs = prereqs;
60     this.action = action;
61     this.async = false;
62     this.taskStatus = Task.runStatuses.UNSTARTED;
63     this.description = null;
64     this.args = [];
65     this.value = UNDEFINED_VALUE;
66     this.concurrency = 1;
67     this.startTime = null;
68     this.endTime = null;
69     this.directory = null;
70     this.namespace = null;
71
72     // Support legacy async-flag -- if not explicitly passed or falsy, will
73     // be set to empty-object
74     if (typeof opts == 'boolean' && opts === true) {
75       this.async = true;
76     }
77     else {
78       if (opts.async) {
79         this.async = true;
80       }
81       if (opts.concurrency) {
82         this.concurrency = opts.concurrency;
83       }
84     }
85
86     //Do a test on self dependencies for this task
87     if(Array.isArray(this.prereqs) && this.prereqs.indexOf(this.name) !== -1) {
88       throw new Error("Cannot use prereq " + this.name + " as a dependency of itself");
89     }
90   }
91
92   get fullName() {
93     return this._getFullName();
94   }
95
96   _initInvocationChain() {
97     // Legacy global invocation chain
98     jake._invocationChain.push(this);
99
100     // New root chain
101     if (!this._invocationChain) {
102       this._invocationChainRoot = true;
103       this._invocationChain = [];
104       if (jake.currentRunningTask) {
105         jake.currentRunningTask._waitForChains = jake.currentRunningTask._waitForChains || [];
106         jake.currentRunningTask._waitForChains.push(this._invocationChain);
107       }
108     }
109   }
110
111   /**
112     @name jake.Task#invoke
113     @function
114     @description Runs prerequisites, then this task. If the task has already
115     been run, will not run the task again.
116    */
117   invoke() {
118     this._initInvocationChain();
119
120     this.args = Array.prototype.slice.call(arguments);
121     this.reenabled = false
122     this.runPrereqs();
123   }
124
125   /**
126     @name jake.Task#execute
127     @function
128     @description Run only this task, without prereqs. If the task has already
129     been run, *will* run the task again.
130    */
131   execute() {
132     this._initInvocationChain();
133
134     this.args = Array.prototype.slice.call(arguments);
135     this.reenable();
136     this.reenabled = true
137     this.run();
138   }
139
140   runPrereqs() {
141     if (this.prereqs && this.prereqs.length) {
142
143       if (this.concurrency > 1) {
144         async.eachLimit(this.prereqs, this.concurrency,
145
146           (name, cb) => {
147             let parsed = parsePrereqName(name);
148
149             let prereq = this.namespace.resolveTask(parsed.name) ||
150           jake.attemptRule(name, this.namespace, 0) ||
151           jake.createPlaceholderFileTask(name, this.namespace);
152
153             if (!prereq) {
154               throw new Error('Unknown task "' + name + '"');
155             }
156
157             //Test for circular invocation
158             if(prereq === this) {
159               setImmediate(function () {
160                 cb(new Error("Cannot use prereq " + prereq.name + " as a dependency of itself"));
161               });
162             }
163
164             if (prereq.taskStatus == Task.runStatuses.DONE) {
165             //prereq already done, return
166               setImmediate(cb);
167             }
168             else {
169             //wait for complete before calling cb
170               prereq.once('_done', () => {
171                 prereq.removeAllListeners('_done');
172                 setImmediate(cb);
173               });
174               // Start the prereq if we are the first to encounter it
175               if (prereq.taskStatus === Task.runStatuses.UNSTARTED) {
176                 prereq.taskStatus = Task.runStatuses.STARTED;
177                 prereq.invoke.apply(prereq, parsed.args);
178               }
179             }
180           },
181
182           (err) => {
183           //async callback is called after all prereqs have run.
184             if (err) {
185               throw err;
186             }
187             else {
188               setImmediate(this.run.bind(this));
189             }
190           }
191         );
192       }
193       else {
194         setImmediate(this.nextPrereq.bind(this));
195       }
196     }
197     else {
198       setImmediate(this.run.bind(this));
199     }
200   }
201
202   nextPrereq() {
203     let self = this;
204     let index = this._currentPrereqIndex;
205     let name = this.prereqs[index];
206     let prereq;
207     let parsed;
208
209     if (name) {
210
211       parsed = parsePrereqName(name);
212
213       prereq = this.namespace.resolveTask(parsed.name) ||
214           jake.attemptRule(name, this.namespace, 0) ||
215           jake.createPlaceholderFileTask(name, this.namespace);
216
217       if (!prereq) {
218         throw new Error('Unknown task "' + name + '"');
219       }
220
221       // Do when done
222       if (prereq.taskStatus == Task.runStatuses.DONE) {
223         self.handlePrereqDone(prereq);
224       }
225       else {
226         prereq.once('_done', () => {
227           this.handlePrereqDone(prereq);
228           prereq.removeAllListeners('_done');
229         });
230         if (prereq.taskStatus == Task.runStatuses.UNSTARTED) {
231           prereq.taskStatus = Task.runStatuses.STARTED;
232           prereq._invocationChain = this._invocationChain;
233           prereq.invoke.apply(prereq, parsed.args);
234         }
235       }
236     }
237   }
238
239   /**
240     @name jake.Task#reenable
241     @function
242     @description Reenables a task so that it can be run again.
243    */
244   reenable(deep) {
245     let prereqs;
246     let prereq;
247     this._skipped = false;
248     this.taskStatus = Task.runStatuses.UNSTARTED;
249     this.value = UNDEFINED_VALUE;
250     if (deep && this.prereqs) {
251       prereqs = this.prereqs;
252       for (let i = 0, ii = prereqs.length; i < ii; i++) {
253         prereq = jake.Task[prereqs[i]];
254         if (prereq) {
255           prereq.reenable(deep);
256         }
257       }
258     }
259   }
260
261   handlePrereqDone(prereq) {
262     this._currentPrereqIndex++;
263     if (this._currentPrereqIndex < this.prereqs.length) {
264       setImmediate(this.nextPrereq.bind(this));
265     }
266     else {
267       setImmediate(this.run.bind(this));
268     }
269   }
270
271   isNeeded() {
272     let needed = true;
273     if (this.taskStatus == Task.runStatuses.DONE) {
274       needed = false;
275     }
276     return needed;
277   }
278
279   run() {
280     let val, previous;
281     let hasAction = typeof this.action == 'function';
282
283     if (!this.isNeeded()) {
284       this.emit('skip');
285       this.emit('_done');
286     }
287     else {
288       if (this._invocationChain.length) {
289         previous = this._invocationChain[this._invocationChain.length - 1];
290         // If this task is repeating and its previous is equal to this, don't check its status because it was set to UNSTARTED by the reenable() method
291         if (!(this.reenabled && previous == this)) {
292           if (previous.taskStatus != Task.runStatuses.DONE) {
293             let now = (new Date()).getTime();
294             if (now - this.startTime > jake._taskTimeout) {
295               return jake.fail(`Timed out waiting for task: ${previous.name} with status of ${previous.taskStatus}`);
296             }
297             setTimeout(this.run.bind(this), POLLING_INTERVAL);
298             return;
299           }
300         }
301       }
302       if (!(this.reenabled && previous == this)) {
303         this._invocationChain.push(this);
304       }
305
306       if (!(this._internal || jake.program.opts.quiet)) {
307         console.log("Starting '" + chalk.green(this.fullName) + "'...");
308       }
309
310       this.startTime = (new Date()).getTime();
311       this.emit('start');
312
313       jake.currentRunningTask = this;
314
315       if (hasAction) {
316         try {
317           if (this.directory) {
318             process.chdir(this.directory);
319           }
320
321           val = this.action.apply(this, this.args);
322
323           if (typeof val == 'object' && typeof val.then == 'function') {
324             this.async = true;
325
326             val.then(
327               (result) => {
328                 setImmediate(() => {
329                   this.complete(result);
330                 });
331               },
332               (err) => {
333                 setImmediate(() => {
334                   this.errorOut(err);
335                 });
336               });
337           }
338         }
339         catch (err) {
340           this.errorOut(err);
341           return; // Bail out, not complete
342         }
343       }
344
345       if (!(hasAction && this.async)) {
346         setImmediate(() => {
347           this.complete(val);
348         });
349       }
350     }
351   }
352
353   errorOut(err) {
354     this.taskStatus = Task.runStatuses.ERROR;
355     this._invocationChain.chainStatus = Task.runStatuses.ERROR;
356     this.emit('error', err);
357   }
358
359   complete(val) {
360
361     if (Array.isArray(this._waitForChains)) {
362       let stillWaiting = this._waitForChains.some((chain) => {
363         return !(chain.chainStatus == Task.runStatuses.DONE ||
364               chain.chainStatus == Task.runStatuses.ERROR);
365       });
366       if (stillWaiting) {
367         let now = (new Date()).getTime();
368         let elapsed = now - this.startTime;
369         if (elapsed > jake._taskTimeout) {
370           return jake.fail(`Timed out waiting for task: ${this.name} with status of ${this.taskStatus}. Elapsed: ${elapsed}`);
371         }
372         setTimeout(() => {
373           this.complete(val);
374         }, POLLING_INTERVAL);
375         return;
376       }
377     }
378
379     jake._invocationChain.splice(jake._invocationChain.indexOf(this), 1);
380
381     if (this._invocationChainRoot) {
382       this._invocationChain.chainStatus = Task.runStatuses.DONE;
383     }
384
385     this._currentPrereqIndex = 0;
386
387     // If 'complete' getting called because task has been
388     // run already, value will not be passed -- leave in place
389     if (!this._skipped) {
390       this.taskStatus = Task.runStatuses.DONE;
391       this.value = val;
392
393       this.emit('complete', this.value);
394       this.emit('_done');
395
396       this.endTime = (new Date()).getTime();
397       let taskTime = this.endTime - this.startTime;
398
399       if (!(this._internal || jake.program.opts.quiet)) {
400         console.log("Finished '" + chalk.green(this.fullName) + "' after " + chalk.magenta(taskTime + ' ms'));
401       }
402
403     }
404   }
405
406   _getFullName() {
407     let ns = this.namespace;
408     let path = (ns && ns.path) || '';
409     path = (path && path.split(':')) || [];
410     if (this.namespace !== jake.defaultNamespace) {
411       path.push(this.namespace.name);
412     }
413     path.push(this.name);
414     return path.join(':');
415   }
416
417   static getBaseNamespacePath(fullName) {
418     return fullName.split(':').slice(0, -1).join(':');
419   }
420
421   static getBaseTaskName(fullName) {
422     return fullName.split(':').pop();
423   }
424 }
425
426 Task.runStatuses = {
427   UNSTARTED: 'unstarted',
428   DONE: 'done',
429   STARTED: 'started',
430   ERROR: 'error'
431 };
432
433 Task.ROOT_TASK_NAME = ROOT_TASK_NAME;
434
435 exports.Task = Task;
436
437 // Required here because circular deps
438 require('../rule');
439