From 436aa3fda5ea3ee56d81b7f8b3598ab15cdcc680 Mon Sep 17 00:00:00 2001 From: AdamSBlack Date: Wed, 19 May 2021 22:55:19 +0100 Subject: [PATCH] Partial refactoring of v1.3/:dongleId/upload_url/, less code, started putting things into their own models. Won't work until I finish --- controllers/authentication.js | 12 + controllers/index.js | 6 + controllers/upload.js | 4 + database.empty.sqlite | Bin models/drives.js | 24 + models/index.js | 6 + server.js | 1580 ++++++++++++++++----------------- worker.js | 6 +- 8 files changed, 840 insertions(+), 798 deletions(-) create mode 100644 controllers/authentication.js create mode 100644 controllers/index.js create mode 100644 controllers/upload.js mode change 100755 => 100644 database.empty.sqlite create mode 100644 models/drives.js create mode 100644 models/index.js diff --git a/controllers/authentication.js b/controllers/authentication.js new file mode 100644 index 0000000..d06478c --- /dev/null +++ b/controllers/authentication.js @@ -0,0 +1,12 @@ + + + + +async function validateJWT(JWT, Key) { + +} + + +module.exports = { + validateJWT +} \ No newline at end of file diff --git a/controllers/index.js b/controllers/index.js new file mode 100644 index 0000000..bda6450 --- /dev/null +++ b/controllers/index.js @@ -0,0 +1,6 @@ + + + +module.exports = { + authenticationController: require('./authentication') +} \ No newline at end of file diff --git a/controllers/upload.js b/controllers/upload.js new file mode 100644 index 0000000..3499d8b --- /dev/null +++ b/controllers/upload.js @@ -0,0 +1,4 @@ + + + +function getUploadURL(driveType) {} \ No newline at end of file diff --git a/database.empty.sqlite b/database.empty.sqlite old mode 100755 new mode 100644 diff --git a/models/drives.js b/models/drives.js new file mode 100644 index 0000000..e96f191 --- /dev/null +++ b/models/drives.js @@ -0,0 +1,24 @@ + + + + + +function getDrives(dongleId) { + +} + +async function getDevice(dongleId) { + return await db.get('SELECT * FROM devices WHERE dongle_id = ?', dongleId); +} + +async function deviceCheckIn(dongleId) { + await db.run( + 'UPDATE devices SET last_ping = ? WHERE dongle_id = ?', + Date.now(), dongle_id + ); +} + +module.exports = { + getDevice, + deviceCheckIn +} \ No newline at end of file diff --git a/models/index.js b/models/index.js new file mode 100644 index 0000000..d0e4c40 --- /dev/null +++ b/models/index.js @@ -0,0 +1,6 @@ + + + +module.exports = { + drivesModel: require('./drives') +} \ No newline at end of file diff --git a/server.js b/server.js index 0e8caa4..107bac8 100644 --- a/server.js +++ b/server.js @@ -2,10 +2,10 @@ const config = require('./config'); const fs = require('fs'); const path = require('path'); const crypto = require('crypto'); -const log4js = require('log4js'); +const log4js = require('log4js'); -import sqlite3 from 'sqlite3' -import { open } from 'sqlite' +const sqlite3 = require('sqlite3') +const {open} = require('sqlite') const lockfile = require('proper-lockfile'); @@ -15,7 +15,7 @@ var https = require('https'); const express = require('express'); const cors = require('cors'); const bodyParser = require('body-parser'); -const cookieParser=require('cookie-parser'); +const cookieParser = require('cookie-parser'); const jwt = require('jsonwebtoken'); const sendmail = require('sendmail')(); @@ -25,31 +25,34 @@ const htmlspecialchars = require('htmlspecialchars'); const dirTree = require("directory-tree"); const execSync = require('child_process').execSync; +const {drivesModel} = require('./models/index'); +const {authenticationController} = require('./controllers/authentication'); + var db = null; -var totalStorageUsed=null; // global variable that is regularly updated in the background to track the total used storage +var totalStorageUsed = null; // global variable that is regularly updated in the background to track the total used storage log4js.configure({ - appenders: { logfile: { type: "file", filename: "server.log" }, out: { type: "console"} }, - categories: { default: { appenders: ['out', 'logfile'], level: 'info' } } + appenders: {logfile: {type: "file", filename: "server.log"}, out: {type: "console"}}, + categories: {default: {appenders: ['out', 'logfile'], level: 'info'}} }); - -var logger = log4js.getLogger('default'); + +var logger = log4js.getLogger('default'); function initializeStorage() { - var verifiedPath = mkDirByPathSync(config.storagePath, {isRelativeToScript: (config.storagePath.indexOf("/")===0 ? false : true)}); - if (verifiedPath!=null) - logger.info("Verified storage path "+verifiedPath); + var verifiedPath = mkDirByPathSync(config.storagePath, {isRelativeToScript: (config.storagePath.indexOf("/") === 0 ? false : true)}); + if (verifiedPath != null) + logger.info("Verified storage path " + verifiedPath); else { - logger.error("Unable to verify storage path '"+config.storagePath+"', check filesystem / permissions"); + logger.error("Unable to verify storage path '" + config.storagePath + "', check filesystem / permissions"); process.exit(); - } + } } function validateJWTToken(token, publicKey) { try { - var decoded = jwt.verify(token.replace("JWT ", ""), publicKey, { algorithms: ['RS256'] }); + var decoded = jwt.verify(token.replace("JWT ", ""), publicKey, {algorithms: ['RS256']}); return decoded; } catch (exception) { logger.error(exception); @@ -62,66 +65,66 @@ function formatDate(timestampMs) { } function formatDuration(durationSeconds) { - durationSeconds=Math.round(durationSeconds); + durationSeconds = Math.round(durationSeconds); var secs = durationSeconds % 60; var mins = Math.floor(durationSeconds / 60); var hours = Math.floor(mins / 60); mins = mins % 60; - var response=''; - if (hours>0) response+=hours+'h '; - if (hours>0 || mins>0) response+=mins+'m '; - response+=secs+'s'; + var response = ''; + if (hours > 0) response += hours + 'h '; + if (hours > 0 || mins > 0) response += mins + 'm '; + response += secs + 's'; return response; } -function mkDirByPathSync(targetDir, { isRelativeToScript = false } = {}) { - const sep = path.sep; - const initDir = path.isAbsolute(targetDir) ? sep : ''; - const baseDir = isRelativeToScript ? __dirname : '.'; +function mkDirByPathSync(targetDir, {isRelativeToScript = false} = {}) { + const sep = path.sep; + const initDir = path.isAbsolute(targetDir) ? sep : ''; + const baseDir = isRelativeToScript ? __dirname : '.'; + + return targetDir.split(sep).reduce((parentDir, childDir) => { + const curDir = path.resolve(baseDir, parentDir, childDir); + try { + fs.mkdirSync(curDir); + } catch (err) { + if (err.code === 'EEXIST') { // curDir already exists! + return curDir; + } + + // To avoid `EISDIR` error on Mac and `EACCES`-->`ENOENT` and `EPERM` on Windows. + if (err.code === 'ENOENT') { // Throw the original parentDir error on curDir `ENOENT` failure. + logger.error(`EACCES: permission denied, mkdir '${parentDir}'`); + return null; + } + + const caughtErr = ['EACCES', 'EPERM', 'EISDIR'].indexOf(err.code) > -1; + if (!caughtErr || (caughtErr && curDir === path.resolve(targetDir))) { + logger.error("'EACCES', 'EPERM', 'EISDIR' during mkdir"); + return null; + } + } - return targetDir.split(sep).reduce((parentDir, childDir) => { - const curDir = path.resolve(baseDir, parentDir, childDir); - try { - fs.mkdirSync(curDir); - } catch (err) { - if (err.code === 'EEXIST') { // curDir already exists! return curDir; - } - - // To avoid `EISDIR` error on Mac and `EACCES`-->`ENOENT` and `EPERM` on Windows. - if (err.code === 'ENOENT') { // Throw the original parentDir error on curDir `ENOENT` failure. - logger.error(`EACCES: permission denied, mkdir '${parentDir}'`); - return null; - } - - const caughtErr = ['EACCES', 'EPERM', 'EISDIR'].indexOf(err.code) > -1; - if (!caughtErr || (caughtErr && curDir === path.resolve(targetDir))) { - logger.error("'EACCES', 'EPERM', 'EISDIR' during mkdir"); - return null; - } - } - - return curDir; - }, initDir); + }, initDir); } -function simpleStringify (object){ +function simpleStringify(object) { var simpleObject = {}; - for (var prop in object ){ - if (!object.hasOwnProperty(prop)){ + for (var prop in object) { + if (!object.hasOwnProperty(prop)) { continue; } - if (typeof(object[prop]) == 'object'){ + if (typeof (object[prop]) == 'object') { continue; } - if (typeof(object[prop]) == 'function'){ + if (typeof (object[prop]) == 'function') { continue; } simpleObject[prop] = object[prop]; } return JSON.stringify(simpleObject); // returns cleaned up JSON -}; +} function writeFileSync(path, buffer, permission) { var fileDescriptor; @@ -135,66 +138,68 @@ function writeFileSync(path, buffer, permission) { if (fileDescriptor) { fs.writeSync(fileDescriptor, buffer, 0, buffer.length, 0); fs.closeSync(fileDescriptor); - logger.info("writeFileSync wiriting to '"+path+"' successful"); + logger.info("writeFileSync wiriting to '" + path + "' successful"); return true; } - logger.error("writeFileSync writing to '"+path+"' failed"); + logger.error("writeFileSync writing to '" + path + "' failed"); return false; } function moveUploadedFile(buffer, directory, filename) { - logger.info("moveUploadedFile called with '"+filename+"' -> '"+directory+"'"); + logger.info("moveUploadedFile called with '" + filename + "' -> '" + directory + "'"); - if (directory.indexOf("..")>=0 || filename.indexOf("..")>=0) { + if (directory.indexOf("..") >= 0 || filename.indexOf("..") >= 0) { logger.error("moveUploadedFile failed, .. in directory or filename"); return false; } - - if (config.storagePath.lastIndexOf("/")!==config.storagePath.length-1) - directory='/'+directory; - if (directory.lastIndexOf("/")!==directory.length-1) - directory=directory+'/'; - - var finalPath = mkDirByPathSync(config.storagePath+directory, {isRelativeToScript: (config.storagePath.indexOf("/")===0 ? false : true)}); - if (finalPath && finalPath.length>0) { - if (writeFileSync(finalPath+"/"+filename, buffer, 0o660)) { - logger.info("moveUploadedFile successfully written '"+(finalPath+"/"+filename)+"'"); - return finalPath+"/"+filename; + + if (config.storagePath.lastIndexOf("/") !== config.storagePath.length - 1) + directory = '/' + directory; + if (directory.lastIndexOf("/") !== directory.length - 1) + directory = directory + '/'; + + var finalPath = mkDirByPathSync(config.storagePath + directory, {isRelativeToScript: (config.storagePath.indexOf("/") === 0 ? false : true)}); + if (finalPath && finalPath.length > 0) { + if (writeFileSync(finalPath + "/" + filename, buffer, 0o660)) { + logger.info("moveUploadedFile successfully written '" + (finalPath + "/" + filename) + "'"); + return finalPath + "/" + filename; } - logger.error("moveUploadedFile failed to writeFileSync"); + logger.error("moveUploadedFile failed to writeFileSync"); return false; } - logger.error("moveUploadedFile invalid final path, check permissions to create / write '"+(config.storagePath+directory)+"'"); - return false; -}; + logger.error("moveUploadedFile invalid final path, check permissions to create / write '" + (config.storagePath + directory) + "'"); + return false; +} async function getAuthenticatedAccount(req) { - var sessionCookie=(req.signedCookies!==undefined ? req.signedCookies.session : null); - if (!sessionCookie || sessionCookie.expires<=Date.now()) { + var sessionCookie = (req.signedCookies !== undefined ? req.signedCookies.session : null); + if (!sessionCookie || sessionCookie.expires <= Date.now()) { return null; } - + const account = await db.get('SELECT * FROM accounts WHERE LOWER(email) = ?', sessionCookie.account.trim().toLowerCase()); if (!account || account.banned) { - res.clearCookie('session'); + res.clearCookie('session'); return null; } const result = await db.run('UPDATE accounts SET last_ping = ? WHERE email = ?', Date.now(), account.email); - return account; + return account; } function updateTotalStorageUsed() { - var verifiedPath = mkDirByPathSync(config.storagePath, {isRelativeToScript: (config.storagePath.indexOf("/")===0 ? false : true)}); - if (verifiedPath!==null) { - totalStorageUsed = execSync("du -hs "+verifiedPath+" | awk -F'\t' '{print $1;}'").toString(); + var verifiedPath = mkDirByPathSync(config.storagePath, {isRelativeToScript: (config.storagePath.indexOf("/") === 0 ? false : true)}); + if (verifiedPath !== null) { + totalStorageUsed = execSync("du -hs " + verifiedPath + " | awk -F'\t' '{print $1;}'").toString(); } - setTimeout(function() {updateTotalStorageUsed();}, 120000); // update the used storage each 120 seconds + setTimeout(function () { + updateTotalStorageUsed(); + }, 120000); // update the used storage each 120 seconds } -function runAsyncWrapper (callback) { +function runAsyncWrapper(callback) { return function (req, res, next) { callback(req, res, next) - .catch(next) + .catch(next) } } @@ -210,63 +215,62 @@ app.use(config.baseDriveDownloadPathMapping, express.static(config.storagePath)) app.use('/.well-known', express.static('.well-known')); // DRIVE & BOOT/CRASH LOG FILE UPLOAD HANDLING -app.put('/backend/post_upload', bodyParser.raw({ inflate: true, limit: '100000kb', type: '*/*' }), runAsyncWrapper(async (req, res) => { - var buf = new Buffer(req.body.toString('binary'),'binary'); - logger.info("HTTP.PUT /backend/post_upload for dongle "+req.query.dongleId+" with body length: "+buf.length); - +app.put('/backend/post_upload', bodyParser.raw({ + inflate: true, + limit: '100000kb', + type: '*/*' +}), runAsyncWrapper(async (req, res) => { + var buf = new Buffer(req.body.toString('binary'), 'binary'); + logger.info("HTTP.PUT /backend/post_upload for dongle " + req.query.dongleId + " with body length: " + buf.length); + var dongleId = req.query.dongleId; var ts = req.query.ts; - - if (req.query.file.indexOf("boot")!=0 && req.query.file.indexOf("crash")!=0) { // drive file upload - var filename = req.query.file; + + if (req.query.file.indexOf("boot") != 0 && req.query.file.indexOf("crash") != 0) { // drive file upload + var filename = req.query.file; var directory = req.query.dir; - var token = crypto.createHmac('sha256', config.applicationSalt).update(dongleId+filename+directory+ts).digest('hex'); - - logger.info("HTTP.PUT /backend/post_upload DRIVE upload with filename: "+filename+", directory: "+directory+", token: "+req.query.token); - - if (token!==req.query.token) { - logger.error("HTTP.PUT /backend/post_upload token mismatch ("+token+" vs "+req.query.token+")"); + var token = crypto.createHmac('sha256', config.applicationSalt).update(dongleId + filename + directory + ts).digest('hex'); + + logger.info("HTTP.PUT /backend/post_upload DRIVE upload with filename: " + filename + ", directory: " + directory + ", token: " + req.query.token); + + if (token !== req.query.token) { + logger.error("HTTP.PUT /backend/post_upload token mismatch (" + token + " vs " + req.query.token + ")"); res.status(400); res.send('Malformed request'); - return; - } - else { - logger.info("HTTP.PUT /backend/post_upload permissions checked, calling moveUploadedFile"); + + } else { + logger.info("HTTP.PUT /backend/post_upload permissions checked, calling moveUploadedFile"); var moveResult = moveUploadedFile(buf, directory, filename); - if (moveResult===false) { - logger.error("HTTP.PUT /backend/post_upload moveUploadedFile failed"); + if (moveResult === false) { + logger.error("HTTP.PUT /backend/post_upload moveUploadedFile failed"); res.status(500); res.send('Internal Server Error'); - } - else { - logger.info("HTTP.PUT /backend/post_upload succesfully uploaded to "+moveResult); + } else { + logger.info("HTTP.PUT /backend/post_upload succesfully uploaded to " + moveResult); res.status(200); res.json(['OK']); } } - } - else { // boot or crash upload - var filename = req.query.file; - var token = crypto.createHmac('sha256', config.applicationSalt).update(dongleId+filename+ts).digest('hex'); + } else { // boot or crash upload + var filename = req.query.file; + var token = crypto.createHmac('sha256', config.applicationSalt).update(dongleId + filename + ts).digest('hex'); var directory = req.query.dir; - logger.info("HTTP.PUT /backend/post_upload BOOT or CRASH upload with filename: "+filename+", token: "+req.query.token); - if (token!==req.query.token) { - logger.error("HTTP.PUT /backend/post_upload token mismatch ("+token+" vs "+req.query.token+")"); + logger.info("HTTP.PUT /backend/post_upload BOOT or CRASH upload with filename: " + filename + ", token: " + req.query.token); + if (token !== req.query.token) { + logger.error("HTTP.PUT /backend/post_upload token mismatch (" + token + " vs " + req.query.token + ")"); res.status(400); res.send('Malformed request'); - return; - } - else { - logger.info("HTTP.PUT /backend/post_upload permissions checked, calling moveUploadedFile"); + + } else { + logger.info("HTTP.PUT /backend/post_upload permissions checked, calling moveUploadedFile"); var moveResult = moveUploadedFile(buf, directory, filename); - if (moveResult===false) { - logger.error("HTTP.PUT /backend/post_upload moveUploadedFile failed"); + if (moveResult === false) { + logger.error("HTTP.PUT /backend/post_upload moveUploadedFile failed"); res.status(500); res.send('Internal Server Error'); - } - else { - logger.info("HTTP.PUT /backend/post_upload succesfully uploaded to "+moveResult); + } else { + logger.info("HTTP.PUT /backend/post_upload succesfully uploaded to " + moveResult); res.status(200); res.json(['OK']); } @@ -276,253 +280,237 @@ app.put('/backend/post_upload', bodyParser.raw({ inflate: true, limit: '100000kb // DRIVE & BOOT/CRASH LOG FILE UPLOAD URL REQUEST -app.get('/v1.3/:dongleId/upload_url/', runAsyncWrapper(async (req, res) => { - var path = req.query.path; - logger.info("HTTP.UPLOAD_URL called for "+req.params.dongleId+" and file "+path+": "+JSON.stringify(req.headers)); - - const device = await db.get('SELECT * FROM devices WHERE dongle_id = ?', req.params.dongleId); + app.get('/v1.3/:dongleId/upload_url/', runAsyncWrapper(async (req, res) => { + var path = req.query.path; + logger.info("HTTP.UPLOAD_URL called for " + req.params.dongleId + " and file " + path + ": " + JSON.stringify(req.headers)); - if (device==null || device.account_id==0) { - logger.info("HTTP.UPLOAD_URL device "+req.params.dongleId+" not found or not linked to an account / refusing uploads"); - res.status(400); - res.send('Unauthorized.'); - return; - } + const dongleId = req.params.dongleId; + const auth = req.params.authorization; - var decoded=null; - if (device.public_key.length>0) { - decoded = validateJWTToken(req.headers.authorization, device.public_key); - } - - if (decoded==null || decoded.identity!==req.params.dongleId) { - logger.info("HTTP.UPLOAD_URL JWT authorization failed, token: '"+req.headers.authorization+"', device: "+JSON.stringify(device)+", decoded: "+JSON.stringify(decoded)+""); - res.status(400); - res.send('Unauthorized.'); - return; - } + const device = await drivesModel.getDevice(dongleId); - const result = await db.run( - 'UPDATE devices SET last_ping = ? WHERE dongle_id = ?', - Date.now(), device.dongle_id - ); + if (!device) { + logger.info(`HTTP.UPLOAD_URL device ${dongleId} not found or not linked to an account / refusing uploads`); + return res.send('Unauthorized.').status(400) + } - var responseUrl = null; - var ts = Date.now(); // we use this to make sure old URLs cannot be reused (timeout after 60min) + let decoded = device.public_key ? await authenticationController.validateJWT(req.headers.authorization, device.public_key) : null; - // boot log upload - if (path.indexOf("boot/")===0) { - var filename = path.replace("/", "-"); // "boot-2021-04-12--01-45-30.bz" for example - var token = crypto.createHmac('sha256', config.applicationSalt).update(req.params.dongleId+filename+ts).digest('hex'); - - var dongleIdHash = crypto.createHmac('sha256', config.applicationSalt).update(req.params.dongleId).digest('hex'); - var directory=req.params.dongleId+"/"+dongleIdHash+"/boot"; - responseUrl = config.baseUploadUrl +'?file='+filename+'&dir='+directory+'&dongleId='+req.params.dongleId+'&ts='+ts+'&token='+token; - logger.info("HTTP.UPLOAD_URL matched 'boot' file upload, constructed responseUrl: "+responseUrl); - } - // crash log upload - if (path.indexOf("crash/")===0) { - var filename = path.replace("/", "-"); // "crash-2021-04-12--01-45-30.bz" for example - var token = crypto.createHmac('sha256', config.applicationSalt).update(req.params.dongleId+filename+ts).digest('hex'); - var directory=req.params.dongleId+"/"+dongleIdHash+"/crash"; - responseUrl = config.baseUploadUrl +'?file='+filename+'&dir='+directory+'&dongleId='+req.params.dongleId+'&ts='+ts+'&token='+token; - logger.info("HTTP.UPLOAD_URL matched 'crash' file upload, constructed responseUrl: "+responseUrl); - } - // drive upload - else { - // "2021-04-12--01-44-25--0/qlog.bz2" for example - var subdirPosition = path.split("--", 2).join("--").length; - var filenamePosition = path.indexOf("/"); - if (subdirPosition>0 && filenamePosition>subdirPosition) { - var driveName = path.split("--")[0]+"--"+path.split("--")[1]; - var segment = parseInt(path.split("--")[2].substr(0, path.split("--")[2].indexOf("/"))); - var directory = path.split("--")[0]+"--"+path.split("--")[1]+"/"+segment; - var filename=path.split("/")[1]; + if (decoded === null || decoded.identity !== req.params.dongleId) { + logger.info(`HTTP.UPLOAD_URL JWT authorization failed, token: ${auth} device: ${JSON.stringify(device)}, decoded: ${JSON.stringify(decoded)}`); + return res.send('Unauthorized.').status(400) + } - var validRequest=false; - - if (filename=='fcamera.hevc' || filename=='qcamera.ts' || filename=='dcamera.hevc' || filename=='rlog.bz2' || filename=='qlog.bz2') - validRequest=true; - if (segment==NaN || segment<0 || segment>10000) - validRequest=false; + await drivesModel.deviceCheckIn(dongleId) - if (!validRequest) { - logger.error("HTTP.UPLOAD_URL invalid filename ("+filename+") or invalid segment ("+segment+"), responding with HTTP 400"); - res.status(400); - res.send('Malformed Request.'); - return; - } + let responseUrl = null; + const ts = Date.now(); // we use this to make sure old URLs cannot be reused (timeout after 60min) - var dongleIdHash = crypto.createHmac('sha256', config.applicationSalt).update(req.params.dongleId).digest('hex'); - var driveIdentifierHash = crypto.createHmac('sha256', config.applicationSalt).update(driveName).digest('hex'); - - directory=req.params.dongleId+"/"+dongleIdHash+"/"+driveIdentifierHash+"/"+directory; + const dongleIdHash = crypto.createHmac('sha256', config.applicationSalt).update(dongleId).digest('hex'); - var token = crypto.createHmac('sha256', config.applicationSalt).update(req.params.dongleId+filename+directory+ts).digest('hex'); - responseUrl = config.baseUploadUrl +'?file='+filename+'&dir='+directory+'&dongleId='+req.params.dongleId+'&ts='+ts+'&token='+token; - logger.info("HTTP.UPLOAD_URL matched 'drive' file upload, constructed responseUrl: "+responseUrl); + // boot log upload - const drive = await db.get('SELECT * FROM drives WHERE identifier = ? AND dongle_id = ?', driveName, req.params.dongleId); + if (path.indexOf("boot/") === 0 || path.indexOf("crash/") === 0) { + let filename = path.replace("/", "-"); + const token = crypto.createHmac('sha256', config.applicationSalt).update(dongleId + filename + ts).digest('hex'); - if (drive==null) { - // create a new drive - var timeSplit = driveName.split('--'); - var timeString = timeSplit[0]+' '+timeSplit[1].replace(/-/g, ':'); + // TODO, allow multiple types + let uplaodType = path.indexOf("boot/") === 0 ? 'boot' : 'crash'; - const driveResult = await db.run( - 'INSERT INTO drives (identifier, dongle_id, max_segment, duration, distance_meters, filesize, upload_complete, is_processed, drive_date, created, last_upload, is_preserved, is_deleted, is_physically_removed) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', - driveName, req.params.dongleId, segment, 0, 0, 0, false, false, Date.parse(timeString), Date.now(), Date.now(), false, false, false); + // "boot-2021-04-12--01-45-30.bz" for example + const directory = `${dongleId}/${dongleIdHash}/${uplaodType}`; + responseUrl = `${config.baseUploadUrl}?file=${filename}&dir=${directory}&dongleId=${dongleId}&ts=${ts}&token=${token}`; + logger.info(`HTTP.UPLOAD_URL matched '${uplaodType}' file upload, constructed responseUrl: ${responseUrl}`); + } else { + // "2021-04-12--01-44-25--0/qlog.bz2" for example + const subdirPosition = path.split("--", 2).join("--").length; + const filenamePosition = path.indexOf("/"); + if (subdirPosition > 0 && filenamePosition > subdirPosition) { + const driveName = path.split("--")[0] + "--" + path.split("--")[1]; + const segment = parseInt(path.split("--")[2].substr(0, path.split("--")[2].indexOf("/"))); + let directory = path.split("--")[0] + "--" + path.split("--")[1] + "/" + segment; + const filename = path.split("/")[1]; + let validRequest = false; - const driveSegmentResult = await db.run( - 'INSERT INTO drive_segments (segment_id, drive_identifier, dongle_id, duration, distance_meters, upload_complete, is_processed, is_stalled, created) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)', - segment, driveName, req.params.dongleId, 0, 0, false, false, false, Date.now()); - - logger.info("HTTP.UPLOAD_URL created new drive #"+JSON.stringify(driveResult.lastID)); - } - else { - const driveResult = await db.run( - 'UPDATE drives SET last_upload = ?, max_segment = ?, upload_complete = ?, is_processed = ? WHERE identifier = ? AND dongle_id = ?', - Date.now(), Math.max(drive.max_segment, segment), false, false, driveName, req.params.dongleId - ); - - const drive_segment = await db.get('SELECT * FROM drive_segments WHERE drive_identifier = ? AND dongle_id = ? AND segment_id = ?', driveName, req.params.dongleId, segment); - - if (drive_segment==null) { - const driveSegmentResult = await db.run( - 'INSERT INTO drive_segments (segment_id, drive_identifier, dongle_id, duration, distance_meters, upload_complete, is_processed, is_stalled, created) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)', - segment, driveName, req.params.dongleId, 0, 0, false, false, false, Date.now() - ); - } else { - const driveSegmentResult = await db.run( - 'UPDATE drive_segments SET upload_complete = ?, is_stalled = ? WHERE drive_identifier = ? AND dongle_id = ? AND segment_id = ?', - false, false, driveName, req.params.dongleId, segment - ); + if (filename === 'fcamera.hevc' || filename === 'qcamera.ts' || filename === 'dcamera.hevc' || filename === 'rlog.bz2' || filename === 'qlog.bz2') { + validRequest = true; + } else if (isNaN(segment) || segment < 0 || segment > 10000) { + validRequest = false; } - logger.info("HTTP.UPLOAD_URL updated existing drive: "+JSON.stringify(drive)); - } + if (!validRequest) { + logger.error(`HTTP.UPLOAD_URL invalid filename (${filename}) or invalid segment (${segment}), responding with HTTP 400`); + return res.send('Malformed Request.').status(400) + } - } - } - - if (responseUrl != null) { - res.status(200); - res.json({'url': responseUrl, 'headers': {'Content-Type': 'application/octet-stream'}}); - } - else { - logger.error("HTTP.UPLOAD_URL unable to match request, responding with HTTP 400"); - res.status(400); - res.send('Malformed Request.'); - } -})), + const driveIdentifierHash = crypto.createHmac('sha256', config.applicationSalt).update(driveName).digest('hex'); + + directory = `${dongleId}/${dongleIdHash}/${driveIdentifierHash}/${directory}`; + + const token = crypto.createHmac('sha256', config.applicationSalt).update(dongleId + filename + directory + ts).digest('hex'); + responseUrl = `${config.baseUploadUrl}?file=${filename}&dir=${directory}&dongleId=${dongleId}&ts=${ts}&token=${token}`; + logger.info(`HTTP.UPLOAD_URL matched 'drive' file upload, constructed responseUrl: ${responseUrl}`); + + const drive = await db.get('SELECT * FROM drives WHERE identifier = ? AND dongle_id = ?', driveName, dongleId); + + if (drive == null) { + // create a new drive + const timeSplit = driveName.split('--'); + const timeString = timeSplit[0] + ' ' + timeSplit[1].replace(/-/g, ':'); + + const driveResult = await db.run( + 'INSERT INTO drives (identifier, dongle_id, max_segment, duration, distance_meters, filesize, upload_complete, is_processed, drive_date, created, last_upload, is_preserved, is_deleted, is_physically_removed) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', + driveName, dongleId, segment, 0, 0, 0, false, false, Date.parse(timeString), Date.now(), Date.now(), false, false, false); + + + const driveSegmentResult = await db.run( + 'INSERT INTO drive_segments (segment_id, drive_identifier, dongle_id, duration, distance_meters, upload_complete, is_processed, is_stalled, created) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)', + segment, driveName, dongleId, 0, 0, false, false, false, Date.now()); + + logger.info("HTTP.UPLOAD_URL created new drive #" + JSON.stringify(driveResult.lastID)); + } else { + const driveResult = await db.run( + 'UPDATE drives SET last_upload = ?, max_segment = ?, upload_complete = ?, is_processed = ? WHERE identifier = ? AND dongle_id = ?', + Date.now(), Math.max(drive.max_segment, segment), false, false, driveName, dongleId + ); + + const drive_segment = await db.get('SELECT * FROM drive_segments WHERE drive_identifier = ? AND dongle_id = ? AND segment_id = ?', driveName, dongleId, segment); + + if (drive_segment == null) { + const driveSegmentResult = await db.run( + 'INSERT INTO drive_segments (segment_id, drive_identifier, dongle_id, duration, distance_meters, upload_complete, is_processed, is_stalled, created) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)', + segment, driveName, dongleId, 0, 0, false, false, false, Date.now() + ); + } else { + const driveSegmentResult = await db.run( + 'UPDATE drive_segments SET upload_complete = ?, is_stalled = ? WHERE drive_identifier = ? AND dongle_id = ? AND segment_id = ?', + false, false, driveName, dongleId, segment + ); + } + + logger.info("HTTP.UPLOAD_URL updated existing drive: " + JSON.stringify(drive)); + } + + } + } + + if (responseUrl != null) { + res.status(200); + res.json({'url': responseUrl, 'headers': {'Content-Type': 'application/octet-stream'}}); + } else { + logger.error("HTTP.UPLOAD_URL unable to match request, responding with HTTP 400"); + res.status(400); + res.send('Malformed Request.'); + } + })), // DEVICE REGISTRATION OR RE-ACTIVATION -app.post('/v2/pilotauth/', bodyParser.urlencoded({ extended: true }), runAsyncWrapper(async (req, res) => { - var imei1 = req.query.imei; - var serial = req.query.serial; - var public_key = req.query.public_key; - var register_token = req.query.register_token; + app.post('/v2/pilotauth/', bodyParser.urlencoded({extended: true}), runAsyncWrapper(async (req, res) => { + var imei1 = req.query.imei; + var serial = req.query.serial; + var public_key = req.query.public_key; + var register_token = req.query.register_token; - if (imei1==null || imei1.length<5 || serial==null || serial.length<5 || public_key==null || public_key.length<5 || register_token==null || register_token.length<5) { - logger.error("HTTP.V2.PILOTAUTH a required parameter is missing or empty"); - res.status(400); - res.send('Malformed Request.'); - return; - } + if (imei1 == null || imei1.length < 5 || serial == null || serial.length < 5 || public_key == null || public_key.length < 5 || register_token == null || register_token.length < 5) { + logger.error("HTTP.V2.PILOTAUTH a required parameter is missing or empty"); + res.status(400); + res.send('Malformed Request.'); + return; + } - var decoded = validateJWTToken(req.query.register_token, public_key); + var decoded = validateJWTToken(req.query.register_token, public_key); - if (decoded==null || decoded.register==undefined) { - logger.error("HTTP.V2.PILOTAUTH JWT token is invalid ("+JSON.stringify(decoded)+")"); - res.status(400); - res.send('Malformed Request.'); - return; - } - - const device = await db.get('SELECT * FROM devices WHERE imei = ? AND serial = ?', imei1, serial); - if (device==null) { - logger.info("HTTP.V2.PILOTAUTH REGISTERING NEW DEVICE ("+imei1+", "+serial+")"); - while(true) { - var dongleId = crypto.randomBytes(4).toString('hex'); - const isDongleIdTaken = await db.get('SELECT * FROM devices WHERE imei = ? AND serial = ?', imei1, serial); - if (isDongleIdTaken==null) { - const resultingDevice = await db.run( - 'INSERT INTO devices (dongle_id, account_id, imei, serial, device_type, public_key, created, last_ping, storage_used) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)', + if (decoded == null || decoded.register == undefined) { + logger.error("HTTP.V2.PILOTAUTH JWT token is invalid (" + JSON.stringify(decoded) + ")"); + res.status(400); + res.send('Malformed Request.'); + return; + } + + const device = await db.get('SELECT * FROM devices WHERE imei = ? AND serial = ?', imei1, serial); + if (device == null) { + logger.info("HTTP.V2.PILOTAUTH REGISTERING NEW DEVICE (" + imei1 + ", " + serial + ")"); + while (true) { + var dongleId = crypto.randomBytes(4).toString('hex'); + const isDongleIdTaken = await db.get('SELECT * FROM devices WHERE imei = ? AND serial = ?', imei1, serial); + if (isDongleIdTaken == null) { + const resultingDevice = await db.run( + 'INSERT INTO devices (dongle_id, account_id, imei, serial, device_type, public_key, created, last_ping, storage_used) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)', dongleId, 0, imei1, serial, 'freon', public_key, Date.now(), Date.now(), 0); - const device = await db.get('SELECT * FROM devices WHERE dongle_id = ?', dongleId); + const device = await db.get('SELECT * FROM devices WHERE dongle_id = ?', dongleId); - logger.info("HTTP.V2.PILOTAUTH REGISTERED NEW DEVICE: "+JSON.stringify(device)); - res.status(200); - res.json({dongle_id: device.dongle_id}); - return; + logger.info("HTTP.V2.PILOTAUTH REGISTERED NEW DEVICE: " + JSON.stringify(device)); + res.status(200); + res.json({dongle_id: device.dongle_id}); + return; + } } - } - } - else { - const result = await db.run( - 'UPDATE devices SET last_ping = ?, public_key = ? WHERE dongle_id = ?', - Date.now(), public_key, device.dongle_id - ); + } else { + const result = await db.run( + 'UPDATE devices SET last_ping = ?, public_key = ? WHERE dongle_id = ?', + Date.now(), public_key, device.dongle_id + ); - logger.info("HTTP.V2.PILOTAUTH REACTIVATING KNOWN DEVICE ("+imei1+", "+serial+") with dongle_id "+device.dongle_id+""); - res.status(200); - res.json({dongle_id: device.dongle_id}); - return; - } -})), + logger.info("HTTP.V2.PILOTAUTH REACTIVATING KNOWN DEVICE (" + imei1 + ", " + serial + ") with dongle_id " + device.dongle_id + ""); + res.status(200); + res.json({dongle_id: device.dongle_id}); + + } + })), // RETRIEVES DATASET FOR OUR MODIFIED CABANA - THIS RESPONSE IS USED TO FAKE A DEMO ROUTE -app.get('/useradmin/cabana_drive/:extendedRouteIdentifier', runAsyncWrapper(async (req, res) => { + app.get('/useradmin/cabana_drive/:extendedRouteIdentifier', runAsyncWrapper(async (req, res) => { + + var params = req.params.extendedRouteIdentifier.split('|'); + var dongleId = params[0]; + var dongleIdHashReq = params[1]; + var driveIdentifier = params[2]; + var driveIdentifierHashReq = params[3]; + + const drive = await db.get('SELECT * FROM drives WHERE identifier = ? AND dongle_id = ?', driveIdentifier, dongleId); + + if (!drive) { + res.status(200); + res.json({'status': 'drive not found'}); + return; + } + + var dongleIdHash = crypto.createHmac('sha256', config.applicationSalt).update(drive.dongle_id).digest('hex'); + var driveIdentifierHash = crypto.createHmac('sha256', config.applicationSalt).update(drive.identifier).digest('hex'); + var driveUrl = config.baseDriveDownloadUrl + drive.dongle_id + "/" + dongleIdHash + "/" + driveIdentifierHash + "/" + drive.identifier; + + if (dongleIdHash != dongleIdHashReq || driveIdentifierHash != driveIdentifierHashReq) { + res.status(200); + res.json({'status': 'hashes not matching'}); + return; + } + + if (!drive.is_processed) { + res.status(200); + res.json({'status': 'drive is not processed yet'}); + return; + } + + + var logUrls = []; + + for (var i = 0; i <= drive.max_segment; i++) { + logUrls.push(driveUrl + '/' + i + '/rlog.bz2'); + } - var params = req.params.extendedRouteIdentifier.split('|'); - var dongleId=params[0]; - var dongleIdHashReq=params[1]; - var driveIdentifier=params[2]; - var driveIdentifierHashReq=params[3]; - - const drive = await db.get('SELECT * FROM drives WHERE identifier = ? AND dongle_id = ?', driveIdentifier, dongleId); - - if (!drive) { res.status(200); - res.json({'status' : 'drive not found'}); - return; - } - - var dongleIdHash = crypto.createHmac('sha256', config.applicationSalt).update(drive.dongle_id).digest('hex'); - var driveIdentifierHash = crypto.createHmac('sha256', config.applicationSalt).update(drive.identifier).digest('hex'); - var driveUrl=config.baseDriveDownloadUrl+drive.dongle_id+"/"+dongleIdHash+"/"+driveIdentifierHash+"/"+drive.identifier; - - if (dongleIdHash!=dongleIdHashReq || driveIdentifierHash!=driveIdentifierHashReq) { - res.status(200); - res.json({'status' : 'hashes not matching'}); - return; - } - - if (!drive.is_processed) { - res.status(200); - res.json({'status' : 'drive is not processed yet'}); - return; - } - - - var logUrls=[]; - - for (var i=0; i<=drive.max_segment; i++) { - logUrls.push(driveUrl+'/'+i+'/rlog.bz2'); - } - - res.status(200); - res.json({ - logUrls: logUrls, - driveUrl: driveUrl, - name: drive.dongle_id+'|'+drive.identifier, - driveIdentifier: drive.identifier, - dongleId: drive.dongle_id - }); -})), + res.json({ + logUrls: logUrls, + driveUrl: driveUrl, + name: drive.dongle_id + '|' + drive.identifier, + driveIdentifier: drive.identifier, + dongleId: drive.dongle_id + }); + })), ////////////////////////////////////////////////////////////////////// @@ -530,193 +518,191 @@ app.get('/useradmin/cabana_drive/:extendedRouteIdentifier', runAsyncWrapper(asyn ////////////////////////////////////////////////////////////////////// -app.post('/useradmin/auth', bodyParser.urlencoded({ extended: true }), runAsyncWrapper(async (req, res) => { - const account = await db.get('SELECT * FROM accounts WHERE email = ? AND password = ?', req.body.email, crypto.createHash('sha256').update(req.body.password+config.applicationSalt).digest('hex')); + app.post('/useradmin/auth', bodyParser.urlencoded({extended: true}), runAsyncWrapper(async (req, res) => { + const account = await db.get('SELECT * FROM accounts WHERE email = ? AND password = ?', req.body.email, crypto.createHash('sha256').update(req.body.password + config.applicationSalt).digest('hex')); - - if (!account || account.banned) { - res.status(200); - res.redirect('/useradmin?status='+encodeURIComponent('Invalid credentials or banned account')); - return; - } - res.cookie('session', {account: account.email, expires: Date.now()+1000*3600*24*365}, {signed: true}); - res.redirect('/useradmin/overview'); -})), - - -app.get('/useradmin/signout', runAsyncWrapper(async (req, res) => { - res.clearCookie('session'); - res.redirect('/useradmin'); -})), - - -app.get('/useradmin', runAsyncWrapper(async (req, res) => { - const account = await getAuthenticatedAccount(req); - if (account!=null) { + if (!account || account.banned) { + res.status(200); + res.redirect('/useradmin?status=' + encodeURIComponent('Invalid credentials or banned account')); + return; + } + res.cookie('session', {account: account.email, expires: Date.now() + 1000 * 3600 * 24 * 365}, {signed: true}); res.redirect('/useradmin/overview'); - return; - } + })), - const accounts = await db.get('SELECT COUNT(*) AS num FROM accounts'); - const devices = await db.get('SELECT COUNT(*) AS num FROM devices'); - const drives= await db.get('SELECT COUNT(*) AS num FROM drives'); - res.status(200); - res.send('

