Delete works

Signed-off-by: James Ketrenos <james_git@ketrenos.com>
This commit is contained in:
James Ketr 2018-10-11 16:59:38 -07:00
parent f780fc7c4d
commit 82e5875e52
5 changed files with 293 additions and 62 deletions

View File

@ -30,6 +30,12 @@
@apply --photo-thumbnail; @apply --photo-thumbnail;
} }
:host([disabled]) {
opacity: 0.5;
pointer-events: none;
border: 3px solid red;
}
#info { #info {
position: absolute; position: absolute;
padding: 0.5em; padding: 0.5em;
@ -75,6 +81,9 @@
Polymer({ Polymer({
is: "photo-thumbnail", is: "photo-thumbnail",
properties: { properties: {
"disabled": {
reflectToAttribute: true
},
"item": { "item": {
type: Object type: Object
}, },

View File

@ -850,7 +850,7 @@
} else { } else {
index = length >> 1; index = length >> 1;
} }
var pos = this.checkPosition(index), last = -1; var pos = this.checkPosition(index), last = -1;
while (pos != 0 && last != index) { while (pos != 0 && last != index) {
last = index; /* safety escape in case the DOM changed and nothing matches */ last = index; /* safety escape in case the DOM changed and nothing matches */
@ -1222,6 +1222,9 @@
if (this.mode == "duplicates") { if (this.mode == "duplicates") {
actions.unshift("text-format"); actions.unshift("text-format");
} }
if (this.mode == "trash") {
actions.unshift("undo");
}
thumbnail.actions = actions; thumbnail.actions = actions;
thumbnail.addEventListener("action", this._imageAction.bind(this)); thumbnail.addEventListener("action", this._imageAction.bind(this));
} }
@ -1239,56 +1242,99 @@
this.processing = false; this.processing = false;
}, },
_removeImageAfterFetch: function(thumbnail, error, xhr) {
thumbnail.disabled = false;
if (error) {
console.log("Unable to take action on photo: " + error);
return;
}
thumbnail.style.pointerEvents = "none";
thumbnail.style.opacity = 0;
this.async(function(thumbnail) {
var parent = thumbnail.parentElement;
var i = this.thumbnails.indexOf(thumbnail);
if (i != -1) {
if (thumbnail.visible) {
thumbnail.visible = false;
/* This changes the index of all thumbnails, so invalidate the entire
* visibleThumbs list */
this.visibleThumbs = [];
}
this.thumbnails.splice(i, 1);
this.notifyPath("thumbnails.length");
}
Polymer.dom(parent).removeChild(thumbnail);
if (parent.querySelectorAll("photo-thumbnail").length != 0) {
this.triggerVisibilityChecks();
return;
}
/* There are no more thumbnails, so look up the document ancestors
* until we find the element with the date-line class, and then
* remove it from it's parent */
while (parent && !parent.classList.contains("date-line")) {
parent = parent.parentElement;
}
if (!parent) {
this.triggerVisibilityChecks();
return;
}
parent.style.opacity = 0;
this.async(function(parent) {
Polymer.dom(this.$.thumbnails).removeChild(parent);
this.triggerVisibilityChecks();
}.bind(this, parent), 250);
}.bind(this, thumbnail), 250);
},
_imageAction: function(event) { _imageAction: function(event) {
switch (event.detail) { switch (event.detail) {
case "delete": /* Delete image */ case "undo": /* Undelete an image */
var thumbnail = event.currentTarget; var thumbnail = event.currentTarget, params = {};
thumbnail.style.opacity = 0; thumbnail.disabled = true;
thumbnail.style.pointerEvents = "none";
this.async(function(thumbnail) { params.undelete = 1;
var parent = thumbnail.parentElement;
for (var i = 0; i < this.thumbnails.length; i++) { var query = "";
if (this.thumbnails[i] == thumbnail) { for (var key in params) {
console.log("Found thumbnail", this.thumbnails.length); if (!query) {
if (thumbnail.visible) { query = "?";
console.log("Removing from visible list");
thumbnail.visible = false;
var j = this.visibleThumbs.indexOf(i);
if (j != -1) {
console.log("Removing visible thumb", this.visibleThumbs.length);
this.visibleThumbs.splice(j, 1);
}
}
this.thumbnails.splice(i, 1);
this.notifyPath("thumbnails.length");
break;
}
}
Polymer.dom(parent).removeChild(thumbnail);
if (parent.querySelectorAll("photo-thumbnail").length == 0) {
/* There are no more thumbnails, so look up the document ancestors
* until we find the element with the date-line class, and then
* remove it from it's parent */
while (parent && !parent.classList.contains("date-line")) {
parent = parent.parentElement;
}
if (!parent) {
return;
}
parent.style.opacity = 0;
this.async(function(parent) {
Polymer.dom(this.$.thumbnails).removeChild(parent);
this.triggerVisibilityChecks();
}.bind(this, parent), 250);
} else { } else {
this.triggerVisibilityChecks(); query += "&";
} }
}.bind(this, thumbnail), 250); query += encodeURIComponent(key) + "=" + encodeURIComponent(params[key]);
}
window.fetch("api/v1/photos/" + thumbnail.item.id + query,
this._removeImageAfterFetch.bind(this, thumbnail), {}, "PUT");
break; break;
case "delete": /* Delete image */
var thumbnail = event.currentTarget, params = {};
thumbnail.disabled = true;
if (this.mode == "trash") {
console.log("TODO: Prompt user 'Are you sure?' ?");
params.permanent = 1;
}
var query = "";
for (var key in params) {
if (!query) {
query = "?";
} else {
query += "&";
}
query += encodeURIComponent(key) + "=" + encodeURIComponent(params[key]);
}
window.fetch("api/v1/photos/" + thumbnail.item.id + query,
this._removeImageAfterFetch.bind(this, thumbnail), {}, "DELETE");
break;
case "text-format": /* Rename this image */ case "text-format": /* Rename this image */
break; break;
} }

View File

@ -19,7 +19,7 @@ const express = require("express"),
require("./console-line.js"); /* Monkey-patch console.log with line numbers */ 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");
let basePath = config.get("basePath"); let basePath = config.get("basePath");

View File

@ -13,6 +13,188 @@ require("../db/photos").then(function(db) {
}); });
const router = express.Router(); const router = express.Router();
const picturesPath = config.get("picturesPath").replace(/\/$/, "") + "/";
const unlink = function (_path) {
if (_path.indexOf(picturesPath.replace(/\/$/, "")) == 0) {
_path = _path.substring(picturesPath.length);
}
let path = picturesPath + _path;
return new Promise(function (resolve, reject) {
fs.unlink(path, function (error, stats) {
if (error) {
return reject(error);
}
return resolve(stats);
});
});
}
router.put("/:id", function(req, res/*, next*/) {
if (!req.user.maintainer) {
return res.status(401).send("Unauthorized to delete photos.");
}
const replacements = {
id: req.params.id
};
let query = "";
console.log("PUT /" + replacements.id, req.query);
for (let key in req.query) {
switch (key) {
case "undelete":
console.log("Undeleting " + req.params.id);
query = "UPDATE photos SET deleted=0 WHERE id=:id";
break;
default:
continue;
}
}
if (!query) {
return res.status(400).send("Invalid request");
}
return photoDB.sequelize.query(query, {
replacements: replacements
}).then(function() {
return res.status(200).send(req.params.id + " updated.");
});
});
router.delete("/:id", function(req, res/*, next*/) {
if (!req.user.maintainer) {
return res.status(401).send("Unauthorized to delete photos.");
}
const replacements = {
id: req.params.id
};
console.log("DELETE /" + replacements.id, req.query);
return photoDB.sequelize.query("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 photos.id=:id", {
replacements: replacements,
type: photoDB.Sequelize.QueryTypes.SELECT,
raw: true
}).then(function(photos) {
if (photos.length == 0) {
res.status(404).send("Unable to find photo " + req.params.id);
return true;
}
const photo = photos[0];
if (!req.query.permanent) {
return photoDB.sequelize.query("UPDATE photos SET deleted=1 WHERE id=:id", {
replacements: replacements
}).then(function() {
return false;
});
}
/* Delete the asset from disk and the DB
* 1. Look if there are duplicates.
* 2. Update all other duplicates to be duplicates of the first image that remains
* 3. If no duplicates, DELETE the entry from photohashes
* 4. If there are duplicates, update the HASH entry to point to the first image that remains
* 5. Delete the entry from photos
* 6. Delete the scaled, thumb, and original from disk
*/
return photoDB.sequelize.transaction(function(transaction) {
return photoDB.sequelize.query("SELECT id FROM photos WHERE duplicate=:id", {
replacements: photo,
type: photoDB.Sequelize.QueryTypes.SELECT,
raw: true
}).then(function(duplicates) {
if (!duplicates.length) {
return null;
}
let first = duplicates.shift();
let needsUpdate = [];
duplicates.forEach(function(duplicate) {
needsUpdate.push(duplicate.id);
});
if (!needsUpdate.length) {
return first;
}
// 2. Update all other duplicates to be duplicates of the first image that remains
console.log("Updating " + needsUpdate + " to point to " + first.id);
return photoDB.sequelize.query(
"UPDATE photos SET duplicate=:first WHERE id IN (:needsUpdate)", {
replacements: {
first: first.id,
needsUpdate: needsUpdate
},
transaction: transaction
}).then(function() {
return first;
});
}).then(function(first) {
if (!first) {
console.log("Deleting "+ photo.id + " from photohash.");
// 3. If no duplicates, DELETE the entry from photohashes
return photoDB.sequelize.query(
"DELETE FROM photohashes WHERE photoId=:photo", {
replacements: {
photo: photo.id
},
transaction: transaction
});
}
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", {
replacements: {
first: first.id,
photo: photo.id
},
transaction: transaction
});
}).then(function() {
console.log("Deleting " + photo.path + photo.filename + " from DB.");
// 5. Delete the entry from photos
return photoDB.sequelize.query("DELETE FROM photos WHERE id=:id", {
replacements: replacements,
transaction: transaction
});
}).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);
});
});
});
}).then(function() {
return false;
});
}).then(function(sent) {
if (sent) {
return;
}
return res.status(200).send({
id: req.params.id,
deleted: true,
permanent: (req.query && req.query.permanent) || false
});
}).catch(function(error) {
console.log(error);
return res.status(500).send("Unable to delete photo " + req.params.id + ": " + error);
});
});
/* Each photos has: /* Each photos has:

View File

@ -225,7 +225,9 @@ function processBlock(items) {
setStatus("Duplicate asset: " + setStatus("Duplicate asset: " +
"'" + asset.album.path + asset.filename + "' is a copy of " + "'" + asset.album.path + asset.filename + "' is a copy of " +
"'" + results[0].path + results[0].filename + "'"); "'" + results[0].path + results[0].filename + "'");
duplicates.push(asset); if (asset.duplicate != results[0].photoId) {
duplicates.push(asset);
}
return null; return null;
} }
@ -394,22 +396,14 @@ function processBlock(items) {
throw error; throw error;
}).then(function() { }).then(function() {
setStatus("Completed processing queue. Marking " + duplicates.length + " duplicates."); setStatus("Completed processing queue. Marking " + duplicates.length + " duplicates.");
let dups = []; return photoDB.sequelize.transaction(function(transaction) {
duplicates.forEach(function(asset) { return Promise.mapSeries(duplicates, function(asset) {
/* If not already marked as a duplicate, mark it. */ return photoDB.sequelize.query("UPDATE photos " +
if (!asset.duplicate) { "SET duplicate=:duplicate,modified=CURRENT_TIMESTAMP,scanned=CURRENT_TIMESTAMP WHERE id=:id", {
dups.push(asset.id); replacements: asset,
} transaction: transaction
}); });
});
if (dups.length == 0) {
return;
}
return photoDB.sequelize.query("UPDATE photos SET duplicate=1,modified=CURRENT_TIMESTAMP,scanned=CURRENT_TIMESTAMP WHERE id IN (:dups)", {
replacements: {
dups: dups
}
}); });
}).then(function() { }).then(function() {
setStatus("Looking for removed assets"); setStatus("Looking for removed assets");