291 lines
8.0 KiB
JavaScript
Executable File
291 lines
8.0 KiB
JavaScript
Executable File
"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);
|
|
});
|
|
});
|
|
});
|