2016-07-28 01:51:52 +00:00
/ *
Node - OpenDroneMap Node . js App and REST API to access OpenDroneMap .
2016-07-18 22:56:27 +00:00
Copyright ( C ) 2016 Node - OpenDroneMap Contributors
This program is free software : you can redistribute it and / or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation , either version 3 of the License , or
( at your option ) any later version .
This program is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
GNU General Public License for more details .
You should have received a copy of the GNU General Public License
along with this program . If not , see < http : //www.gnu.org/licenses/>.
* /
2016-07-04 20:59:07 +00:00
"use strict" ;
2016-09-19 21:15:19 +00:00
let fs = require ( 'fs' ) ;
2016-07-29 22:16:22 +00:00
let config = require ( './config.js' ) ;
2016-09-19 21:15:19 +00:00
let packageJson = JSON . parse ( fs . readFileSync ( './package.json' ) ) ;
2016-07-28 01:51:52 +00:00
2016-07-29 16:59:58 +00:00
let logger = require ( './libs/logger' ) ;
2016-07-30 18:30:56 +00:00
let path = require ( 'path' ) ;
2016-07-08 20:44:48 +00:00
let async = require ( 'async' ) ;
2016-07-04 20:59:07 +00:00
let express = require ( 'express' ) ;
let app = express ( ) ;
2016-07-08 20:44:48 +00:00
let addRequestId = require ( './libs/expressRequestId' ) ( ) ;
2016-07-05 18:06:22 +00:00
let multer = require ( 'multer' ) ;
2016-07-07 22:07:17 +00:00
let bodyParser = require ( 'body-parser' ) ;
2016-07-08 20:44:48 +00:00
let morgan = require ( 'morgan' ) ;
2016-07-28 01:51:52 +00:00
2016-07-29 16:59:58 +00:00
let TaskManager = require ( './libs/TaskManager' ) ;
let Task = require ( './libs/Task' ) ;
let odmOptions = require ( './libs/odmOptions' ) ;
2016-09-25 22:35:44 +00:00
let Directories = require ( './libs/Directories' ) ;
2016-07-28 01:51:52 +00:00
2016-07-28 22:59:08 +00:00
let winstonStream = {
2016-07-28 01:51:52 +00:00
write : function ( message , encoding ) {
2016-07-29 22:16:22 +00:00
logger . debug ( message . slice ( 0 , - 1 ) ) ;
2016-07-28 01:51:52 +00:00
}
} ;
app . use ( morgan ( 'combined' , { stream : winstonStream } ) ) ;
2016-07-07 22:07:17 +00:00
app . use ( bodyParser . urlencoded ( { extended : true } ) ) ;
app . use ( bodyParser . json ( ) ) ;
2016-07-05 18:06:22 +00:00
app . use ( express . static ( 'public' ) ) ;
2016-09-20 20:08:30 +00:00
app . use ( '/swagger.json' , express . static ( 'docs/swagger.json' ) ) ;
2016-07-05 18:06:22 +00:00
let upload = multer ( {
storage : multer . diskStorage ( {
destination : ( req , file , cb ) => {
2016-09-25 22:35:44 +00:00
let dstPath = path . join ( "tmp" , req . id ) ;
fs . exists ( dstPath , exists => {
2016-07-05 18:06:22 +00:00
if ( ! exists ) {
2016-09-25 22:35:44 +00:00
fs . mkdir ( dstPath , undefined , ( ) => {
cb ( null , dstPath ) ;
2016-07-05 18:06:22 +00:00
} ) ;
} else {
2016-09-25 22:35:44 +00:00
cb ( null , dstPath ) ;
2016-07-05 18:06:22 +00:00
}
} ) ;
} ,
filename : ( req , file , cb ) => {
2016-08-02 16:07:54 +00:00
cb ( null , file . originalname ) ;
2016-07-05 18:06:22 +00:00
}
} )
} ) ;
2016-07-28 01:51:52 +00:00
2016-08-02 16:07:54 +00:00
let taskManager ;
let server ;
2016-09-17 00:20:30 +00:00
/ * * @ s w a g g e r
* / t a s k / n e w :
* post :
* description : Creates a new task and places it at the end of the processing queue
2016-09-20 21:07:32 +00:00
* tags : [ task ]
2016-09-17 00:20:30 +00:00
* consumes :
* - multipart / form - data
* parameters :
* -
* name : images
* in : formData
* description : Images to process , plus an optional GPC file . If included , the GPC file should have . txt extension
* required : true
* type : file
* -
* name : name
* in : formData
* description : An optional name to be associated with the task
* required : false
* type : string
* -
* name : options
* in : formData
2016-09-19 21:25:39 +00:00
* description : 'Serialized JSON string of the options to use for processing, as an array of the format: [{name: option1, value: value1}, {name: option2, value: value2}, ...]. For example, [{"name":"cmvs-maxImages","value":"500"},{"name":"time","value":true}]. For a list of all options, call /options'
2016-09-17 00:20:30 +00:00
* required : false
* type : string
* responses :
* 200 :
* description : Success
* schema :
* type : object
* required : [ uuid ]
* properties :
* uuid :
* type : string
* description : UUID of the newly created task
* default :
* description : Error
* schema :
* $ref : '#/definitions/Error'
* /
2016-07-09 16:58:14 +00:00
app . post ( '/task/new' , addRequestId , upload . array ( 'images' ) , ( req , res ) => {
2016-10-26 16:50:08 +00:00
if ( ! req . files || req . files . length === 0 ) res . json ( { error : "Need at least 1 file." } ) ;
2016-07-06 18:44:20 +00:00
else {
2016-09-25 22:35:44 +00:00
let srcPath = path . join ( "tmp" , req . id ) ;
let destPath = path . join ( Directories . data , req . id ) ;
let destImagesPath = path . join ( destPath , "images" ) ;
let destGpcPath = path . join ( destPath , "gpc" ) ;
2016-07-30 18:30:56 +00:00
2016-07-08 20:44:48 +00:00
async . series ( [
2016-07-28 23:11:55 +00:00
cb => {
2016-07-26 19:20:37 +00:00
odmOptions . filterOptions ( req . body . options , ( err , options ) => {
if ( err ) cb ( err ) ;
else {
req . body . options = options ;
cb ( null ) ;
}
2016-07-28 23:11:55 +00:00
} ) ;
2016-07-26 19:20:37 +00:00
} ,
2016-07-28 23:11:55 +00:00
2016-07-30 18:30:56 +00:00
// Move all uploads to data/<uuid>/images dir
2016-07-28 01:51:52 +00:00
cb => {
2016-07-30 18:30:56 +00:00
fs . stat ( destPath , ( err , stat ) => {
2016-07-08 20:44:48 +00:00
if ( err && err . code === 'ENOENT' ) cb ( ) ;
else cb ( new Error ( ` Directory exists (should not have happened: ${ err . code } ) ` ) ) ;
} ) ;
} ,
2016-07-30 18:30:56 +00:00
cb => fs . mkdir ( destPath , undefined , cb ) ,
cb => fs . mkdir ( destGpcPath , undefined , cb ) ,
cb => fs . rename ( srcPath , destImagesPath , cb ) ,
2016-07-08 20:44:48 +00:00
cb => {
2016-07-30 18:30:56 +00:00
// Find any *.txt (GPC) file and move it to the data/<uuid>/gpc directory
fs . readdir ( destImagesPath , ( err , entries ) => {
if ( err ) cb ( err ) ;
else {
async . eachSeries ( entries , ( entry , cb ) => {
if ( /\.txt$/gi . test ( entry ) ) {
fs . rename ( path . join ( destImagesPath , entry ) , path . join ( destGpcPath , entry ) , cb ) ;
} else cb ( ) ;
} , cb ) ;
}
2016-07-07 22:07:17 +00:00
} ) ;
2016-07-26 19:20:37 +00:00
} ,
// Create task
cb => {
new Task ( req . id , req . body . name , ( err , task ) => {
if ( err ) cb ( err ) ;
else {
taskManager . addNew ( task ) ;
2016-09-17 00:20:30 +00:00
res . json ( { uuid : req . id } ) ;
2016-07-26 19:20:37 +00:00
cb ( ) ;
}
} , req . body . options ) ;
2016-07-06 18:44:20 +00:00
}
2016-07-08 20:44:48 +00:00
] , err => {
2016-08-02 16:07:54 +00:00
if ( err ) res . json ( { error : err . message } ) ;
2016-07-06 18:44:20 +00:00
} ) ;
2016-07-05 18:06:22 +00:00
}
2016-07-06 18:44:20 +00:00
} ) ;
2016-07-08 20:44:48 +00:00
let getTaskFromUuid = ( req , res , next ) => {
2016-07-06 18:44:20 +00:00
let task = taskManager . find ( req . params . uuid ) ;
if ( task ) {
2016-07-08 20:44:48 +00:00
req . task = task ;
next ( ) ;
2016-07-06 18:44:20 +00:00
} else res . json ( { error : ` ${ req . params . uuid } not found ` } ) ;
2016-08-02 16:07:54 +00:00
} ;
2016-07-08 20:44:48 +00:00
2016-09-17 00:20:30 +00:00
/ * * @ s w a g g e r
* /task/ { uuid } / info :
* get :
* description : Gets information about this task , such as name , creation date , processing time , status , command line options and number of images being processed . See schema definition for a full list .
2016-09-20 21:07:32 +00:00
* tags : [ task ]
2016-09-17 00:20:30 +00:00
* parameters :
* -
* name : uuid
* in : path
* description : UUID of the task
* required : true
* type : string
* responses :
* 200 :
* description : Task Information
* schema :
2016-09-17 00:42:30 +00:00
* title : TaskInfo
2016-09-17 00:20:30 +00:00
* type : object
2016-09-17 00:42:30 +00:00
* required : [ uuid , name , dateCreated , processingTime , status , options , imagesCount ]
2016-09-17 00:20:30 +00:00
* properties :
* uuid :
* type : string
* description : UUID
* name :
* type : string
* description : Name
* dateCreated :
* type : integer
* description : Timestamp
* processingTime :
* type : integer
* description : Milliseconds that have elapsed since the task started being processed .
* status :
* type : integer
* description : Status code ( 10 = QUEUED , 20 = RUNNING , 30 = FAILED , 40 = COMPLETED , 50 = CANCELED )
* enum : [ 10 , 20 , 30 , 40 , 50 ]
* options :
* type : array
* description : List of options used to process this task
* items :
* type : object
2016-09-17 00:42:30 +00:00
* required : [ name , value ]
2016-09-17 00:20:30 +00:00
* properties :
* name :
* type : string
* description : 'Option name (example: "odm_meshing-octreeDepth")'
* value :
* type : string
* description : 'Value (example: 9)'
* imagesCount :
* type : integer
* description : Number of images
* default :
* description : Error
* schema :
* $ref : '#/definitions/Error'
* /
2016-07-09 16:58:14 +00:00
app . get ( '/task/:uuid/info' , getTaskFromUuid , ( req , res ) => {
2016-07-08 20:44:48 +00:00
res . json ( req . task . getInfo ( ) ) ;
} ) ;
2016-09-17 00:20:30 +00:00
/ * * @ s w a g g e r
* /task/ { uuid } / output :
* get :
* description : Retrieves the console output of the OpenDroneMap ' s process . Useful for monitoring execution and to provide updates to the user .
2016-09-20 21:07:32 +00:00
* tags : [ task ]
2016-09-17 00:20:30 +00:00
* parameters :
* -
* name : uuid
* in : path
* description : UUID of the task
* required : true
* type : string
* -
* name : line
* in : query
* description : Optional line number that the console output should be truncated from . For example , passing a value of 100 will retrieve the console output starting from line 100. Defaults to 0 ( retrieve all console output ) .
2016-09-17 00:48:28 +00:00
* default : 0
2016-09-17 00:20:30 +00:00
* required : false
* type : integer
* responses :
* 200 :
* description : Console Output
* schema :
* type : string
* default :
* description : Error
* schema :
* $ref : '#/definitions/Error'
* /
2016-07-09 16:58:14 +00:00
app . get ( '/task/:uuid/output' , getTaskFromUuid , ( req , res ) => {
res . json ( req . task . getOutput ( req . query . line ) ) ;
2016-07-04 20:59:07 +00:00
} ) ;
2016-09-17 00:20:30 +00:00
/ * * @ s w a g g e r
* /task/ { uuid } / download / { asset } :
* get :
* description : Retrieves an asset ( the output of OpenDroneMap ' s processing ) associated with a task
2016-09-20 21:07:32 +00:00
* tags : [ task ]
* produces : [ application / zip ]
2016-09-17 00:20:30 +00:00
* parameters :
* - name : uuid
* in : path
* type : string
* description : UUID of the task
* required : true
* - name : asset
* in : path
* type : string
2016-11-05 18:51:15 +00:00
* description : Type of asset to download . Use "all.zip" for zip file containing all assets .
2016-09-17 00:20:30 +00:00
* required : true
* enum :
2016-11-05 18:46:32 +00:00
* - all . zip
* - georeferenced _model . ply . zip
* - georeferenced _model . las . zip
* - georeferenced _model . csv . zip
* - orthophoto . png
* - orthophoto . tif
* - textured _model . zip
2016-11-08 18:02:26 +00:00
* - orthophoto _tiles . zip
2016-09-17 00:20:30 +00:00
* responses :
* 200 :
* description : Asset File
* schema :
* type : file
* default :
* description : Error message
* schema :
* $ref : '#/definitions/Error'
* /
2016-07-18 21:00:01 +00:00
app . get ( '/task/:uuid/download/:asset' , getTaskFromUuid , ( req , res ) => {
2016-11-05 18:46:32 +00:00
let asset = req . params . asset !== undefined ? req . params . asset : "all.zip" ;
let filePath = req . task . getAssetsArchivePath ( asset ) ;
if ( filePath ) {
res . download ( filePath , filePath , err => {
2016-07-18 21:00:01 +00:00
if ( err ) res . json ( { error : "Asset not ready" } ) ;
} ) ;
} else {
res . json ( { error : "Invalid asset" } ) ;
}
} ) ;
2016-07-04 20:59:07 +00:00
2016-09-14 23:02:44 +00:00
/ * * @ s w a g g e r
* definition :
2016-09-17 00:20:30 +00:00
* Error :
* type : object
* required :
* - error
* properties :
* error :
* type : string
* description : Description of the error
2016-09-14 23:02:44 +00:00
* Response :
* type : object
* required :
* - success
* properties :
* success :
* type : boolean
2016-09-20 21:07:32 +00:00
* description : true if the command succeeded , false otherwise
2016-09-14 23:02:44 +00:00
* error :
* type : string
* description : Error message if an error occured
* /
2016-09-17 00:20:30 +00:00
let uuidCheck = ( req , res , next ) => {
if ( ! req . body . uuid ) res . json ( { error : "uuid param missing." } ) ;
else next ( ) ;
} ;
2016-09-14 23:02:44 +00:00
2016-07-07 22:07:17 +00:00
let successHandler = res => {
return err => {
if ( ! err ) res . json ( { success : true } ) ;
2016-09-14 23:02:44 +00:00
else res . json ( { success : false , error : err . message } ) ;
2016-07-07 22:07:17 +00:00
} ;
} ;
2016-09-14 23:02:44 +00:00
/ * * @ s w a g g e r
* / t a s k / c a n c e l :
* post :
* description : Cancels a task ( stops its execution , or prevents it from being executed )
* parameters :
* -
* name : uuid
* in : body
2016-09-17 00:20:30 +00:00
* description : UUID of the task
2016-09-14 23:02:44 +00:00
* required : true
* schema :
* type : string
* responses :
* 200 :
* description : Command Received
* schema :
* $ref : "#/definitions/Response"
* /
2016-07-09 16:58:14 +00:00
app . post ( '/task/cancel' , uuidCheck , ( req , res ) => {
2016-09-19 21:15:19 +00:00
taskManager . cancel ( req . body . uuid , successHandler ( res ) ) ;
2016-07-07 22:07:17 +00:00
} ) ;
2016-09-14 23:02:44 +00:00
/ * * @ s w a g g e r
* / t a s k / r e m o v e :
* post :
* description : Removes a task and deletes all of its assets
* parameters :
* -
* name : uuid
* in : body
2016-09-17 00:20:30 +00:00
* description : UUID of the task
2016-09-14 23:02:44 +00:00
* required : true
* schema :
* type : string
* responses :
* 200 :
* description : Command Received
* schema :
* $ref : "#/definitions/Response"
* /
2016-07-09 16:58:14 +00:00
app . post ( '/task/remove' , uuidCheck , ( req , res ) => {
2016-07-07 22:07:17 +00:00
taskManager . remove ( req . body . uuid , successHandler ( res ) ) ;
} ) ;
2016-09-14 23:02:44 +00:00
/ * * @ s w a g g e r
* / t a s k / r e s t a r t :
* post :
* description : Restarts a task that was previously canceled or that had failed to process
* parameters :
* -
* name : uuid
* in : body
2016-09-17 00:20:30 +00:00
* description : UUID of the task
2016-09-14 23:02:44 +00:00
* required : true
* schema :
* type : string
* responses :
* 200 :
* description : Command Received
* schema :
* $ref : "#/definitions/Response"
* /
2016-07-09 16:58:14 +00:00
app . post ( '/task/restart' , uuidCheck , ( req , res ) => {
2016-07-07 22:07:17 +00:00
taskManager . restart ( req . body . uuid , successHandler ( res ) ) ;
} ) ;
2016-09-17 00:20:30 +00:00
/ * * @ s w a g g e r
2016-09-19 21:25:39 +00:00
* / o p t i o n s :
2016-09-17 00:20:30 +00:00
* get :
* description : Retrieves the command line options that can be passed to process a task
2016-09-20 21:07:32 +00:00
* tags : [ server ]
2016-09-17 00:20:30 +00:00
* responses :
* 200 :
* description : Options
* schema :
* type : array
* items :
2016-09-17 00:42:30 +00:00
* title : Option
2016-09-17 00:20:30 +00:00
* type : object
2016-09-17 00:47:05 +00:00
* required : [ name , type , value , domain , help ]
2016-09-17 00:20:30 +00:00
* properties :
* name :
* type : string
* description : Command line option ( exactly as it is passed to the OpenDroneMap process , minus the leading '--' )
* type :
* type : string
* description : Datatype of the value of this option
* enum :
* - int
* - float
* - string
* - bool
* value :
* type : string
* description : Default value of this option
* domain :
* type : string
* description : Valid range of values ( for example , "positive integer" or "float > 0.0" )
* help :
* type : string
* description : Description of what this option does
* /
2016-09-19 21:25:39 +00:00
app . get ( '/options' , ( req , res ) => {
2016-07-26 19:20:37 +00:00
odmOptions . getOptions ( ( err , options ) => {
2016-07-26 01:10:18 +00:00
if ( err ) res . json ( { error : err . message } ) ;
else res . json ( options ) ;
} ) ;
} ) ;
2016-09-19 21:15:19 +00:00
/ * * @ s w a g g e r
2016-09-19 21:25:39 +00:00
* / i n f o :
2016-09-19 21:15:19 +00:00
* get :
2016-09-19 21:25:39 +00:00
* description : Retrieves information about this node
2016-09-20 21:07:32 +00:00
* tags : [ server ]
2016-09-19 21:15:19 +00:00
* responses :
* 200 :
* description : Info
* schema :
* type : object
* required : [ version , taskQueueCount ]
* properties :
* version :
* type : string
* description : Current version
* taskQueueCount :
* type : integer
* description : Number of tasks currently being processed or waiting to be processed
* /
2016-09-19 21:25:39 +00:00
app . get ( '/info' , ( req , res ) => {
2016-09-19 21:15:19 +00:00
res . json ( {
version : packageJson . version ,
2016-09-20 21:07:32 +00:00
taskQueueCount : taskManager . getQueueCount ( )
2016-09-19 21:15:19 +00:00
} ) ;
} ) ;
2016-07-15 21:19:50 +00:00
let gracefulShutdown = done => {
async . series ( [
2016-07-28 21:46:28 +00:00
cb => taskManager . dumpTaskList ( cb ) ,
2016-07-28 01:51:52 +00:00
cb => {
logger . info ( "Closing server" ) ;
2016-07-15 21:19:50 +00:00
server . close ( ) ;
2016-07-28 01:51:52 +00:00
logger . info ( "Exiting..." ) ;
2016-07-15 21:19:50 +00:00
process . exit ( 0 ) ;
}
] , done ) ;
} ;
2016-07-28 01:51:52 +00:00
// listen for TERM signal .e.g. kill
2016-07-15 21:19:50 +00:00
process . on ( 'SIGTERM' , gracefulShutdown ) ;
// listen for INT signal e.g. Ctrl-C
process . on ( 'SIGINT' , gracefulShutdown ) ;
// Startup
2016-09-25 00:07:02 +00:00
if ( config . test ) logger . info ( "Running in test mode" ) ;
2016-07-15 21:19:50 +00:00
async . series ( [
2016-07-29 22:16:22 +00:00
cb => odmOptions . initialize ( cb ) ,
2016-07-29 16:59:58 +00:00
cb => { taskManager = new TaskManager ( cb ) ; } ,
2016-07-28 01:51:52 +00:00
cb => { server = app . listen ( config . port , err => {
if ( ! err ) logger . info ( 'Server has started on port ' + String ( config . port ) ) ;
2016-07-15 21:19:50 +00:00
cb ( err ) ;
} ) ;
}
] , err => {
2016-07-28 01:51:52 +00:00
if ( err ) logger . error ( "Error during startup: " + err . message ) ;
2016-07-15 21:19:50 +00:00
} ) ;