kopia lustrzana https://github.com/OpenDroneMap/NodeODM
Cancel task, restart task, minor refactoring
rodzic
514bc654c0
commit
c0e239d709
40
index.js
40
index.js
|
@ -6,10 +6,13 @@ let app = express();
|
|||
|
||||
let addRequestId = require('./libs/express-request-id')();
|
||||
let multer = require('multer');
|
||||
let bodyParser = require('body-parser');
|
||||
|
||||
let taskManager = new (require('./libs/taskManager'))();
|
||||
let Task = require('./libs/Task');
|
||||
|
||||
app.use(bodyParser.urlencoded({extended: true}));
|
||||
app.use(bodyParser.json());
|
||||
app.use(express.static('public'));
|
||||
|
||||
let upload = multer({
|
||||
|
@ -32,16 +35,21 @@ let upload = multer({
|
|||
})
|
||||
});
|
||||
|
||||
app.post('/newTask', addRequestId, upload.array('images'), (req, res, next) => {
|
||||
app.post('/newTask', addRequestId, upload.array('images'), (req, res) => {
|
||||
if (req.files.length === 0) res.json({error: "Need at least 1 file."});
|
||||
else{
|
||||
console.log(`Received ${req.files.length} files`);
|
||||
|
||||
// Move to data
|
||||
fs.rename(`tmp/${req.id}`, `data/${req.id}`, (err) => {
|
||||
fs.rename(`tmp/${req.id}`, `data/${req.id}`, err => {
|
||||
if (!err){
|
||||
taskManager.addNew(new Task(req.id, req.body.name));
|
||||
new Task(req.id, req.body.name, (err, task) => {
|
||||
if (err) res.json({error: err.message});
|
||||
else{
|
||||
taskManager.addNew(task);
|
||||
res.json({uuid: req.id, success: true});
|
||||
}
|
||||
});
|
||||
}else{
|
||||
res.json({error: "Could not move images folder."});
|
||||
}
|
||||
|
@ -49,13 +57,37 @@ app.post('/newTask', addRequestId, upload.array('images'), (req, res, next) => {
|
|||
}
|
||||
});
|
||||
|
||||
app.get('/taskInfo/:uuid', (req, res, next) => {
|
||||
app.get('/taskInfo/:uuid', (req, res) => {
|
||||
let task = taskManager.find(req.params.uuid);
|
||||
if (task){
|
||||
res.json(task.getInfo());
|
||||
}else res.json({error: `${req.params.uuid} not found`});
|
||||
});
|
||||
|
||||
let uuidCheck = (req, res, next) => {
|
||||
if (!req.body.uuid) res.json({error: "uuid param missing."});
|
||||
else next();
|
||||
};
|
||||
|
||||
let successHandler = res => {
|
||||
return err => {
|
||||
if (!err) res.json({success: true});
|
||||
else res.json({error: err.message});
|
||||
};
|
||||
};
|
||||
|
||||
app.post('/cancelTask', uuidCheck, (req, res) => {
|
||||
taskManager.cancel(req.body.uuid, successHandler(res));
|
||||
});
|
||||
|
||||
app.post('/removeTask', uuidCheck, (req, res) => {
|
||||
taskManager.remove(req.body.uuid, successHandler(res));
|
||||
});
|
||||
|
||||
app.post('/restartTask', uuidCheck, (req, res) => {
|
||||
taskManager.restart(req.body.uuid, successHandler(res));
|
||||
});
|
||||
|
||||
app.listen(3000, () => {
|
||||
console.log('Example app listening on port 3000!');
|
||||
});
|
44
libs/Task.js
44
libs/Task.js
|
@ -1,16 +1,19 @@
|
|||
"use strict";
|
||||
let assert = require('assert');
|
||||
let fs = require('fs');
|
||||
|
||||
let statusCodes = {
|
||||
QUEUED: 10,
|
||||
RUNNING: 20,
|
||||
FAILED: 30,
|
||||
COMPLETED: 40
|
||||
COMPLETED: 40,
|
||||
CANCELED: 50
|
||||
};
|
||||
|
||||
module.exports = class Task{
|
||||
constructor(uuid, name){
|
||||
constructor(uuid, name, readyCb){
|
||||
assert(uuid !== undefined, "uuid must be set");
|
||||
assert(readyCb !== undefined, "ready must be set");
|
||||
|
||||
this.uuid = uuid;
|
||||
this.name = name != "" ? name : "Task of " + (new Date()).toISOString();
|
||||
|
@ -19,6 +22,40 @@ module.exports = class Task{
|
|||
code: statusCodes.QUEUED
|
||||
};
|
||||
this.options = {};
|
||||
|
||||
// Read images info
|
||||
fs.readdir(`data/${this.uuid}`, (err, files) => {
|
||||
if (err) readyCb(err);
|
||||
else{
|
||||
this.images = files;
|
||||
readyCb(null, this);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
cancel(cb){
|
||||
if (this.status.code !== statusCodes.CANCELED){
|
||||
this.status.code = statusCodes.CANCELED;
|
||||
|
||||
console.log("Requested to cancel " + this.name);
|
||||
// TODO
|
||||
cb(null);
|
||||
}else{
|
||||
cb(new Error("Task already cancelled"));
|
||||
}
|
||||
}
|
||||
|
||||
restart(cb){
|
||||
if (this.status.code === statusCodes.CANCELED){
|
||||
this.status.code = statusCodes.QUEUED;
|
||||
|
||||
console.log("Requested to restart " + this.name);
|
||||
// TODO
|
||||
|
||||
cb(null);
|
||||
}else{
|
||||
cb(new Error("Task cannot be restarted"));
|
||||
}
|
||||
}
|
||||
|
||||
getInfo(){
|
||||
|
@ -27,7 +64,8 @@ module.exports = class Task{
|
|||
name: this.name,
|
||||
dateCreated: this.dateCreated,
|
||||
status: this.status,
|
||||
options: this.options
|
||||
options: this.options,
|
||||
imagesCount: this.images.length
|
||||
}
|
||||
}
|
||||
};
|
|
@ -12,7 +12,33 @@ module.exports = class TaskManager{
|
|||
this.tasks[task.uuid] = task;
|
||||
}
|
||||
|
||||
find(uuid){
|
||||
return this.tasks[uuid];
|
||||
cancel(uuid, cb){
|
||||
let task;
|
||||
if (task = this.find(uuid, cb)){
|
||||
task.cancel(cb);
|
||||
}
|
||||
}
|
||||
|
||||
remove(uuid, cb){
|
||||
this.cancel(uuid, err => {
|
||||
if (!err){
|
||||
delete(this.tasks[uuid]);
|
||||
// TODO: other cleanup
|
||||
cb(null);
|
||||
}else cb(err);
|
||||
});
|
||||
}
|
||||
|
||||
restart(uuid, cb){
|
||||
let task;
|
||||
if (task = this.find(uuid, cb)){
|
||||
task.restart(cb);
|
||||
}
|
||||
}
|
||||
|
||||
find(uuid, errCb){
|
||||
let task = this.tasks[uuid];
|
||||
if (!task && errCb) errCb(new Error(`${uuid} not found`));
|
||||
return task;
|
||||
}
|
||||
};
|
|
@ -59,15 +59,21 @@
|
|||
<p data-bind="visible: tasks().length === 0">No running tasks.</p>
|
||||
<div data-bind="foreach: tasks">
|
||||
<div class="task">
|
||||
<p data-bind="visible: loading()">Retrieving <span data-bind="text: uuid"></span> ... <span class="spinning glyphicon glyphicon glyphicon glyphicon-refresh"></span></p>
|
||||
<p data-bind="visible: loading()">Retrieving <span data-bind="text: uuid"></span> ... <span class="glyphicon glyphicon-refresh spinning"></span></p>
|
||||
<div data-bind="visible: !loading() && !info().error">
|
||||
<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>
|
||||
<span class="statusIcon spinning glyphicon glyphicon glyphicon-cog"></span>
|
||||
|
||||
<span data-bind="css: 'statusIcon glyphicon ' + icon()"></span>
|
||||
|
||||
<div class="actionButtons">
|
||||
<button type="button" class="btn btn-primary btn-sm">
|
||||
<span class="glyphicon glyphicon glyphicon-remove-circle"></span> Cancel
|
||||
<button data-bind="click: cancel, visible: !canceled()" 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" >
|
||||
<span class="glyphicon glyphicon-play"></span> Restart
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -77,7 +83,7 @@
|
|||
<div class="alert alert-warning" role="alert" data-bind="text: info().error"></div>
|
||||
<div class="actionButtons">
|
||||
<button data-bind="click: remove" type="button" class="btn btn-primary btn-sm">
|
||||
<span class="glyphicon glyphicon glyphicon-remove-circle"></span> Remove Task
|
||||
<span class="glyphicon glyphicon-remove-circle"></span> Remove
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -28,35 +28,105 @@ $(function(){
|
|||
this.uuid = uuid;
|
||||
this.loading = ko.observable(true);
|
||||
this.info = ko.observable({});
|
||||
|
||||
var statusCodes = {
|
||||
10: {
|
||||
descr: "Queued",
|
||||
icon: "glyphicon-hourglass"
|
||||
},
|
||||
20: {
|
||||
descr: "Running",
|
||||
icon: "glyphicon-refresh spinning"
|
||||
},
|
||||
30: {
|
||||
descr: "Failed",
|
||||
icon: "glyphicon-remove-circle"
|
||||
},
|
||||
40: {
|
||||
descr: "Completed",
|
||||
icon: "glyphicon-ok-circle"
|
||||
},
|
||||
50: {
|
||||
descr: "Canceled",
|
||||
icon: "glyphicon-ban-circle"
|
||||
}
|
||||
};
|
||||
|
||||
this.statusDescr = ko.pureComputed(function(){
|
||||
if (this.info().status && this.info().status.code){
|
||||
switch(this.info().status.code){
|
||||
case 10: return "Queued";
|
||||
case 20: return "Running";
|
||||
case 30: return "Failed";
|
||||
case 40: return "Completed";
|
||||
default: return "Unknown (Status Code: " + this.info().status.code + ")";
|
||||
}
|
||||
if(statusCodes[this.info().status.code]){
|
||||
return statusCodes[this.info().status.code].descr;
|
||||
}else return "Unknown (Status Code: " + this.info().status.code + ")";
|
||||
}else return "-";
|
||||
}, this);
|
||||
this.icon = ko.pureComputed(function(){
|
||||
if (this.info().status && this.info().status.code){
|
||||
if(statusCodes[this.info().status.code]){
|
||||
console.log(statusCodes[this.info().status.code].icon);
|
||||
return statusCodes[this.info().status.code].icon;
|
||||
}else return "glyphicon-question-sign";
|
||||
}else return "";
|
||||
}, this);
|
||||
this.canceled = ko.pureComputed(function(){
|
||||
return this.info().status && this.info().status.code === 50;
|
||||
}, this);
|
||||
|
||||
this.refreshInfo();
|
||||
}
|
||||
Task.prototype.refreshInfo = function(){
|
||||
var self = this;
|
||||
var url = "/taskInfo/" + uuid;
|
||||
var url = "/taskInfo/" + this.uuid;
|
||||
$.get(url)
|
||||
.done(self.info)
|
||||
.fail(function(){
|
||||
self.info({error: url + " is unreachable."});
|
||||
})
|
||||
.always(function(){ self.loading(false); });
|
||||
}
|
||||
Task.prototype.remove = function() {
|
||||
taskList.remove(this);
|
||||
};
|
||||
Task.prototype.remove = function() {
|
||||
var self = this;
|
||||
var url = "/removeTask";
|
||||
|
||||
$.post(url, {
|
||||
uuid: this.uuid
|
||||
})
|
||||
.done(function(json){
|
||||
if (json.success || self.info().error){
|
||||
taskList.remove(self);
|
||||
}else{
|
||||
self.info({error: json.error});
|
||||
}
|
||||
})
|
||||
.fail(function(){
|
||||
self.info({error: url + " is unreachable."});
|
||||
});
|
||||
};
|
||||
|
||||
function genApiCall(url){
|
||||
return function(){
|
||||
var self = this;
|
||||
|
||||
$.post(url, {
|
||||
uuid: this.uuid
|
||||
})
|
||||
.done(function(json){
|
||||
if (json.success){
|
||||
self.refreshInfo();
|
||||
}else{
|
||||
self.info({error: json.error});
|
||||
}
|
||||
})
|
||||
.fail(function(){
|
||||
self.info({error: url + " is unreachable."});
|
||||
});
|
||||
}
|
||||
};
|
||||
Task.prototype.cancel = genApiCall("/cancelTask");
|
||||
Task.prototype.restart = genApiCall("/restartTask");
|
||||
|
||||
var taskList = new TaskList();
|
||||
ko.applyBindings(taskList);
|
||||
|
||||
|
||||
// Handle uploads
|
||||
$("#images").fileinput({
|
||||
uploadUrl: '/newTask',
|
||||
|
@ -73,17 +143,18 @@ $(function(){
|
|||
});
|
||||
|
||||
$("#btnUpload").click(function(){
|
||||
var btnUploadLabel = $("#btnUpload").val();
|
||||
$("#btnUpload").attr('disabled', true)
|
||||
.val("Uploading...");
|
||||
|
||||
// Start upload
|
||||
$("#images").fileinput('upload');
|
||||
});
|
||||
|
||||
var btnUploadLabel = $("#btnUpload").val();
|
||||
$("#images")
|
||||
.fileinput('upload')
|
||||
.on('filebatchuploadsuccess', function(e, params){
|
||||
$("#images").fileinput('reset');
|
||||
|
||||
// TODO: this is called multiple times
|
||||
// consider switching file upload plugin.
|
||||
if (params.response.success && params.response.uuid){
|
||||
taskList.addNew(new Task(params.response.uuid));
|
||||
}
|
||||
|
@ -94,5 +165,4 @@ $(function(){
|
|||
})
|
||||
.on('filebatchuploaderror', function(e, data, msg){
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Ładowanie…
Reference in New Issue