hopjs
Version:
A RESTful declarative API framework, with stub generators for Shell, and Android
293 lines (252 loc) • 7.98 kB
JavaScript
/**
Provides generic job functionality
@beta
@module Hop
@submodule Job
**/
var Hop = require('./api');
var crypto = require('crypto');
var JobStatus = function(){
}
JobStatus.wait=function(input,onComplete){
Hop.Job.load(input.jobID,function(err,job){
if(err)
return onComplete(err);
if(job && job.finished){
return onComplete(job.err,job.result);
}
Hop.Job.wait(input.jobID,function(err,result){
return onComplete(err,result);
});
});
}
JobStatus.listen=function(input,onComplete){
Hop.Job.load(input.jobID,function(err,job){
if(err)
return onComplete(err);
if(job && job.finished){
return onComplete(null, {msg: job.msg||"", percent: job.percent||100});
}
Hop.Job.listen(input.jobID,function(msg,percent){
return onComplete(null,{msg: msg, percent: percent});
});
});
}
JobStatus.load=function(input,onComplete){
Hop.Job.load(input.jobID,function(err,job){
return onComplete(err,job);
});
}
Hop.defineClass("JobStatus",JobStatus,function(api){
api.get("wait","/_job/wait/:jobID").demand("jobID");
api.get("listen","/_job/listen/:jobID").demand("jobID");
api.get("load","/_job/:jobID").demand("jobID");
});
Hop.addBeforeTemplate("JavaScript","job/preJSHop.comb");
Hop.Method.prototype.jobMaxStall=function(duration){
if(!this.job)
this.job={};
this.job.maxStall=duration;
return this;
}
Hop.Method.prototype.jobMinDuration=function(duration){
if(!this.job)
this.job={};
this.job.minDuration=duration;
return this;
}
Hop.Method.prototype.jobMaxDuration=function(duration){
if(!this.job)
this.job={};
this.job.maxDuration=duration;
return this;
}
Hop.Method.prototype.trackJob=function(jobPath,duration,allowRepeatIfFinished){
duration=duration||(24*60*60);
var self=this;
self.addAfterTemplate("JavaScript","job/postJSMethod.comb");
self.addPreCall(function(req,input,onComplete,next){
if(jobPath){
jobPath=Hop.Job.resolvePath(jobPath,req,input);
}
if(jobPath){
Hop.Job.loadByPath(jobPath,function(err,job){
if(job){
var forceNew = false;
if(job.started && !job.finished){
var jobDuration = Math.ceil(((new Date()).getTime()-job.started)/1000.0);
if(self.job && self.job.maxDuration){
if(jobDuration > self.job.maxDuration)
forceNew = true;
}
Hop.log("jobDuration",jobDuration);
}
if(job.updated && !job.finished){
var jobStall = Math.ceil(((new Date()).getTime()-job.updated)/1000.0);
if(self.job && self.job.maxStall){
if(jobStall > self.job.maxStall)
forceNew = true;
}
Hop.log("jobStall",jobStall);
}
if(allowRepeatIfFinished && job.finished){
var jobID = Hop.Job.id();
req.getResponse().set("Job-ID",jobID);
req.getResponse().set("Job-Status","new-repeat");
req.job = new Hop.Job(jobID);
req.job.start(jobPath,duration,function(){
if(req.get("Job-No-Wait")==null){
next();
} else {
onComplete(err,jobID);
next();
}
});
} else {
if(job.finished){
req.getResponse().set("Job-Status","finished");
req.getResponse().set("Job-ID",job.jobID);
return onComplete(job.err,job.result);
} else {
//FIXME we have issue here, what if the job is actually dead?
// we need some metrics to determine at what point the job should
// be considererd dead.
if(forceNew){
var jobID = Hop.Job.id();
req.getResponse().set("Job-ID",jobID);
req.getResponse().set("Job-Status","new-repeat");
req.job = new Hop.Job(jobID);
req.job.start(jobPath,duration,function(){
if(req.get("Job-No-Wait")==null){
next();
} else {
onComplete(err,jobID);
next();
}
});
} else {
req.getResponse().set("Job-Status","existing");
req.getResponse().set("Job-ID",job.jobID);
if(req.get("Job-No-Wait")==null){
Hop.Job.wait(job.jobID,function(err,result){
return onComplete(err,result);
});
} else return onComplete(err,jobID);
}
}
}
} else {
var jobID = Hop.Job.id();
req.getResponse().set("Job-ID",jobID);
req.getResponse().set("Job-Status","new-foo");
req.job = new Hop.Job(jobID);
req.job.start(jobPath,duration,function(){
if(req.get("Job-No-Wait")==null){
next();
} else {
onComplete(err,jobID);
next();
}
});
}
});
} else {
var jobID = Hop.Job.id();
req.getResponse().set("Job-ID",jobID);
req.getResponse().set("Job-Status","new-bar");
req.job = new Hop.Job(jobID);
req.job.start(jobPath,duration,function(){
if(req.get("Job-No-Wait")==null){
next();
} else {
onComplete(err,jobID);
next();
}
});
}
});
self.addPostCall(function(req,input,error,result,onComplete){
if(req.job){
req.job.finish(error,result,function(err,res){
return onComplete();
});
} else {
return onComplete();
}
});
return this;
}
Hop.Job=function(id){
this.jobID=id;
}
Hop.Job.id=function(){
var id=crypto.createHash("md5");
id.update((new Date().toString())+":"+Math.random()*10000+"");
return id.digest("hex");
}
Hop.Job.resolvePath=function(path,req,input){
var request=req;
return path.replace(/:([^\/]+)/g,function(m,s){
if(s){
try {
with(input){
var v = eval(s);
if(v==undefined){
throw "Undefined value found in cache path: "+s;
}
return v.toString();
}
} catch(e){
throw "Error in cache path: "+s+" is "+e;
}
}
});
}
Hop.Job.prototype.start=function(jobPath,duration,onComplete){
var job = {
jobID: this.jobID,
started: new Date().getTime(),
expiresAt: new Date().getTime() + (duration*1000)
};
if(jobPath!=null){
job.path = jobPath;
}
Hop.Job.save(this.jobID,job,onComplete);
}
Hop.Job.prototype.finish=function(jobErr,jobResult,onComplete){
var self=this;
Hop.Job.load(self.jobID,function(err,job){
if(!err && job){
job.finished = new Date().getTime();
job.err=jobErr;
job.result=jobResult;
Hop.Job.save(self.jobID,job,function(err,res){
Hop.Job.notifyStatus(self.jobID, job.msg||"", job.percent||100,function(){
Hop.Job.notifyFinished(self.jobID,jobErr,jobResult,onComplete);
});
});
} else onComplete(err,null);
});
}
Hop.Job.prototype.setStatus=function(msg,percent,onComplete){
onComplete=onComplete||function(){};
var self=this;
Hop.Job.load(self.jobID,function(err,job){
if(!err && job){
job.msg=msg;
job.percent=percent;
job.updated = (new Date()).getTime();
Hop.Job.save(self.jobID,job,function(err,res){
Hop.Job.notifyStatus(self.jobID, job.msg, job.percent,onComplete);
});
} else onComplete(err,null);
});
}
Hop.Job.prototype.getStatus=function(onComplete){
Hop.Job.load(this.jobID,function(err,job){
if(!err && job){
onComplete(null, { percent: job.percent, msg: job.msg });
} else onComplete(err,null);
});
}
module.exports=Hop;