From 1e24e5a93497bc89e2a4c6b9aef99bf37da49666 Mon Sep 17 00:00:00 2001 From: James Ketrenos Date: Mon, 15 Jan 2018 20:31:13 -0800 Subject: [PATCH] Scanning and loading dirs Signed-off-by: James Ketrenos --- frontend/assets/favicon.ico | Bin 0 -> 318 bytes frontend/ie9-block.js | 55 ++++++ frontend/index.html | 31 ++++ frontend/src/ketr-photos/fetch.js | 72 ++++++++ frontend/src/ketr-photos/ketr-photos.html | 208 ++++++++++++++++++++++ server/app.js | 110 ++++++++++++ server/console-line.js | 30 ++++ server/db/index.js | 64 +++++++ server/routes/index.js | 55 ++++++ server/routes/photos.js | 14 ++ server/scanner.js | 81 +++++++++ 11 files changed, 720 insertions(+) create mode 100644 frontend/assets/favicon.ico create mode 100644 frontend/ie9-block.js create mode 100644 frontend/index.html create mode 100755 frontend/src/ketr-photos/fetch.js create mode 100755 frontend/src/ketr-photos/ketr-photos.html create mode 100644 server/app.js create mode 100644 server/console-line.js create mode 100644 server/db/index.js create mode 100755 server/routes/index.js create mode 100644 server/routes/photos.js create mode 100644 server/scanner.js diff --git a/frontend/assets/favicon.ico b/frontend/assets/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..ddf3b88c478d75c8263792b26d8c4ff96c4f4339 GIT binary patch literal 318 zcmZQzU<5(|0RbS%!l1#(z#zuJz@P!d0zj+)#2|5;fQf;DUr2yKN=Ax7T~mX>(#DD* zEHa!SGbf9ot+SnB#p;y|2agPW1OW1UZ7To( literal 0 HcmV?d00001 diff --git a/frontend/ie9-block.js b/frontend/ie9-block.js new file mode 100644 index 0000000..4119770 --- /dev/null +++ b/frontend/ie9-block.js @@ -0,0 +1,55 @@ +/** +@license +Copyright (C) 2017 Intel Corporation + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +/* Do not populate the global namespace */ +(function() { + "use strict"; + + function getIEVersion() { + var agent = navigator.userAgent.toLowerCase(), + tokenIndex = agent.indexOf('msie'); + return (tokenIndex != -1) ? parseInt(agent.substring(tokenIndex)) : false; + } + + /* If the User Agent is IE9 or older, block loading in the browser */ + if (getIEVersion() && getIEVersion() <= 9) { + console.log("IE9 or older detected.") + + /* Start a recurring timeout that keeps checking for document.body and + * once found, set the body to a "Browser not supported..." message */ + function blockLoading() { + if (!document.body) { + setTimeout(blockLoading, 10); + return; + } + + while (document.body.firstChild) { + document.body.removeChild(document.body.firstChild); + } + + /* Try and stop loading of any objects by replacing the DOM immediately */ + document.body.innerHTML = "The Board Explorer requires Chrome, Firefox, or Internet Explorer 10 or higher."; + + /* Polymer and other agents may still be running and DOM stamping, so + * wait for DOMContentLoaded to complete, then re-blank the DOM */ + document.addEventListener('DOMContentLoaded', function() { + document.body.innerHTML = "
The Board Explorer requires Chrome, Firefox, or Internet Explorer 10 or higher.
"; + }); + } + + blockLoading(); + } +})(); diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..9467ecc --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,31 @@ + + + + + + + Photos.Ketr + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/src/ketr-photos/fetch.js b/frontend/src/ketr-photos/fetch.js new file mode 100755 index 0000000..849d6d7 --- /dev/null +++ b/frontend/src/ketr-photos/fetch.js @@ -0,0 +1,72 @@ +"use strict"; +(function() { + /* + * Async resource GET + * + * path: path to a file on the domain serving this script + * callback: function with signature function(response) + * + * response is: + * instanceof String --- sucess. response contains content + * instanceof Error --- error. Reason in response.message + */ + function fetch(path, callback, headers, method, params) { + var xhr = new XMLHttpRequest(); + method = method || 'GET'; + // xhr.timeout = 15000; + if (callback) { + xhr.ontimeout = function() { + callback("Request timed out for path " + path, xhr); + }; + + xhr.onreadystatechange = function() { + if (this.readyState != 4) { + return; + } + + if (this.status >= 400) { + return callback(this.responseText, this); + } + + if (this.status == 200) { + return callback(undefined, this); + } + }; + + xhr.onerror = function(err) { + callback( + "Unable to load request. This can occur if you try and REFRESH the authentication page.", + this); + } + } + + xhr.open(method, path, true); + + var hasType = false; + if (headers && typeof headers === 'object') { + Array.prototype.forEach.call(Object.getOwnPropertyNames(headers), function(header) { + if (/content-type/i.exec(header)) { + hasType = headers[header]; + } + xhr.setRequestHeader(header, headers[header]); + }); + } + + if (params && typeof params === 'object') { + if (!hasType) { + xhr.setRequestHeader('Content-Type', 'application/json;charset=UTF-8'); + } + params = JSON.stringify(params); + } + + xhr.send(params); + } + + var exports = { + fetch: fetch + }; + + Object.getOwnPropertyNames(exports).forEach(function(name) { + window[name] = exports[name]; + }); +})(); diff --git a/frontend/src/ketr-photos/ketr-photos.html b/frontend/src/ketr-photos/ketr-photos.html new file mode 100755 index 0000000..4f9619a --- /dev/null +++ b/frontend/src/ketr-photos/ketr-photos.html @@ -0,0 +1,208 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/server/app.js b/server/app.js new file mode 100644 index 0000000..b33a750 --- /dev/null +++ b/server/app.js @@ -0,0 +1,110 @@ +"use strict"; + +console.log("Loading photos.ketr"); + +const express = require("express"), + morgan = require("morgan"), + bodyParser = require("body-parser"), + config = require("config"), + scanner = require("./scanner"), + db = require("./db"); + +require("./console-line.js"); /* Monkey-patch console.log with line numbers */ + +const picturesPath = config.get("picturesPath"), + serverConfig = config.get("server"); + +let basePath = config.get("basePath"); + +basePath = "/" + basePath.replace(/^\/+/, "").replace(/\/+$/, "") + "/"; + +console.log("Loading pictures out of: " + picturesPath); +console.log("Hosting server from: " + basePath); + +const app = express(); + +app.set("basePath", basePath); + +/* App is behind an nginx proxy which we trust, so use the remote address + * set in the headers */ +app.set("trust proxy", true); + +/* Handle static files first so excessive logging doesn't occur */ +app.use(basePath, express.static("frontend", { index: false })); + +app.use(basePath, express.static(".", { index: false })); + +app.use(morgan("common")); + +app.use(bodyParser.urlencoded({ + extended: false +})); + +/* body-parser does not support text/*, so add support for that here */ +app.use(function(req, res, next){ + if (!req.is('text/*')) { + return next(); + } + req.setEncoding('utf8'); + let text = ''; + req.on('data', function(chunk) { + text += chunk; + }); + req.on('end', function() { + req.text = text; + next(); + }); +}); + +app.use(basePath + "api/v1/photos", require("./routes/photos")); + +/* Declare the "catch all" index route last; the final route is a 404 dynamic router */ +app.use(basePath, require("./routes/index")); + +app.use(function(err, req, res, next) { + res.status(err.status || 500).json({ + message: err.message, + error: {} + }); +}); + +/** + * Create HTTP server and listen for new connections + */ +app.set("port", serverConfig.port); + +const server = require("http").createServer(app); + +Promise.all([ + db, scanner.then(function(files) { app.set("files", files); }) +]).then(function() { + console.log("Done scanning. Opening server."); + server.listen(serverConfig.port); +}).catch(function(error) { + console.error(error); + process.exit(-1); +}); + +server.on("error", function(error) { + if (error.syscall !== "listen") { + throw error; + } + + // handle specific listen errors with friendly messages + switch (error.code) { + case "EACCES": + console.error(serverConfig.port + " requires elevated privileges"); + process.exit(1); + break; + case "EADDRINUSE": + console.error(serverConfig.port + " is already in use"); + process.exit(1); + break; + default: + throw error; + } +}); + +server.on("listening", function() { + console.log("Listening on " + serverConfig.port); +}); diff --git a/server/console-line.js b/server/console-line.js new file mode 100644 index 0000000..fb47e05 --- /dev/null +++ b/server/console-line.js @@ -0,0 +1,30 @@ +/* monkey-patch console.log to prefix with file/line-number */ +if (process.env.LOG_LINE) { + let cwd = process.cwd(), + cwdRe = new RegExp("^[^/]*" + cwd.replace("/", "\\/") + "\/([^:]*:[0-9]*).*$"); + [ "log", "warn", "error" ].forEach(function(method) { + console[method] = (function () { + let orig = console[method]; + return function () { + function getErrorObject() { + try { + throw Error(''); + } catch (err) { + return err; + } + } + + let err = getErrorObject(), + caller_line = err.stack.split("\n")[4], + args = [caller_line.replace(cwdRe, "$1 -")]; + + /* arguments.unshift() doesn't exist... */ + for (var i = 0; i < arguments.length; i++) { + args.push(arguments[i]); + } + + orig.apply(this, args); + }; + })(); + }); +} diff --git a/server/db/index.js b/server/db/index.js new file mode 100644 index 0000000..b09d272 --- /dev/null +++ b/server/db/index.js @@ -0,0 +1,64 @@ +/** + * * Copyright (c) 2016, Intel Corporation. + * + * This program is licensed under the terms and conditions of the + * Apache License, version 2.0. The full text of the Apache License is at + * http://www.apache.org/licenses/LICENSE-2.0 + */ + +"use strict"; + +/** + * This class will instantiate the ORM, load in the models, call the method + * to create db connections, test the connection, then create the tables and + * relationships if not present + */ +const fs = require('fs'), + path = require('path'), + Sequelize = require('sequelize'), + config = require('config'); + +function init() { + const db = { + sequelize: new Sequelize(config.get("db.host"), config.get("db.options")) + }; + + return new Promise(function(resolve, reject) { + console.log("DB initialization beginning. DB access will block."); + let models = []; + + /* Load models */ + fs.readdirSync(__dirname).forEach(function (file) { + if (file == "." || file == ".." || file == "index.js") { + return; + } + let model = db.sequelize.import(path.join(__dirname, file)); + db[model.name] = model; + models.push(model); + }); + + /* After all the models are loaded, associate any that need it */ + models.forEach(function (model) { + if (model.associate) { + model.associate(db); + } + }); + + return db.sequelize.authenticate().then(function () { + console.log("Connection established successfully with DB."); + return db.sequelize.sync({ + force: false + }).then(function () { + console.log("DB relationships successfully mapped. DB access unblocked."); + return resolve(db); + }); + }).catch(function (error) { + console.log("ERROR: Failed to authenticate with DB"); + console.log("ERROR: " + JSON.stringify(config.get("db"), null, 2)); + console.log(error); + return reject(error); + }); + }); +} + +module.exports = init(); diff --git a/server/routes/index.js b/server/routes/index.js new file mode 100755 index 0000000..d335938 --- /dev/null +++ b/server/routes/index.js @@ -0,0 +1,55 @@ +"use strict"; + +const express = require("express"), + fs = require("fs"), + url = require("url"), + config = require("config"); + +const router = express.Router(); + +/* List of filename extensions we know are "potential" file extensions for +* assets we don"t want to return "index.html" for */ +const extensions = [ + "html", "js", "css", "eot", "gif", "ico", "jpeg", "jpg", "mp4", + "md", "ttf", "txt", "woff", "woff2", "yml", "svg" +]; + +/* Build the extension match RegExp from the list of extensions */ +const extensionMatch = new RegExp("^.*?(" + extensions.join("|") + ")$", "i"); + +/* To handle dynamic routes, we return index.html to every request that + * gets this far -- so this needs to be the last route. + * + * However, that introduces site development problems when assets are + * referenced which don't yet exist (due to bugs, or sequence of adds) -- + * the server would return HTML content instead of the 404. + * + * So, check to see if the requested path is for an asset with a recognized + * file extension. + * + * If so, 404 because the asset isn't there. otherwise assume it is a + * dynamic client side route and *then* return index.html. + */ +router.get("/*", function(req, res/*, next*/) { + const parts = url.parse(req.url), + basePath = req.app.get("basePath"); + + if (req.url == "/" || !extensionMatch.exec(parts.pathname)) { + /* Replace in index.html with + * the basePath */ + const index = fs.readFileSync("frontend/index.html", "utf8"); + + res.send(index.replace( + /