diff --git a/frontend/src/ketr-photos/ketr-photos.html b/frontend/src/ketr-photos/ketr-photos.html index bdc99bf..1099033 100755 --- a/frontend/src/ketr-photos/ketr-photos.html +++ b/frontend/src/ketr-photos/ketr-photos.html @@ -770,6 +770,14 @@ this._loadPhotos(); }, + triggerLoadMore: function() { + if (this.$.bottom.getBoundingClientRect().bottom < window.innerHeight + 200) { + if (this.pendingPhotos.length) { + this.async(this.processItems.bind(this)); + } + } + }, + onScroll: function(event) { if (this.disableScrolling) { if (event) event.preventDefault(); @@ -777,6 +785,8 @@ return; } + this.triggerLoadMore(); + if (this.$.bottom.getBoundingClientRect().bottom < window.innerHeight + 200) { if (this.pendingPhotos.length) { this.async(this.processItems.bind(this)); @@ -1245,7 +1255,7 @@ this.processing = false; }, - _removeImageAfterFetch: function(thumbnail, error, xhr) { + _removeImageAfterFetch: function(thumbnail, callback, error, xhr) { thumbnail.disabled = false; if (error) { console.log("Unable to take action on photo: " + error); @@ -1267,12 +1277,28 @@ } this.thumbnails.splice(i, 1); this.notifyPath("thumbnails.length"); + + /* Invoke callback once thumbnails has changed */ + if (callback) { + callback(); + } } + if (!parent) { + if (callback) { + callback(); + } + return; + } + Polymer.dom(parent).removeChild(thumbnail); + this.triggerLoadMore(); if (parent.querySelectorAll("photo-thumbnail").length != 0) { this.triggerVisibilityChecks(); + if (callback) { + callback(); + } return; } @@ -1300,11 +1326,10 @@ var params = {}; thumbnail.disabled = true; window.fetch("api/v1/photos/" + thumbnail.item.id + "?a=undelete", - this._removeImageAfterFetch.bind(this, thumbnail), {}, "PUT"); + this._removeImageAfterFetch.bind(this, thumbnail, null), {}, "PUT"); }, - deleteAction: function(photo) { - var thumbnail = event.currentTarget, params = {}; + deleteAction: function(thumbnail) { thumbnail.disabled = true; var query = ""; if (this.mode == "trash") { @@ -1312,17 +1337,54 @@ query += "?permanent=1"; } window.fetch("api/v1/photos/" + thumbnail.item.id + query, - this._removeImageAfterFetch.bind(this, thumbnail), {}, "DELETE"); + this._removeImageAfterFetch.bind(this, thumbnail, function() { + /* In duplicates mode, if an image is deleted it has the same + * effect as renaming it since it removes it from the view */ + if (this.mode != "duplicates") { + return; + } + /* If there is now only one item with this thumbnail's name, + * remove it from the view */ + var orphan = null; + for (var i = 0; i < this.thumbnails.length; i++) { + if (this.thumbnails[i].item.filename == thumbnail.item.filename) { + if (orphan) { + return; + } + orphan = this.thumbnails[i]; + } + } + if (orphan) { + this._removeImageAfterFetch(orphan, null); + } + }.bind(this)), {}, "DELETE"); }, renameAction: function(thumbnail) { - return; + thumbnail.disabled = true; + window.fetch("api/v1/photos/" + thumbnail.item.id + "?a=rename", + this._removeImageAfterFetch.bind(this, thumbnail, function() { + /* If there is now only one item with this thumbnail's name, + * remove it from the view */ + var orphan = null; + for (var i = 0; i < this.thumbnails.length; i++) { + if (this.thumbnails[i].item.filename == thumbnail.item.filename) { + if (orphan) { + return; + } + orphan = this.thumbnails[i]; + } + } + if (orphan) { + this._removeImageAfterFetch(orphan, null); + } + }.bind(this)), {}, "PUT"); }, rotateAction: function(thumbnail, direction) { thumbnail.disabled = true; window.fetch("api/v1/photos/" + thumbnail.item.id + "?a=rotate&direction=" + direction, - function(thumbnail, error, xhr) { + function(thumbnail, error, xhr) { thumbnail.disabled = false; diff --git a/server/routes/photos.js b/server/routes/photos.js index 129a6c6..13c1c35 100755 --- a/server/routes/photos.js +++ b/server/routes/photos.js @@ -92,6 +92,24 @@ router.put("/:id", function(req, res/*, next*/) { return res.status(200).send(req.params.id + " updated."); }); + case "rename": + return getPhoto(req.params.id).then(function(asset) { + if (!asset) { + return res.status(404).send(req.params.id + " not found."); + } + + let src = asset.filename; + + asset.filename = asset.filename.replace(/(\.[^.]*)$/, "-" + asset.hash.substring(0, 8) + "$1"); + return rename(picturesPath + asset.path + src, picturesPath + asset.path + asset.filename).then(function() { + return photoDB.sequelize.query("UPDATE photos SET filename=:filename WHERE id=:id", { + replacements: asset + }).then(function() { + return res.status(200).send(asset); + }); + }); + }); + case "rotate": let direction = req.query.direction || "right"; if (direction == "right") { @@ -259,7 +277,7 @@ router.delete("/:id", function(req, res/*, next*/) { console.log("Updating photohash for " + photo.id + " to point to " + first.id); // 4. If there are duplicates, update the HASH entry to point to the first image that remains return photoDB.sequelize.query( - "UPDATE phothashes SET photoId=:first WHERE photoId=:photo", { + "UPDATE photohashes SET photoId=:first WHERE photoId=:photo", { replacements: { first: first.id, photo: photo.id @@ -276,9 +294,11 @@ router.delete("/:id", function(req, res/*, next*/) { }).then(function() { // 6. Delete the scaled, thumb, and original from disk console.log("Deleting " + photo.path + photo.filename + " from disk."); - return unlink(photo.path + "thumbs/scaled/" + photo.filename).then(function() { - return unlink(photo.path + "thumbs/" + photo.filename).then(function() { - return unlink(photo.path + photo.filename); + return unlink(photo.path + "thumbs/scaled/" + photo.filename).catch(function() {}).then(function() { + return unlink(photo.path + "thumbs/" + photo.filename).catch(function() {}).then(function() { + return unlink(photo.path + photo.filename).catch(function(error) { + console.log("Error removing file: " + error); + }); }); }); }); @@ -386,7 +406,8 @@ router.get("/duplicates", function(req, res/*, next*/) { let replacements = {}; return photoDB.sequelize.query( - "SELECT filename,COUNT(*) AS count FROM photos WHERE photos.duplicate!=1 AND photos.deleted!=1 GROUP BY filename HAVING count > 1", { + "SELECT filename,COUNT(*) AS count FROM photos WHERE photos.duplicate=0 AND photos.deleted!=1 " + + "GROUP BY filename HAVING count > 1", { replacements: replacements, type: photoDB.Sequelize.QueryTypes.SELECT, raw: true @@ -402,7 +423,7 @@ router.get("/duplicates", function(req, res/*, next*/) { "SELECT photos.*,albums.path AS path,photohashes.hash,(albums.path || photos.filename) AS filepath FROM photos " + "LEFT JOIN albums ON albums.id=photos.albumId " + "LEFT JOIN photohashes ON photohashes.photoId=photos.id " + - "WHERE filename IN (:filenames) ORDER BY photos.filename", { + "WHERE filename IN (:filenames) ORDER BY photos.filename,photos.width,photos.height,photos.size DESC", { replacements: replacements, type: photoDB.Sequelize.QueryTypes.SELECT, raw: true diff --git a/server/scanner.js b/server/scanner.js index 6f6124c..a9d1a47 100755 --- a/server/scanner.js +++ b/server/scanner.js @@ -226,6 +226,7 @@ function processBlock(items) { "'" + asset.album.path + asset.filename + "' is a copy of " + "'" + results[0].path + results[0].filename + "'"); if (asset.duplicate != results[0].photoId) { + asset.duplicate = results[0].photoId; duplicates.push(asset); } return null;