Welcome To The RetroPilot Server Dashboard!

'+ - `

+ app.get('/useradmin/signout', runAsyncWrapper(async (req, res) => { + res.clearCookie('session'); + res.redirect('/useradmin'); + })), + + + app.get('/useradmin', runAsyncWrapper(async (req, res) => { + const account = await getAuthenticatedAccount(req); + if (account != null) { + res.redirect('/useradmin/overview'); + return; + } + + const accounts = await db.get('SELECT COUNT(*) AS num FROM accounts'); + const devices = await db.get('SELECT COUNT(*) AS num FROM devices'); + const drives = await db.get('SELECT COUNT(*) AS num FROM drives'); + + res.status(200); + res.send('

Welcome To The RetroPilot Server Dashboard!

' + + `

Login

- `+(req.query.status!==undefined ? ''+htmlspecialchars(req.query.status)+'
' : '')+` + ` + (req.query.status !== undefined ? '' + htmlspecialchars(req.query.status) + '
' : '') + `
-


`+(!config.allowAccountRegistration ? 'User Account Registration is disabled on this Server' : 'Register new Account')+`

`+ - 'Accounts: '+accounts.num+' | '+ - 'Devices: '+devices.num+' | '+ - 'Drives: '+drives.num+' | '+ - 'Storage Used: '+(totalStorageUsed!==null ? totalStorageUsed : '--')+'

'+config.welcomeMessage+''); -})), +

` + (!config.allowAccountRegistration ? 'User Account Registration is disabled on this Server' : 'Register new Account') + `

` + + 'Accounts: ' + accounts.num + ' | ' + + 'Devices: ' + devices.num + ' | ' + + 'Drives: ' + drives.num + ' | ' + + 'Storage Used: ' + (totalStorageUsed !== null ? totalStorageUsed : '--') + '

' + config.welcomeMessage + ''); + })), -app.post('/useradmin/register/token', bodyParser.urlencoded({ extended: true }), runAsyncWrapper(async (req, res) => { - if (!config.allowAccountRegistration) { - res.status(400); - res.send('Unauthorized.'); - return; - } - - const authAccount = await getAuthenticatedAccount(req); - if (authAccount!=null) { - res.redirect('/useradmin/overview'); - return; - } - - const account = await db.get('SELECT * FROM accounts WHERE LOWER(email) = ?', req.body.email.trim().toLowerCase()); - if (account!=null) { - res.redirect('/useradmin/register?status='+encodeURIComponent('Email is already registered')); - return; - } - - var token = crypto.createHmac('sha256', config.applicationSalt).update(req.body.email.trim()).digest('hex'); - - var infoText=''; - - if (req.body.token==undefined) { // email entered, token request - logger.info("USERADMIN REGISTRATION sending token to "+htmlspecialchars(req.body.email.trim())+": \""+token+"\""); - infoText='Please check your inbox (SPAM) for an email with the registration token.
If the token was not delivered, please ask the administrator to check the server.log for the token generated for your email.

'; - - sendmail({ - from: 'no-reply@retropilot.com', - to: req.body.email.trim(), - subject: 'RetroPilot Registration Token', - html: 'Your Email Registration Token Is: "'+token+'"', - }, function(err, reply) { - if (err) - logger.error("USERADMIN REGISTRATION - failed to send registration token email "+(err && err.stack)+" "+reply); - }); - } - else { // final registration form filled - if (req.body.token!=token) { - infoText='The registration token you entered was incorrect, please try again.

'; - } - else if (req.body.password!=req.body.password2 || req.body.password.length<3) { - infoText='The passwords you entered did not or were shorter than 3 characters, please try again.

'; + app.post('/useradmin/register/token', bodyParser.urlencoded({extended: true}), runAsyncWrapper(async (req, res) => { + if (!config.allowAccountRegistration) { + res.status(400); + res.send('Unauthorized.'); + return; } - else { - const result = await db.run( - 'INSERT INTO accounts (email, password, created, banned) VALUES (?, ?, ?, ?)', - req.body.email, - crypto.createHash('sha256').update(req.body.password+config.applicationSalt).digest('hex'), + const authAccount = await getAuthenticatedAccount(req); + if (authAccount != null) { + res.redirect('/useradmin/overview'); + return; + } + + const account = await db.get('SELECT * FROM accounts WHERE LOWER(email) = ?', req.body.email.trim().toLowerCase()); + if (account != null) { + res.redirect('/useradmin/register?status=' + encodeURIComponent('Email is already registered')); + return; + } + + var token = crypto.createHmac('sha256', config.applicationSalt).update(req.body.email.trim()).digest('hex'); + + var infoText = ''; + + if (req.body.token == undefined) { // email entered, token request + logger.info("USERADMIN REGISTRATION sending token to " + htmlspecialchars(req.body.email.trim()) + ": \"" + token + "\""); + infoText = 'Please check your inbox (SPAM) for an email with the registration token.
If the token was not delivered, please ask the administrator to check the server.log for the token generated for your email.

'; + + sendmail({ + from: 'no-reply@retropilot.com', + to: req.body.email.trim(), + subject: 'RetroPilot Registration Token', + html: 'Your Email Registration Token Is: "' + token + '"', + }, function (err, reply) { + if (err) + logger.error("USERADMIN REGISTRATION - failed to send registration token email " + (err && err.stack) + " " + reply); + }); + } else { // final registration form filled + if (req.body.token != token) { + infoText = 'The registration token you entered was incorrect, please try again.

'; + } else if (req.body.password != req.body.password2 || req.body.password.length < 3) { + infoText = 'The passwords you entered did not or were shorter than 3 characters, please try again.

'; + } else { + + const result = await db.run( + 'INSERT INTO accounts (email, password, created, banned) VALUES (?, ?, ?, ?)', + req.body.email, + crypto.createHash('sha256').update(req.body.password + config.applicationSalt).digest('hex'), Date.now(), false); - - if (result.lastID!=undefined) { - logger.info("USERADMIN REGISTRATION - created new account #"+result.lastID+" with email "+req.body.email+""); - res.cookie('session', {account: req.body.email, expires: Date.now()+1000*3600*24*365}, {signed: true}); - res.redirect('/useradmin/overview'); - return; - } - else { - logger.error("USERADMIN REGISTRATION - account creation failed, resulting account data for email "+req.body.email+" is: "+result); - infoText='Unable to complete account registration (database error).

'; + + if (result.lastID != undefined) { + logger.info("USERADMIN REGISTRATION - created new account #" + result.lastID + " with email " + req.body.email + ""); + res.cookie('session', { + account: req.body.email, + expires: Date.now() + 1000 * 3600 * 24 * 365 + }, {signed: true}); + res.redirect('/useradmin/overview'); + return; + } else { + logger.error("USERADMIN REGISTRATION - account creation failed, resulting account data for email " + req.body.email + " is: " + result); + infoText = 'Unable to complete account registration (database error).

'; + } } } - } - res.status(200); - res.send('

Welcome To The RetroPilot Server Dashboard!

'+ - ` + res.status(200); + res.send('

Welcome To The RetroPilot Server Dashboard!

' + + ` < < < Back To Login

Register / Finish Registration

- `+infoText+` + ` + infoText + `
- -
- - + +
+ + `); -})), + })), -app.get('/useradmin/register', runAsyncWrapper(async (req, res) => { - if (!config.allowAccountRegistration) { - res.status(400); - res.send('Unauthorized.'); - return; - } + app.get('/useradmin/register', runAsyncWrapper(async (req, res) => { + if (!config.allowAccountRegistration) { + res.status(400); + res.send('Unauthorized.'); + return; + } - const account = await getAuthenticatedAccount(req); - if (account!=null) { - res.redirect('/useradmin/overview'); - return; - } + const account = await getAuthenticatedAccount(req); + if (account != null) { + res.redirect('/useradmin/overview'); + return; + } - res.status(200); - res.send('

Welcome To The RetroPilot Server Dashboard!

'+ - ` + res.status(200); + res.send('

Welcome To The RetroPilot Server Dashboard!

' + + ` < < < Back To Login

Register / Request Email Token

- `+(req.query.status!==undefined ? ''+htmlspecialchars(req.query.status)+'
' : '')+` + ` + (req.query.status !== undefined ? '' + htmlspecialchars(req.query.status) + '
' : '') + ` `); -})), + })), -app.get('/useradmin/overview', runAsyncWrapper(async (req, res) => { - const account = await getAuthenticatedAccount(req); - if (account==null) { - res.redirect('/useradmin?status='+encodeURIComponent('Invalid or expired session')); - return; - } - - const devices = await db.all('SELECT * FROM devices WHERE account_id = ? ORDER BY dongle_id ASC', account.id) + app.get('/useradmin/overview', runAsyncWrapper(async (req, res) => { + const account = await getAuthenticatedAccount(req); + if (account == null) { + res.redirect('/useradmin?status=' + encodeURIComponent('Invalid or expired session')); + return; + } - var response = '

Welcome To The RetroPilot Server Dashboard!

'+ - - `

Account Overview

- Account: #`+account.id+`
- Email: `+account.email+`
- Created: `+formatDate(account.created)+`

+ const devices = await db.all('SELECT * FROM devices WHERE account_id = ? ORDER BY dongle_id ASC', account.id) + + var response = '

Welcome To The RetroPilot Server Dashboard!

' + + + `

Account Overview

+ Account: #` + account.id + `
+ Email: ` + account.email + `
+ Created: ` + formatDate(account.created) + `

Devices:
`; - - for (var i in devices) { - response+=''; - } - response+=`
dongle_iddevice_typecreatedlast_pingstorage_used
'+devices[i].dongle_id+''+devices[i].device_type+''+formatDate(devices[i].created)+''+formatDate(devices[i].last_ping)+''+devices[i].storage_used+' MB
+ + for (var i in devices) { + response += '' + devices[i].dongle_id + '' + devices[i].device_type + '' + formatDate(devices[i].created) + '' + formatDate(devices[i].last_ping) + '' + devices[i].storage_used + ' MB'; + } + response += `

Pair New Devices

* To pair a new device, first have it auto-register on this server.
Then scan the QR Code and paste the Device Token below.

- `+(req.query.linkstatus!==undefined ? '
'+htmlspecialchars(req.query.linkstatus)+'

' : '')+` + ` + (req.query.linkstatus !== undefined ? '
' + htmlspecialchars(req.query.linkstatus) + '

' : '') + ` @@ -724,394 +710,398 @@ app.get('/useradmin/overview', runAsyncWrapper(async (req, res) => {
Sign Out`; - res.status(200); - res.send(response); - -})), + res.status(200); + res.send(response); + + })), -app.get('/useradmin/unpair_device/:dongleId', runAsyncWrapper(async (req, res) => { - const account = await getAuthenticatedAccount(req); - if (account==null) { - res.redirect('/useradmin?status='+encodeURIComponent('Invalid or expired session')); - return; - } - - const device = await db.get('SELECT * FROM devices WHERE account_id = ? AND dongle_id = ?', account.id, req.params.dongleId); - - if (device==null) { - res.status(400); - res.send('Unauthorized.'); - return; - } - - const result = await db.run( - 'UPDATE devices SET account_id = ? WHERE dongle_id = ?', - 0, - req.params.dongleId - ); - - res.redirect('/useradmin/overview'); -})), - - -app.post('/useradmin/pair_device', bodyParser.urlencoded({ extended: true }), runAsyncWrapper(async (req, res) => { - const account = await getAuthenticatedAccount(req); - if (account==null) { - res.redirect('/useradmin?status='+encodeURIComponent('Invalid or expired session')); - return; - } - - var qrCodeParts = req.body.qr_string.split("--"); // imei, serial, jwtToken - - const device = await db.get('SELECT * FROM devices WHERE imei = ? AND serial = ?', qrCodeParts[0], qrCodeParts[1]); - if (device==null) { - res.redirect('/useradmin/overview?linkstatus='+encodeURIComponent('Device not registered on Server')); - } - var decoded = validateJWTToken(qrCodeParts[2], device.public_key); - if (decoded==null || decoded.pair==undefined) { - res.redirect('/useradmin/overview?linkstatus='+encodeURIComponent('Device QR Token is invalid or has expired')); - } - if (device.account_id!=0) { - res.redirect('/useradmin/overview?linkstatus='+encodeURIComponent('Device is already paired, unpair in that account first')); - } - - const result = await db.run( - 'UPDATE devices SET account_id = ? WHERE dongle_id = ?', - account.id, - device.dongle_id - ); - - res.redirect('/useradmin/overview'); -})), - - -app.get('/useradmin/device/:dongleId', runAsyncWrapper(async (req, res) => { - const account = await getAuthenticatedAccount(req); - if (account==null) { - res.redirect('/useradmin?status='+encodeURIComponent('Invalid or expired session')); - return; - } - - const device = await db.get('SELECT * FROM devices WHERE account_id = ? AND dongle_id = ?', account.id, req.params.dongleId); - - if (device==null) { - res.status(400); - res.send('Unauthorized.'); - return; - } - - - const drives = await db.all('SELECT * FROM drives WHERE dongle_id = ? AND is_deleted = ? ORDER BY created DESC', device.dongle_id, false); - - var dongleIdHash = crypto.createHmac('sha256', config.applicationSalt).update(device.dongle_id).digest('hex'); - - const bootlogDirectoryTree = dirTree(config.storagePath+device.dongle_id+"/"+dongleIdHash+"/boot/", {attributes:['size']}); - var bootlogFiles = []; - if (bootlogDirectoryTree!=undefined) { - for (var i=0; i { + const account = await getAuthenticatedAccount(req); + if (account == null) { + res.redirect('/useradmin?status=' + encodeURIComponent('Invalid or expired session')); + return; } - bootlogFiles.sort((a,b) => (a.date < b.date) ? 1 : -1); - } - const crashlogDirectoryTree = dirTree(config.storagePath+device.dongle_id+"/"+dongleIdHash+"/crash/", {attributes:['size']}); - var crashlogFiles = []; - if (crashlogDirectoryTree!=undefined) { - for (var i=0; i (a.date < b.date) ? 1 : -1); - } - - var response = '

Welcome To The RetroPilot Server Dashboard!

'+ - - ` + const result = await db.run( + 'UPDATE devices SET account_id = ? WHERE dongle_id = ?', + 0, + req.params.dongleId + ); + + res.redirect('/useradmin/overview'); + })), + + + app.post('/useradmin/pair_device', bodyParser.urlencoded({extended: true}), runAsyncWrapper(async (req, res) => { + const account = await getAuthenticatedAccount(req); + if (account == null) { + res.redirect('/useradmin?status=' + encodeURIComponent('Invalid or expired session')); + return; + } + + var qrCodeParts = req.body.qr_string.split("--"); // imei, serial, jwtToken + + const device = await db.get('SELECT * FROM devices WHERE imei = ? AND serial = ?', qrCodeParts[0], qrCodeParts[1]); + if (device == null) { + res.redirect('/useradmin/overview?linkstatus=' + encodeURIComponent('Device not registered on Server')); + } + var decoded = validateJWTToken(qrCodeParts[2], device.public_key); + if (decoded == null || decoded.pair == undefined) { + res.redirect('/useradmin/overview?linkstatus=' + encodeURIComponent('Device QR Token is invalid or has expired')); + } + if (device.account_id != 0) { + res.redirect('/useradmin/overview?linkstatus=' + encodeURIComponent('Device is already paired, unpair in that account first')); + } + + const result = await db.run( + 'UPDATE devices SET account_id = ? WHERE dongle_id = ?', + account.id, + device.dongle_id + ); + + res.redirect('/useradmin/overview'); + })), + + + app.get('/useradmin/device/:dongleId', runAsyncWrapper(async (req, res) => { + const account = await getAuthenticatedAccount(req); + if (account == null) { + res.redirect('/useradmin?status=' + encodeURIComponent('Invalid or expired session')); + return; + } + + const device = await db.get('SELECT * FROM devices WHERE account_id = ? AND dongle_id = ?', account.id, req.params.dongleId); + + if (device == null) { + res.status(400); + res.send('Unauthorized.'); + return; + } + + + const drives = await db.all('SELECT * FROM drives WHERE dongle_id = ? AND is_deleted = ? ORDER BY created DESC', device.dongle_id, false); + + var dongleIdHash = crypto.createHmac('sha256', config.applicationSalt).update(device.dongle_id).digest('hex'); + + const bootlogDirectoryTree = dirTree(config.storagePath + device.dongle_id + "/" + dongleIdHash + "/boot/", {attributes: ['size']}); + var bootlogFiles = []; + if (bootlogDirectoryTree != undefined) { + for (var i = 0; i < bootlogDirectoryTree.children.length; i++) { + + var timeSplit = bootlogDirectoryTree.children[i].name.replace('boot-', '').replace('crash-', '').replace('\.bz2', '').split('--'); + var timeString = timeSplit[0] + ' ' + timeSplit[1].replace(/-/g, ':'); + bootlogFiles.push({ + 'name': bootlogDirectoryTree.children[i].name, + 'size': bootlogDirectoryTree.children[i].size, + 'date': Date.parse(timeString) + }); + } + bootlogFiles.sort((a, b) => (a.date < b.date) ? 1 : -1); + } + + const crashlogDirectoryTree = dirTree(config.storagePath + device.dongle_id + "/" + dongleIdHash + "/crash/", {attributes: ['size']}); + var crashlogFiles = []; + if (crashlogDirectoryTree != undefined) { + for (var i = 0; i < crashlogDirectoryTree.children.length; i++) { + + var timeSplit = crashlogDirectoryTree.children[i].name.replace('boot-', '').replace('crash-', '').replace('\.bz2', '').split('--'); + var timeString = timeSplit[0] + ' ' + timeSplit[1].replace(/-/g, ':'); + crashlogFiles.push({ + 'name': crashlogDirectoryTree.children[i].name, + 'size': crashlogDirectoryTree.children[i].size, + 'date': Date.parse(timeString) + }); + } + crashlogFiles.sort((a, b) => (a.date < b.date) ? 1 : -1); + } + + + var response = '

Welcome To The RetroPilot Server Dashboard!

' + + + ` < < < Back To Overview -

Device `+device.dongle_id+`

- Type: `+device.device_type+`
- Serial: `+device.serial+`
- IMEI: `+device.imei+`
- Registered: `+formatDate(device.created)+`
- Last Ping: `+formatDate(device.last_ping)+`
- Public Key:
`+device.public_key.replace(/\r?\n|\r/g, "
")+`
+

Device ` + device.dongle_id + `

+ Type: ` + device.device_type + `
+ Serial: ` + device.serial + `
+ IMEI: ` + device.imei + `
+ Registered: ` + formatDate(device.created) + `
+ Last Ping: ` + formatDate(device.last_ping) + `
+ Public Key:
` + device.public_key.replace(/\r?\n|\r/g, "
") + `

- Stored Drives: `+drives.length+`
- Quota Storage: `+device.storage_used+` MB / `+config.deviceStorageQuotaMb+` MB
+ Stored Drives: ` + drives.length + `
+ Quota Storage: ` + device.storage_used + ` MB / ` + config.deviceStorageQuotaMb + ` MB

`; - response += `Boot Logs (last 5):
+ response += `Boot Logs (last 5):
- `; - for (var i=0; i`; - } - response += `
datefilesize
`+formatDate(bootlogFiles[i].date)+``+bootlogFiles[i].name+``+bootlogFiles[i].size+`


`; + `; + for (var i = 0; i < Math.min(5, bootlogFiles.length); i++) { + response += `` + formatDate(bootlogFiles[i].date) + `` + bootlogFiles[i].name + `` + bootlogFiles[i].size + ``; + } + response += `

`; - response += `Crash Logs (last 5):
+ response += `Crash Logs (last 5):
- `; - for (var i=0; i`; - } - response += `
datefilesize
`+formatDate(crashlogFiles[i].date)+``+crashlogFiles[i].name+``+crashlogFiles[i].size+`


`; + `; + for (var i = 0; i < Math.min(5, crashlogFiles.length); i++) { + response += `` + formatDate(crashlogFiles[i].date) + `` + crashlogFiles[i].name + `` + crashlogFiles[i].size + ``; + } + response += `

`; - - response += `Drives (non-preserved drives expire `+config.deviceDriveExpirationDays+` days after upload):
+ + response += `Drives (non-preserved drives expire ` + config.deviceDriveExpirationDays + ` days after upload):
- `; + `; - for (var i in drives) { - response+=''; - } - response+=`
identifierfilesizedurationdistance_metersupload_completeis_processedupload_dateactions
'+(drives[i].is_preserved ? '' : '')+drives[i].identifier+(drives[i].is_preserved ? '' : '')+''+Math.round(drives[i].filesize/1024)+' MiB'+formatDuration(drives[i].duration)+''+Math.round(drives[i].distance_meters/1000)+' km'+drives[i].upload_complete+''+drives[i].is_processed+''+formatDate(drives[i].created)+''+'[delete]'+(drives[i].is_preserved ? '' : '  [preserve]')+'
+ for (var i in drives) { + response += '' + (drives[i].is_preserved ? '' : '') + drives[i].identifier + (drives[i].is_preserved ? '' : '') + '' + Math.round(drives[i].filesize / 1024) + ' MiB' + formatDuration(drives[i].duration) + '' + Math.round(drives[i].distance_meters / 1000) + ' km' + drives[i].upload_complete + '' + drives[i].is_processed + '' + formatDate(drives[i].created) + '' + '[delete]' + (drives[i].is_preserved ? '' : '  [preserve]') + ''; + } + response += `

- Unpair Device + Unpair Device


Sign Out`; - res.status(200); - res.send(response); - -})), + res.status(200); + res.send(response); + + })), -app.get('/useradmin/drive/:dongleId/:driveIdentifier/:action', runAsyncWrapper(async (req, res) => { - const account = await getAuthenticatedAccount(req); - if (account==null) { - res.redirect('/useradmin?status='+encodeURIComponent('Invalid or expired session')); - return; - } - - const device = await db.get('SELECT * FROM devices WHERE account_id = ? AND dongle_id = ?', account.id, req.params.dongleId); - - if (device==null) { - res.status(400); - res.send('Unauthorized.'); - return; - } - - const drive = await db.get('SELECT * FROM drives WHERE identifier = ? AND dongle_id = ?', req.params.driveIdentifier, req.params.dongleId); - - if (drive==null) { - res.status(400); - res.send('Unauthorized.'); - return; - } + app.get('/useradmin/drive/:dongleId/:driveIdentifier/:action', runAsyncWrapper(async (req, res) => { + const account = await getAuthenticatedAccount(req); + if (account == null) { + res.redirect('/useradmin?status=' + encodeURIComponent('Invalid or expired session')); + return; + } - if (req.params.action=='delete') { - const result = await db.run( - 'UPDATE drives SET is_deleted = ? WHERE id = ?', - true, drive.id - ); - } - else if (req.params.action=='preserve') { - const result = await db.run( - 'UPDATE drives SET is_preserved = ? WHERE id = ?', - true, drive.id - ); - } + const device = await db.get('SELECT * FROM devices WHERE account_id = ? AND dongle_id = ?', account.id, req.params.dongleId); - res.redirect('/useradmin/device/'+device.dongle_id); + if (device == null) { + res.status(400); + res.send('Unauthorized.'); + return; + } -})), + const drive = await db.get('SELECT * FROM drives WHERE identifier = ? AND dongle_id = ?', req.params.driveIdentifier, req.params.dongleId); + + if (drive == null) { + res.status(400); + res.send('Unauthorized.'); + return; + } + + if (req.params.action == 'delete') { + const result = await db.run( + 'UPDATE drives SET is_deleted = ? WHERE id = ?', + true, drive.id + ); + } else if (req.params.action == 'preserve') { + const result = await db.run( + 'UPDATE drives SET is_preserved = ? WHERE id = ?', + true, drive.id + ); + } + + res.redirect('/useradmin/device/' + device.dongle_id); + + })), -app.get('/useradmin/drive/:dongleId/:driveIdentifier', runAsyncWrapper(async (req, res) => { - const account = await getAuthenticatedAccount(req); - if (account==null) { - res.redirect('/useradmin?status='+encodeURIComponent('Invalid or expired session')); - return; - } - - const device = await db.get('SELECT * FROM devices WHERE account_id = ? AND dongle_id = ?', account.id, req.params.dongleId); - - if (device==null) { - res.status(400); - res.send('Unauthorized.'); - return; - } - - const drive = await db.get('SELECT * FROM drives WHERE identifier = ? AND dongle_id = ?', req.params.driveIdentifier, req.params.dongleId); - - if (drive==null) { - res.status(400); - res.send('Unauthorized.'); - return; - } + app.get('/useradmin/drive/:dongleId/:driveIdentifier', runAsyncWrapper(async (req, res) => { + const account = await getAuthenticatedAccount(req); + if (account == null) { + res.redirect('/useradmin?status=' + encodeURIComponent('Invalid or expired session')); + return; + } - var dongleIdHash = crypto.createHmac('sha256', config.applicationSalt).update(device.dongle_id).digest('hex'); - var driveIdentifierHash = crypto.createHmac('sha256', config.applicationSalt).update(drive.identifier).digest('hex'); + const device = await db.get('SELECT * FROM devices WHERE account_id = ? AND dongle_id = ?', account.id, req.params.dongleId); - var driveUrl=config.baseDriveDownloadUrl+device.dongle_id+"/"+dongleIdHash+"/"+driveIdentifierHash+"/"+drive.identifier+"/"; + if (device == null) { + res.status(400); + res.send('Unauthorized.'); + return; + } - var cabanaUrl = null; - if (drive.is_processed) { - cabanaUrl=config.cabanaUrl+'?retropilotIdentifier='+device.dongle_id+'|'+dongleIdHash+'|'+drive.identifier+'|'+driveIdentifierHash+'&retropilotHost='+encodeURIComponent(config.baseUrl)+'&demo=1"'; - } + const drive = await db.get('SELECT * FROM drives WHERE identifier = ? AND dongle_id = ?', req.params.driveIdentifier, req.params.dongleId); - const directoryTree = dirTree(config.storagePath+device.dongle_id+"/"+dongleIdHash+"/"+driveIdentifierHash+"/"+drive.identifier); + if (drive == null) { + res.status(400); + res.send('Unauthorized.'); + return; + } + + var dongleIdHash = crypto.createHmac('sha256', config.applicationSalt).update(device.dongle_id).digest('hex'); + var driveIdentifierHash = crypto.createHmac('sha256', config.applicationSalt).update(drive.identifier).digest('hex'); + + var driveUrl = config.baseDriveDownloadUrl + device.dongle_id + "/" + dongleIdHash + "/" + driveIdentifierHash + "/" + drive.identifier + "/"; + + var cabanaUrl = null; + if (drive.is_processed) { + cabanaUrl = config.cabanaUrl + '?retropilotIdentifier=' + device.dongle_id + '|' + dongleIdHash + '|' + drive.identifier + '|' + driveIdentifierHash + '&retropilotHost=' + encodeURIComponent(config.baseUrl) + '&demo=1"'; + } + + const directoryTree = dirTree(config.storagePath + device.dongle_id + "/" + dongleIdHash + "/" + driveIdentifierHash + "/" + drive.identifier); - var response = '

Welcome To The RetroPilot Server Dashboard!

'+ - ` - < < < Back To Device `+device.dongle_id+` -

Drive `+drive.identifier+` on `+drive.dongle_id+`

- Drive Date: `+formatDate(drive.drive_date)+`
- Upload Date: `+formatDate(drive.created)+`
- Num Segments: `+(drive.max_segment+1)+`
- Storage: `+Math.round(drive.filesize/1024)+` MiB
- Duration: `+formatDuration(drive.duration)+`
- Distance: `+Math.round(drive.distance_meters/1000)+` km
- Is Preserved: `+drive.is_preserved+`
- Upload Complete: `+drive.upload_complete+`
- Processed: `+drive.is_processed+`
+ var response = '

Welcome To The RetroPilot Server Dashboard!

' + + ` + < < < Back To Device ` + device.dongle_id + ` +

Drive ` + drive.identifier + ` on ` + drive.dongle_id + `

+ Drive Date: ` + formatDate(drive.drive_date) + `
+ Upload Date: ` + formatDate(drive.created) + `
+ Num Segments: ` + (drive.max_segment + 1) + `
+ Storage: ` + Math.round(drive.filesize / 1024) + ` MiB
+ Duration: ` + formatDuration(drive.duration) + `
+ Distance: ` + Math.round(drive.distance_meters / 1000) + ` km
+ Is Preserved: ` + drive.is_preserved + `
+ Upload Complete: ` + drive.upload_complete + `
+ Processed: ` + drive.is_processed + `


- `+(cabanaUrl ? 'View Drive in CABANA

' : '')+` + ` + (cabanaUrl ? 'View Drive in CABANA

' : '') + ` Files:
`; - - var directorySegments={}; - for (var i in directoryTree.children) { - // skip any non-directory entries (for example m3u8 file in the drive directory) - if (directoryTree.children[i].type!='directory') continue; + var directorySegments = {}; + for (var i in directoryTree.children) { + // skip any non-directory entries (for example m3u8 file in the drive directory) + if (directoryTree.children[i].type != 'directory') continue; + + var segment = directoryTree.children[i].name; + + + var qcamera = '--'; + var fcamera = '--'; + var dcamera = '--'; + var qlog = '--'; + var rlog = '--'; + for (var c in directoryTree.children[i].children) { + if (directoryTree.children[i].children[c].name == 'fcamera.hevc') fcamera = '' + directoryTree.children[i].children[c].name + ''; + if (directoryTree.children[i].children[c].name == 'dcamera.hevc') fcamera = '' + directoryTree.children[i].children[c].name + ''; + if (directoryTree.children[i].children[c].name == 'qcamera.ts') qcamera = '' + directoryTree.children[i].children[c].name + ''; + if (directoryTree.children[i].children[c].name == 'qlog.bz2') qlog = '' + directoryTree.children[i].children[c].name + ''; + if (directoryTree.children[i].children[c].name == 'rlog.bz2') rlog = '' + directoryTree.children[i].children[c].name + ''; + } + + var isProcessed = '?'; + var isStalled = '?'; + + const drive_segment = await db.get('SELECT * FROM drive_segments WHERE segment_id = ? AND drive_identifier = ? AND dongle_id = ?', parseInt(segment), drive.identifier, device.dongle_id); + + if (drive_segment) { + isProcessed = drive_segment.is_processed; + isStalled = drive_segment.is_stalled; + } + + directorySegments["seg-" + segment] = ''; + } - var segment=directoryTree.children[i].name; - - var qcamera = '--'; var fcamera = '--'; var dcamera = '--'; var qlog = '--'; var rlog = '--'; - for (var c in directoryTree.children[i].children) { - if (directoryTree.children[i].children[c].name=='fcamera.hevc') fcamera=''+directoryTree.children[i].children[c].name+''; - if (directoryTree.children[i].children[c].name=='dcamera.hevc') fcamera=''+directoryTree.children[i].children[c].name+''; - if (directoryTree.children[i].children[c].name=='qcamera.ts') qcamera=''+directoryTree.children[i].children[c].name+''; - if (directoryTree.children[i].children[c].name=='qlog.bz2') qlog=''+directoryTree.children[i].children[c].name+''; - if (directoryTree.children[i].children[c].name=='rlog.bz2') rlog=''+directoryTree.children[i].children[c].name+''; + var isProcessed = '?'; + var isStalled = '?'; + + for (var i = 0; i <= drive.max_segment; i++) { + if (directorySegments["seg-" + i] == undefined) { + response += ''; + } else + response += directorySegments["seg-" + i]; } - var isProcessed='?'; - var isStalled='?'; - - const drive_segment = await db.get('SELECT * FROM drive_segments WHERE segment_id = ? AND drive_identifier = ? AND dongle_id = ?', parseInt(segment), drive.identifier, device.dongle_id); - - if (drive_segment) { - isProcessed=drive_segment.is_processed; - isStalled=drive_segment.is_stalled; - } - - directorySegments["seg-"+segment] = ''; - } - - var qcamera = '--'; - var fcamera = '--'; - var dcamera = '--'; - var qlog = '--'; - var rlog = '--'; - var isProcessed='?'; - var isStalled='?'; - - for (var i=0; i<=drive.max_segment; i++) { - if (directorySegments["seg-"+i]==undefined) { - response+=''; - } - else - response+=directorySegments["seg-"+i]; - } - - response+=`
segmentqcameraqlogfcamerarlogdcameraprocessedstalled
' + segment + '' + qcamera + '' + qlog + '' + fcamera + '' + rlog + '' + dcamera + '' + isProcessed + '' + isStalled + '
' + i + '' + qcamera + '' + qlog + '' + fcamera + '' + rlog + '' + dcamera + '' + isProcessed + '' + isStalled + '
'+segment+''+qcamera+''+qlog+''+fcamera+''+rlog+''+dcamera+''+isProcessed+''+isStalled+'
'+i+''+qcamera+''+qlog+''+fcamera+''+rlog+''+dcamera+''+isProcessed+''+isStalled+'
+ response += `


Sign Out`; - res.status(200); - res.send(response); - -})), + res.status(200); + res.send(response); + + })), -app.get('/', runAsyncWrapper(async (req, res) => { - res.status(404); - var response = '

404 Not found

'+ - 'Are you looking for the useradmin dashboard?'; - res.send(response); -})), + app.get('/', runAsyncWrapper(async (req, res) => { + res.status(404); + var response = '

404 Not found

' + + 'Are you looking for the useradmin dashboard?'; + res.send(response); + })), -app.get('*', runAsyncWrapper(async (req, res) => { - logger.error("HTTP.GET unhandled request: "+simpleStringify(req)+", "+simpleStringify(res)+"") - res.status(400); - res.send('Not Implemented'); -})), + app.get('*', runAsyncWrapper(async (req, res) => { + logger.error("HTTP.GET unhandled request: " + simpleStringify(req) + ", " + simpleStringify(res) + "") + res.status(400); + res.send('Not Implemented'); + })), -app.post('*', runAsyncWrapper(async (req, res) => { - logger.error("HTTP.POST unhandled request: "+simpleStringify(req)+", "+simpleStringify(res)+"") - res.status(400); - res.send('Not Implemented'); -})); + app.post('*', runAsyncWrapper(async (req, res) => { + logger.error("HTTP.POST unhandled request: " + simpleStringify(req) + ", " + simpleStringify(res) + "") + res.status(400); + res.send('Not Implemented'); + })); +lockfile.lock('retropilot_server.lock', {realpath: false, stale: 30000, update: 2000}) + .then((release) => { + console.log("STARTING SERVER..."); -lockfile.lock('retropilot_server.lock', { realpath: false, stale: 30000, update: 2000 }) -.then((release) => { - console.log("STARTING SERVER..."); + (async () => { + try { + db = await open({ + filename: config.databaseFile, + driver: sqlite3.Database, + mode: sqlite3.OPEN_READWRITE + }); + await db.get('SELECT * FROM accounts LIMIT 1') + await db.get('SELECT * FROM devices LIMIT 1') + await db.get('SELECT * FROM drives LIMIT 1') + await db.get('SELECT * FROM drive_segments LIMIT 1') - (async () => { - try { - db = await open({ - filename: config.databaseFile, - driver: sqlite3.Database, - mode: sqlite3.OPEN_READWRITE + } catch (exception) { + logger.error(exception); + process.exit(); + } + + initializeStorage(); + updateTotalStorageUsed(); + + var privateKey = fs.readFileSync(config.sslKey, 'utf8'); + var certificate = fs.readFileSync(config.sslCrt, 'utf8'); + var sslCredentials = {key: privateKey, cert: certificate/* , ca: fs.readFileSync('certs/ca.crt') */}; + + var httpServer = http.createServer(app); + var httpsServer = https.createServer(sslCredentials, app); + + httpServer.listen(config.httpPort, config.httpInterface, () => { + logger.info(`Retropilot Server listening at http://` + config.httpInterface + `:` + config.httpPort) }); - await db.get('SELECT * FROM accounts LIMIT 1') - await db.get('SELECT * FROM devices LIMIT 1') - await db.get('SELECT * FROM drives LIMIT 1') - await db.get('SELECT * FROM drive_segments LIMIT 1') + httpsServer.listen(config.httpsPort, config.httpsInterface, () => { + logger.info(`Retropilot Server listening at https://` + config.httpsInterface + `:` + config.httpsPort) + }); + })(); - } catch(exception) { - logger.error(exception); - process.exit(); - } - - initializeStorage(); - updateTotalStorageUsed(); - - var privateKey = fs.readFileSync(config.sslKey, 'utf8'); - var certificate = fs.readFileSync(config.sslCrt, 'utf8'); - var sslCredentials = {key: privateKey, cert: certificate/* , ca: fs.readFileSync('certs/ca.crt') */}; - - var httpServer = http.createServer(app); - var httpsServer = https.createServer(sslCredentials, app); - - httpServer.listen(config.httpPort, config.httpInterface, () => { - logger.info(`Retropilot Server listening at http://`+config.httpInterface+`:`+config.httpPort) - }); - httpsServer.listen(config.httpsPort, config.httpsInterface, () => { - logger.info(`Retropilot Server listening at https://`+config.httpsInterface+`:`+config.httpsPort) - }); - })(); - -}).catch((e) => { + }).catch((e) => { console.error(e) - process.exit(); + process.exit(); }); \ No newline at end of file diff --git a/worker.js b/worker.js index 5b86d2b..1589846 100644 --- a/worker.js +++ b/worker.js @@ -130,7 +130,7 @@ function simpleStringify (object){ simpleObject[prop] = object[prop]; } return JSON.stringify(simpleObject); // returns cleaned up JSON -}; +} function writeFileSync(path, buffer, permission) { var fileDescriptor; @@ -175,7 +175,7 @@ function moveUploadedFile(buffer, directory, filename) { } logger.error("moveUploadedFile invalid final path, check permissions to create / write '"+(config.storagePath+directory)+"'"); return false; -}; +} function deleteFolderRecursive(directoryPath) { @@ -190,7 +190,7 @@ function deleteFolderRecursive(directoryPath) { }); fs.rmdirSync(directoryPath); } -}; +} var segmentProcessQueue=[]; var segmentProcessPosition=0;