Delete works
Signed-off-by: James Ketrenos <james_git@ketrenos.com>
This commit is contained in:
parent
f780fc7c4d
commit
82e5875e52
@ -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
|
||||||
},
|
},
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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");
|
||||||
|
@ -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:
|
||||||
|
|
||||||
|
@ -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");
|
||||||
|
Loading…
x
Reference in New Issue
Block a user