Account creation technically plumpbed; UX isn't the best when setting up account
Signed-off-by: James Ketrenos <james_git@ketrenos.com>
This commit is contained in:
parent
1dc3d74743
commit
389185a59c
134
server/app.js
134
server/app.js
@ -13,6 +13,7 @@ const express = require("express"),
|
|||||||
bodyParser = require("body-parser"),
|
bodyParser = require("body-parser"),
|
||||||
config = require("config"),
|
config = require("config"),
|
||||||
session = require('express-session'),
|
session = require('express-session'),
|
||||||
|
hb = require("handlebars"),
|
||||||
SQLiteStore = require('connect-sqlite3')(session),
|
SQLiteStore = require('connect-sqlite3')(session),
|
||||||
scanner = require("./scanner");
|
scanner = require("./scanner");
|
||||||
|
|
||||||
@ -21,8 +22,14 @@ require("./console-line.js"); /* Monkey-patch console.log with line numbers */
|
|||||||
const picturesPath = config.get("picturesPath").replace(/\/$/, ""),
|
const picturesPath = config.get("picturesPath").replace(/\/$/, ""),
|
||||||
serverConfig = config.get("server");
|
serverConfig = config.get("server");
|
||||||
|
|
||||||
|
config.get("admin.mail");
|
||||||
|
config.get("smtp.host");
|
||||||
|
config.get("smtp.sender");
|
||||||
|
|
||||||
let basePath = config.get("basePath");
|
let basePath = config.get("basePath");
|
||||||
|
|
||||||
|
let photoDB = null, userDB = null
|
||||||
|
|
||||||
basePath = "/" + basePath.replace(/^\/+/, "").replace(/\/+$/, "") + "/";
|
basePath = "/" + basePath.replace(/^\/+/, "").replace(/\/+$/, "") + "/";
|
||||||
if (basePath == "//") {
|
if (basePath == "//") {
|
||||||
basePath = "/";
|
basePath = "/";
|
||||||
@ -70,7 +77,123 @@ app.use(session({
|
|||||||
|
|
||||||
const index = require("./routes/index");
|
const index = require("./routes/index");
|
||||||
|
|
||||||
|
const transporter = require("nodemailer").createTransport({
|
||||||
|
host: config.get("smtp.host"),
|
||||||
|
pool: true,
|
||||||
|
port: config.has("smtp.port") ? config.get("smtp.port") : 25
|
||||||
|
});
|
||||||
|
|
||||||
|
const templates = {
|
||||||
|
"html": [
|
||||||
|
"<p>The user {{displayName}} has verified their email address ({{mail}}).</p>",
|
||||||
|
"",
|
||||||
|
"<p>They indicated they know:</p>",
|
||||||
|
"<pre>{{notes}}</pre>",
|
||||||
|
"",
|
||||||
|
"<p>To authenticate:</p>",
|
||||||
|
"<p>echo 'UPDATE users SET authenticated=1 WHERE id={{id}};' | sqlite3 users.db</p>",
|
||||||
|
"",
|
||||||
|
"<p>Sincerely,<br>",
|
||||||
|
"James</p>"
|
||||||
|
].join("\n"),
|
||||||
|
"text": [
|
||||||
|
"The user {{displayName}} has verified their email address ({{mail}}).",
|
||||||
|
"",
|
||||||
|
"They indicated they know:",
|
||||||
|
"{{notes}}",
|
||||||
|
"",
|
||||||
|
"To authenticate:",
|
||||||
|
"echo 'UPDATE users SET authenticated=1 WHERE id={{id}};' | sqlite3 users.db",
|
||||||
|
"",
|
||||||
|
"Sincerely,",
|
||||||
|
"James"
|
||||||
|
].join("\n")
|
||||||
|
};
|
||||||
|
|
||||||
/* Allow loading of the app w/out being logged in */
|
/* Allow loading of the app w/out being logged in */
|
||||||
|
app.use(basePath, function(req, res, next) {
|
||||||
|
let match = req.url.match(/^\/([0-9a-f]+)$/);
|
||||||
|
if (!match) {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
|
||||||
|
let key = match[1];
|
||||||
|
return userDB.sequelize.query("SELECT * FROM authentications WHERE key=:key", {
|
||||||
|
replacements: {
|
||||||
|
key: key
|
||||||
|
},
|
||||||
|
type: userDB.sequelize.QueryTypes.SELECT
|
||||||
|
}).then(function(results) {
|
||||||
|
let token;
|
||||||
|
if (results.length == 0) {
|
||||||
|
console.log("Invalid key. Ignoring.");
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
|
||||||
|
token = results[0];
|
||||||
|
|
||||||
|
console.log("Matched token: " + JSON.stringify(token, null, 2));
|
||||||
|
switch (token.type) {
|
||||||
|
case "account-setup":
|
||||||
|
return userDB.sequelize.query("UPDATE users SET mailVerified=1 WHERE id=:userId", {
|
||||||
|
replacements: token
|
||||||
|
}).then(function() {
|
||||||
|
return userDB.sequelize.query("DELETE FROM authentications WHERE key=:key", {
|
||||||
|
replacements: token
|
||||||
|
});
|
||||||
|
}).then(function() {
|
||||||
|
return userDB.sequelize.query("SELECT * FROM users WHERE id=:userId", {
|
||||||
|
replacements: token,
|
||||||
|
type: userDB.sequelize.QueryTypes.SELECT
|
||||||
|
}).then(function(results) {
|
||||||
|
if (results.length == 0) {
|
||||||
|
throw "DB mis-match between authentications and users table";
|
||||||
|
}
|
||||||
|
let user = results[0],
|
||||||
|
envelope = {
|
||||||
|
to: config.get("admin.mail"),
|
||||||
|
from: config.get("smtp.sender"),
|
||||||
|
subject: "VERIFIED: Account'" + user.displayName + "'",
|
||||||
|
cc: "",
|
||||||
|
bcc: "",
|
||||||
|
text: hb.compile(templates.text)(user),
|
||||||
|
html: hb.compile(templates.html)(user)
|
||||||
|
};
|
||||||
|
|
||||||
|
return new Promise(function (resolve, reject) {
|
||||||
|
let attempts = 10;
|
||||||
|
|
||||||
|
function send(envelope) {
|
||||||
|
/* Rate limit to ten per second */
|
||||||
|
transporter.sendMail(envelope, function (error, info) {
|
||||||
|
if (!error) {
|
||||||
|
console.log('Message sent: ' + info.response);
|
||||||
|
return resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attempts == 0) {
|
||||||
|
console.log("Error sending email: ", error)
|
||||||
|
return reject(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
attempts--;
|
||||||
|
console.log("Unable to send mail. Trying again in 100ms (" + attempts + " attempts remain): ", error);
|
||||||
|
setTimeout(send.bind(undefined, envelope), 100);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
send(envelope);
|
||||||
|
});
|
||||||
|
}).then(function() {
|
||||||
|
return res.redirect(308, basePath);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return next();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
app.use(basePath, index);
|
app.use(basePath, index);
|
||||||
|
|
||||||
app.use(basePath + "api/v1/users", require("./routes/users"));
|
app.use(basePath + "api/v1/users", require("./routes/users"));
|
||||||
@ -125,11 +248,16 @@ app.set("port", serverConfig.port);
|
|||||||
|
|
||||||
const server = require("http").createServer(app);
|
const server = require("http").createServer(app);
|
||||||
|
|
||||||
require("./db/photos").then(function(photoDB) {
|
require("./db/photos").then(function(db) {
|
||||||
|
photoDB = db;
|
||||||
|
}).then(function() {
|
||||||
|
return require("./db/users").then(function(db) {
|
||||||
|
userDB = db;
|
||||||
|
});
|
||||||
|
}).then(function() {
|
||||||
console.log("DB connected. Opening server.");
|
console.log("DB connected. Opening server.");
|
||||||
server.listen(serverConfig.port);
|
server.listen(serverConfig.port);
|
||||||
return photoDB;
|
}).then(function() {
|
||||||
}).then(function(photoDB) {
|
|
||||||
console.log("Scanning.");
|
console.log("Scanning.");
|
||||||
scanner.init(photoDB);
|
scanner.init(photoDB);
|
||||||
return scanner.scan();
|
return scanner.scan();
|
||||||
|
@ -38,6 +38,7 @@ function init() {
|
|||||||
authToken: Sequelize.STRING,
|
authToken: Sequelize.STRING,
|
||||||
authDate: Sequelize.DATE,
|
authDate: Sequelize.DATE,
|
||||||
authenticated: Sequelize.BOOLEAN,
|
authenticated: Sequelize.BOOLEAN,
|
||||||
|
mailVerified: Sequelize.BOOLEAN,
|
||||||
mail: Sequelize.STRING,
|
mail: Sequelize.STRING,
|
||||||
memberSince: Sequelize.DATE,
|
memberSince: Sequelize.DATE,
|
||||||
password: Sequelize.STRING, /* SHA hash of user supplied password for !isLDAP users */
|
password: Sequelize.STRING, /* SHA hash of user supplied password for !isLDAP users */
|
||||||
|
@ -37,21 +37,25 @@ router.get("/", function(req, res/*, next*/) {
|
|||||||
|
|
||||||
const templates = {
|
const templates = {
|
||||||
"html": [
|
"html": [
|
||||||
"<p>Dear {{name}},</p>",
|
"<p>Dear {{username}},</p>",
|
||||||
"",
|
"",
|
||||||
"<p>Welcome to HTML {{username}}.</p>",
|
"<p>Welcome to <b>ketrenos.com</b>. Before you can access the system, you must authenticate",
|
||||||
|
"your email address ({{mail}}).</p>",
|
||||||
"",
|
"",
|
||||||
"<p>Your secret is: <b>{{secret}}</b>.</p>",
|
"<p>To do so, simply access this link, which contains your authentication ",
|
||||||
|
"token: <a href=\"{{url}}{{secret}}\">{{secret}}</a></p>",
|
||||||
"",
|
"",
|
||||||
"<p>Sincerely,</p>",
|
"<p>Sincerely,</p>",
|
||||||
"<p>James</p>"
|
"<p>James</p>"
|
||||||
].join("\n"),
|
].join("\n"),
|
||||||
"text": [
|
"text": [
|
||||||
"Dear {{name}},",
|
"Dear {{username}},",
|
||||||
"",
|
"",
|
||||||
"Welcome to TEXT {{username}}.",
|
"Welcome to ketrenos.com. Before you can access the system, you must authenticate",
|
||||||
|
"your email address ({{mail}}).",
|
||||||
"",
|
"",
|
||||||
"Your secret is: {{secret}}.",
|
"To do so, simply access this link, which contains your authentication ",
|
||||||
|
"token: {{url}}{{secret}}",
|
||||||
"",
|
"",
|
||||||
"Sincerely,",
|
"Sincerely,",
|
||||||
"James"
|
"James"
|
||||||
@ -75,7 +79,7 @@ function ldapPromise(username, password) {
|
|||||||
router.post("/create", function(req, res) {
|
router.post("/create", function(req, res) {
|
||||||
let who = req.query.w || req.body.w || "",
|
let who = req.query.w || req.body.w || "",
|
||||||
password = req.query.p || req.body.p || "",
|
password = req.query.p || req.body.p || "",
|
||||||
name = req.query.n || req.body.n || username,
|
name = req.query.n || req.body.n || "",
|
||||||
mail = req.query.m || req.body.m;
|
mail = req.query.m || req.body.m;
|
||||||
|
|
||||||
if (!who || !password || !mail || !name) {
|
if (!who || !password || !mail || !name) {
|
||||||
@ -93,13 +97,21 @@ router.post("/create", function(req, res) {
|
|||||||
return res.status(400).send("Email address already used.");
|
return res.status(400).send("Email address already used.");
|
||||||
}
|
}
|
||||||
|
|
||||||
let re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/,
|
let re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
|
||||||
secret = "magic cookie";
|
|
||||||
if (!re.exec(mail)) {
|
if (!re.exec(mail)) {
|
||||||
console.log("Invalid email address: " + mail);
|
console.log("Invalid email address: " + mail);
|
||||||
return res.status(400).send("Invalid email address.");
|
return res.status(400).send("Invalid email address.");
|
||||||
}
|
}
|
||||||
|
}).then(function() {
|
||||||
|
return new Promise(function(resolve, reject) {
|
||||||
|
crypto.randomBytes(16, function(error, buffer) {
|
||||||
|
if (error) {
|
||||||
|
return reject(error);
|
||||||
|
}
|
||||||
|
return resolve(buffer.toString('hex'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}).then(function(secret) {
|
||||||
return userDB.sequelize.query("INSERT INTO users " +
|
return userDB.sequelize.query("INSERT INTO users " +
|
||||||
"(uid,displayName,password,mail,memberSince,authenticated,notes) " +
|
"(uid,displayName,password,mail,memberSince,authenticated,notes) " +
|
||||||
"VALUES(:username,:name,:password,:mail,CURRENT_TIMESTAMP,0,:notes)", {
|
"VALUES(:username,:name,:password,:mail,CURRENT_TIMESTAMP,0,:notes)", {
|
||||||
@ -122,17 +134,18 @@ router.post("/create", function(req, res) {
|
|||||||
console.log(error);
|
console.log(error);
|
||||||
throw error;
|
throw error;
|
||||||
});
|
});
|
||||||
}).spread(function(results, metadata) {
|
}).then(function() {
|
||||||
let data = {
|
let data = {
|
||||||
username: name,
|
username: name,
|
||||||
mail: mail,
|
mail: mail,
|
||||||
secret: secret
|
secret: secret,
|
||||||
|
url: req.protocol + "://" + req.hostname + req.app.get("basePath")
|
||||||
}, envelope = {
|
}, envelope = {
|
||||||
to: mail,
|
to: mail,
|
||||||
from: config.get("smtp.sender"),
|
from: config.get("smtp.sender"),
|
||||||
subject: "Request to create account for " + name,
|
subject: "Request to ketrenos.com create account for '" + name + "'",
|
||||||
cc: "",
|
cc: "",
|
||||||
bcc: "",
|
bcc: config.get("admin.mail"),
|
||||||
text: hb.compile(templates.text)(data),
|
text: hb.compile(templates.text)(data),
|
||||||
html: hb.compile(templates.html)(data)
|
html: hb.compile(templates.html)(data)
|
||||||
};
|
};
|
||||||
@ -208,7 +221,7 @@ router.post("/login", function(req, res) {
|
|||||||
});
|
});
|
||||||
}).then(function(user) {
|
}).then(function(user) {
|
||||||
if (!user) {
|
if (!user) {
|
||||||
console.log(username + " not found.");
|
console.log(username + " not found (or invalid password.)");
|
||||||
req.session.user = {};
|
req.session.user = {};
|
||||||
return res.status(401).send("Invalid login credentials");
|
return res.status(401).send("Invalid login credentials");
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ let photoDB = null;
|
|||||||
|
|
||||||
const picturesPath = config.get("picturesPath").replace(/\/$/, "") + "/";
|
const picturesPath = config.get("picturesPath").replace(/\/$/, "") + "/";
|
||||||
|
|
||||||
let processQueue = [], triedClean = [], lastScan = new Date("1900-01-01");
|
let processQueue = [], triedClean = [], lastScan = new Date("1800-01-01");
|
||||||
|
|
||||||
//const rawExtension = /\.(nef|orf)$/i, extensions = [ "jpg", "jpeg", "png", "gif", "nef", "orf" ];
|
//const rawExtension = /\.(nef|orf)$/i, extensions = [ "jpg", "jpeg", "png", "gif", "nef", "orf" ];
|
||||||
|
|
||||||
@ -574,7 +574,8 @@ function findOrCreateDBAlbum(transaction, album) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function findOrUpdateDBAsset(transaction, asset) {
|
function findOrUpdateDBAsset(transaction, asset) {
|
||||||
let query = "SELECT id,DATETIME(scanned) AS scanned,DATETIME(modified) AS modified FROM photos WHERE albumId=:albumId AND filename=:filename";
|
let query = "SELECT id,DATETIME(scanned) AS scanned,DATETIME(modified) AS modified FROM photos " +
|
||||||
|
"WHERE albumId=:albumId AND filename=:filename";
|
||||||
if (!asset.album || !asset.album.id) {
|
if (!asset.album || !asset.album.id) {
|
||||||
let error = "Asset being processed without an album";
|
let error = "Asset being processed without an album";
|
||||||
console.error(error);
|
console.error(error);
|
||||||
@ -651,111 +652,106 @@ function doScan() {
|
|||||||
return Promise.resolve("scanning");
|
return Promise.resolve("scanning");
|
||||||
}
|
}
|
||||||
|
|
||||||
return photoDB.sequelize.query("SELECT max(scanned) AS scanned FROM photos", {
|
return scanDir(null, picturesPath).spread(function(albums, assets) {
|
||||||
type: photoDB.sequelize.QueryTypes.SELECT
|
console.log("Found " + assets.length + " assets in " + albums.length + " albums after " +
|
||||||
}).then(function(results) {
|
((Date.now() - now) / 1000) + "s");
|
||||||
if (results[0].scanned == null) {
|
/* One at a time, in series, as the album[] array has parents first, then descendants.
|
||||||
lastScan = new Date("1800-01-01");
|
* Operating in parallel could result in a child being searched for prior to the parent */
|
||||||
} else {
|
now = Date.now();
|
||||||
lastScan = new Date(results[0].scanned);
|
|
||||||
}
|
|
||||||
console.log("Updating any asset newer than " + moment(lastScan).format());
|
|
||||||
}).then(function() {
|
|
||||||
return scanDir(null, picturesPath).spread(function(albums, assets) {
|
|
||||||
console.log("Found " + assets.length + " assets in " + albums.length + " albums after " +
|
|
||||||
((Date.now() - now) / 1000) + "s");
|
|
||||||
/* One at a time, in series, as the album[] array has parents first, then descendants.
|
|
||||||
* Operating in parallel could result in a child being searched for prior to the parent */
|
|
||||||
now = Date.now();
|
|
||||||
|
|
||||||
let toProcess = albums.length, lastMessage = moment();
|
let toProcess = albums.length, lastMessage = moment();
|
||||||
return photoDB.sequelize.transaction(function(transaction) {
|
return photoDB.sequelize.transaction(function(transaction) {
|
||||||
return Promise.mapSeries(albums, function(album) {
|
return Promise.mapSeries(albums, function(album) {
|
||||||
return findOrCreateDBAlbum(transaction, album).then(function() {
|
return findOrCreateDBAlbum(transaction, album).then(function() {
|
||||||
toProcess--;
|
toProcess--;
|
||||||
if (moment().add(-5, 'seconds') > lastMessage) {
|
if (moment().add(-5, 'seconds') > lastMessage) {
|
||||||
console.log("Albums to be created in DB: " + toProcess);
|
console.log("Albums to be created in DB: " + toProcess);
|
||||||
lastMessage = moment();
|
lastMessage = moment();
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}).then(function() {
|
|
||||||
console.log("Processed " + albums.length + " album DB entries in " +
|
|
||||||
((Date.now() - now) / 1000) + "s");
|
|
||||||
now = Date.now();
|
|
||||||
|
|
||||||
console.log(assets.length + " assets remaining to be verified/updated. ETA N/A");
|
|
||||||
|
|
||||||
let processed = 0, start = Date.now(), last = 0, updateScanned = [], newEntries = 0;
|
|
||||||
return photoDB.sequelize.transaction(function(transaction) {
|
|
||||||
return Promise.map(assets, function(asset) {
|
|
||||||
return new Promise(function(resolve, reject) {
|
|
||||||
/* If both mtime and ctime of the asset are older than the lastScan,
|
|
||||||
* skip this asset. */
|
|
||||||
if (lastScan != null && asset.stats.mtime < lastScan && asset.stats.ctime < lastScan) {
|
|
||||||
updateScanned.push(asset.id);
|
|
||||||
return resolve(asset);
|
|
||||||
}
|
|
||||||
|
|
||||||
return findOrUpdateDBAsset(transaction, asset).then(function(asset) {
|
|
||||||
if (!asset.scanned) {
|
|
||||||
newEntries++;
|
|
||||||
}
|
|
||||||
console.log(typeof asset.scanned);
|
|
||||||
if (!asset.scanned || asset.scanned < asset.stats.mtime || !asset.modified) {
|
|
||||||
console.log("Process asset: " + asset.scanned);
|
|
||||||
needsProcessing.push(asset);
|
|
||||||
} else {
|
|
||||||
console.log("Update scanned tag: " + asset.scanned);
|
|
||||||
updateScanned.push(asset.id);
|
|
||||||
}
|
|
||||||
return asset;
|
|
||||||
}).then(function(asset) {
|
|
||||||
return resolve(asset);
|
|
||||||
}).catch(function(error) {
|
|
||||||
return reject(error);
|
|
||||||
});
|
|
||||||
}).then(function(asset) {
|
|
||||||
processed++;
|
|
||||||
|
|
||||||
let elapsed = Date.now() - start;
|
|
||||||
if (elapsed < 5000) {
|
|
||||||
return asset;
|
|
||||||
}
|
|
||||||
|
|
||||||
let remaining = assets.length - processed, eta = Math.ceil((elapsed / 1000) * remaining / (processed - last));
|
|
||||||
console.log(remaining + " assets remaining be verified/updated " +
|
|
||||||
"(" + newEntries + " new entries, " + (processed - newEntries) + " up-to-date so far). ETA " + eta + "s");
|
|
||||||
last = processed;
|
|
||||||
start = Date.now();
|
|
||||||
});
|
|
||||||
}, {
|
|
||||||
concurrency: 10
|
|
||||||
});
|
|
||||||
}).then(function() {
|
|
||||||
if (updateScanned.length) {
|
|
||||||
return photoDB.sequelize.query("UPDATE photos SET scanned=CURRENT_TIMESTAMP WHERE id IN (:ids)", {
|
|
||||||
replacements: {
|
|
||||||
ids: updateScanned
|
|
||||||
}
|
|
||||||
}).then(function() {
|
|
||||||
console.log("Updated scan date of " + updateScanned.length + " assets");
|
|
||||||
updateScanned = [];
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}).then(function() {
|
|
||||||
console.log(newEntries + " assets are new. " + (needsProcessing.length - newEntries) + " assets have been modified.");
|
|
||||||
console.log(needsProcessing.length + " assets need HASH computed. " + (assets.length - needsProcessing.length) + " need no update.");;
|
|
||||||
processBlock(needsProcessing);
|
|
||||||
needsProcessing = [];
|
|
||||||
}).then(function() {
|
|
||||||
console.log("Scanned " + assets.length + " asset DB entries in " +
|
|
||||||
((Date.now() - now) / 1000) + "s");
|
|
||||||
assets = [];
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}).then(function() {
|
}).then(function() {
|
||||||
console.log("Total time to initialize DB and all scans: " + ((Date.now() - initialized) / 1000) + "s");
|
console.log("Processed " + albums.length + " album DB entries in " +
|
||||||
|
((Date.now() - now) / 1000) + "s");
|
||||||
|
now = Date.now();
|
||||||
|
|
||||||
|
console.log(assets.length + " assets remaining to be verified/updated. ETA N/A");
|
||||||
|
|
||||||
|
let processed = 0, start = Date.now(), last = 0, updateScanned = [], newEntries = 0;
|
||||||
|
return photoDB.sequelize.transaction(function(transaction) {
|
||||||
|
return Promise.map(assets, function(asset) {
|
||||||
|
return new Promise(function(resolve, reject) {
|
||||||
|
/* If both mtime and ctime of the asset are older than the lastScan, skip it
|
||||||
|
* Can only do this after a full scan has occurred */
|
||||||
|
if (lastScan != null && asset.stats.mtime < lastScan && asset.stats.ctime < lastScan) {
|
||||||
|
return resolve(asset);
|
||||||
|
}
|
||||||
|
|
||||||
|
return findOrUpdateDBAsset(transaction, asset).then(function(asset) {
|
||||||
|
if (!asset.scanned) {
|
||||||
|
newEntries++;
|
||||||
|
}
|
||||||
|
if (!asset.scanned || asset.scanned < asset.stats.mtime || !asset.modified) {
|
||||||
|
needsProcessing.push(asset);
|
||||||
|
} else {
|
||||||
|
updateScanned.push(asset.id);
|
||||||
|
}
|
||||||
|
return asset;
|
||||||
|
}).then(function(asset) {
|
||||||
|
return resolve(asset);
|
||||||
|
}).catch(function(error) {
|
||||||
|
return reject(error);
|
||||||
|
});
|
||||||
|
}).then(function(asset) {
|
||||||
|
processed++;
|
||||||
|
|
||||||
|
let elapsed = Date.now() - start;
|
||||||
|
if (elapsed < 5000) {
|
||||||
|
return asset;
|
||||||
|
}
|
||||||
|
|
||||||
|
let remaining = assets.length - processed, eta = Math.ceil((elapsed / 1000) * remaining / (processed - last));
|
||||||
|
console.log(remaining + " assets remaining be verified/updated " +
|
||||||
|
"(" + newEntries + " new entries, " + (processed - newEntries) + " up-to-date so far). ETA " + eta + "s");
|
||||||
|
last = processed;
|
||||||
|
start = Date.now();
|
||||||
|
});
|
||||||
|
}, {
|
||||||
|
concurrency: 10
|
||||||
|
});
|
||||||
|
}).then(function() {
|
||||||
|
if (updateScanned.length) {
|
||||||
|
return photoDB.sequelize.query("UPDATE photos SET scanned=CURRENT_TIMESTAMP WHERE id IN (:ids)", {
|
||||||
|
replacements: {
|
||||||
|
ids: updateScanned
|
||||||
|
}
|
||||||
|
}).then(function() {
|
||||||
|
console.log("Updated scan date of " + updateScanned.length + " assets");
|
||||||
|
updateScanned = [];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).then(function() {
|
||||||
|
console.log(newEntries + " assets are new. " + (needsProcessing.length - newEntries) + " assets have been modified.");
|
||||||
|
console.log(needsProcessing.length + " assets need HASH computed. " + (assets.length - needsProcessing.length) + " need no update.");;
|
||||||
|
processBlock(needsProcessing);
|
||||||
|
needsProcessing = [];
|
||||||
|
}).then(function() {
|
||||||
|
console.log("Scanned " + assets.length + " asset DB entries in " +
|
||||||
|
((Date.now() - now) / 1000) + "s");
|
||||||
|
assets = [];
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}).then(function() {
|
||||||
|
console.log("Total time to initialize DB and all scans: " + ((Date.now() - initialized) / 1000) + "s");
|
||||||
|
return photoDB.sequelize.query("SELECT max(scanned) AS scanned FROM photos", {
|
||||||
|
type: photoDB.sequelize.QueryTypes.SELECT
|
||||||
|
}).then(function(results) {
|
||||||
|
if (results[0].scanned == null) {
|
||||||
|
lastScan = new Date("1800-01-01");
|
||||||
|
} else {
|
||||||
|
lastScan = new Date(results[0].scanned);
|
||||||
|
}
|
||||||
|
console.log("Updating any asset newer than " + moment(lastScan).format());
|
||||||
});
|
});
|
||||||
}).then(function() {
|
}).then(function() {
|
||||||
scanning = false;
|
scanning = false;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user