antfarm
Version:
File automation framework for ants.
256 lines (219 loc) • 7.17 kB
text/typescript
import { FolderNest } from "../nest/folderNest";
import { Nest } from "../nest/nest";
import { Job } from "../job/job";
import { Environment } from "../environment/environment";
import {WebhookNest} from "../nest/webhookNest";
import {FtpNest} from "../nest/ftpNest";
const async = require("async"),
mm = require("micromatch");
/**
* Tunnels are runnable work flow units that can watch nests.
*/
export class Tunnel {
protected _name: string;
protected _nests: Nest[];
protected _run_list: any[];
protected _run_sync_list: any[];
protected _run_fail: any;
protected e: Environment;
protected job_counter: number;
protected match_obj = {
queue: [],
run: null,
pattern: null,
orphan_minutes: null
};
constructor(e: Environment, tunnelName: string) {
this.e = e;
this._nests = [];
this._name = tunnelName;
this._run_list = [];
this._run_sync_list = [];
this.job_counter = 0;
}
public toString() {
return "Tunnel";
}
public get name() {
return this._name;
}
public set name(name: string) {
this._name = name;
}
public get nests() {
return this._nests;
}
public get runList() {
return this._run_list;
}
public get runSyncList() {
return this._run_sync_list;
}
public get runFail() {
return this._run_fail;
}
public set runFail(callback: any) {
this._run_fail = callback;
}
/**
* Instructs the tunnel to watch a nest for new jobs.
* @param nest
*/
public watch(nest: FolderNest | WebhookNest | FtpNest) {
nest.register(this);
nest.load();
nest.watch();
this.nests.push(nest);
}
public arrive(job: Job, nest?: Nest) {
this.job_counter++;
this.e.log(1, `Job ${this.job_counter} triggered tunnel arrive.`, this, [job, nest]);
this.executeRun(job, nest);
this.executeRunSync(job, nest);
this.executeMatch(job, nest);
}
/**
* Run program logic asynchronously.
* @param callback
*/
public run(callback) {
this.runList.push(callback);
}
/**
* Run program logic synchronously.
* @param callback
*/
public runSync(callback) {
this.runSyncList.push(callback);
}
/**
* Failed jobs runner.
* @param callback
*/
public fail(callback) {
this.runFail = callback;
}
/**
* Asynchronous run event.
* @param job
* @param nest
*/
protected executeRun(job: Job, nest: Nest) {
let tn = this;
if (tn.runList.length > 0) {
tn.runList.forEach((callback) => {
try {
callback(job, nest);
} catch (e) {
// Fail if an error is thrown
tn.executeFail(job, nest, e);
}
});
}
}
/**
* Synchronous run event.
* @param job
* @param nest
*/
protected executeRunSync(job: Job, nest: Nest) {
let tn = this;
let breakFailure = false;
let successfulRuns = 0;
if (tn.runSyncList.length > 0) {
async.eachSeries(tn.runSyncList, (run, doNextRun) => {
if (breakFailure === false) {
run(job, nest, () => {
successfulRuns++;
doNextRun();
});
}
}, (err) => {
if (err) {
breakFailure = true;
tn.executeFail(job, nest, err);
}
tn.e.log(0, `Completed ${successfulRuns}/${tn.runSyncList.length} synchronous run list(s).`, tn, [job, nest]);
});
}
}
/**
* Fail run event.
* @param job
* @param nest
* @param reason
*/
public executeFail(job: Job, nest: Nest, reason: string) {
let tn = this;
tn.e.log(3, `Failed for reason "${reason}".`, tn, [job, nest]);
let failCallback = tn.runFail;
failCallback(job, nest, reason);
}
/**
* Interface for matching two or more _files together based on an array of glob filename patterns.
* @param pattern
* @param orphanMinutes
* @param callback
*/
public match(pattern: string[]|string, orphanMinutes: number, callback: any) {
this.match_obj.pattern = pattern;
this.match_obj.orphan_minutes = orphanMinutes;
this.match_obj.run = callback;
}
/**
* Match execution
* @param job
* @param nest
*/
protected executeMatch(job: Job, nest: Nest) {
if (this.match_obj.run) {
// Try to find match in queue
let tn = this;
let qjob_pattern_match_result, job_pattern_match_result;
let matched_jobs = [];
let job_base, qjob_base;
tn.e.log(0, "Executing matching process.", tn);
tn.match_obj.pattern.forEach(function (pattern, i) {
job_pattern_match_result = mm.isMatch(job.name, pattern);
if (job_pattern_match_result === true) {
job_base = job.name.substr(0, job.name.indexOf(pattern.replace("*", "")));
}
});
tn.match_obj.queue.slice().reverse().forEach(function (qJob, qIndex, qObject) {
tn.match_obj.pattern.forEach(function (pattern) {
qjob_pattern_match_result = mm.isMatch(qJob.name, pattern);
if (qjob_pattern_match_result === true) {
qjob_base = qJob.name.substr(0, qJob.name.indexOf(pattern.replace("*", "")));
if (job_base === qjob_base) {
// Pull out qjob from queue
tn.match_obj.queue.splice(qObject.length - 1 - qIndex, 1);
matched_jobs.push(qJob);
return;
}
}
});
});
// if found
if (matched_jobs.length > 0) {
matched_jobs.push(job);
tn.e.log(0, `Matched ${matched_jobs.length} jobs.`, tn);
tn.match_obj.run(matched_jobs);
} else {
// If not found, add this job to the queue
tn.match_obj.queue.push(job);
}
// Need to set a timeout for the job to fail
setTimeout(function () {
if (tn.match_obj.queue.length !== 0) {
// Fail all jobs in the queue
tn.match_obj.queue.forEach(function (fJob) {
fJob.fail("Orphan timeout.");
});
// Wipe the queue
tn.e.log(0, `Orphan timeout executed on ${tn.match_obj.queue.length} jobs.`, tn);
tn.match_obj.queue = [];
}
}, tn.match_obj.orphan_minutes * 60000);
}
}
}