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";
|
||||
let assert = require('assert');
|
||||
let fs = require('fs');
|
||||
let rmdir = require('rimraf');
|
||||
let odmRunner = require('./odmRunner');
|
||||
|
||||
let statusCodes = require('./statusCodes');
|
||||
|
@ -13,9 +14,8 @@ module.exports = class Task{
|
|||
this.uuid = uuid;
|
||||
this.name = name != "" ? name : "Task of " + (new Date()).toISOString();
|
||||
this.dateCreated = new Date().getTime();
|
||||
this.status = {
|
||||
code: statusCodes.QUEUED
|
||||
};
|
||||
this.processingTime = -1;
|
||||
this.setStatus(statusCodes.QUEUED);
|
||||
this.options = {};
|
||||
this.output = [];
|
||||
this.runnerProcess = null;
|
||||
|
@ -42,6 +42,11 @@ module.exports = class Task{
|
|||
return `data/${this.uuid}`;
|
||||
}
|
||||
|
||||
// Deletes files and folders related to this task
|
||||
cleanup(cb){
|
||||
rmdir(this.getProjectFolderPath(), cb);
|
||||
}
|
||||
|
||||
setStatus(code, extra){
|
||||
this.status = {
|
||||
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(){
|
||||
return this.status.code;
|
||||
}
|
||||
|
||||
isCanceled(){
|
||||
return this.status.code === statusCodes.CANCELED;
|
||||
}
|
||||
|
||||
// Cancels the current task (unless it's already canceled)
|
||||
cancel(cb){
|
||||
if (this.status.code !== statusCodes.CANCELED){
|
||||
let wasRunning = this.status.code === statusCodes.RUNNING;
|
||||
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);
|
||||
// TODO
|
||||
this.stopTrackingProcessingTime(true);
|
||||
cb(null);
|
||||
}else{
|
||||
cb(new Error("Task already cancelled"));
|
||||
|
@ -72,6 +113,7 @@ module.exports = class Task{
|
|||
// This will spawn a new process.
|
||||
start(done){
|
||||
if (this.status.code === statusCodes.QUEUED){
|
||||
this.startTrackingProcessingTime();
|
||||
this.setStatus(statusCodes.RUNNING);
|
||||
this.runnerProcess = odmRunner.run({
|
||||
projectPath: `${__dirname}/../${this.getProjectFolderPath()}`
|
||||
|
@ -79,12 +121,16 @@ module.exports = class Task{
|
|||
if (err){
|
||||
this.setStatus(statusCodes.FAILED, {errorMessage: `Could not start process (${err.message})`});
|
||||
}else{
|
||||
if (code === 0){
|
||||
this.setStatus(statusCodes.COMPLETED);
|
||||
}else{
|
||||
this.setStatus(statusCodes.FAILED, {errorMessage: `Process exited with code ${code}`});
|
||||
// Don't evaluate if we caused the process to exit via SIGINT?
|
||||
if (this.status.code !== statusCodes.CANCELED){
|
||||
if (code === 0){
|
||||
this.setStatus(statusCodes.COMPLETED);
|
||||
}else{
|
||||
this.setStatus(statusCodes.FAILED, {errorMessage: `Process exited with code ${code}`});
|
||||
}
|
||||
}
|
||||
}
|
||||
this.stopTrackingProcessingTime();
|
||||
done();
|
||||
}, output => {
|
||||
// Replace console colors
|
||||
|
@ -103,10 +149,9 @@ module.exports = class Task{
|
|||
restart(cb){
|
||||
if (this.status.code === statusCodes.CANCELED || this.status.code === statusCodes.FAILED){
|
||||
this.setStatus(statusCodes.QUEUED);
|
||||
|
||||
console.log("Requested to restart " + this.name);
|
||||
// TODO
|
||||
|
||||
this.dateCreated = new Date().getTime();
|
||||
this.output = [];
|
||||
this.stopTrackingProcessingTime(true);
|
||||
cb(null);
|
||||
}else{
|
||||
cb(new Error("Task cannot be restarted"));
|
||||
|
@ -119,6 +164,7 @@ module.exports = class Task{
|
|||
uuid: this.uuid,
|
||||
name: this.name,
|
||||
dateCreated: this.dateCreated,
|
||||
processingTime: this.processingTime,
|
||||
status: this.status,
|
||||
options: this.options,
|
||||
imagesCount: this.images.length
|
||||
|
|
|
@ -64,10 +64,15 @@ module.exports = class TaskManager{
|
|||
cancel(uuid, cb){
|
||||
let task;
|
||||
if (task = this.find(uuid, cb)){
|
||||
task.cancel(err => {
|
||||
this.removeFromRunningQueue(task);
|
||||
cb(err);
|
||||
});
|
||||
if (!task.isCanceled()){
|
||||
task.cancel(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){
|
||||
this.cancel(uuid, err => {
|
||||
if (!err){
|
||||
delete(this.tasks[uuid]);
|
||||
// TODO: other cleanup
|
||||
cb(null);
|
||||
let task;
|
||||
if (task = this.find(uuid, cb)){
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
@ -89,7 +101,7 @@ module.exports = class TaskManager{
|
|||
let task;
|
||||
if (task = this.find(uuid, cb)){
|
||||
task.restart(err => {
|
||||
this.processNextTask();
|
||||
if (!err) this.processNextTask();
|
||||
cb(err);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -25,7 +25,8 @@
|
|||
"express": "^4.14.0",
|
||||
"morgan": "^1.7.0",
|
||||
"multer": "^1.1.0",
|
||||
"node-uuid": "^1.4.7"
|
||||
"node-uuid": "^1.4.7",
|
||||
"rimraf": "^2.5.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"nodemon": "^1.9.2"
|
||||
|
|
|
@ -42,7 +42,7 @@
|
|||
}
|
||||
@keyframes pulsateNegative {
|
||||
0% {background-color: #fff;}
|
||||
50% {background-color: lightred;}
|
||||
50% {background-color: pink;}
|
||||
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>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>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>
|
||||
<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>
|
||||
|
||||
<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
|
||||
</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
|
||||
</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>
|
||||
|
||||
|
|
|
@ -1,4 +1,22 @@
|
|||
$(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(){
|
||||
var uuids = JSON.parse(localStorage.getItem("odmTaskList") || "[]");
|
||||
if (Object.prototype.toString.call(uuids) !== "[object Array]") uuids = [];
|
||||
|
@ -31,8 +49,17 @@ $(function(){
|
|||
this.loading = ko.observable(true);
|
||||
this.info = ko.observable({});
|
||||
this.viewingOutput = ko.observable(false);
|
||||
this.output = ko.observableArray();
|
||||
this.resetOutput();
|
||||
this.timeElapsed = ko.observable("00:00:00");
|
||||
|
||||
var codes = {
|
||||
QUEUED: 10,
|
||||
RUNNING: 20,
|
||||
FAILED: 30,
|
||||
COMPLETED: 40,
|
||||
CANCELED: 50
|
||||
};
|
||||
var statusCodes = {
|
||||
10: {
|
||||
descr: "Queued",
|
||||
|
@ -40,7 +67,7 @@ $(function(){
|
|||
},
|
||||
20: {
|
||||
descr: "Running",
|
||||
icon: "glyphicon-refresh spinning"
|
||||
icon: "glyphicon-cog spinning"
|
||||
},
|
||||
30: {
|
||||
descr: "Failed",
|
||||
|
@ -70,8 +97,17 @@ $(function(){
|
|||
}else return "glyphicon-question-sign";
|
||||
}else return "";
|
||||
}, this);
|
||||
this.canceled = ko.pureComputed(function(){
|
||||
return this.info().status && this.info().status.code === 50;
|
||||
this.showCancel = ko.pureComputed(function(){
|
||||
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.startRefreshingInfo();
|
||||
|
@ -80,7 +116,14 @@ $(function(){
|
|||
var self = this;
|
||||
var url = "/task/" + this.uuid + "/info";
|
||||
$.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(){
|
||||
self.info({error: url + " is unreachable."});
|
||||
})
|
||||
|
@ -91,7 +134,7 @@ $(function(){
|
|||
Task.prototype.resetOutput = function(){
|
||||
this.viewOutputLine = 0;
|
||||
this.autoScrollOutput = true;
|
||||
this.output = ko.observableArray();
|
||||
this.output.removeAll();
|
||||
};
|
||||
Task.prototype.viewOutput = function(){
|
||||
var self = this;
|
||||
|
@ -130,7 +173,7 @@ $(function(){
|
|||
this.refreshInfo();
|
||||
this.refreshInterval = setInterval(function(){
|
||||
self.refreshInfo();
|
||||
}, 2000);
|
||||
}, 500); // TODO: change to larger value
|
||||
};
|
||||
Task.prototype.stopRefreshingInfo = function() {
|
||||
if (this.refreshInterval){
|
||||
|
@ -160,7 +203,7 @@ $(function(){
|
|||
});
|
||||
};
|
||||
|
||||
function genApiCall(url){
|
||||
function genApiCall(url, onSuccess){
|
||||
return function(){
|
||||
var self = this;
|
||||
|
||||
|
@ -169,6 +212,7 @@ $(function(){
|
|||
})
|
||||
.done(function(json){
|
||||
if (json.success){
|
||||
if (onSuccess !== undefined) onSuccess(self, json);
|
||||
self.startRefreshingInfo();
|
||||
}else{
|
||||
self.stopRefreshingInfo();
|
||||
|
@ -182,7 +226,9 @@ $(function(){
|
|||
}
|
||||
};
|
||||
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();
|
||||
ko.applyBindings(taskList);
|
||||
|
|
Ładowanie…
Reference in New Issue