Scanning and loading dirs
Signed-off-by: James Ketrenos <james_git@ketrenos.com>
This commit is contained in:
parent
92c64c67d6
commit
1e24e5a934
BIN
frontend/assets/favicon.ico
Normal file
BIN
frontend/assets/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 318 B |
55
frontend/ie9-block.js
Normal file
55
frontend/ie9-block.js
Normal file
@ -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 = "<div style='margin:5em;text-align:center;font-size:30pt;'>The Board Explorer requires Chrome, Firefox, or Internet Explorer 10 or higher.</div>";
|
||||
});
|
||||
}
|
||||
|
||||
blockLoading();
|
||||
}
|
||||
})();
|
31
frontend/index.html
Normal file
31
frontend/index.html
Normal file
@ -0,0 +1,31 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1, user-scalable=yes">
|
||||
|
||||
<title>Photos.Ketr</title>
|
||||
<meta name="description" content="Self hosted photo management">
|
||||
|
||||
<!-- This is replaced by server/app.js when providing index.html to
|
||||
clients -->
|
||||
<script>'<base href="BASEPATH">';</script>
|
||||
|
||||
<link rel="icon" href="assets/favicon.ico" type="image/x-icon">
|
||||
|
||||
<!-- See https://goo.gl/OOhYW5 -->
|
||||
<link rel="manifest" href="manifest.json">
|
||||
|
||||
<script src="bower_components/webcomponentsjs/webcomponents-lite.js"></script>
|
||||
<link rel="import" href="bower_components/iron-flex-layout/iron-flex-layout-classes.html">
|
||||
<link rel="import" href="bower_components/app-layout/app-toolbar/app-toolbar.html">
|
||||
<style is="custom-style" include="iron-flex iron-positioning"></style>
|
||||
|
||||
<link rel="import" href="src/ketr-photos/ketr-photos.html">
|
||||
</head>
|
||||
|
||||
<body class="fullbleed vertical layout">
|
||||
<script src="ie9-block.js"></script>
|
||||
<ketr-photos class="fit"></ketr-photos>
|
||||
</body>
|
||||
</html>
|
72
frontend/src/ketr-photos/fetch.js
Executable file
72
frontend/src/ketr-photos/fetch.js
Executable file
@ -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];
|
||||
});
|
||||
})();
|
208
frontend/src/ketr-photos/ketr-photos.html
Executable file
208
frontend/src/ketr-photos/ketr-photos.html
Executable file
@ -0,0 +1,208 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<link rel="import" href="../../bower_components/polymer/polymer.html">
|
||||
|
||||
<link rel="import" href="../../bower_components/app-layout/app-header/app-header.html">
|
||||
<link rel="import" href="../../bower_components/app-layout/app-header-layout/app-header-layout.html">
|
||||
<link rel="import" href="../../bower_components/app-layout/app-toolbar/app-toolbar.html">
|
||||
<link rel="import" href="../../bower_components/app-route/app-location.html">
|
||||
|
||||
<link rel="import" href="../../bower_components/iron-icon/iron-icon.html">
|
||||
<link rel="import" href="../../bower_components/iron-icons/iron-icons.html">
|
||||
<link rel="import" href="../../bower_components/iron-pages/iron-pages.html">
|
||||
|
||||
<link rel="import" href="../../bower_components/paper-button/paper-button.html">
|
||||
<link rel="import" href="../../bower_components/paper-dialog/paper-dialog.html">
|
||||
<link rel="import" href="../../bower_components/paper-dialog-scrollable/paper-dialog-scrollable.html">
|
||||
<link rel="import" href="../../bower_components/paper-input/paper-input.html">
|
||||
<link rel="import" href="../../bower_components/paper-spinner/paper-spinner.html">
|
||||
<link rel="import" href="../../bower_components/paper-toast/paper-toast.html">
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="https://fonts.googleapis.com/css?family=Droid+Sans+Mono" />
|
||||
|
||||
<script src="fetch.js"></script>
|
||||
|
||||
<style>
|
||||
body,* {
|
||||
font-family: Helvetica Neue,Helvetica,Arial,sans-serif;
|
||||
}
|
||||
|
||||
b,strong {
|
||||
font-family: Helvetica Neue,Helvetica,Arial,sans-serif;
|
||||
}
|
||||
</style>
|
||||
|
||||
</head>
|
||||
|
||||
<dom-module id="ketr-photos">
|
||||
<template>
|
||||
<style is="custom-style" include="iron-flex iron-flex-alignment iron-positioning">
|
||||
:host {
|
||||
}
|
||||
|
||||
app-toolbar {
|
||||
background-color: rgba(64, 0, 64, 0.5);
|
||||
color: white;
|
||||
}
|
||||
|
||||
#toast[error] {
|
||||
--paper-toast-background-color: red;
|
||||
--paper-toast-color: white;
|
||||
}
|
||||
|
||||
.thumbnails > div {
|
||||
margin: 0.5em;
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
background-position: 50% 50%;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.folders > div {
|
||||
margin: 0.5em;
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.thumbnails > div:hover,
|
||||
.folders > div:hover {
|
||||
box-shadow: 0px 4px 2px -2px #ddd;
|
||||
text-decoration: underline;
|
||||
}
|
||||
</style>
|
||||
|
||||
<app-location route="{{route}}"></app-location>
|
||||
|
||||
<app-header-layout fullbleed has-scrolling-region>
|
||||
<app-header slot="header" fixed>
|
||||
<paper-spinner active$="[[loading]]" class="thin"></paper-spinner>
|
||||
<div>[[path]]</div>
|
||||
</app-header>
|
||||
<div class="thumbnails layout horizontal wrap">
|
||||
<template is="dom-repeat" items="[[photos.files]]">
|
||||
<div style$="background-image:url([[item.filepath]])" on-tap="_imageTap" info="[[item]]"></div>
|
||||
</template>
|
||||
</div>
|
||||
<div class="folders layout horizontal wrap">
|
||||
<template is="dom-repeat" items="[[photos.paths]]">
|
||||
<div info="[[item]]" on-tap="_pathTap" >[[item.filepath]]</div>
|
||||
</template>
|
||||
</div>
|
||||
</app-header-layout>
|
||||
|
||||
<paper-toast id="toast"></paper-toast>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
document.addEventListener("WebComponentsReady", function() {
|
||||
"use strict";
|
||||
Polymer({
|
||||
is: "ketr-photos",
|
||||
properties: {
|
||||
"loading": Boolean,
|
||||
"photos": Array
|
||||
},
|
||||
|
||||
observers: [
|
||||
],
|
||||
|
||||
_imageTap: function(event) {
|
||||
window.open(event.model.item.filepath, "image");
|
||||
},
|
||||
|
||||
_pathTap: function(event) {
|
||||
window.location.href = event.model.item.filepath;
|
||||
},
|
||||
|
||||
_loadPhotos: function() {
|
||||
if (this.loading == true) {
|
||||
return;
|
||||
}
|
||||
this.loading = true;
|
||||
window.fetch("api/v1/photos", function(error, xhr) {
|
||||
this.loading = false;
|
||||
if (error) {
|
||||
console.error(JSON.stringify(error, null, 2));
|
||||
return;
|
||||
}
|
||||
|
||||
let photos;
|
||||
try {
|
||||
photos = JSON.parse(xhr.responseText);
|
||||
} catch (___) {
|
||||
this.$.toast.text = "Unable to load/parse photo list.";
|
||||
this.$.toast.setAttribute("error", true);
|
||||
this.$.toast.updateStyles();
|
||||
this.$.toast.show();
|
||||
console.error("Unable to parse photos");
|
||||
return;
|
||||
}
|
||||
|
||||
var base = document.querySelector("base");
|
||||
if (base) {
|
||||
base = new URL(base.href).pathname;
|
||||
var parts = new RegExp("http.*" + base.replace(/\//, "\\/") + "(.*)").exec(window.location.href);
|
||||
if (parts && parts.length == 2) {
|
||||
this.base = "./" + parts[1];
|
||||
} else {
|
||||
this.base = "";
|
||||
}
|
||||
} else {
|
||||
this.base = "";
|
||||
}
|
||||
|
||||
function findPath(path, item) {
|
||||
if (path.indexOf(item.filepath) != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (path == item.filepath || path == item.filepath + "/") {
|
||||
return item;
|
||||
}
|
||||
|
||||
for (var i = 0; i < item.paths.length; i++) {
|
||||
var tmp = findPath(path, item.paths[i]);
|
||||
if (tmp) {
|
||||
return tmp;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.base != "") {
|
||||
this.photos = findPath(this.base, photos) || photos;
|
||||
} else {
|
||||
this.photos = photos;
|
||||
}
|
||||
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
ready: function() {
|
||||
window.addEventListener("hashchange", function(event) {
|
||||
this.hash = event.newURL.replace(/^[^#]*/, "");
|
||||
}.bind(this), false);
|
||||
|
||||
/* Hash changes due to anchor clicks aren't firing the 'hashchange'
|
||||
* event... possibly due to app-location? */
|
||||
window.setInterval(function() {
|
||||
if (this.hash != window.location.hash) {
|
||||
this.hash = window.location.hash;
|
||||
}
|
||||
}.bind(this), 100);
|
||||
|
||||
this._loadPhotos();
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</dom-module>
|
||||
</html>
|
110
server/app.js
Normal file
110
server/app.js
Normal file
@ -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);
|
||||
});
|
30
server/console-line.js
Normal file
30
server/console-line.js
Normal file
@ -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);
|
||||
};
|
||||
})();
|
||||
});
|
||||
}
|
64
server/db/index.js
Normal file
64
server/db/index.js
Normal file
@ -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();
|
55
server/routes/index.js
Executable file
55
server/routes/index.js
Executable file
@ -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 <script>'<base href="/BASEPATH/">';</script> in index.html with
|
||||
* the basePath */
|
||||
const index = fs.readFileSync("frontend/index.html", "utf8");
|
||||
|
||||
res.send(index.replace(
|
||||
/<script>'<base href="BASEPATH">';<\/script>/,
|
||||
"<base href='" + basePath + "'>"));
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("Page not found: " + req.url);
|
||||
return res.status(404).json({
|
||||
message: "Page not found",
|
||||
status: 404
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
14
server/routes/photos.js
Normal file
14
server/routes/photos.js
Normal file
@ -0,0 +1,14 @@
|
||||
"use strict";
|
||||
|
||||
const express = require("express"),
|
||||
fs = require("fs"),
|
||||
url = require("url"),
|
||||
config = require("config");
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.get("/", function(req, res/*, next*/) {
|
||||
return res.status(200).json(req.app.get("files"));
|
||||
});
|
||||
|
||||
module.exports = router;
|
81
server/scanner.js
Normal file
81
server/scanner.js
Normal file
@ -0,0 +1,81 @@
|
||||
"use strict";
|
||||
|
||||
const db = require("sequelize"),
|
||||
Promise = require("bluebird"),
|
||||
fs = require("fs"),
|
||||
config = require("config");
|
||||
|
||||
let scanning = 0;
|
||||
|
||||
const picturesPath = config.get("picturesPath");
|
||||
|
||||
function scanFile(path, stats) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
const meta = { filepath: path };
|
||||
console.log("Scanning file: " + path);
|
||||
meta.created = stats.ctime;
|
||||
return resolve(meta);
|
||||
});
|
||||
}
|
||||
|
||||
function scanDir(path) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
const meta = { filepath: path, files: [], paths: [] };
|
||||
|
||||
console.log("Scanning path " + path);
|
||||
|
||||
fs.readdir(path, function(err, files) {
|
||||
if (err) {
|
||||
console.warn(" Could not readdir " + path);
|
||||
return resolve(null);
|
||||
}
|
||||
|
||||
scanning++;
|
||||
|
||||
return Promise.map(files, function(file) {
|
||||
let filepath = path + "/" + file;
|
||||
|
||||
|
||||
return new Promise(function(resolve, reject) {
|
||||
fs.stat(filepath, function(err, stats) {
|
||||
if (err) {
|
||||
console.warn("Could not stat " + filepath);
|
||||
return resolve(null);
|
||||
}
|
||||
|
||||
if (stats.isDirectory()) {
|
||||
return scanDir(filepath, stats).then(function(entry) {
|
||||
if (entry && (entry.files.length || entry.paths.length)) {
|
||||
meta.paths.push(entry);
|
||||
}
|
||||
return resolve(entry);
|
||||
});
|
||||
}
|
||||
|
||||
/* stats.isFile() */
|
||||
return scanFile(filepath, stats).then(function(entry) {
|
||||
if (entry) {
|
||||
meta.files.push(entry);
|
||||
}
|
||||
return resolve(entry);
|
||||
});
|
||||
});
|
||||
});
|
||||
}, {
|
||||
concurrency: 10
|
||||
}).then(function() {
|
||||
scanning--;
|
||||
if (scanning == 0) {
|
||||
const endStamp = Date.now();
|
||||
console.log("Scanning completed in " + Math.round(((endStamp - startStamp))) + "ms.");
|
||||
}
|
||||
}).then(function() {
|
||||
return resolve(meta);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const startStamp = Date.now();
|
||||
|
||||
module.exports = scanDir(picturesPath);
|
Loading…
x
Reference in New Issue
Block a user