Changed 'DELETE' action to be a long-running task
Signed-off-by: James Ketrenos <james_git@ketrenos.com>
This commit is contained in:
parent
2b2af1d965
commit
744e2535bc
@ -1427,19 +1427,83 @@
|
|||||||
}.bind(this, thumbnail), {}, "PUT");
|
}.bind(this, thumbnail), {}, "PUT");
|
||||||
},
|
},
|
||||||
|
|
||||||
|
purgeStatus: function() {
|
||||||
|
console.log("Checking purge status", this.purgeTask);
|
||||||
|
window.fetch("api/v1/photos/status/" + this.purgeTask.token, function(error, xhr) {
|
||||||
|
if (error && xhr.status != 404) {
|
||||||
|
console.log("Unable to take action on photo: " + error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (xhr.status == 404) {
|
||||||
|
console.log("Task complete");
|
||||||
|
this.purgeTask = null;
|
||||||
|
this.resetPhotos();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var results;
|
||||||
|
try {
|
||||||
|
results = JSON.parse(xhr.responseText);
|
||||||
|
} catch (___) {
|
||||||
|
this.$.toast.text = "Unable to parse delete task.";
|
||||||
|
this.$.toast.setAttribute("error", true);
|
||||||
|
this.$.toast.updateStyles();
|
||||||
|
this.$.toast.show();
|
||||||
|
console.error("Unable to parse photos");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (results.remaining == 0) {
|
||||||
|
console.log("Task complete");
|
||||||
|
this.purgeTask = null;
|
||||||
|
this.resetPhotos();
|
||||||
|
} else {
|
||||||
|
this.purgeTask = results;
|
||||||
|
if (this.purgeTask.eta == -1) {
|
||||||
|
this.purgeTask.eta = 1000;
|
||||||
|
}
|
||||||
|
console.log("Task ETA: " + this.purgeTask.eta);
|
||||||
|
this.async(function() {
|
||||||
|
this.purgeStatus();
|
||||||
|
this.resetPhotos();
|
||||||
|
}, this.purgeTask.eta || 1000);
|
||||||
|
}
|
||||||
|
}.bind(this));
|
||||||
|
},
|
||||||
|
|
||||||
purgeTrashAction: function() {
|
purgeTrashAction: function() {
|
||||||
var query = "";
|
var query = "";
|
||||||
if (this.mode == "trash") {
|
if (this.mode == "trash") {
|
||||||
console.log("TODO: Prompt user 'Are you sure?' ?");
|
console.log("TODO: Prompt user 'Are you sure?' ?");
|
||||||
query = "?permanent=1";
|
query = "?permanent=1";
|
||||||
}
|
}
|
||||||
window.fetch("api/v1/photos/" + query, function(error) {
|
window.fetch("api/v1/photos/" + query, function(error, xhr) {
|
||||||
if (error) {
|
if (error) {
|
||||||
console.log("Unable to take action on photo: " + error);
|
console.log("Unable to take action on photo: " + error);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.resetPhotos();
|
var results;
|
||||||
|
try {
|
||||||
|
results = JSON.parse(xhr.responseText);
|
||||||
|
} catch (___) {
|
||||||
|
this.$.toast.text = "Unable to parse delete task.";
|
||||||
|
this.$.toast.setAttribute("error", true);
|
||||||
|
this.$.toast.updateStyles();
|
||||||
|
this.$.toast.show();
|
||||||
|
console.error("Unable to parse photos");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (results.task) {
|
||||||
|
this.purgeTask = results.task;
|
||||||
|
this.async(function() {
|
||||||
|
this.purgeStatus();
|
||||||
|
}, 250);
|
||||||
|
} else {
|
||||||
|
this.resetPhotos();
|
||||||
|
}
|
||||||
}.bind(this), {}, "DELETE");
|
}.bind(this), {}, "DELETE");
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -13,9 +13,7 @@
|
|||||||
* to create db connections, test the connection, then create the tables and
|
* to create db connections, test the connection, then create the tables and
|
||||||
* relationships if not present
|
* relationships if not present
|
||||||
*/
|
*/
|
||||||
const fs = require('fs'),
|
const Sequelize = require('sequelize'),
|
||||||
path = require('path'),
|
|
||||||
Sequelize = require('sequelize'),
|
|
||||||
config = require('config');
|
config = require('config');
|
||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
|
@ -315,6 +315,134 @@ const getPhoto = function(id) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const tasks = [];
|
||||||
|
const Task = function() {
|
||||||
|
return new Promise(function(resolve, reject) {
|
||||||
|
crypto.randomBytes(16, function(error, buffer) {
|
||||||
|
if (error) {
|
||||||
|
return reject(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
let now = Date.now(), task;
|
||||||
|
|
||||||
|
task = {
|
||||||
|
started: now,
|
||||||
|
lastUpdate: now,
|
||||||
|
eta: now,
|
||||||
|
processed: 0,
|
||||||
|
remaining: 0,
|
||||||
|
token: buffer.toString('hex'),
|
||||||
|
};
|
||||||
|
|
||||||
|
tasks.push(task);
|
||||||
|
return resolve(task);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeTask = function(task) {
|
||||||
|
let i = tasks.indexOf(task);
|
||||||
|
if (i != -1) {
|
||||||
|
tasks.splice(i, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
router.get("/status/:token", function(req, res) {
|
||||||
|
if (!req.params.token) {
|
||||||
|
return res.status(400).send("Usage /status/:token");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < tasks.length; i++) {
|
||||||
|
if (tasks[i].token == req.params.token) {
|
||||||
|
return res.status(200).send(tasks[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(404).send("Task " + req.params.token + " not found.");
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
const deletePhoto = function(photo) {
|
||||||
|
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=:id", {
|
||||||
|
replacements: photo,
|
||||||
|
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 photohashes 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: photo,
|
||||||
|
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).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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
router.delete("/:id?", function(req, res/*, next*/) {
|
router.delete("/:id?", function(req, res/*, next*/) {
|
||||||
if (!req.user.maintainer) {
|
if (!req.user.maintainer) {
|
||||||
return res.status(401).send("Unauthorized to delete photos.");
|
return res.status(401).send("Unauthorized to delete photos.");
|
||||||
@ -340,6 +468,8 @@ router.delete("/:id?", function(req, res/*, next*/) {
|
|||||||
|
|
||||||
console.log("DELETE /" + replacements.id, req.query);
|
console.log("DELETE /" + replacements.id, req.query);
|
||||||
|
|
||||||
|
let sent = false;
|
||||||
|
|
||||||
return photoDB.sequelize.query("SELECT " +
|
return photoDB.sequelize.query("SELECT " +
|
||||||
"photos.*,albums.path AS path,photohashes.hash,(albums.path || photos.filename) AS filepath FROM photos " +
|
"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 albums ON albums.id=photos.albumId " +
|
||||||
@ -350,115 +480,61 @@ router.delete("/:id?", function(req, res/*, next*/) {
|
|||||||
raw: true
|
raw: true
|
||||||
}).then(function(photos) {
|
}).then(function(photos) {
|
||||||
if (photos.length == 0) {
|
if (photos.length == 0) {
|
||||||
res.status(404).send("Unable to find photo " + replacements.id);
|
sent = true;
|
||||||
return true;
|
return res.status(404).send("Unable to find photo " + replacements.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!req.query.permanent) {
|
if (!req.query.permanent) {
|
||||||
return photoDB.sequelize.query("UPDATE photos SET deleted=1,updated=CURRENT_TIMESTAMP WHERE id=:id", {
|
return photoDB.sequelize.query("UPDATE photos SET deleted=1,updated=CURRENT_TIMESTAMP WHERE id=:id", {
|
||||||
replacements: replacements
|
replacements: replacements
|
||||||
}).then(function() {
|
}).then(function() {
|
||||||
return false;
|
sent = true;
|
||||||
|
return res.status(200).send({
|
||||||
|
id: req.params.id,
|
||||||
|
deleted: true,
|
||||||
|
permanent: (req.query && req.query.permanent) || false
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete the asset from disk and the DB
|
* 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 Promise.mapSeries(photos, function(photo) {
|
return Task().then(function(task) {
|
||||||
return photoDB.sequelize.transaction(function(transaction) {
|
task.remaining = photos.length;
|
||||||
return photoDB.sequelize.query("SELECT id FROM photos WHERE duplicate=:id", {
|
task.eta = -1;
|
||||||
replacements: photo,
|
|
||||||
type: photoDB.Sequelize.QueryTypes.SELECT,
|
|
||||||
raw: true
|
|
||||||
}).then(function(duplicates) {
|
|
||||||
if (!duplicates.length) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
let first = duplicates.shift();
|
sent = true;
|
||||||
let needsUpdate = [];
|
res.status(200).send({
|
||||||
duplicates.forEach(function(duplicate) {
|
id: req.params.id,
|
||||||
needsUpdate.push(duplicate.id);
|
task: task,
|
||||||
});
|
permanent: (req.query && req.query.permanent) || false
|
||||||
|
|
||||||
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=:id", {
|
|
||||||
replacements: photo,
|
|
||||||
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 photohashes 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: photo,
|
|
||||||
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).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);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}).then(function() {
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
}).then(function(sent) {
|
|
||||||
if (sent) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(200).send({
|
return Promise.mapSeries(photos, function(photo) {
|
||||||
id: req.params.id,
|
let lastStamp = Date.now();
|
||||||
deleted: true,
|
return deletePhoto(photo).then(function() {
|
||||||
permanent: (req.query && req.query.permanent) || false
|
let now = Date.now(), elapsed = now - lastStamp;
|
||||||
|
lastStamp= now;
|
||||||
|
task.processed++;
|
||||||
|
task.remaining--;
|
||||||
|
task.lastUpdate = Date.now();
|
||||||
|
task.eta = elapsed * task.remaining;
|
||||||
|
})
|
||||||
|
}).then(function() {
|
||||||
|
console.log("Processing task " + task.token + " finished: " + (task.lastUpdate - task.started));
|
||||||
|
removeTask(task);
|
||||||
|
}).catch(function(error) {
|
||||||
|
console.log("Processing task " + task.token + " failed: " + error);
|
||||||
|
removeTask(task);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}).catch(function(error) {
|
}).catch(function(error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
return res.status(500).send("Unable to delete photo " + req.params.id + ": " + error);
|
if (!sent) {
|
||||||
|
sent = true;
|
||||||
|
res.status(500).send("Unable to delete photo " + req.params.id + ": " + error);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -662,5 +738,4 @@ router.get("/*", function(req, res/*, next*/) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user