Source: jive-sdk-api/lib/scheduler/scheduler.js

/*
 * Copyright 2013 Jive Software
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

var q = require('q');
var jive = require('../../api');

function Scheduler() {
    return this;
}

/**
 * An in-memory implementation of scheduler.
 * @class memoryScheduler
 */
module.exports = Scheduler;

var tasks = {};
var running = {};
var lastRunTs = {};

var eventHandlerMap = {};


/**
 * @memberof memoryScheduler
 * @param _eventHandlerMap
 */
function init( _eventHandlerMap ) {
    eventHandlerMap = _eventHandlerMap || jive.events.eventHandlerMap;

    // setup listeners
    jive.events.globalEvents.forEach( function(event) {
        var handlers = eventHandlerMap[event];

        if ( handlers ) {
            if ( typeof handlers === 'function' ) {
                // normalize single handler into an array
                handlers = [ handlers ];
            }

            handlers.forEach( function( handler ) {
                jive.events.addLocalEventListener( event, handler );
            });
        }

    });

    return this;
}
Scheduler.prototype.init = init;

/**
 * Schedule a task.
 * @memberof memoryScheduler
 * @param eventID which event to fire
 * @param context what to pass to the event
 * @param interval The interval to invoke the callback
 * @param delay The number of milliseconds after which the event will be fired for the first time.
 * @param exclusive If true, then will not execute if another event named with the same eventID is already executing.
 * @param timeout The number of milliseconds, after which the schedule will declare the event has timed out, and will fire the reject on any promise that was returned.
 * @returns {Object} Promise
 */
function schedule(eventID, context, interval, delay, exclusive, timeout) {
    eventID = eventID || jive.util.guid();

    context = context || {};
    var deferred = q.defer();
    var handlers;
    if (context['eventListener']) {
        if ( eventHandlerMap[context['eventListener']] ) {
            handlers = eventHandlerMap[context['eventListener']][eventID];
        }
    } else {
        handlers = eventHandlerMap[eventID];
    }

    handlers = handlers || [];

    var next = function(timer, eventID) {
        var promises = [];
        running[eventID] = true;
        lastRunTs[eventID] = new Date().getTime();

        handlers.forEach( function(handler) {
            var p = handler(context);
            if ( p && p['then'] ) {
                promises.push(p);
            }
        });

        q.all( promises).then(
            // success
            function(result ) {
                result = result['forEach'] && result.length == 1 ? result[0] : result;
                // nuke self, if no longer scheduled
                if ( timer && eventID && !tasks[eventID] ) {
                    clearInterval(timer);
                }
                delete running[eventID];
                deferred.resolve(result);
            },

            // fail
            function(e) {
                if ( timer && eventID && !tasks[eventID] ) {
                    clearInterval(timer);
                }
                delete running[eventID];
                deferred.reject(e);
            }
        );
    };

    if (interval) {
        if ( !running[eventID] ) {
            var d = delay || 1;
            setTimeout( function() {
                var timer = tasks[eventID] = setInterval(function() {
                    var hasTimedOut = timeout ? new Date().getTime() - (lastRunTs[eventID] || 0) > timeout : false;
                    if ( !running[eventID] || hasTimedOut ) {
                        if (hasTimedOut ) {
                            jive.logger.debug(eventID,'timed out!');
                        }
                        next(timer, eventID);
                    }
                }, interval);
            }, delay - interval > 0 ? (delay - interval) : delay );
        } else {
            jive.logger.debug("Skipping", eventID, "already running.");
        }
    }
    else {
        setTimeout( function() {
            var hasTimedOut = timeout ? new Date().getTime() - (lastRunTs[eventID] || 0) > timeout : false;
            if ( !exclusive || !running[eventID] || hasTimedOut ) {
                if (hasTimedOut ) {
                    jive.logger.debug(eventID,'timed out!');
                }
                next(undefined, eventID);
            }
        }, delay || 1);
    }
    jive.logger.debug("Scheduled task: " + eventID, interval || "immediate");

    return deferred.promise;
}
Scheduler.prototype.schedule = schedule;

/**
 * @memberof memoryScheduler
 * @param eventID
 * @returns {Object} Promise
 */
function unschedule(eventID){
    clearInterval(tasks[eventID]);
    delete tasks[eventID];
    return q.resolve();
}
Scheduler.prototype.unschedule = unschedule;

/**
 * @memberof memoryScheduler
 * @returns {Object} Promise
 */
function getTasks(){
    return Object.keys(tasks);
}
Scheduler.prototype.getTasks = getTasks;

/**
 * @memberof memoryScheduler
 * @param eventID
 * @returns {Object} Promise
 */
function isScheduled( eventID ) {
    var deferred = q.defer();
    if (tasks[eventID]) {
        deferred.resolve(true);
    } else {
        deferred.resolve(false);
    }

    return deferred.promise;
}
Scheduler.prototype.isScheduled = isScheduled;

/**
 * @memberof memoryScheduler
 * @returns {Object} Promise
 */
function shutdown(){
    var scheduler = this;
    eventHandlerMap = {};
    running = {};
    lastRunTs = {};
    this.getTasks().forEach(function(taskKey){
        scheduler.unschedule(taskKey);
    });

    return q.resolve();
}
Scheduler.prototype.shutdown =  shutdown;