//################################################################################################
/** @file Daemon mixin for ish.js
* @mixin ish.io.daemon
* @author Nick Campbell
* @license MIT
* @copyright 2014-2023, Nick Campbell
*/ //############################################################################################
/*global module, define */ //# Enable Node globals for JSHint
/*jshint maxcomplexity:9 */ //# Enable max complexity warnings for JSHint
(function () {
'use strict'; //<MIXIN>
function init(core) {
//################################################################################################
/** Collection of Daemon-based functionality.
* @namespace ish.io.daemon
* @ignore
*/ //############################################################################################
core.oop.partial(core.io, function (/*oProtected*/) {
var oDaemons = {
intervals: {
//m60000: undefined
},
ids: [],
data: [],
counter: 0
};
//# Factory function called by setInterval to execute the .register'ed .ids's
function daemon(iInterval) {
var sKey = "m" + iInterval;
return function () {
var fn, oData, i,
iCount = 0
;
//# Traverse our .register'ed .ids's, pulling the oData and fn for each loop and resetting out iCount
for (i = 0; i < oDaemons.ids.length; i++) {
oData = oDaemons.data[i];
fn = oData.fn;
//# If the current oData's .target equals our iInterval, inc our iCount
if (oData.target === iInterval) {
iCount++;
//# If we have to look at oData's .delayXIntervals
if (oData.interval !== iInterval || core.type.date.time.is(oData.at)) {
//# If the current .delayXIntervals is due to be called (pre-decrementing as we go), reset the .delayXIntervals and call the fn while pre-inc'ing .callCount
if (--oData.delayXIntervals <= 0) {
//# Calculate our .delayXIntervals
//# NOTE: We subtract 1 to count this interval
oData.delayXIntervals = (core.type.date.time.is(oData.at) ?
core.io.daemon.units.day :
(oData.interval / oData.target)
) - 1;
setTimeout(fn(++oData.callCount, core.extend({}, oData)), 0);
oData.lastCall = new Date();
}
}
//# Else we call the fn on every loop, so do so now while pre-inc'ing .callCount
else {
setTimeout(fn(++oData.callCount, core.extend({}, oData)), 0);
oData.lastCall = new Date();
}
//# If we've reached our .maxIntervals, .unregister the fn
/*if (oData.maxIntervals === oData.callCount) {
core.io.daemon.unregister(fn);
}*/
}
}
//# If there are no longer any .registered .ids's, .clearInterval and reset its ID so it can be respawned in .register
if (iCount === 0) {
clearInterval(oDaemons.intervals[sKey]);
oDaemons.intervals[sKey] = 0;
}
};
} //# daemon
return {
daemon: {
//#########
/** Registers the passed function as a daemon executed at the passed interval.
* @function ish.io.daemon.register
* @param {function} fn Value representing the function to execute.
* @param {integer|string} [vInterval=ish.io.daemon.units.minute] Value representing the number of milliseconds between invocations or the <code>hh:mm</code> to execute as a time string (in 24-hour format).
* @returns {integer} Value representing the ID of the registered daemon function or <code>0</code> if the call was unsuccessful.
*/ //#####
register: function (fn, vInterval) {
var oSeconds, sKey,
iID = 0,
oData = {
interval: vInterval
}
;
//# If the passed fn is valid, populate our oData
if (core.type.fn.is(fn)) {
oData.registered = true;
oData.callCount = 0;
oData.fn = fn;
oData.started = new Date();
//oData.lastCall = undefined;
//oData.maxIntervals = core.type.int.mk(oData.maxIntervals, -1);
//oData.maxIntervals = (oData.maxIntervals < 1 ? -1 : oData.maxIntervals);
//# If we are to call daily at a specific time, determine the oSeconds
if (core.type.date.time.is(oData.interval)) {
oSeconds = {
at: core.type.date.time.seconds(oData.interval),
now: core.type.date.time.seconds()
};
//# Set our .interval, .target and .delayXIntervals to fire at the next .at
//# NOTE: If we haven't passed .at yet we can subtract .now from .at, else we need to determine the remaining seconds in today plus the .at into tomorrow
oData.at = oData.interval;
oData.interval = 60000;
oData.target = 60000;
oData.delayXIntervals = (oSeconds.at > oSeconds.now ?
oSeconds.at - oSeconds.now :
core.io.daemon.units.day - oSeconds.now + oSeconds.at
);
}
//#
/*else if (core.type.arr.is(oData.at, true)) {
for (oData.i = 0; oData.i < oData.at.length; oData.i++) {
core.io.daemon.register(fn, core.extend({}, oData, { at: oData[oData.i] }));
}
}*/
//# Else we are to call on the .interval, so determine it, the .target and it's related .delayXIntervals
else {
//oData.at = undefined;
oData.interval = core.type.int.mk(oData.interval, 60000);
oData.target = (oData.interval % 60000 === 0 ? 60000 : oData.interval);
oData.delayXIntervals = (oData.interval / oData.target);
}
//# Determine the iID for this daemon then set the .id and .push the above transformed oData into our arrays and determine the sKey name
iID = ++oDaemons.counter;
oData.id = iID;
oDaemons.ids.push(iID);
oDaemons.data.push(oData);
sKey = "m" + oData.target;
//# If we don't have an active .setInterval for the .target, kick it off now
if (!oDaemons.intervals[sKey]) {
oDaemons.intervals[sKey] = setInterval(daemon(oData.target), oData.target);
}
}
return iID;
}, //# io.daemon.register
//#########
/** Unregisters the referenced daemon function.
* @function ish.io.daemon.unregister
* @param {integer} iDaemonID Value representing the ID of the registered daemon function.
* @returns {object} Value representing the status of the referenced daemon function.
*/ //#####
unregister: function (iDaemonID) {
var oStatus,
iIndex = oDaemons.ids.indexOf(iDaemonID),
bReturnVal = (iIndex > -1)
;
//# If we were able to locate the iIndex, .splice the entries from .ids and .data
if (bReturnVal) {
oDaemons.ids.splice(iIndex, 1);
oStatus = oDaemons.data.splice(iIndex, 1);
}
return (bReturnVal ? oStatus : { registered: false });
}, //# io.daemon.unregister
//#########
/** Determines the status of the referenced daemon function.
* @function ish.io.daemon.status
* @$note If <code>iDaemonID</code> isn't numeric, then all the status of all registered daemon functions are returned.
* @param {integer} [iDaemonID] Value representing the ID of the registered daemon function.
* @returns {object|object[]} Value representing the status of the referenced daemon function(s).
*/ //#####
status: function (iDaemonID) {
var vReturnVal, i;
//# If the passed iDaemonID .is .int, determine it's .indexOf and reset our vReturnVal accordingly
if (core.type.int.is(iDaemonID)) {
i = oDaemons.ids.indexOf(iDaemonID);
vReturnVal = (i > -1 ? core.extend({}, oDaemons.data[i]) : { registered: false });
}
//# Else we need to return the .status of all registered daemons, so reset our vReturnVal to an array and .push a copy of each .data entry in
else {
vReturnVal = [];
for (i = 0; i < oDaemons.data.length; i++) {
vReturnVal.push(core.extend({}, oDaemons.data[i]));
}
}
return vReturnVal;
}, //# io.daemon.status
//#########
/** Halts all registered daemon functions.
* @function ish.io.daemon.halt
* @note <code>bConfirm</code> is used as a safety measure to help ensure that <code>ish.io.daemon.halt</code> is not called accidentally.
* @param {boolean} bConfirm Value representing if all registered daemon functions are to be halted. <code>true</code> unregisters all functions; all other values leave the registered daemon functions unchanged.
* @returns {object[]} Value representing the final status of all daemon functions that were successfully unregistered.
*/ //#####
halt: function (bConfirm) {
var i,
a_oReturnVal = []
;
//# If the caller bConfirm'd that they REALLY want to .halt all .registered .ids's, traverse them and .unregister them one by one while .push'ing each .status result into our a_oReturnVal
if (bConfirm === true) {
for (i = 0; i < oDaemons.ids.length; i++) {
a_oReturnVal.push(core.io.daemon.unregister(oDaemons.ids[i]));
}
}
return a_oReturnVal;
}, //# io.daemon.halt
//#########
/** Represents units of time as milliseconds.
* @$note <code>second</code>, <code>minute</code>, <code>hour</code>, <code>day</code> and <code>week</code> are provided in the returned <code>object</code>.
* @function ish.io.daemon.units
* @$asProperty
* @returns {object} Value representing units of time as milliseconds.
*/ //#####
units: {
second: 1000,
minute: 60000,
hour: (60000 * 60),
day: (60000 * 60 * 24),
week: (60000 * 60 * 24 * 7)
} //# io.daemon.units
} //# io.daemon
};
}); //# core.io.daemon
//# .fire the plugin's loaded event
core.io.event.fire("ish.io.daemon");
//# Return core to allow for chaining
return core;
} //# init
//# If we are running server-side
//# NOTE: Generally compliant with UMB, see: https://github.com/umdjs/umd/blob/master/templates/returnExports.js
//# NOTE: Does not work with strict CommonJS, but only CommonJS-like environments that support module.exports, like Node.
if (typeof module === 'object' && module.exports) { //if (typeof module !== 'undefined' && this.module !== module && module.exports) {
module.exports = init;
}
//# Else if we are running in an .amd environment, register as an anonymous module
else if (typeof define === 'function' && define.amd) {
define([], init);
}
//# Else we are running in the browser, so we need to setup the _document-based features
else {
return init(window.head.ish || document.querySelector("SCRIPT[ish]").ish);
}
//</MIXIN>
}());