kopia lustrzana https://github.com/OpenDroneMap/NodeODM
Task cancel, remove, time elapsed
rodzic
48e292a6cf
commit
2b3411dadc
72
libs/Task.js
72
libs/Task.js
|
@ -1,6 +1,7 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
let assert = require('assert');
|
let assert = require('assert');
|
||||||
let fs = require('fs');
|
let fs = require('fs');
|
||||||
|
let rmdir = require('rimraf');
|
||||||
let odmRunner = require('./odmRunner');
|
let odmRunner = require('./odmRunner');
|
||||||
|
|
||||||
let statusCodes = require('./statusCodes');
|
let statusCodes = require('./statusCodes');
|
||||||
|
@ -13,9 +14,8 @@ module.exports = class Task{
|
||||||
this.uuid = uuid;
|
this.uuid = uuid;
|
||||||
this.name = name != "" ? name : "Task of " + (new Date()).toISOString();
|
this.name = name != "" ? name : "Task of " + (new Date()).toISOString();
|
||||||
this.dateCreated = new Date().getTime();
|
this.dateCreated = new Date().getTime();
|
||||||
this.status = {
|
this.processingTime = -1;
|
||||||
code: statusCodes.QUEUED
|
this.setStatus(statusCodes.QUEUED);
|
||||||
};
|
|
||||||
this.options = {};
|
this.options = {};
|
||||||
this.output = [];
|
this.output = [];
|
||||||
this.runnerProcess = null;
|
this.runnerProcess = null;
|
||||||
|
@ -42,6 +42,11 @@ module.exports = class Task{
|
||||||
return `data/${this.uuid}`;
|
return `data/${this.uuid}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deletes files and folders related to this task
|
||||||
|
cleanup(cb){
|
||||||
|
rmdir(this.getProjectFolderPath(), cb);
|
||||||
|
}
|
||||||
|
|
||||||
setStatus(code, extra){
|
setStatus(code, extra){
|
||||||
this.status = {
|
this.status = {
|
||||||
code: code
|
code: code
|
||||||
|
@ -51,17 +56,53 @@ module.exports = class Task{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateProcessingTime(resetTime){
|
||||||
|
this.processingTime = resetTime ?
|
||||||
|
-1 :
|
||||||
|
new Date().getTime() - this.dateCreated;
|
||||||
|
}
|
||||||
|
|
||||||
|
startTrackingProcessingTime(){
|
||||||
|
this.updateProcessingTime();
|
||||||
|
if (!this._updateProcessingTimeInterval){
|
||||||
|
this._updateProcessingTimeInterval = setInterval(() => {
|
||||||
|
this.updateProcessingTime();
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stopTrackingProcessingTime(resetTime){
|
||||||
|
this.updateProcessingTime(resetTime);
|
||||||
|
if (this._updateProcessingTimeInterval){
|
||||||
|
clearInterval(this._updateProcessingTimeInterval);
|
||||||
|
this._updateProcessingTimeInterval = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
getStatus(){
|
getStatus(){
|
||||||
return this.status.code;
|
return this.status.code;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isCanceled(){
|
||||||
|
return this.status.code === statusCodes.CANCELED;
|
||||||
|
}
|
||||||
|
|
||||||
// Cancels the current task (unless it's already canceled)
|
// Cancels the current task (unless it's already canceled)
|
||||||
cancel(cb){
|
cancel(cb){
|
||||||
if (this.status.code !== statusCodes.CANCELED){
|
if (this.status.code !== statusCodes.CANCELED){
|
||||||
|
let wasRunning = this.status.code === statusCodes.RUNNING;
|
||||||
this.setStatus(statusCodes.CANCELED);
|
this.setStatus(statusCodes.CANCELED);
|
||||||
|
|
||||||
|
if (wasRunning && this.runnerProcess){
|
||||||
|
// TODO: this does guarantee that
|
||||||
|
// the process will immediately terminate.
|
||||||
|
// In fact, often times ODM will continue running for a while
|
||||||
|
// This might need to be fixed on ODM's end.
|
||||||
|
this.runnerProcess.kill('SIGINT');
|
||||||
|
this.runnerProcess = null;
|
||||||
|
}
|
||||||
|
|
||||||
console.log("Requested to cancel " + this.name);
|
this.stopTrackingProcessingTime(true);
|
||||||
// TODO
|
|
||||||
cb(null);
|
cb(null);
|
||||||
}else{
|
}else{
|
||||||
cb(new Error("Task already cancelled"));
|
cb(new Error("Task already cancelled"));
|
||||||
|
@ -72,6 +113,7 @@ module.exports = class Task{
|
||||||
// This will spawn a new process.
|
// This will spawn a new process.
|
||||||
start(done){
|
start(done){
|
||||||
if (this.status.code === statusCodes.QUEUED){
|
if (this.status.code === statusCodes.QUEUED){
|
||||||
|
this.startTrackingProcessingTime();
|
||||||
this.setStatus(statusCodes.RUNNING);
|
this.setStatus(statusCodes.RUNNING);
|
||||||
this.runnerProcess = odmRunner.run({
|
this.runnerProcess = odmRunner.run({
|
||||||
projectPath: `${__dirname}/../${this.getProjectFolderPath()}`
|
projectPath: `${__dirname}/../${this.getProjectFolderPath()}`
|
||||||
|
@ -79,12 +121,16 @@ module.exports = class Task{
|
||||||
if (err){
|
if (err){
|
||||||
this.setStatus(statusCodes.FAILED, {errorMessage: `Could not start process (${err.message})`});
|
this.setStatus(statusCodes.FAILED, {errorMessage: `Could not start process (${err.message})`});
|
||||||
}else{
|
}else{
|
||||||
if (code === 0){
|
// Don't evaluate if we caused the process to exit via SIGINT?
|
||||||
this.setStatus(statusCodes.COMPLETED);
|
if (this.status.code !== statusCodes.CANCELED){
|
||||||
}else{
|
if (code === 0){
|
||||||
this.setStatus(statusCodes.FAILED, {errorMessage: `Process exited with code ${code}`});
|
this.setStatus(statusCodes.COMPLETED);
|
||||||
|
}else{
|
||||||
|
this.setStatus(statusCodes.FAILED, {errorMessage: `Process exited with code ${code}`});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
this.stopTrackingProcessingTime();
|
||||||
done();
|
done();
|
||||||
}, output => {
|
}, output => {
|
||||||
// Replace console colors
|
// Replace console colors
|
||||||
|
@ -103,10 +149,9 @@ module.exports = class Task{
|
||||||
restart(cb){
|
restart(cb){
|
||||||
if (this.status.code === statusCodes.CANCELED || this.status.code === statusCodes.FAILED){
|
if (this.status.code === statusCodes.CANCELED || this.status.code === statusCodes.FAILED){
|
||||||
this.setStatus(statusCodes.QUEUED);
|
this.setStatus(statusCodes.QUEUED);
|
||||||
|
this.dateCreated = new Date().getTime();
|
||||||
console.log("Requested to restart " + this.name);
|
this.output = [];
|
||||||
// TODO
|
this.stopTrackingProcessingTime(true);
|
||||||
|
|
||||||
cb(null);
|
cb(null);
|
||||||
}else{
|
}else{
|
||||||
cb(new Error("Task cannot be restarted"));
|
cb(new Error("Task cannot be restarted"));
|
||||||
|
@ -119,6 +164,7 @@ module.exports = class Task{
|
||||||
uuid: this.uuid,
|
uuid: this.uuid,
|
||||||
name: this.name,
|
name: this.name,
|
||||||
dateCreated: this.dateCreated,
|
dateCreated: this.dateCreated,
|
||||||
|
processingTime: this.processingTime,
|
||||||
status: this.status,
|
status: this.status,
|
||||||
options: this.options,
|
options: this.options,
|
||||||
imagesCount: this.images.length
|
imagesCount: this.images.length
|
||||||
|
|
|
@ -64,10 +64,15 @@ module.exports = class TaskManager{
|
||||||
cancel(uuid, cb){
|
cancel(uuid, cb){
|
||||||
let task;
|
let task;
|
||||||
if (task = this.find(uuid, cb)){
|
if (task = this.find(uuid, cb)){
|
||||||
task.cancel(err => {
|
if (!task.isCanceled()){
|
||||||
this.removeFromRunningQueue(task);
|
task.cancel(err => {
|
||||||
cb(err);
|
this.removeFromRunningQueue(task);
|
||||||
});
|
this.processNextTask();
|
||||||
|
cb(err);
|
||||||
|
});
|
||||||
|
}else{
|
||||||
|
cb(null); // Nothing to be done
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,9 +81,16 @@ module.exports = class TaskManager{
|
||||||
remove(uuid, cb){
|
remove(uuid, cb){
|
||||||
this.cancel(uuid, err => {
|
this.cancel(uuid, err => {
|
||||||
if (!err){
|
if (!err){
|
||||||
delete(this.tasks[uuid]);
|
let task;
|
||||||
// TODO: other cleanup
|
if (task = this.find(uuid, cb)){
|
||||||
cb(null);
|
task.cleanup(err => {
|
||||||
|
if (!err){
|
||||||
|
delete(this.tasks[uuid]);
|
||||||
|
this.processNextTask();
|
||||||
|
cb(null);
|
||||||
|
}else cb(err);
|
||||||
|
});
|
||||||
|
}else; // cb is called by find on error
|
||||||
}else cb(err);
|
}else cb(err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -89,7 +101,7 @@ module.exports = class TaskManager{
|
||||||
let task;
|
let task;
|
||||||
if (task = this.find(uuid, cb)){
|
if (task = this.find(uuid, cb)){
|
||||||
task.restart(err => {
|
task.restart(err => {
|
||||||
this.processNextTask();
|
if (!err) this.processNextTask();
|
||||||
cb(err);
|
cb(err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,8 @@
|
||||||
"express": "^4.14.0",
|
"express": "^4.14.0",
|
||||||
"morgan": "^1.7.0",
|
"morgan": "^1.7.0",
|
||||||
"multer": "^1.1.0",
|
"multer": "^1.1.0",
|
||||||
"node-uuid": "^1.4.7"
|
"node-uuid": "^1.4.7",
|
||||||
|
"rimraf": "^2.5.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"nodemon": "^1.9.2"
|
"nodemon": "^1.9.2"
|
||||||
|
|
|
@ -42,7 +42,7 @@
|
||||||
}
|
}
|
||||||
@keyframes pulsateNegative {
|
@keyframes pulsateNegative {
|
||||||
0% {background-color: #fff;}
|
0% {background-color: #fff;}
|
||||||
50% {background-color: lightred;}
|
50% {background-color: pink;}
|
||||||
100% {background-color: #fff;}
|
100% {background-color: #fff;}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -64,19 +64,24 @@
|
||||||
<div class="taskItem"><strong>Name:</strong> <span data-bind="text: info().name"></span></div>
|
<div class="taskItem"><strong>Name:</strong> <span data-bind="text: info().name"></span></div>
|
||||||
<div class="taskItem"><strong>Images:</strong> <span data-bind="text: info().imagesCount"></span></div>
|
<div class="taskItem"><strong>Images:</strong> <span data-bind="text: info().imagesCount"></span></div>
|
||||||
<div class="taskItem"><strong>Status:</strong> <span data-bind="text: statusDescr()"></span></div>
|
<div class="taskItem"><strong>Status:</strong> <span data-bind="text: statusDescr()"></span></div>
|
||||||
|
<div class="taskItem"><strong>Time Elapsed:</strong> <span data-bind="text: timeElapsed()"></span></div>
|
||||||
<div class="taskItem"><strong>Output:</strong> <a href="javascript:void(0);" data-bind="click: viewOutput, visible: !viewingOutput()">View</a><a href="javascript:void(0);" data-bind="click: hideOutput, visible: viewingOutput()">Hide</a></div>
|
<div class="taskItem"><strong>Output:</strong> <a href="javascript:void(0);" data-bind="click: viewOutput, visible: !viewingOutput()">View</a><a href="javascript:void(0);" data-bind="click: hideOutput, visible: viewingOutput()">Hide</a></div>
|
||||||
<textarea class="consoleOutput" data-bind="value: output().join(''), visible: viewingOutput(), event: {mouseover: consoleMouseOver, mouseout: consoleMouseOut}, attr: {id: 'console_' + uuid}"></textarea>
|
<textarea class="consoleOutput" data-bind="value: output().join(''), visible: viewingOutput(), event: {mouseover: consoleMouseOver, mouseout: consoleMouseOut}, attr: {id: 'console_' + uuid}"></textarea>
|
||||||
|
|
||||||
<span data-bind="css: 'statusIcon glyphicon ' + icon()"></span>
|
<span data-bind="css: 'statusIcon glyphicon ' + icon()"></span>
|
||||||
|
|
||||||
<div class="actionButtons">
|
<div class="actionButtons">
|
||||||
<button data-bind="click: cancel, visible: !canceled()" type="button" class="btn btn-primary btn-sm" >
|
<button data-bind="click: cancel, visible: showCancel()" type="button" class="btn btn-primary btn-sm" >
|
||||||
<span class="glyphicon glyphicon-remove-circle"></span> Cancel
|
<span class="glyphicon glyphicon-remove-circle"></span> Cancel
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<button data-bind="click: restart, visible: canceled()" type="button" class="btn btn-primary btn-sm" >
|
<button data-bind="click: restart, visible: showRestart()" type="button" class="btn btn-primary btn-sm" >
|
||||||
<span class="glyphicon glyphicon-play"></span> Restart
|
<span class="glyphicon glyphicon-play"></span> Restart
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<button data-bind="click: remove, visible: showRemove()" type="button" class="btn btn-primary btn-sm">
|
||||||
|
<span class="glyphicon glyphicon-remove-circle"></span> Remove
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,22 @@
|
||||||
$(function(){
|
$(function(){
|
||||||
|
function hoursMinutesSecs(t){
|
||||||
|
var ch = 60 * 60 * 1000,
|
||||||
|
cm = 60 * 1000,
|
||||||
|
h = Math.floor(t / ch),
|
||||||
|
m = Math.floor( (t - h * ch) / cm),
|
||||||
|
s = Math.round( (t - h * ch - m * cm) / 1000),
|
||||||
|
pad = function(n){ return n < 10 ? '0' + n : n; };
|
||||||
|
if( s === 60 ){
|
||||||
|
h++;
|
||||||
|
s = 0;
|
||||||
|
}
|
||||||
|
if( m === 60 ){
|
||||||
|
h++;
|
||||||
|
m = 0;
|
||||||
|
}
|
||||||
|
return [pad(h), pad(m), pad(s)].join(':');
|
||||||
|
}
|
||||||
|
|
||||||
function TaskList(){
|
function TaskList(){
|
||||||
var uuids = JSON.parse(localStorage.getItem("odmTaskList") || "[]");
|
var uuids = JSON.parse(localStorage.getItem("odmTaskList") || "[]");
|
||||||
if (Object.prototype.toString.call(uuids) !== "[object Array]") uuids = [];
|
if (Object.prototype.toString.call(uuids) !== "[object Array]") uuids = [];
|
||||||
|
@ -31,8 +49,17 @@ $(function(){
|
||||||
this.loading = ko.observable(true);
|
this.loading = ko.observable(true);
|
||||||
this.info = ko.observable({});
|
this.info = ko.observable({});
|
||||||
this.viewingOutput = ko.observable(false);
|
this.viewingOutput = ko.observable(false);
|
||||||
|
this.output = ko.observableArray();
|
||||||
this.resetOutput();
|
this.resetOutput();
|
||||||
|
this.timeElapsed = ko.observable("00:00:00");
|
||||||
|
|
||||||
|
var codes = {
|
||||||
|
QUEUED: 10,
|
||||||
|
RUNNING: 20,
|
||||||
|
FAILED: 30,
|
||||||
|
COMPLETED: 40,
|
||||||
|
CANCELED: 50
|
||||||
|
};
|
||||||
var statusCodes = {
|
var statusCodes = {
|
||||||
10: {
|
10: {
|
||||||
descr: "Queued",
|
descr: "Queued",
|
||||||
|
@ -40,7 +67,7 @@ $(function(){
|
||||||
},
|
},
|
||||||
20: {
|
20: {
|
||||||
descr: "Running",
|
descr: "Running",
|
||||||
icon: "glyphicon-refresh spinning"
|
icon: "glyphicon-cog spinning"
|
||||||
},
|
},
|
||||||
30: {
|
30: {
|
||||||
descr: "Failed",
|
descr: "Failed",
|
||||||
|
@ -70,8 +97,17 @@ $(function(){
|
||||||
}else return "glyphicon-question-sign";
|
}else return "glyphicon-question-sign";
|
||||||
}else return "";
|
}else return "";
|
||||||
}, this);
|
}, this);
|
||||||
this.canceled = ko.pureComputed(function(){
|
this.showCancel = ko.pureComputed(function(){
|
||||||
return this.info().status && this.info().status.code === 50;
|
return this.info().status &&
|
||||||
|
(this.info().status.code === codes.QUEUED || this.info().status.code === codes.RUNNING);
|
||||||
|
}, this);
|
||||||
|
this.showRestart = ko.pureComputed(function(){
|
||||||
|
return this.info().status &&
|
||||||
|
(this.info().status.code === codes.CANCELED);
|
||||||
|
}, this);
|
||||||
|
this.showRemove = ko.pureComputed(function(){
|
||||||
|
return this.info().status &&
|
||||||
|
(this.info().status.code === codes.FAILED || this.info().status.code === codes.COMPLETED || this.info().status.code === codes.CANCELED);
|
||||||
}, this);
|
}, this);
|
||||||
|
|
||||||
this.startRefreshingInfo();
|
this.startRefreshingInfo();
|
||||||
|
@ -80,7 +116,14 @@ $(function(){
|
||||||
var self = this;
|
var self = this;
|
||||||
var url = "/task/" + this.uuid + "/info";
|
var url = "/task/" + this.uuid + "/info";
|
||||||
$.get(url)
|
$.get(url)
|
||||||
.done(self.info)
|
.done(function(json){
|
||||||
|
// Track time
|
||||||
|
|
||||||
|
if (json.processingTime && json.processingTime !== -1){
|
||||||
|
self.timeElapsed(hoursMinutesSecs(json.processingTime));
|
||||||
|
}
|
||||||
|
self.info(json);
|
||||||
|
})
|
||||||
.fail(function(){
|
.fail(function(){
|
||||||
self.info({error: url + " is unreachable."});
|
self.info({error: url + " is unreachable."});
|
||||||
})
|
})
|
||||||
|
@ -91,7 +134,7 @@ $(function(){
|
||||||
Task.prototype.resetOutput = function(){
|
Task.prototype.resetOutput = function(){
|
||||||
this.viewOutputLine = 0;
|
this.viewOutputLine = 0;
|
||||||
this.autoScrollOutput = true;
|
this.autoScrollOutput = true;
|
||||||
this.output = ko.observableArray();
|
this.output.removeAll();
|
||||||
};
|
};
|
||||||
Task.prototype.viewOutput = function(){
|
Task.prototype.viewOutput = function(){
|
||||||
var self = this;
|
var self = this;
|
||||||
|
@ -130,7 +173,7 @@ $(function(){
|
||||||
this.refreshInfo();
|
this.refreshInfo();
|
||||||
this.refreshInterval = setInterval(function(){
|
this.refreshInterval = setInterval(function(){
|
||||||
self.refreshInfo();
|
self.refreshInfo();
|
||||||
}, 2000);
|
}, 500); // TODO: change to larger value
|
||||||
};
|
};
|
||||||
Task.prototype.stopRefreshingInfo = function() {
|
Task.prototype.stopRefreshingInfo = function() {
|
||||||
if (this.refreshInterval){
|
if (this.refreshInterval){
|
||||||
|
@ -160,7 +203,7 @@ $(function(){
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
function genApiCall(url){
|
function genApiCall(url, onSuccess){
|
||||||
return function(){
|
return function(){
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
|
@ -169,6 +212,7 @@ $(function(){
|
||||||
})
|
})
|
||||||
.done(function(json){
|
.done(function(json){
|
||||||
if (json.success){
|
if (json.success){
|
||||||
|
if (onSuccess !== undefined) onSuccess(self, json);
|
||||||
self.startRefreshingInfo();
|
self.startRefreshingInfo();
|
||||||
}else{
|
}else{
|
||||||
self.stopRefreshingInfo();
|
self.stopRefreshingInfo();
|
||||||
|
@ -182,7 +226,9 @@ $(function(){
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Task.prototype.cancel = genApiCall("/task/cancel");
|
Task.prototype.cancel = genApiCall("/task/cancel");
|
||||||
Task.prototype.restart = genApiCall("/task/restart");
|
Task.prototype.restart = genApiCall("/task/restart", function(task){
|
||||||
|
task.resetOutput();
|
||||||
|
});
|
||||||
|
|
||||||
var taskList = new TaskList();
|
var taskList = new TaskList();
|
||||||
ko.applyBindings(taskList);
|
ko.applyBindings(taskList);
|
||||||
|
|
Ładowanie…
Reference in New Issue