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