1. //################################################################################################
  2. /** @file Daemon mixin for ish.js
  3. * @mixin ish.io.daemon
  4. * @author Nick Campbell
  5. * @license MIT
  6. * @copyright 2014-2023, Nick Campbell
  7. */ //############################################################################################
  8. /*global module, define */ //# Enable Node globals for JSHint
  9. /*jshint maxcomplexity:9 */ //# Enable max complexity warnings for JSHint
  10. (function () {
  11. 'use strict'; //<MIXIN>
  12. function init(core) {
  13. //################################################################################################
  14. /** Collection of Daemon-based functionality.
  15. * @namespace ish.io.daemon
  16. * @ignore
  17. */ //############################################################################################
  18. core.oop.partial(core.io, function (/*oProtected*/) {
  19. var oDaemons = {
  20. intervals: {
  21. //m60000: undefined
  22. },
  23. ids: [],
  24. data: [],
  25. counter: 0
  26. };
  27. //# Factory function called by setInterval to execute the .register'ed .ids's
  28. function daemon(iInterval) {
  29. var sKey = "m" + iInterval;
  30. return function () {
  31. var fn, oData, i,
  32. iCount = 0
  33. ;
  34. //# Traverse our .register'ed .ids's, pulling the oData and fn for each loop and resetting out iCount
  35. for (i = 0; i < oDaemons.ids.length; i++) {
  36. oData = oDaemons.data[i];
  37. fn = oData.fn;
  38. //# If the current oData's .target equals our iInterval, inc our iCount
  39. if (oData.target === iInterval) {
  40. iCount++;
  41. //# If we have to look at oData's .delayXIntervals
  42. if (oData.interval !== iInterval || core.type.date.time.is(oData.at)) {
  43. //# 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
  44. if (--oData.delayXIntervals <= 0) {
  45. //# Calculate our .delayXIntervals
  46. //# NOTE: We subtract 1 to count this interval
  47. oData.delayXIntervals = (core.type.date.time.is(oData.at) ?
  48. core.io.daemon.units.day :
  49. (oData.interval / oData.target)
  50. ) - 1;
  51. setTimeout(fn(++oData.callCount, core.extend({}, oData)), 0);
  52. oData.lastCall = new Date();
  53. }
  54. }
  55. //# Else we call the fn on every loop, so do so now while pre-inc'ing .callCount
  56. else {
  57. setTimeout(fn(++oData.callCount, core.extend({}, oData)), 0);
  58. oData.lastCall = new Date();
  59. }
  60. //# If we've reached our .maxIntervals, .unregister the fn
  61. /*if (oData.maxIntervals === oData.callCount) {
  62. core.io.daemon.unregister(fn);
  63. }*/
  64. }
  65. }
  66. //# If there are no longer any .registered .ids's, .clearInterval and reset its ID so it can be respawned in .register
  67. if (iCount === 0) {
  68. clearInterval(oDaemons.intervals[sKey]);
  69. oDaemons.intervals[sKey] = 0;
  70. }
  71. };
  72. } //# daemon
  73. return {
  74. daemon: {
  75. //#########
  76. /** Registers the passed function as a daemon executed at the passed interval.
  77. * @function ish.io.daemon.register
  78. * @param {function} fn Value representing the function to execute.
  79. * @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).
  80. * @returns {integer} Value representing the ID of the registered daemon function or <code>0</code> if the call was unsuccessful.
  81. */ //#####
  82. register: function (fn, vInterval) {
  83. var oSeconds, sKey,
  84. iID = 0,
  85. oData = {
  86. interval: vInterval
  87. }
  88. ;
  89. //# If the passed fn is valid, populate our oData
  90. if (core.type.fn.is(fn)) {
  91. oData.registered = true;
  92. oData.callCount = 0;
  93. oData.fn = fn;
  94. oData.started = new Date();
  95. //oData.lastCall = undefined;
  96. //oData.maxIntervals = core.type.int.mk(oData.maxIntervals, -1);
  97. //oData.maxIntervals = (oData.maxIntervals < 1 ? -1 : oData.maxIntervals);
  98. //# If we are to call daily at a specific time, determine the oSeconds
  99. if (core.type.date.time.is(oData.interval)) {
  100. oSeconds = {
  101. at: core.type.date.time.seconds(oData.interval),
  102. now: core.type.date.time.seconds()
  103. };
  104. //# Set our .interval, .target and .delayXIntervals to fire at the next .at
  105. //# 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
  106. oData.at = oData.interval;
  107. oData.interval = 60000;
  108. oData.target = 60000;
  109. oData.delayXIntervals = (oSeconds.at > oSeconds.now ?
  110. oSeconds.at - oSeconds.now :
  111. core.io.daemon.units.day - oSeconds.now + oSeconds.at
  112. );
  113. }
  114. //#
  115. /*else if (core.type.arr.is(oData.at, true)) {
  116. for (oData.i = 0; oData.i < oData.at.length; oData.i++) {
  117. core.io.daemon.register(fn, core.extend({}, oData, { at: oData[oData.i] }));
  118. }
  119. }*/
  120. //# Else we are to call on the .interval, so determine it, the .target and it's related .delayXIntervals
  121. else {
  122. //oData.at = undefined;
  123. oData.interval = core.type.int.mk(oData.interval, 60000);
  124. oData.target = (oData.interval % 60000 === 0 ? 60000 : oData.interval);
  125. oData.delayXIntervals = (oData.interval / oData.target);
  126. }
  127. //# Determine the iID for this daemon then set the .id and .push the above transformed oData into our arrays and determine the sKey name
  128. iID = ++oDaemons.counter;
  129. oData.id = iID;
  130. oDaemons.ids.push(iID);
  131. oDaemons.data.push(oData);
  132. sKey = "m" + oData.target;
  133. //# If we don't have an active .setInterval for the .target, kick it off now
  134. if (!oDaemons.intervals[sKey]) {
  135. oDaemons.intervals[sKey] = setInterval(daemon(oData.target), oData.target);
  136. }
  137. }
  138. return iID;
  139. }, //# io.daemon.register
  140. //#########
  141. /** Unregisters the referenced daemon function.
  142. * @function ish.io.daemon.unregister
  143. * @param {integer} iDaemonID Value representing the ID of the registered daemon function.
  144. * @returns {object} Value representing the status of the referenced daemon function.
  145. */ //#####
  146. unregister: function (iDaemonID) {
  147. var oStatus,
  148. iIndex = oDaemons.ids.indexOf(iDaemonID),
  149. bReturnVal = (iIndex > -1)
  150. ;
  151. //# If we were able to locate the iIndex, .splice the entries from .ids and .data
  152. if (bReturnVal) {
  153. oDaemons.ids.splice(iIndex, 1);
  154. oStatus = oDaemons.data.splice(iIndex, 1);
  155. }
  156. return (bReturnVal ? oStatus : { registered: false });
  157. }, //# io.daemon.unregister
  158. //#########
  159. /** Determines the status of the referenced daemon function.
  160. * @function ish.io.daemon.status
  161. * @$note If <code>iDaemonID</code> isn't numeric, then all the status of all registered daemon functions are returned.
  162. * @param {integer} [iDaemonID] Value representing the ID of the registered daemon function.
  163. * @returns {object|object[]} Value representing the status of the referenced daemon function(s).
  164. */ //#####
  165. status: function (iDaemonID) {
  166. var vReturnVal, i;
  167. //# If the passed iDaemonID .is .int, determine it's .indexOf and reset our vReturnVal accordingly
  168. if (core.type.int.is(iDaemonID)) {
  169. i = oDaemons.ids.indexOf(iDaemonID);
  170. vReturnVal = (i > -1 ? core.extend({}, oDaemons.data[i]) : { registered: false });
  171. }
  172. //# 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
  173. else {
  174. vReturnVal = [];
  175. for (i = 0; i < oDaemons.data.length; i++) {
  176. vReturnVal.push(core.extend({}, oDaemons.data[i]));
  177. }
  178. }
  179. return vReturnVal;
  180. }, //# io.daemon.status
  181. //#########
  182. /** Halts all registered daemon functions.
  183. * @function ish.io.daemon.halt
  184. * @note <code>bConfirm</code> is used as a safety measure to help ensure that <code>ish.io.daemon.halt</code> is not called accidentally.
  185. * @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.
  186. * @returns {object[]} Value representing the final status of all daemon functions that were successfully unregistered.
  187. */ //#####
  188. halt: function (bConfirm) {
  189. var i,
  190. a_oReturnVal = []
  191. ;
  192. //# 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
  193. if (bConfirm === true) {
  194. for (i = 0; i < oDaemons.ids.length; i++) {
  195. a_oReturnVal.push(core.io.daemon.unregister(oDaemons.ids[i]));
  196. }
  197. }
  198. return a_oReturnVal;
  199. }, //# io.daemon.halt
  200. //#########
  201. /** Represents units of time as milliseconds.
  202. * @$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>.
  203. * @function ish.io.daemon.units
  204. * @$asProperty
  205. * @returns {object} Value representing units of time as milliseconds.
  206. */ //#####
  207. units: {
  208. second: 1000,
  209. minute: 60000,
  210. hour: (60000 * 60),
  211. day: (60000 * 60 * 24),
  212. week: (60000 * 60 * 24 * 7)
  213. } //# io.daemon.units
  214. } //# io.daemon
  215. };
  216. }); //# core.io.daemon
  217. //# .fire the plugin's loaded event
  218. core.io.event.fire("ish.io.daemon");
  219. //# Return core to allow for chaining
  220. return core;
  221. } //# init
  222. //# If we are running server-side
  223. //# NOTE: Generally compliant with UMB, see: https://github.com/umdjs/umd/blob/master/templates/returnExports.js
  224. //# NOTE: Does not work with strict CommonJS, but only CommonJS-like environments that support module.exports, like Node.
  225. if (typeof module === 'object' && module.exports) { //if (typeof module !== 'undefined' && this.module !== module && module.exports) {
  226. module.exports = init;
  227. }
  228. //# Else if we are running in an .amd environment, register as an anonymous module
  229. else if (typeof define === 'function' && define.amd) {
  230. define([], init);
  231. }
  232. //# Else we are running in the browser, so we need to setup the _document-based features
  233. else {
  234. return init(window.head.ish || document.querySelector("SCRIPT[ish]").ish);
  235. }
  236. //</MIXIN>
  237. }());