From b75e86183fd0443dff4f7ef74ced777c8aaa1f39 Mon Sep 17 00:00:00 2001 From: Piero Toffanin Date: Fri, 9 Nov 2018 11:09:19 -0500 Subject: [PATCH] maxImages limit, expose odmVersion --- config-default.json | 3 ++- config.js | 5 +++- docs/index.adoc | 8 +++++-- docs/swagger.json | 2 +- index.js | 38 ++++++++++++++++++++---------- libs/{odmOptions.js => odmInfo.js} | 16 ++++++++++++- libs/odmRunner.js | 7 ++++++ package.json | 2 +- 8 files changed, 62 insertions(+), 19 deletions(-) rename libs/{odmOptions.js => odmInfo.js} (96%) diff --git a/config-default.json b/config-default.json index 99f85de..f404ded 100644 --- a/config-default.json +++ b/config-default.json @@ -16,5 +16,6 @@ "test": false, "testSkipOrthophotos": false, "testSkipDems": false, - "token": "" + "token": "", + "maxImages": -1 } \ No newline at end of file diff --git a/config.js b/config.js index 177f163..1c11dc7 100644 --- a/config.js +++ b/config.js @@ -37,7 +37,8 @@ Options: --test_skip_orthophotos If test mode is enabled, skip orthophoto results when generating assets. (default: false) --test_skip_dems If test mode is enabled, skip dems results when generating assets. (default: false) --powercycle When set, the application exits immediately after powering up. Useful for testing launch and compilation issues. - --token Sets a token that needs to be passed for every request. This can be used to limit access to the node only to token holders. (default: none) + --token Sets a token that needs to be passed for every request. This can be used to limit access to the node only to token holders. (default: none) + --max_images Specify the maximum number of images that this processing node supports. (default: unlimited) Log Levels: error | debug | info | verbose | debug | silly `); @@ -86,5 +87,7 @@ config.testSkipOrthophotos = argv.test_skip_orthophotos || fromConfigFile("testS config.testSkipDems = argv.test_skip_dems || fromConfigFile("testSkipDems", false); config.powercycle = argv.powercycle || fromConfigFile("powercycle", false); config.token = argv.token || fromConfigFile("token", ""); +config.maxImages = argv.max_images || fromConfigFile("maxImages", ""); + module.exports = config; diff --git a/docs/index.adoc b/docs/index.adoc index 32c38a6..b28d10b 100644 --- a/docs/index.adoc +++ b/docs/index.adoc @@ -8,7 +8,7 @@ REST API to access OpenDroneMap === Version information [%hardbreaks] -_Version_ : 1.1.1 +_Version_ : 1.2.0 === Contact information @@ -68,12 +68,16 @@ Retrieves information about this node _optional_|Amount of RAM available in bytes|integer |*cpuCores* + _optional_|Number of CPU cores (virtual)|integer +|*maxImages* + +_optional_|Maximum number of images allowed for new tasks|integer +|*odmVersion* + +_optional_|Current version of ODM|string |*taskQueueCount* + _required_|Number of tasks currently being processed or waiting to be processed|integer |*totalMemory* + _optional_|Amount of total RAM in the system in bytes|integer |*version* + -_required_|Current version|string +_required_|Current API version|string |=== diff --git a/docs/swagger.json b/docs/swagger.json index 9d76843..34c1be8 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -1 +1 @@ -{"info":{"title":"node-opendronemap","version":"1.1.1","description":"REST API to access OpenDroneMap","license":{"name":"GPL-3.0"},"contact":{"name":"Piero Toffanin"}},"consumes":["application/json"],"produces":["application/json","application/zip"],"basePath":"/","schemes":["http"],"swagger":"2.0","paths":{"/task/new":{"post":{"description":"Creates a new task and places it at the end of the processing queue","tags":["task"],"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":false,"type":"file"},{"name":"zipurl","in":"formData","description":"URL of the zip file containing the images to process, plus an optional GPC file. If included, the GPC file should have .txt extension","required":false,"type":"string"},{"name":"name","in":"formData","description":"An optional name to be associated with the task","required":false,"type":"string"},{"name":"options","in":"formData","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","required":false,"type":"string"},{"name":"token","in":"query","description":"Token required for authentication (when authentication is required).","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"}}}}},"/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.","tags":["task"],"parameters":[{"name":"uuid","in":"path","description":"UUID of the task","required":true,"type":"string"},{"name":"options","in":"formData","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","required":false,"type":"string"},{"name":"token","in":"query","description":"Token required for authentication (when authentication is required).","required":false,"type":"string"}],"responses":{"200":{"description":"Task Information","schema":{"title":"TaskInfo","type":"object","required":["uuid","name","dateCreated","processingTime","status","options","imagesCount"],"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","required":["name","value"],"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"}}}}},"/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.","tags":["task"],"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).","default":0,"required":false,"type":"integer"},{"name":"token","in":"query","description":"Token required for authentication (when authentication is required).","required":false,"type":"string"}],"responses":{"200":{"description":"Console Output","schema":{"type":"string"}},"default":{"description":"Error","schema":{"$ref":"#/definitions/Error"}}}}},"/task/{uuid}/download/{asset}":{"get":{"description":"Retrieves an asset (the output of OpenDroneMap's processing) associated with a task","tags":["task"],"produces":["application/zip"],"parameters":[{"name":"uuid","in":"path","type":"string","description":"UUID of the task","required":true},{"name":"asset","in":"path","type":"string","description":"Type of asset to download. Use \"all.zip\" for zip file containing all assets.","required":true,"enum":["all.zip","orthophoto.tif"]},{"name":"token","in":"query","description":"Token required for authentication (when authentication is required).","required":false,"type":"string"}],"responses":{"200":{"description":"Asset File","schema":{"type":"file"}},"default":{"description":"Error message","schema":{"$ref":"#/definitions/Error"}}}}},"/task/cancel":{"post":{"description":"Cancels a task (stops its execution, or prevents it from being executed)","parameters":[{"name":"uuid","in":"body","description":"UUID of the task","required":true,"schema":{"type":"string"}},{"name":"token","in":"query","description":"Token required for authentication (when authentication is required).","required":false,"type":"string"}],"responses":{"200":{"description":"Command Received","schema":{"$ref":"#/definitions/Response"}}}}},"/task/remove":{"post":{"description":"Removes a task and deletes all of its assets","parameters":[{"name":"uuid","in":"body","description":"UUID of the task","required":true,"schema":{"type":"string"}},{"name":"token","in":"query","description":"Token required for authentication (when authentication is required).","required":false,"type":"string"}],"responses":{"200":{"description":"Command Received","schema":{"$ref":"#/definitions/Response"}}}}},"/task/restart":{"post":{"description":"Restarts a task that was previously canceled, that had failed to process or that successfully completed","parameters":[{"name":"uuid","in":"body","description":"UUID of the task","required":true,"schema":{"type":"string"}},{"name":"options","in":"body","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. Overrides the previous options set for this task.","required":false,"schema":{"type":"string"}},{"name":"token","in":"query","description":"Token required for authentication (when authentication is required).","required":false,"type":"string"}],"responses":{"200":{"description":"Command Received","schema":{"$ref":"#/definitions/Response"}}}}},"/options":{"get":{"description":"Retrieves the command line options that can be passed to process a task","tags":["server"],"responses":{"200":{"description":"Options","schema":{"type":"array","items":{"title":"Option","type":"object","required":["name","type","value","domain","help"],"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"}}}}}}}},"/info":{"get":{"description":"Retrieves information about this node","tags":["server"],"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"},"availableMemory":{"type":"integer","description":"Amount of RAM available in bytes"},"totalMemory":{"type":"integer","description":"Amount of total RAM in the system in bytes"},"cpuCores":{"type":"integer","description":"Number of CPU cores (virtual)"}}}}}}}},"definitions":{"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Description of the error"}}},"Response":{"type":"object","required":["success"],"properties":{"success":{"type":"boolean","description":"true if the command succeeded, false otherwise"},"error":{"type":"string","description":"Error message if an error occured"}}}},"responses":{},"parameters":{},"securityDefinitions":{},"tags":[]} \ No newline at end of file +{"info":{"title":"node-opendronemap","version":"1.2.0","description":"REST API to access OpenDroneMap","license":{"name":"GPL-3.0"},"contact":{"name":"Piero Toffanin"}},"consumes":["application/json"],"produces":["application/json","application/zip"],"basePath":"/","schemes":["http"],"swagger":"2.0","paths":{"/task/new":{"post":{"description":"Creates a new task and places it at the end of the processing queue","tags":["task"],"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":false,"type":"file"},{"name":"zipurl","in":"formData","description":"URL of the zip file containing the images to process, plus an optional GPC file. If included, the GPC file should have .txt extension","required":false,"type":"string"},{"name":"name","in":"formData","description":"An optional name to be associated with the task","required":false,"type":"string"},{"name":"options","in":"formData","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","required":false,"type":"string"},{"name":"token","in":"query","description":"Token required for authentication (when authentication is required).","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"}}}}},"/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.","tags":["task"],"parameters":[{"name":"uuid","in":"path","description":"UUID of the task","required":true,"type":"string"},{"name":"options","in":"formData","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","required":false,"type":"string"},{"name":"token","in":"query","description":"Token required for authentication (when authentication is required).","required":false,"type":"string"}],"responses":{"200":{"description":"Task Information","schema":{"title":"TaskInfo","type":"object","required":["uuid","name","dateCreated","processingTime","status","options","imagesCount"],"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","required":["name","value"],"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"}}}}},"/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.","tags":["task"],"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).","default":0,"required":false,"type":"integer"},{"name":"token","in":"query","description":"Token required for authentication (when authentication is required).","required":false,"type":"string"}],"responses":{"200":{"description":"Console Output","schema":{"type":"string"}},"default":{"description":"Error","schema":{"$ref":"#/definitions/Error"}}}}},"/task/{uuid}/download/{asset}":{"get":{"description":"Retrieves an asset (the output of OpenDroneMap's processing) associated with a task","tags":["task"],"produces":["application/zip"],"parameters":[{"name":"uuid","in":"path","type":"string","description":"UUID of the task","required":true},{"name":"asset","in":"path","type":"string","description":"Type of asset to download. Use \"all.zip\" for zip file containing all assets.","required":true,"enum":["all.zip","orthophoto.tif"]},{"name":"token","in":"query","description":"Token required for authentication (when authentication is required).","required":false,"type":"string"}],"responses":{"200":{"description":"Asset File","schema":{"type":"file"}},"default":{"description":"Error message","schema":{"$ref":"#/definitions/Error"}}}}},"/task/cancel":{"post":{"description":"Cancels a task (stops its execution, or prevents it from being executed)","parameters":[{"name":"uuid","in":"body","description":"UUID of the task","required":true,"schema":{"type":"string"}},{"name":"token","in":"query","description":"Token required for authentication (when authentication is required).","required":false,"type":"string"}],"responses":{"200":{"description":"Command Received","schema":{"$ref":"#/definitions/Response"}}}}},"/task/remove":{"post":{"description":"Removes a task and deletes all of its assets","parameters":[{"name":"uuid","in":"body","description":"UUID of the task","required":true,"schema":{"type":"string"}},{"name":"token","in":"query","description":"Token required for authentication (when authentication is required).","required":false,"type":"string"}],"responses":{"200":{"description":"Command Received","schema":{"$ref":"#/definitions/Response"}}}}},"/task/restart":{"post":{"description":"Restarts a task that was previously canceled, that had failed to process or that successfully completed","parameters":[{"name":"uuid","in":"body","description":"UUID of the task","required":true,"schema":{"type":"string"}},{"name":"options","in":"body","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. Overrides the previous options set for this task.","required":false,"schema":{"type":"string"}},{"name":"token","in":"query","description":"Token required for authentication (when authentication is required).","required":false,"type":"string"}],"responses":{"200":{"description":"Command Received","schema":{"$ref":"#/definitions/Response"}}}}},"/options":{"get":{"description":"Retrieves the command line options that can be passed to process a task","tags":["server"],"responses":{"200":{"description":"Options","schema":{"type":"array","items":{"title":"Option","type":"object","required":["name","type","value","domain","help"],"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"}}}}}}}},"/info":{"get":{"description":"Retrieves information about this node","tags":["server"],"responses":{"200":{"description":"Info","schema":{"type":"object","required":["version","taskQueueCount"],"properties":{"version":{"type":"string","description":"Current API version"},"taskQueueCount":{"type":"integer","description":"Number of tasks currently being processed or waiting to be processed"},"availableMemory":{"type":"integer","description":"Amount of RAM available in bytes"},"totalMemory":{"type":"integer","description":"Amount of total RAM in the system in bytes"},"cpuCores":{"type":"integer","description":"Number of CPU cores (virtual)"},"maxImages":{"type":"integer","description":"Maximum number of images allowed for new tasks"},"odmVersion":{"type":"string","description":"Current version of ODM"}}}}}}}},"definitions":{"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Description of the error"}}},"Response":{"type":"object","required":["success"],"properties":{"success":{"type":"boolean","description":"true if the command succeeded, false otherwise"},"error":{"type":"string","description":"Error message if an error occured"}}}},"responses":{},"parameters":{},"securityDefinitions":{},"tags":[]} \ No newline at end of file diff --git a/index.js b/index.js index c224fbe..6b18f19 100644 --- a/index.js +++ b/index.js @@ -36,7 +36,7 @@ let morgan = require('morgan'); let TaskManager = require('./libs/TaskManager'); let Task = require('./libs/Task'); -let odmOptions = require('./libs/odmOptions'); +let odmInfo = require('./libs/odmInfo'); let Directories = require('./libs/Directories'); let unzip = require('node-unzip-2'); let si = require('systeminformation'); @@ -148,6 +148,7 @@ let server; app.post('/task/new', authCheck, addRequestId, upload.array('images'), (req, res) => { if ((!req.files || req.files.length === 0) && !req.body.zipurl) res.json({ error: "Need at least 1 file or a zip file url." }); + if (config.maxImages > 0 && req.files && req.files.length > config.maxImages) res.json({error: `${req.files.length} images uploaded, but this node can only process up to ${config.maxImages}.`}) else { let srcPath = path.join("tmp", req.id); @@ -157,7 +158,7 @@ app.post('/task/new', authCheck, addRequestId, upload.array('images'), (req, res async.series([ cb => { - odmOptions.filterOptions(req.body.options, (err, options) => { + odmInfo.filterOptions(req.body.options, (err, options) => { if (err) cb(err); else { req.body.options = options; @@ -208,18 +209,22 @@ app.post('/task/new', authCheck, addRequestId, upload.array('images'), (req, res else { async.eachSeries(entries, (entry, cb) => { if (/\.zip$/gi.test(entry)) { + let filesCount = 0; fs.createReadStream(path.join(destImagesPath, entry)).pipe(unzip.Parse()) .on('entry', function(entry) { if (entry.type === 'File') { + filesCount++; entry.pipe(fs.createWriteStream(path.join(destImagesPath, path.basename(entry.path)))); } else { entry.autodrain(); } }) - .on('close', cb) + .on('close', () => { + // Verify max images limit + if (config.maxImages > 0 && filesCount > config.maxImages) cb(`${filesCount} images uploaded, but this node can only process up to ${config.maxImages}.`); + else cb(); + }) .on('error', cb); - - } else cb(); }, cb); } @@ -559,7 +564,7 @@ app.post('/task/remove', authCheck, uuidCheck, (req, res) => { */ app.post('/task/restart', authCheck, uuidCheck, (req, res, next) => { if (req.body.options){ - odmOptions.filterOptions(req.body.options, (err, options) => { + odmInfo.filterOptions(req.body.options, (err, options) => { if (err) res.json({ error: err.message }); else { req.body.options = options; @@ -608,7 +613,7 @@ app.post('/task/restart', authCheck, uuidCheck, (req, res, next) => { * description: Description of what this option does */ app.get('/options', (req, res) => { - odmOptions.getOptions((err, options) => { + odmInfo.getOptions((err, options) => { if (err) res.json({ error: err.message }); else res.json(options); }); @@ -628,7 +633,7 @@ app.get('/options', (req, res) => { * properties: * version: * type: string - * description: Current version + * description: Current API version * taskQueueCount: * type: integer * description: Number of tasks currently being processed or waiting to be processed @@ -640,21 +645,30 @@ app.get('/options', (req, res) => { * description: Amount of total RAM in the system in bytes * cpuCores: * type: integer - * description: Number of CPU cores (virtual) + * description: Number of CPU cores (virtual) + * maxImages: + * type: integer + * description: Maximum number of images allowed for new tasks + * odmVersion: + * type: string + * description: Current version of ODM */ app.get('/info', (req, res) => { async.parallel({ cpu: cb => si.cpu(data => cb(null, data)), mem: cb => si.mem(data => cb(null, data)), + odmVersion: odmInfo.getVersion }, (_, data) => { - const { cpu, mem } = data; + const { cpu, mem, odmVersion } = data; res.json({ version: packageJson.version, taskQueueCount: taskManager.getQueueCount(), totalMemory: mem.total, availableMemory: mem.available, - cpuCores: cpu.cores + cpuCores: cpu.cores, + maxImages: config.maxImages, + odmVersion: odmVersion }); }); }); @@ -682,7 +696,7 @@ process.on('SIGINT', gracefulShutdown); if (config.test) logger.info("Running in test mode"); let commands = [ - cb => odmOptions.initialize(cb), + cb => odmInfo.initialize(cb), cb => auth.initialize(cb), cb => { taskManager = new TaskManager(cb); }, cb => { diff --git a/libs/odmOptions.js b/libs/odmInfo.js similarity index 96% rename from libs/odmOptions.js rename to libs/odmInfo.js index dee1b54..62b2547 100644 --- a/libs/odmOptions.js +++ b/libs/odmInfo.js @@ -17,14 +17,28 @@ along with this program. If not, see . */ "use strict"; let odmRunner = require('./odmRunner'); +let async = require('async'); let assert = require('assert'); let logger = require('./logger'); let odmOptions = null; +let odmVersion = null; module.exports = { initialize: function(done){ - this.getOptions(done); + async.parallel([ + this.getOptions, + this.getVersion + ], done); + }, + + getVersion: function(done){ + if (odmVersion){ + done(null, odmVersion); + return; + } + + odmRunner.getVersion(done); }, getOptions: function(done){ diff --git a/libs/odmRunner.js b/libs/odmRunner.js index c4b112c..da29843 100644 --- a/libs/odmRunner.js +++ b/libs/odmRunner.js @@ -81,6 +81,13 @@ module.exports = { return childProcess; }, + + getVersion: function(done){ + fs.readFile(path.join(config.odm_path, 'VERSION'), {encoding: 'utf8'}, (err, content) => { + if (err) done(null, "?"); + else done(null, content.split("\n").map(l => l.trim())[0]); + }); + }, getJsonOptions: function(done){ // In test mode, we don't call ODM, diff --git a/package.json b/package.json index eff60f8..1b7a9cb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-opendronemap", - "version": "1.1.1", + "version": "1.2.0", "description": "REST API to access OpenDroneMap", "main": "index.js", "scripts": {