"use strict"; const fs = require("fs"), Promise = require("bluebird"), config = require("config"); if (process.argv.length <= 2) { console.log("usage: node check file|id [file|id] ... "); process.exit(-1); } const items = process.argv.splice(2); const picturesPath = config.get("picturesPath").replace(/\/$/, "") + "/"; for (let i = 0; i < items.length; i++) { if (parseInt(items[i]) == items[i]) { items[i] = { type: "id", value: items[i] }; } else { items[i] = { type: "file", value: items[i] } } } const exists = function(path) { return stat(path).then(function(stats) { return stats; }).catch(function() { return null; }); } const stat = function (_path) { if (_path.indexOf(picturesPath.replace(/\/$/, "")) == 0) { _path = _path.substring(picturesPath.length); } let path = picturesPath + _path; return new Promise(function (resolve, reject) { fs.stat(path, function (error, stats) { if (error) { return reject(error); } return resolve(stats); }); }); } const imageExtensions = [ "jpg", "jpeg", "png", "gif", "nef" ], imageRegExp = new RegExp("\.((" + imageExtensions.join(")|(") + "))$", "i"), videoExtensions = [ "mov", "avi", "mp4", "webm" ], videoRegExp = new RegExp("\.((" + videoExtensions.join(")|(") + "))$", "i"); function getAssetInfoFromDisk(asset, filepath) { return exists(filepath).then(function(stats) { asset.filepath = filepath; if (stats) { asset.stats = stats; } /* Check if entry exists in DB */ let parts = /^(.*)\/([^/]+)$/.exec(filepath); if (parts) { asset.path = parts[1] + "/"; asset.filename = parts[2]; } else { asset.path = ""; asset.filename = filepath; } asset.name = asset.filename.replace(/\.[^.]*$/, ""); console.log(asset); return photoDB.sequelize.query("SELECT photos.*,(albums.path || photos.filename) AS filepath " + "FROM photos LEFT JOIN albums ON albums.id=photos.albumId " + "WHERE albums.path=:path AND photos.filename=:filename", { replacements: asset, type: photoDB.Sequelize.QueryTypes.SELECT, raw: true }).then(function(photos) { if (photos.length == 1) { Object.assign(asset, photos[0]); } return photoDB.sequelize.query("SELECT * FROM albums WHERE path=:path", { replacements: asset, type: photoDB.Sequelize.QueryTypes.SELECT, raw: true }).then(function(albums) { if (albums.length == 1) { asset.album = albums[0]; asset.albumId = asset.album.id; } }); }); }).then(function() { return asset; }); } function getAssetInfoFromDB(asset, id) { return photoDB.sequelize.query("SELECT photos.*,(albums.path || photos.filename) AS filepath " + "FROM photos LEFT JOIN albums ON albums.id=photos.albumId " + "WHERE photos.id=:id", { replacements: { id: id }, type: photoDB.Sequelize.QueryTypes.SELECT, raw: true }).then(function(photos) { if (photos.length == 1) { Object.assign(asset, photos[0]); } }).then(function() { if (!asset.filepath) { return; } return exists(asset.filepath).then(function(stats) { if (stats) { asset.stats = stats; } }); }).then(function() { return asset; }); } function computeHash(filepath) { return new Promise(function(resolve, reject) { let input = fs.createReadStream(filepath), hash = crypto.createHash("sha256"); if (!input) { return reject() } input.on("readable", function() { const data = input.read(); if (data) { hash.update(data); } else { input.close(); resolve(hash.digest("hex")); hash = null; input = null; } }); }); } let photoDB; require("../server/db/photos").then(function(db) { photoDB = db; return Promise.mapSeries(items, function(item) { const asset = {}; console.log("Processing " + item.value); return Promise.resolve().then(function() { switch (item.type) { case "file": return getAssetInfoFromDisk(asset, item.value); break; case "id": return getAssetInfoFromDB(asset, item.value); break; } }).then(function() { if (!asset.stats && !asset.id) { throw "...exists neither on disk nor in the DB."; } }).then(function() { if (asset.id) { return; } console.log("...does not exist in the DB."); asset.size = asset.stats.size; asset.needsHash = true; asset.needsUpdate = true; return photoDB.sequelize.query("INSERT INTO photos " + "(albumId,filename,name,size) VALUES(:albumId,:filename,:name,:size)", { replacements: asset }).then(([ results, metadata ]) => { asset.id = metadata.lastID; }); }).then(function() { if (asset.stats) { if (asset.modified < asset.stats.mtime) { asset.modified = asset.stats.mtime; asset.needsUpdate = true; asset.needsHash = true; } if (asset.deleted) { console.log("...setting deleted=0 for " + asset.id); return photoDB.sequelize.query("UPDATE photos SET deleted=0 WHERE id=:id", { replacements: asset }); } return; } console.log("...does not exist on disk."); if (!asset.deleted) { console.log("...setting deleted=1 for " + asset.id); return photoDB.sequelize.query("UPDATE photos SET deleted=1 WHERE id=:id", { replacements: asset }); } }).then(function() { if (!asset.needsHash) { return; } return computeHash(asset.path + asset.filename).then(function(hash) { asset.hash = hash; }).then(function() { return photoDB.sequelize.query("SELECT photos.id,photohashes.*,photos.filename,albums.path FROM photohashes " + "LEFT JOIN photos ON (photos.id=photohashes.photoId) " + "LEFT JOIN albums ON (albums.id=photos.albumId) " + "WHERE hash=:hash OR photoId=:id", { replacements: asset, type: photoDB.sequelize.QueryTypes.SELECT }).then(function(results) { let query; if (results.length == 0) { query = "INSERT INTO photohashes (hash,photoId) VALUES(:hash,:id)"; console.log("...creating photohash."); } else if (results[0].hash != asset.hash) { query = "UPDATE photohashes SET hash=:hash WHERE photoId=:id"; console.log("...updating photohash."); } else if (results[0].photoId != asset.id) { console.log("...duplicate asset: " + "'" + asset.album.path + asset.filename + "' is a copy of " + "'" + results[0].path + results[0].filename + "'"); asset.duplicate = results[0].id; } if (!query) { return asset; } return photoDB.sequelize.query(query, { replacements: asset, }); }); }); }).then(function() { if (asset.deleted) { throw "...deleted. Skipping rest of processing."; } if (asset.duplicate) { throw "...duplicate of " + asset.duplicate + ". Skipping rest of processing."; } return exists(asset.path + "thumbs/" + asset.filename).then(function(stats) { if (!stats || stats.mtime < asset.modified) { console.log("...needs thumb"); asset.needsThumb = true; } }); }).then(function() { return exists(asset.path + "thumbs/scaled/" + asset.filename).then(function(stats) { if (!stats || stats.mtime < asset.modified) { console.log("...needs scaled"); asset.needsScaled = true; } }); }).then(function() { if (asset.needsUpdate) { console.log("...DB needs to be updated for " + item.value); } else { console.log("...does not need to be updated."); } }).catch(function(error) { console.log(error); }); }); });