Jump To …

lscheduler.js

/*
*
* Copyright (C) 2011, The Locker Project
* All rights reserved.
*
* Please see the LICENSE file for more information.
*
*/

var lfs = require("lfs");
var fs = require("fs");
var serviceManager = require("lservicemanager");
var url = require("url");
var request = require("request");
var lconfig = require('lconfig');
var path = require('path');

SCHEDULE_ACTION_DIRECT = 0; // Live direct callbacks, not savable
SCHEDULE_ACTION_URI = 1; // Indirect service URIs, savable

exports.Scheduler = function() {
    this.scheduledActions = [];
    this.filename = path.join(lconfig.lockerDir, lconfig.me, "scheduler.json");
};

exports.Scheduler.prototype.loadAndStart = function() {
    var self = this;
    lfs.readObjectsFromFile(this.filename, function(objects) {
        objects.forEach(function(action) {
            self.scheduleURL(new Date(action.at), action.serviceId, action.url);
        });
    });
}

exports.Scheduler.prototype.savePending = function() {
    var data = "";
    for(var i = 0; i < this.scheduledActions.length; ++i) {
        if (this.scheduledActions[i].type == SCHEDULE_ACTION_URI)  {
            data += JSON.stringify(this.scheduledActions[i]) + '\n';
        }
    }
    fs.writeFileSync(this.filename, data);
}

exports.Scheduler.prototype.scheduleURL = function(atTime, serviceID, callbackURL) {
    if(callbackURL.substr(0,1) != "/") callbackURL = "/"+callbackURL; // be flexible in what you take
    var trackingInfo = {
        at:atTime,
        type:SCHEDULE_ACTION_URI,
        serviceId:serviceID,
        url:callbackURL
    };
    this.scheduledActions.push(trackingInfo);
    if (typeof(atTime) == "number") {
        runTime = new Date;
        runTime.setTime(runTime.getTime() + atTime);
        atTime = runTime;
    }
    var milliseconds = atTime.getTime() - Date.now();
    if (milliseconds < 0) milliseconds = 0;

    var self = this;
    function runUrl() {
        request.get({url:lconfig.lockerBase + "/Me/" + serviceID + callbackURL}, function() {
            self.scheduledActions.splice(self.scheduledActions.indexOf(trackingInfo));
            self.savePending();
        });
    }
    setTimeout(function() {
        if (!serviceManager.map(serviceID)) {
            self.scheduledActions.splice(self.scheduledActions.indexOf(trackingInfo));
            self.savePending();
        } else {
            if (!serviceManager.isRunning(serviceID)) {
                serviceManager.spawn(serviceID, runUrl);
            } else {
                runUrl();
            }
        }
    }, milliseconds);
}

exports.Scheduler.prototype.scheduleInternal = function(atTime, callback) {
    if (typeof(atTime) == "number") {
        runTime = new Date;
        runTime.setTime(runTime.getTime() + atTime);
        atTime = runTime;
    }
    var trackingInfo = {
        at:atTime,
        type:SCHEDULE_ACTION_DIRECT,
        cb:callback
    };
    var self = this;
    this.scheduledActions.push(trackingInfo);
    var now = new Date;
    setTimeout(function() {
        self.scheduledActions.splice(self.scheduledActions.indexOf(trackingInfo));
        self.savePending();
        trackingInfo.cb();
    }, trackingInfo.at.getTime() - now.getTime());
}

/**
* Register a callback for a given time
*
* There are two ways that this may be called.  For both methods the first argument
* is always a date object that the action should be fired at or as close to as
* possible.
*
* For a direct internal function callback the second argument is the callback to be
* fired when the time is hit.
*
* For a service URI callback the second argument is the service id to call into and
* the third argument is the URI path to call.
*/
exports.Scheduler.prototype.at = function() {
    if (arguments.length == 2) {
        this.scheduleInternal.apply(this, arguments);
    } else if (arguments.length == 3) {
        this.scheduleURL.apply(this, arguments);
    } else {
        console.error("Invalid scheduler call.");
    }
    this.savePending();
}

exports.masterScheduler = new exports.Scheduler;