diff --git a/frontend/elements/photo-lightbox.html b/frontend/elements/photo-lightbox.html index 37d4b35..2a30ab5 100644 --- a/frontend/elements/photo-lightbox.html +++ b/frontend/elements/photo-lightbox.html @@ -151,7 +151,7 @@ Polymer({ download: function(event) { console.log("Download tapped"); var anchor = document.createElement('a'); - anchor.href = this.src; + anchor.href = this.base + this.item.path + "/" + this.item.filename; anchor.setAttribute("download", this.src.replace(/.*\/([^/]+)$/, "$1")); anchor.style.display = "none"; document.body.appendChild(anchor); @@ -233,6 +233,12 @@ Polymer({ }, attached: function() { + var base = document.querySelector("base"); + if (base) { + this.base = new URL(base.href).pathname.replace(/\/$/, ""); /* Remove trailing slash if there */ + } else { + this.base = ""; + } } }); diff --git a/frontend/src/ketr-photos/ketr-photos.html b/frontend/src/ketr-photos/ketr-photos.html index 783ec1c..7c627a5 100755 --- a/frontend/src/ketr-photos/ketr-photos.html +++ b/frontend/src/ketr-photos/ketr-photos.html @@ -876,7 +876,8 @@ this.lightBoxElement.selected = false; } - this.$.lightbox.src = this.base + el.item.path + "/" + el.item.filename; + this.$.lightbox.item = el.item; + this.$.lightbox.src = this.base + el.item.path + "/thumbs/scaled/" + el.item.filename; this.lightBoxElement = el; this.lightBoxElement.selected = true; this.disableScrolling = true; diff --git a/server/scanner.js b/server/scanner.js index 90d729a..f6fa682 100644 --- a/server/scanner.js +++ b/server/scanner.js @@ -71,112 +71,83 @@ function scanDir(parent, path) { } }).then(function(parent) { return new Promise(function(resolve, reject) { - console.log("Scanning " + replacements.path); + console.log("Scanning ..." + replacements.path); fs.readdir(path, function(err, files) { if (err) { - console.warn(" Could not readdir " + path); - return resolve(null); + console.warn("Could not readdir " + path); + return resolve([]); } - scanning++; + return resolve(files); + }); + }).then(function(files) { + scanning++; - let hasThumbs = false; - for (let i = 0; i < files.length; i++) { - if (files[i] == "thumbs") { - hasThumbs = true; - break; + /* Remove 'thumbs' and 'raw' directories from being processed */ + files = files.filter(function(file) { + for (var i = 0; i < files.length; i++) { + /* If this file has an original NEF/ORF on the system, don't add the JPG to the DB */ + if (rawExtension.exec(files[i]) && file == files[i].replace(rawExtension, ".jpg")) { + return false; } - } - - let tmp; - if (!hasThumbs) { - tmp = mkdirPromise(path + "/thumbs"); - } else { - tmp = Promise.resolve(); - } - - /* Remove 'thumbs' and 'raw' directories from being processed */ - files = files.filter(function(file) { - for (var i = 0; i < files.length; i++) { - /* If this file has an original NEF/ORF on the system, don't add the JPG to the DB */ - if (rawExtension.exec(files[i]) && file == files[i].replace(rawExtension, ".jpg")) { - return false; - } - - /* If there is a different CASE (eg. JPG vs jpg) don't add it, and remove the 'lower case' - * version from disk. */ - if (file != files[i] && file.toUpperCase() == files[i]) { - removeNewerFile(path, file, files[i]); - console.log("Duplicate file in " + path + ": ", file, files[i]); - return false; - } - } - - return file != "raw" && file != "thumbs" && file != ".git" && file != "corrupt"; - }); - - return tmp.then(function() { - 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(false); - } - - if (stats.isDirectory()) { - return scanDir(parent, filepath, stats).then(function(entry) { - return resolve(true); - }).catch(function(error) { - console.warn("scanDir failed"); - return reject(error); - }); - } - - /* Check file extensions */ - if (!re.exec(file)) { - return resolve(true); - } + /* If there is a different CASE (eg. JPG vs jpg) don't add it, and remove the 'lower case' + * version from disk. */ + if (file != files[i] && file.toUpperCase() == files[i]) { + removeNewerFile(path, file, files[i]); + console.log("Duplicate file in " + path + ": ", file, files[i]); + return false; + } + } - const replacements = { - path: path.slice(picturesPath.length), - filename: file.replace(rawExtension, ".jpg") /* We will be converting from NEF/ORF => JPG */ - }; + return file != "raw" && file != "thumbs" && file != ".git" && file != "corrupt"; + }); - replacements.path = replacements.path || "/"; + return mkdir(path + "/thumbs/scaled").then(function() { + return Promise.map(files, function(file) { + let filepath = path + "/" + file; - return photoDB.sequelize.query("SELECT id FROM photos WHERE path=:path AND filename=:filename", { - replacements: replacements, - type: photoDB.sequelize.QueryTypes.SELECT - }).then(function(photo) { - if (photo.length == 0) { - processQueue.push([ replacements.path, file, stats.mtime, parent ]); - } - return resolve(true); - }).catch(function(error) { - console.error("Sequelize.query failed"); - return reject(error); - }); + return stat(filepath).then(function(stats) { + if (stats.isDirectory()) { + return scanDir(parent, filepath).catch(function(error) { + console.warn("Could not scanDir " + filepath + ": " + error); }); - }); - }, { - concurrency: 1 - }).then(function() { - scanning--; - if (scanning == 0) { - const endStamp = Date.now(); - console.log("Scanning completed in " + Math.round(((endStamp - startStamp))) + "ms. " + processQueue.length + " items to process."); } - }).then(function() { - return resolve(); + + /* Check file extensions */ + if (!re.exec(file)) { + return; + } + + const replacements = { + path: path.slice(picturesPath.length), + filename: file.replace(rawExtension, ".jpg") /* We will be converting from NEF/ORF => JPG */ + }; + + replacements.path = replacements.path || "/"; + + return photoDB.sequelize.query("SELECT id FROM photos WHERE path=:path AND filename=:filename", { + replacements: replacements, + type: photoDB.sequelize.QueryTypes.SELECT + }).then(function(photo) { + if (photo.length == 0) { + processQueue.push([ replacements.path, file, stats.mtime, parent ]); + } + }).catch(function(error) { + console.error("Sequelize.query failed: ", error); + }); + }).catch(function(error) { + console.warn("Could not stat " + filepath + ": " + error); }); - }).catch(function(error) { - console.error("Processing 'tmp' failed"); - return reject(error); + }, { + concurrency: 1 + }).then(function() { + scanning--; + if (scanning == 0) { + const endStamp = Date.now(); + console.log("Scanning completed in " + Math.round(((endStamp - startStamp))) + "ms. " + processQueue.length + " items to process."); + } }); }); }); @@ -190,44 +161,59 @@ let processRunning = false; const { spawn } = require('child_process'); const sharp = require("sharp"), exif = require("exif-reader"); - -function mkdirPromise(path) { +const stat = function (path) { if (path.indexOf(picturesPath) != 0) { path = picturesPath + path; } - return new Promise(function(resolve, reject) { - fs.stat(path, function(err) { - if (err && err.code != 'ENOENT') { - console.warn("Could not stat " + path); - return reject(); + return new Promise(function (resolve, reject) { + fs.stat(path, function (error, stats) { + if (error) { + return reject(error); + } + return resolve(stats); + }); + }); +} + +const mkdir = function (_path) { + if (_path.indexOf(picturesPath) == 0) { + _path = _path.substring(picturesPath.length + 1); + } + + let parts = _path.split("/"), path; + parts.unshift(picturesPath); + return Promise.mapSeries(parts, function (part) { + if (!path) { + path = picturesPath; + } else { + path += "/" + part; + } + return stat(path).catch(function (error) { + if (error.code != "ENOENT") { + throw error; } - if (!err) { - return resolve(); - } + return new Promise(function (resolve, reject) { + console.log("mkdir " + path); + fs.mkdir(path, function (error) { + if (error) { + return reject(error); + } - fs.mkdir(path, function(err) { - if (err && err.code != 'EEXIST') { - return reject("Unable to create " + path + "\n" + err); - } - return resolve(); + return resolve(); + }); }); }); }); } -function existsPromise(path) { - return new Promise(function(resolve, reject) { - fs.stat(path, function(err, stats) { - if (!err) { - return resolve(true); - } - if (err.code == 'ENOENT') { - return resolve(false); - } - return reject(err); - }); - }) + +const exists = function(path) { + return stat(path).then(function() { + return true; + }).catch(function() { + return false; + }); } function convertRawToJpg(path, file) { @@ -236,10 +222,10 @@ function convertRawToJpg(path, file) { path = picturesPath + path; return new Promise(function(resolve, reject) { - fs.stat(path + "/" + file.replace(rawExtension, ".jpg"), function(err, stats) { - if (!err) { + return exists(path + "/" + file.replace(rawExtension, ".jpg")).then(function(exist) { + if (exist) { console.log("Skipping already converted file: " + file); - return resolve(); + return; } const ufraw = spawn("ufraw-batch", [ @@ -259,30 +245,32 @@ function convertRawToJpg(path, file) { stderr.push(data); }); - ufraw.on('exit', function(stderr, code, signal) { - if (signal || code != 0) { - let error = "UFRAW for " + path + "/" + file + " returned an error: " + code + "\n" + signal + "\n" + stderr.join("\n") + "\n"; - console.error(error); - return moveCorrupt(path, file).then(function() { - console.warn("ufraw failed"); - return reject(error); + return new Promise(function(ufraw, resolve, reject) { + ufraw.on('exit', function(stderr, code, signal) { + if (signal || code != 0) { + let error = "UFRAW for " + path + "/" + file + " returned an error: " + code + "\n" + signal + "\n" + stderr.join("\n") + "\n"; + console.error(error); + return moveCorrupt(path, file).then(function() { + console.warn("ufraw failed"); + return reject(error); + }).catch(function(error) { + console.warn("moveCorrupt failed"); + return reject(error); + }); + } + return mkdir(path + "/raw").then(function() { + fs.rename(path + "/" + file, path + "/raw/" + file, function(err) { + if (err) { + console.error("Unable to move RAW file: " + path + "/" + file); + return reject(err); + } + return resolve(); + }); }).catch(function(error) { - console.warn("moveCorrupt failed"); + console.warn("mkdir failed"); return reject(error); }); - } - return mkdirPromise(path + "/raw").then(function() { - fs.rename(path + "/" + file, path + "/raw/" + file, function(err) { - if (err) { - console.error("Unable to move RAW file: " + path + "/" + file); - return reject(err); - } - return resolve(); - }); - }).catch(function(error) { - console.warn("mkdirPromise failed"); - return reject(error); - }); + }.bind(this, ufraw)); }.bind(this, stderr)); }); }); @@ -295,7 +283,7 @@ function moveCorrupt(path, file) { console.warn("Moving corrupt file '" + file + "' to " + path + "/corrupt"); - return mkdirPromise(path + "/corrupt").then(function() { + return mkdir(path + "/corrupt").then(function() { return new Promise(function(resolve, reject) { fs.rename(path + "/" + file, path + "/corrupt/" + file, function(err) { if (err) { @@ -323,17 +311,15 @@ function triggerWatcher() { return Promise.map(processing, function(entry) { var path = entry[0], file = entry[1], created = entry[2], albumId = entry[3]; -// console.log("Processing " + src); - let tmp = Promise.resolve(file); /* If this is a Nikon RAW file, convert it to JPG and move to /raw dir */ if (rawExtension.exec(file)) { - tmp = existsPromise(picturesPath + path + "/" + file.replace(rawExtension, ".jpg")).then(function(exists) { - if (exists) { + tmp = exists(picturesPath + path + "/" + file.replace(rawExtension, ".jpg")).then(function(exist) { + if (exist) { return file.replace(rawExtension, ".jpg"); /* We converted from NEF/ORF => JPG */ } - return mkdirPromise(picturesPath + path + "/raw").then(function() { + return mkdir(picturesPath + path + "/raw").then(function() { return convertRawToJpg(path, file); }).then(function() { return file.replace(rawExtension, ".jpg"); /* We converted from NEF/ORF => JPG */ @@ -343,7 +329,6 @@ function triggerWatcher() { return tmp.then(function(file) { var src = picturesPath + path + "/" + file, - dst = picturesPath + path + "/thumbs/" + file, image = sharp(src); return image.limitInputPixels(1073741824).metadata().then(function(metadata) { @@ -407,24 +392,35 @@ function triggerWatcher() { replacements.taken = replacements.modified = date; } - return existsPromise(dst).then(function(exists) { - let resize; - if (!exists) { - resize = image.resize(256, 256).toFile(dst); - } else { - resize = Promise.resolve(); + let dst = picturesPath + path + "/thumbs/" + file; + + return exists(dst).then(function(exist) { + if (exist) { + return; } - return resize.then(function() { - return photoDB.sequelize.query("INSERT INTO photos " + - "(albumId,path,filename,added,modified,taken,width,height,name)" + - "VALUES(:albumId,:path,:filename,DATETIME(:added),DATETIME(:modified),DATETIME(:taken),:width,:height,:name)", { - replacements: replacements - }); - }).catch(function(error) { - console.error("Error resizing image, writing to disc, or updating DB: " + src, error); + return image.resize(256, 256).toFile(dst).catch(function(error) { + console.error("Error resizing image: " + dst, error); throw error; }); + }).then(function() { + let dst = picturesPath + path + "/thumbs/scaled/" + file; + return exists(dst).then(function(exist) { + if (exist) { + return; + } + + return image.resize(Math.min(1024, metadata.width)).toFile(dst).catch(function(error) { + console.error("Error resizing image: " + dst, error); + throw error; + }); + }); + }).then(function() { + return photoDB.sequelize.query("INSERT INTO photos " + + "(albumId,path,filename,added,modified,taken,width,height,name)" + + "VALUES(:albumId,:path,:filename,DATETIME(:added),DATETIME(:modified),DATETIME(:taken),:width,:height,:name)", { + replacements: replacements + }); }).then(function() { toProcess--; if (moment().add(-5, 'seconds') > lastMessage) {