jw-automator 2.0.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +76 -0
- package/README.md +366 -192
- package/docs/ARCHITECTURE.md +342 -0
- package/docs/MIGRATION.md +407 -0
- package/docs/QUICKSTART.md +350 -0
- package/examples/basic-example.js +135 -0
- package/examples/hello-world.js +40 -0
- package/examples/iot-sensor-example.js +149 -0
- package/index.js +7 -0
- package/package.json +53 -19
- package/src/Automator.js +429 -0
- package/src/core/CoreEngine.js +210 -0
- package/src/core/RecurrenceEngine.js +237 -0
- package/src/host/SchedulerHost.js +174 -0
- package/src/storage/FileStorage.js +59 -0
- package/src/storage/MemoryStorage.js +27 -0
- package/.actions.json +0 -1
- package/.jshintrc +0 -16
- package/.vscode/settings.json +0 -6
- package/LICENSE +0 -674
- package/automator.js +0 -696
- package/demo.js +0 -76
package/automator.js
DELETED
|
@@ -1,696 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
ver 2.0.0 21-08-02
|
|
3
|
-
-rename GetActionsByName to getActionsInRange (BREAKING CHANGE)
|
|
4
|
-
ver 1.0.2 19-11-13
|
|
5
|
-
-fixed DST-Standard time changeover bug
|
|
6
|
-
ver 1.0.1 19-07-18
|
|
7
|
-
-bug-fixes, 1.0.0 was actually non-functional...
|
|
8
|
-
ver 1.0.0 19-06-27
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
'use strict';
|
|
12
|
-
|
|
13
|
-
var fs = require('fs'); //for saving state
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Creates an automator
|
|
17
|
-
*
|
|
18
|
-
* @param {object} [options] - startup options
|
|
19
|
-
* @param {Boolean} [options.save] - Should the automator save it's state
|
|
20
|
-
* @param {string} [options.saveFile] - alternate path/file for save
|
|
21
|
-
*/
|
|
22
|
-
function automator(options) {
|
|
23
|
-
if (typeof options === 'undefined') { options = {}; }
|
|
24
|
-
var _self = this;
|
|
25
|
-
|
|
26
|
-
var _actions = []; //The actions to be automated
|
|
27
|
-
var _functions = {}; //the functions that the automator can run
|
|
28
|
-
var _saveFile = '.actions.json'; //default save path
|
|
29
|
-
var _saveState = true;
|
|
30
|
-
var _lastTickTime = Date.now(); //this works with action.unBuffered to prevent actions from being missed
|
|
31
|
-
var _newTickTime = Date.now();
|
|
32
|
-
|
|
33
|
-
if (options.saveFile) { _saveFile = options.saveFile; }
|
|
34
|
-
if (!options.save) { _saveState = false; }
|
|
35
|
-
|
|
36
|
-
//The timer always runs but it may be muted, i.e. will execute no actions, they
|
|
37
|
-
//will however have their counters updated as if they were being run and will be
|
|
38
|
-
//removed if their end dates pass
|
|
39
|
-
//can be set directly by the client
|
|
40
|
-
this.mute = false;
|
|
41
|
-
|
|
42
|
-
//holds the timer reference
|
|
43
|
-
//var _autoTimer = null;
|
|
44
|
-
|
|
45
|
-
/******************* Custom Emitter Code **************************************************/
|
|
46
|
-
//this is for potential browser compatibility
|
|
47
|
-
var _events = {};
|
|
48
|
-
this.on = function(event, callback) {
|
|
49
|
-
//attaches a callback function to an event
|
|
50
|
-
_events[event] = callback;
|
|
51
|
-
};
|
|
52
|
-
function emit(event, payload) {
|
|
53
|
-
if (typeof _events[event] === 'function') { //the client has registered the event
|
|
54
|
-
_events[event](payload); //run the event function provided
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
/*******************************************************************************************/
|
|
58
|
-
|
|
59
|
-
function debug(msg) {
|
|
60
|
-
emit('debug', msg);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function startUp() {
|
|
64
|
-
//first load the actions from file.
|
|
65
|
-
//we're going to do this in sync mode because we want to guarantee that the file is loaded
|
|
66
|
-
//before we start the automator and possibly accept a new action that is then overwritten
|
|
67
|
-
//by the save file.
|
|
68
|
-
if (_saveState) {
|
|
69
|
-
try {
|
|
70
|
-
_actions = JSON.parse(fs.readFileSync(_saveFile, 'utf8'));
|
|
71
|
-
debug('Loaded save file');
|
|
72
|
-
} catch (err) {
|
|
73
|
-
debug('Save file does not exist yet.');
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// we want the tick to run exactly on the second, so calculate a wait period before starting
|
|
78
|
-
var wait = 1000 - new Date(Date.now()).getMilliseconds();
|
|
79
|
-
debug('Waiting ' + wait + ' milliseconds...');
|
|
80
|
-
setTimeout(function() {
|
|
81
|
-
emit('ready');
|
|
82
|
-
tick();
|
|
83
|
-
}, wait);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
function tick() {
|
|
87
|
-
_lastTickTime = _newTickTime;
|
|
88
|
-
_newTickTime = Date.now();
|
|
89
|
-
//run all the actions with:
|
|
90
|
-
// -Date.now() set as the tick time with the milliseconds set to 0
|
|
91
|
-
// -the opposite of this.mute set as the execute flag, so execute if this.mute = false
|
|
92
|
-
// -run the callback with the list of actions run during this tick passed back
|
|
93
|
-
|
|
94
|
-
_actions = executeAllActions(_actions, clearMilliSeconds(Date.now()), !_self.mute, function(actionsUpdated, actionsRun) {
|
|
95
|
-
if (actionsUpdated) {
|
|
96
|
-
emit('update'); //tell the client that the action list has updated
|
|
97
|
-
if (_saveState) { saveActions(_actions); }
|
|
98
|
-
}
|
|
99
|
-
if (actionsRun.length > 0) {
|
|
100
|
-
emit('action', actionsRun); //emit to any listening clients the list of actions run
|
|
101
|
-
}
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
var wait = 1000-new Date(Date.now()).getMilliseconds();
|
|
105
|
-
setTimeout(function() {
|
|
106
|
-
tick();
|
|
107
|
-
},wait);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
function saveActions(actions) {
|
|
111
|
-
//saves the actions object to a file
|
|
112
|
-
fs.writeFile(_saveFile, JSON.stringify(actions), function(err) {
|
|
113
|
-
if(err) { emit('error', 'Error saving actions: ' + err); }
|
|
114
|
-
});
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
function clearMilliSeconds(date) {
|
|
118
|
-
//returns a date object with the milliseconds set to 0
|
|
119
|
-
date = new Date(date); //force a date object
|
|
120
|
-
date.setMilliseconds(0); //clear the milliseconds
|
|
121
|
-
return date;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
function dateToMilliseconds(date) {
|
|
125
|
-
//converts dates and date strings to milliseconds
|
|
126
|
-
//more robust then Date.parse()
|
|
127
|
-
return(Date.parse(new Date(date).toString()));
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
function checkActionLimit(action) {
|
|
131
|
-
var removeAction = false;
|
|
132
|
-
if (action.repeat) {
|
|
133
|
-
//limit will be FALSE if it doesn't exist and also check if the limit is 0 (=no limit)
|
|
134
|
-
if (action.repeat.limit && action.repeat.limit > 0) {
|
|
135
|
-
if (action.repeat.count >= action.repeat.limit) {
|
|
136
|
-
removeAction = true;
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
return removeAction;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
function checkActionEndDate(action, now) {
|
|
145
|
-
var removeAction = false;
|
|
146
|
-
if (action.repeat) {
|
|
147
|
-
if (action.repeat.endDate) { //endDate will be FALSE if it doesn't exist
|
|
148
|
-
if (dateToMilliseconds(action.repeat.endDate < dateToMilliseconds(now))) {
|
|
149
|
-
removeAction = true;
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
return removeAction;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
function executeAllActions(actions, now, shouldExecute, callback) {
|
|
158
|
-
//debug('execute actions...');
|
|
159
|
-
//debug(JSON.stringify(actions));
|
|
160
|
-
|
|
161
|
-
if (typeof shouldExecute === 'undefined') { shouldExecute = false; }
|
|
162
|
-
|
|
163
|
-
//actions: the actions object to work with
|
|
164
|
-
//now: the time of the tick (may be a virtual or test time)
|
|
165
|
-
//shouldExecute: should the action actually run or just update
|
|
166
|
-
//callback: function to execute when we complete
|
|
167
|
-
|
|
168
|
-
var actionsUpdated = false; //to emit only when something changed
|
|
169
|
-
var actionsRun = []; //a list of all the actions run this tick
|
|
170
|
-
var actionInfo = {}; //the object saved in the actionRun array
|
|
171
|
-
var dateOld = false; //test var for updating passed actions to the next tick in the future
|
|
172
|
-
|
|
173
|
-
//MAIN LOOP:
|
|
174
|
-
//we're going to iterate down so we can remove elements without messing up the iteration
|
|
175
|
-
for (var i = actions.length; i--;) {
|
|
176
|
-
//sanitize date (and force .date into a date object)
|
|
177
|
-
actions[i].date = clearMilliSeconds(actions[i].date);
|
|
178
|
-
|
|
179
|
-
//it's possible that we have failed to run actions that were proscribed, so what
|
|
180
|
-
//we're going to do is virtually run this action until it's next run time is now
|
|
181
|
-
//or in the future. This is important for actions that were intended to run for
|
|
182
|
-
//a certain amount of executions starting at a particular time. Say every hour
|
|
183
|
-
//for 4 hours starting at noon. If the automator is started at 3:00 it should only
|
|
184
|
-
//run the action twice.
|
|
185
|
-
//check if the date is in the past
|
|
186
|
-
//pre-set the dateOld test for the while loop
|
|
187
|
-
dateOld = dateToMilliseconds(actions[i].date) < dateToMilliseconds(now); //true if in the past
|
|
188
|
-
while (dateOld) {
|
|
189
|
-
//debug('date is in the past: ' + actions[i].date);
|
|
190
|
-
//while the next action date is still in the past, increment it until it isn't
|
|
191
|
-
//the action count will increase as if the action was run, but the action will not execute
|
|
192
|
-
actionsUpdated = true; //something has changed, in this case the date of at least one action.
|
|
193
|
-
|
|
194
|
-
//the false flag will keep the action from running, but will update it's timing
|
|
195
|
-
//update the action, but don't run it's command, do increment its counter
|
|
196
|
-
var run = false;
|
|
197
|
-
//go ahead and run the action if it was missed and unBuffered = false
|
|
198
|
-
if (actions[i].date > _lastTickTime && !actions[i].unBuffered) {
|
|
199
|
-
run = true;
|
|
200
|
-
} else {
|
|
201
|
-
|
|
202
|
-
}
|
|
203
|
-
actions[i] = executeAction(actions[i], run, true);
|
|
204
|
-
dateOld = dateToMilliseconds(actions[i].date) < dateToMilliseconds(now); //check again
|
|
205
|
-
}
|
|
206
|
-
//the current action date is now in the future (or now, now being the time passed to the function)
|
|
207
|
-
//check if the action's date is this tick
|
|
208
|
-
if (dateToMilliseconds(actions[i].date) === dateToMilliseconds(now)) {
|
|
209
|
-
//run the action command and increment the counter
|
|
210
|
-
actions[i] = executeAction(actions[i], shouldExecute, true);
|
|
211
|
-
actionsUpdated = true; //something has changed, in this case an action has run and it's date has updated
|
|
212
|
-
//create a new actionInfo obj and add it to the list of actions that have run
|
|
213
|
-
actionInfo = {
|
|
214
|
-
id: actions[i].id,
|
|
215
|
-
name: actions[i].name,
|
|
216
|
-
date: now
|
|
217
|
-
};
|
|
218
|
-
actionsRun.push(actionInfo);
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
//now we will test if the actions are old/past limits, if either are true, remove it
|
|
222
|
-
//also remove if repeat is set to false
|
|
223
|
-
//pass the provided tick time as the "current" date in case the time is simulated
|
|
224
|
-
if (checkActionLimit(actions[i]) || checkActionEndDate(actions[i], now) || !actions[i].repeat) {
|
|
225
|
-
debug('>>>> Removed action: ' + actions[i].name + ' - ' + actions[i].id);
|
|
226
|
-
debug(">>>> Limit: " + checkActionLimit(actions[i]));
|
|
227
|
-
debug(">>>> Date: " + checkActionEndDate(actions[i], now));
|
|
228
|
-
debug('>>>> Type: ' + typeof actions[i].repeat);
|
|
229
|
-
actions.splice(i,1); //remove the offending action
|
|
230
|
-
actionsUpdated = true; //something has changed, in this case the action list
|
|
231
|
-
|
|
232
|
-
}
|
|
233
|
-
} //END MAIN LOOP
|
|
234
|
-
|
|
235
|
-
if (typeof callback === 'function') {
|
|
236
|
-
callback(actionsUpdated, actionsRun); //callback with the list of actions run this tick
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
return actions; //return the updated actions object
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
function executeAction(action, execute, increment) {
|
|
244
|
-
if (typeof execute === 'undefined') { execute = false; }
|
|
245
|
-
if (typeof increment === 'undefined') { increment = true; }
|
|
246
|
-
|
|
247
|
-
if (action.repeat) { //if the action has a repeat object
|
|
248
|
-
//update the count, even if the action isn't set to run
|
|
249
|
-
//set this to false if you are manually executing the action and don't want
|
|
250
|
-
//this time to count against the total runs
|
|
251
|
-
if (increment) {
|
|
252
|
-
action.repeat.count++;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
//get/set the next run time
|
|
256
|
-
action.date = getNextActionTime(action.date, action.repeat);
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
if (execute) {
|
|
260
|
-
debug('Time: ' + printDate(Date.now()));
|
|
261
|
-
debug('Executing Action "' + action.name + '"...');
|
|
262
|
-
|
|
263
|
-
//update the execute var even if there is no function to run
|
|
264
|
-
action.repeat.executed ++;
|
|
265
|
-
|
|
266
|
-
//run the cmd with the payload (make sure the cmd is a function first)
|
|
267
|
-
//it will obviously not be if the cmd is not defined at all or if the user
|
|
268
|
-
//didn't pass a "functions" object into the automator
|
|
269
|
-
if (typeof _functions[action.cmd] === 'function') {
|
|
270
|
-
try {
|
|
271
|
-
_functions[action.cmd](action.payload);
|
|
272
|
-
} catch (err) {
|
|
273
|
-
emit('error','Problem executing action ' + action.name + ': ' + err);
|
|
274
|
-
}
|
|
275
|
-
} else {
|
|
276
|
-
//emit('error', action.name + ' has no function.');
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
return action; //return the modified action
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
function getNextActionTime(start, repeat) {
|
|
284
|
-
//returns a new date based on a start date (may be a string) and a repeat options object.
|
|
285
|
-
|
|
286
|
-
//if a repeat object isn't supplied or is false, return the start (same) time
|
|
287
|
-
if (typeof repeat === 'undefined') { return new Date(start); }
|
|
288
|
-
|
|
289
|
-
var dateStart = new Date(start); //convert input to valid date object
|
|
290
|
-
|
|
291
|
-
//the following call breaks the DST->Standard time crossover, so for now don't do it
|
|
292
|
-
//potentially if a user sets an action start time and includes milliseconds it might
|
|
293
|
-
//break the action, not sure
|
|
294
|
-
//dateStart.setMilliseconds(0); //since the minimum tick is 1 second;
|
|
295
|
-
|
|
296
|
-
start = Date.parse(dateStart.toString()); //convert to milliseconds
|
|
297
|
-
var nextTime = null; //by default there will be no next action time
|
|
298
|
-
var inc = 0; //the number of milliseconds to increment by
|
|
299
|
-
var i = 0;
|
|
300
|
-
switch (repeat.type) {
|
|
301
|
-
case 'second':
|
|
302
|
-
inc = 1000 * repeat.interval;
|
|
303
|
-
break;
|
|
304
|
-
case 'minute':
|
|
305
|
-
inc = (1000 * 60) * repeat.interval;
|
|
306
|
-
break;
|
|
307
|
-
case 'hour':
|
|
308
|
-
inc = (1000 * 60 * 60) * repeat.interval;
|
|
309
|
-
break;
|
|
310
|
-
case 'day':
|
|
311
|
-
inc = (1000 * 60 * 60 * 24) * repeat.interval;
|
|
312
|
-
break;
|
|
313
|
-
case 'week':
|
|
314
|
-
inc = (1000 * 60 * 60 * 24 * 7) * repeat.interval;
|
|
315
|
-
break;
|
|
316
|
-
case 'month':
|
|
317
|
-
dateStart.setMonth(dateStart.getMonth() + repeat.interval);
|
|
318
|
-
start = Date.parse(dateStart.toString());
|
|
319
|
-
inc = 0;
|
|
320
|
-
break;
|
|
321
|
-
case 'year':
|
|
322
|
-
//12 months in a year
|
|
323
|
-
dateStart.setMonth(dateStart.getMonth() + (repeat.interval * 12));
|
|
324
|
-
start = Date.parse(dateStart.toString());
|
|
325
|
-
inc = 0;
|
|
326
|
-
break;
|
|
327
|
-
case 'weekday':
|
|
328
|
-
while (i<repeat.interval) {
|
|
329
|
-
//we're going to iterate ahead 1 day at a time manually until we get to the interval
|
|
330
|
-
//if we hit a weekend date (Sun=0, Sat=6) then we're going to jump ahead the
|
|
331
|
-
//appropriate amount to the next weekday.
|
|
332
|
-
dateStart.setDate(dateStart.getDate() + 1); //increment the day by one
|
|
333
|
-
|
|
334
|
-
if (dateStart.getDay() == 0) { //we've hit sun, extra inc by 1 day
|
|
335
|
-
dateStart.setDate(dateStart.getDate() + 1);
|
|
336
|
-
}
|
|
337
|
-
if (dateStart.getDay() == 6) { //we've hit sat, extra inc by 2 day
|
|
338
|
-
dateStart.setDate(dateStart.getDate() + 2);
|
|
339
|
-
}
|
|
340
|
-
i++; //next day
|
|
341
|
-
}
|
|
342
|
-
start = Date.parse(dateStart.toString());
|
|
343
|
-
inc = 0;
|
|
344
|
-
break;
|
|
345
|
-
case 'weekend':
|
|
346
|
-
while (i<repeat.interval) {
|
|
347
|
-
//similar to weekdays we will increment manually through each day, but this time
|
|
348
|
-
//we will jump the weekdays by adding the number of days necessary to get to the
|
|
349
|
-
//next weekend
|
|
350
|
-
dateStart.setDate(dateStart.getDate() + 1); //increment the day by one
|
|
351
|
-
//if the day is 1-5 (Mon-Fri)
|
|
352
|
-
if (dateStart.getDay() > 0 && dateStart.getDay() < 6) {
|
|
353
|
-
//add the needed days to get to the weekend (6 minus today's day#)
|
|
354
|
-
dateStart.setDate(dateStart.getDate() + (6-dateStart.getDay()));
|
|
355
|
-
}
|
|
356
|
-
i++; //next day
|
|
357
|
-
}
|
|
358
|
-
start = Date.parse(dateStart.toString());
|
|
359
|
-
inc = 0;
|
|
360
|
-
break;
|
|
361
|
-
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
nextTime = new Date(start + inc);
|
|
365
|
-
return nextTime; //returns a date object
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
function printDate(date) {
|
|
370
|
-
date = new Date(date);
|
|
371
|
-
var days = ['Sun','Mon','Tues','Wed','Thurs','Fri','Sat'];
|
|
372
|
-
var ds = days[date.getDay()] + ' ' + date.getFullYear() + '/' + parseInt(date.getMonth() + 1) + '/';
|
|
373
|
-
if (date.getDate() < 10) { ds +=0; }
|
|
374
|
-
ds += date.getDate();
|
|
375
|
-
ds += ' ' + date.getHours() + ':';
|
|
376
|
-
if (date.getMinutes() < 10) { ds +=0; }
|
|
377
|
-
ds += date.getMinutes() + ':';
|
|
378
|
-
if (date.getSeconds() < 10) { ds +=0; }
|
|
379
|
-
ds += date.getSeconds();
|
|
380
|
-
|
|
381
|
-
return ds; //a string
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
function updateAction(oldAction, newAction) {
|
|
385
|
-
Object.keys(newAction).forEach(function(key) {
|
|
386
|
-
oldAction[key] = newAction[key];
|
|
387
|
-
});
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
/******************* Public functions **************************************************/
|
|
392
|
-
|
|
393
|
-
/**
|
|
394
|
-
* Start the Automator
|
|
395
|
-
*/
|
|
396
|
-
this.start = function() {
|
|
397
|
-
startUp();
|
|
398
|
-
};
|
|
399
|
-
|
|
400
|
-
/**
|
|
401
|
-
* Add an action to the automator
|
|
402
|
-
* @param {object} action
|
|
403
|
-
* @param {string} action.name - Name of the action
|
|
404
|
-
* @param {string|object} [action.date] - The start time. Blank for now
|
|
405
|
-
* @param {string} [action.cmd] - The name of the function (from addFunction)
|
|
406
|
-
* @param {string} [action.payload] - The param to pass to the cmd (use JSON for multi-var)
|
|
407
|
-
* @param {boolean} [action.unBuffered] - If true actions missed due to delays will be skipped
|
|
408
|
-
* @param {object} [action.repeat] - Define how the action should repeat
|
|
409
|
-
* @param {'second'|'minute'|'hour'|'day'|'week'|'month'|'year'|'weekday'|'weekend'} [action.repeat.type] - How should the action repeat
|
|
410
|
-
* @param {number} [action.repeat.interval] - 3 means run every 3rd interval, etc...
|
|
411
|
-
* @param {number} [action.repeat.count] - Usually not needed, how many times the action has already run
|
|
412
|
-
* @param {number|boolean} [action.repeat.limit] - Total number of times to run
|
|
413
|
-
* @param {string} [action.repeat.endDate] - The date/time to remove the action
|
|
414
|
-
*/
|
|
415
|
-
this.addAction = function(action) {
|
|
416
|
-
if (!action.date) {
|
|
417
|
-
action.date = new Date();
|
|
418
|
-
action.date.setSeconds(action.date.getSeconds() + 1 );
|
|
419
|
-
}
|
|
420
|
-
/* should we allow undefined actions? Maybe yes for calendar entry support....
|
|
421
|
-
if (!action.cmd) {
|
|
422
|
-
emit('error','No action cmd provided');
|
|
423
|
-
return undefined;
|
|
424
|
-
}
|
|
425
|
-
*/
|
|
426
|
-
action.date = clearMilliSeconds(action.date); //force a date object and clear the seconds
|
|
427
|
-
if (action.repeat) {
|
|
428
|
-
if (action.repeat.endDate) {
|
|
429
|
-
action.repeat.endDate = clearMilliSeconds(action.repeat.endDate);
|
|
430
|
-
}
|
|
431
|
-
// @ts-ignore
|
|
432
|
-
action.repeat.executed = 0;
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
// @ts-ignore
|
|
436
|
-
action.id = Date.now(); //will always be a unique ID
|
|
437
|
-
_actions.push(action); //add the new action to the global list
|
|
438
|
-
if (_saveState) { saveActions(_actions); }//save the new actions to a file
|
|
439
|
-
|
|
440
|
-
//run the newly added action if its first tick is now
|
|
441
|
-
//actually, don't. This causes more problems then it solves and the need is minimal
|
|
442
|
-
/*
|
|
443
|
-
if (dateToMilliseconds(action.date) === dateToMilliseconds(clearMilliSeconds(Date.now()))) {
|
|
444
|
-
action = executeAction(action, true);
|
|
445
|
-
//emit a list of just this one action being run to the client
|
|
446
|
-
emit('action',
|
|
447
|
-
[
|
|
448
|
-
{
|
|
449
|
-
// @ts-ignore
|
|
450
|
-
id: action.id,
|
|
451
|
-
name: action.name,
|
|
452
|
-
date: clearMilliSeconds(Date.now())
|
|
453
|
-
}
|
|
454
|
-
]
|
|
455
|
-
);
|
|
456
|
-
}
|
|
457
|
-
*/
|
|
458
|
-
|
|
459
|
-
};
|
|
460
|
-
|
|
461
|
-
/**
|
|
462
|
-
* @returns {object} - A copy of the automator actions
|
|
463
|
-
*/
|
|
464
|
-
this.getActions = function() {
|
|
465
|
-
//return a copy not the actual object
|
|
466
|
-
return JSON.parse(JSON.stringify(_actions));
|
|
467
|
-
};
|
|
468
|
-
|
|
469
|
-
/**
|
|
470
|
-
* Add a function to the automator
|
|
471
|
-
* @param {string} name - Common name for the function
|
|
472
|
-
* @param {Function} cmd - The function
|
|
473
|
-
*/
|
|
474
|
-
this.addFunction = function(name, cmd) {
|
|
475
|
-
//add a new function to the functions object
|
|
476
|
-
//due to the nature of Javascript you may use this function to modify an existing function too.
|
|
477
|
-
//Currently there is no way to remove a function, but since you can modify it or set it to null
|
|
478
|
-
//there's really no need/benefit of "removing" it completely.
|
|
479
|
-
_functions[name] = cmd;
|
|
480
|
-
};
|
|
481
|
-
|
|
482
|
-
/**
|
|
483
|
-
* Remove an action by it's ID
|
|
484
|
-
* @param {Number|string} ID - The ID of the action
|
|
485
|
-
*/
|
|
486
|
-
this.removeActionByID = function(ID) {
|
|
487
|
-
//removes an action from the list based on the ActionID
|
|
488
|
-
//returns true if the action was successfully removed
|
|
489
|
-
//Returns false if not
|
|
490
|
-
//ActionID's should be unique, but this will remove all matches just in case
|
|
491
|
-
var removed = false;
|
|
492
|
-
for (var i = _actions.length; i--;) {
|
|
493
|
-
if (_actions[i].id === ID) {
|
|
494
|
-
_actions.splice(i,1);
|
|
495
|
-
removed = true;
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
_self.emit('update'); //tell the clients that the actions have changed
|
|
499
|
-
if (_saveState) { saveActions(_actions); } //save the new actions to a file
|
|
500
|
-
return removed;
|
|
501
|
-
};
|
|
502
|
-
|
|
503
|
-
/**
|
|
504
|
-
* Remove an action by it's name
|
|
505
|
-
* @param {string} name - The name of the action
|
|
506
|
-
*/
|
|
507
|
-
this.removeActionByName = function(name) {
|
|
508
|
-
//removes an action from the list based on the action name
|
|
509
|
-
//returns true if the action was successfully removed
|
|
510
|
-
//Returns false if not
|
|
511
|
-
//this will remove all matches, so useful if you want to use the "name" field
|
|
512
|
-
//as more of a "type" field, so for example this could remove all your "Backup" actions
|
|
513
|
-
var removed = false;
|
|
514
|
-
for (var i = _actions.length; i--;) {
|
|
515
|
-
if (_actions[i].name === name) {
|
|
516
|
-
_actions.splice(i,1);
|
|
517
|
-
removed = true;
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
_self.emit('update'); //tell the clients that the actions have changed
|
|
521
|
-
if (_saveState) { saveActions(_actions); } //save the new actions to a file
|
|
522
|
-
return removed;
|
|
523
|
-
};
|
|
524
|
-
|
|
525
|
-
/**
|
|
526
|
-
* Execute an action by it's ID
|
|
527
|
-
* @param {Number|string} ID - The action ID
|
|
528
|
-
* @param {Boolean} increment - Should this execution count towards the action's total
|
|
529
|
-
*
|
|
530
|
-
* @returns {Boolean} - Did the action(s) run
|
|
531
|
-
*/
|
|
532
|
-
this.executeActionByID = function(ID, increment) {
|
|
533
|
-
//executes an action based on it's action ID
|
|
534
|
-
//returns true if the action is run
|
|
535
|
-
//this shouldn't ever happen, but if more then one action shares an ID, run all
|
|
536
|
-
|
|
537
|
-
//assume we don't want this to count against the total
|
|
538
|
-
if (typeof increment === 'undefined') { increment = false; }
|
|
539
|
-
|
|
540
|
-
for (var i=0; i<_actions.length; i++) {
|
|
541
|
-
// @ts-ignore
|
|
542
|
-
if(_actions[i].id === parseInt(ID)) {
|
|
543
|
-
_actions[i] = executeAction(_actions[i], true, increment);
|
|
544
|
-
_self.emit('action', [{
|
|
545
|
-
id: _actions[i].id,
|
|
546
|
-
name: _actions[i].name,
|
|
547
|
-
date: clearMilliSeconds(Date.now())
|
|
548
|
-
}]);
|
|
549
|
-
if (_saveState) { saveActions(_actions); } //save the new actions to a file
|
|
550
|
-
return true;
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
return false;
|
|
554
|
-
};
|
|
555
|
-
|
|
556
|
-
/**
|
|
557
|
-
* Execute an action by it's name
|
|
558
|
-
* @param {string} name - The action name
|
|
559
|
-
* @param {Boolean} increment - Should this execution count towards the action's total
|
|
560
|
-
*
|
|
561
|
-
* @returns {Boolean} - Did the action run?
|
|
562
|
-
*/
|
|
563
|
-
this.executeActionByName = function(name, increment) {
|
|
564
|
-
debug('Execute by name: ' + name);
|
|
565
|
-
//executes an action based on it's name
|
|
566
|
-
//returns true if the action is run
|
|
567
|
-
//will run all matching actions.
|
|
568
|
-
|
|
569
|
-
//assume we don't want this to count against the total
|
|
570
|
-
if (typeof increment === 'undefined') { increment = false; }
|
|
571
|
-
|
|
572
|
-
var actionRan = false;
|
|
573
|
-
var actionsRun = [];
|
|
574
|
-
var actionInfo = {};
|
|
575
|
-
|
|
576
|
-
for (var i=0; i<_actions.length; i++) {
|
|
577
|
-
if(_actions[i].name === name) {
|
|
578
|
-
_actions[i] = executeAction(_actions[i], true, increment);
|
|
579
|
-
actionRan = true;
|
|
580
|
-
//since more then one action may run we're going to create the actionRun list
|
|
581
|
-
//to emit to the client
|
|
582
|
-
// @ts-ignore
|
|
583
|
-
actionInfo = {};
|
|
584
|
-
actionInfo.id = _actions[i].id;
|
|
585
|
-
actionInfo.name = _actions[i].name;
|
|
586
|
-
actionInfo.date = clearMilliSeconds(Date.now());
|
|
587
|
-
actionsRun.push(actionInfo);
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
|
-
if (actionRan) {
|
|
591
|
-
_self.emit('action', actionsRun); //emit the list of actions run
|
|
592
|
-
if (_saveState) { saveActions(_actions); } //save the new actions to a file
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
return actionRan;
|
|
596
|
-
};
|
|
597
|
-
|
|
598
|
-
/**
|
|
599
|
-
* This is for simulation/debugging or for showing upcoming actions on a calendar.
|
|
600
|
-
* @param {string|Date} start - Start date
|
|
601
|
-
* @param {string|Date} end - End date
|
|
602
|
-
* @param {Function} [callback] - Array of scheduled actions within the specified date range
|
|
603
|
-
*/
|
|
604
|
-
this.getActionsInRange = function(start, end, callback) {
|
|
605
|
-
//returns an array of scheduled actions within the specified date range.
|
|
606
|
-
//This is for simulation or for showing upcoming actions on a calendar.
|
|
607
|
-
|
|
608
|
-
var tick = clearMilliSeconds(new Date(start)); //the tick time for our virtual automator
|
|
609
|
-
end = clearMilliSeconds(new Date(end)); //the date/time to stop the simulation
|
|
610
|
-
var actionList = []; //the list of actions to return
|
|
611
|
-
//This will create a unique copy of the global _actions list
|
|
612
|
-
//it wont copy the functions, but we're not running them anyway
|
|
613
|
-
var actions = JSON.parse(JSON.stringify(_actions));
|
|
614
|
-
|
|
615
|
-
while (dateToMilliseconds(tick) <= dateToMilliseconds(end)) {
|
|
616
|
-
//run the actions in non-execute mode with a copy actions object
|
|
617
|
-
actions = executeAllActions(actions, tick, false, function(actionsUpdated, actionsRun) {
|
|
618
|
-
//instead of emitting the action list, we're going to add them to our master list
|
|
619
|
-
//of actions run during the simulation
|
|
620
|
-
if (actionsUpdated) {
|
|
621
|
-
for (var i=0; i<actionsRun.length; i++) {
|
|
622
|
-
actionList.push(JSON.parse(JSON.stringify(actionsRun[i])));
|
|
623
|
-
}
|
|
624
|
-
}
|
|
625
|
-
});
|
|
626
|
-
tick.setMinutes(tick.getMinutes() + 1); //add a minute to the virtual tick
|
|
627
|
-
if (actions.length === 0) { //if there are no remaining actions, quit.
|
|
628
|
-
if (typeof callback === 'function') {
|
|
629
|
-
callback(actionList);
|
|
630
|
-
}
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
//we should now have a list of every action run within the specified period listed by
|
|
635
|
-
//id and name and the tick-time they would have run
|
|
636
|
-
//return actionList; //let's use/require a callback instead
|
|
637
|
-
|
|
638
|
-
if (typeof callback === 'function') {
|
|
639
|
-
callback(actionList);
|
|
640
|
-
}
|
|
641
|
-
};
|
|
642
|
-
|
|
643
|
-
/**
|
|
644
|
-
* Updates an action by it's name
|
|
645
|
-
* @param {string} name - Name of the action
|
|
646
|
-
* @param {object} newAction - The modified action object
|
|
647
|
-
*/
|
|
648
|
-
this.updateActionByName = function(name, newAction) {
|
|
649
|
-
for (var i=0; i<_actions.length; i++) {
|
|
650
|
-
if(_actions[i].name === name) {
|
|
651
|
-
updateAction(_actions[i], newAction);
|
|
652
|
-
}
|
|
653
|
-
}
|
|
654
|
-
};
|
|
655
|
-
|
|
656
|
-
/**
|
|
657
|
-
* Updates an action by it's ID
|
|
658
|
-
* @param {string} ID - ID of the action
|
|
659
|
-
* @param {object} newAction - The modified action object
|
|
660
|
-
*/
|
|
661
|
-
this.updateActionByID = function(ID, newAction) {
|
|
662
|
-
for (var i=0; i<_actions.length; i++) {
|
|
663
|
-
if(_actions[i].id === ID) {
|
|
664
|
-
updateAction(_actions[i], newAction);
|
|
665
|
-
}
|
|
666
|
-
}
|
|
667
|
-
};
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
exports.automator = automator;
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
/**************************************************************************************** */
|
|
678
|
-
|
|
679
|
-
//Here's a copy/paste version of the action object:
|
|
680
|
-
|
|
681
|
-
var action = {
|
|
682
|
-
name: '', //user definable
|
|
683
|
-
date: null, //next time the action should run, set default immediately
|
|
684
|
-
cmd: null, //cmd to call
|
|
685
|
-
payload: null, //payload to send to cmd
|
|
686
|
-
unBuffered: null, //when true actions missed due to sync delay will be skipped
|
|
687
|
-
repeat: { //set this to null to only run the action once, alternatively set limit to 1
|
|
688
|
-
type:'minute', // second/minute/hour/day/week/month/year/weekday/weekend
|
|
689
|
-
interval: 1, //how many of the type to skip, 3=every 3rd type
|
|
690
|
-
count: 0, //number of times the action has run, 0=hasn't run yet
|
|
691
|
-
limit: null, //number of times the action should run, false means don't limit
|
|
692
|
-
endDate: null //null = no end date
|
|
693
|
-
}
|
|
694
|
-
};
|
|
695
|
-
|
|
696
|
-
/**************************************************************************************** */
|