Changed 'DELETE' action to be a long-running task

Signed-off-by: James Ketrenos <james_git@ketrenos.com>
This commit is contained in:
James Ketr 2018-10-24 20:44:08 -07:00
parent 2b2af1d965
commit 744e2535bc
3 changed files with 239 additions and 102 deletions

View File

@ -1427,19 +1427,83 @@
}.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() {
var query = "";
if (this.mode == "trash") {
console.log("TODO: Prompt user 'Are you sure?' ?");
query = "?permanent=1";
}
window.fetch("api/v1/photos/" + query, function(error) {
window.fetch("api/v1/photos/" + query, function(error, xhr) {
if (error) {
console.log("Unable to take action on photo: " + error);
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.task) {
this.purgeTask = results.task;
this.async(function() {
this.purgeStatus();
}, 250);
} else {
this.resetPhotos();
}
}.bind(this), {}, "DELETE");
},

View File

@ -13,9 +13,7 @@
* to create db connections, test the connection, then create the tables and
* relationships if not present
*/
const fs = require('fs'),
path = require('path'),
Sequelize = require('sequelize'),
const Sequelize = require('sequelize'),
config = require('config');
function init() {

View File

@ -315,55 +315,53 @@ const getPhoto = function(id) {
});
}
router.delete("/:id?", function(req, res/*, next*/) {
if (!req.user.maintainer) {
return res.status(401).send("Unauthorized to delete photos.");
const tasks = [];
const Task = function() {
return new Promise(function(resolve, reject) {
crypto.randomBytes(16, function(error, buffer) {
if (error) {
return reject(error);
}
const replacements = {
id: req.params.id || "*"
let now = Date.now(), task;
task = {
started: now,
lastUpdate: now,
eta: now,
processed: 0,
remaining: 0,
token: buffer.toString('hex'),
};
if (!req.params.id && !req.query.permanent) {
return res.status(400).send("Trash can only be emptied if permanent.");
}
let where = "";
if (req.params.id) {
where = "photos.id=:id";
if (req.query.permanent) {
where += " AND photos.deleted=1";
}
} else {
where = "photos.deleted=1";
}
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 " + where, {
replacements: replacements,
type: photoDB.Sequelize.QueryTypes.SELECT,
raw: true
}).then(function(photos) {
if (photos.length == 0) {
res.status(404).send("Unable to find photo " + replacements.id);
return true;
}
if (!req.query.permanent) {
return photoDB.sequelize.query("UPDATE photos SET deleted=1,updated=CURRENT_TIMESTAMP WHERE id=:id", {
replacements: replacements
}).then(function() {
return false;
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.");
});
/**
* 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
@ -371,7 +369,7 @@ router.delete("/:id?", function(req, res/*, next*/) {
* 5. Delete the entry from photos
* 6. Delete the scaled, thumb, and original from disk
*/
return Promise.mapSeries(photos, function(photo) {
const deletePhoto = function(photo) {
return photoDB.sequelize.transaction(function(transaction) {
return photoDB.sequelize.query("SELECT id FROM photos WHERE duplicate=:id", {
replacements: photo,
@ -443,22 +441,100 @@ router.delete("/:id?", function(req, res/*, next*/) {
});
});
});
}).then(function() {
return false;
});
}).then(function(sent) {
if (sent) {
return;
}
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 || "*"
};
if (!req.params.id && !req.query.permanent) {
return res.status(400).send("Trash can only be emptied if permanent.");
}
let where = "";
if (req.params.id) {
where = "photos.id=:id";
if (req.query.permanent) {
where += " AND photos.deleted=1";
}
} else {
where = "photos.deleted=1";
}
console.log("DELETE /" + replacements.id, req.query);
let sent = false;
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 " + where, {
replacements: replacements,
type: photoDB.Sequelize.QueryTypes.SELECT,
raw: true
}).then(function(photos) {
if (photos.length == 0) {
sent = true;
return res.status(404).send("Unable to find photo " + replacements.id);
}
if (!req.query.permanent) {
return photoDB.sequelize.query("UPDATE photos SET deleted=1,updated=CURRENT_TIMESTAMP WHERE id=:id", {
replacements: replacements
}).then(function() {
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
*/
return Task().then(function(task) {
task.remaining = photos.length;
task.eta = -1;
sent = true;
res.status(200).send({
id: req.params.id,
task: task,
permanent: (req.query && req.query.permanent) || false
});
return Promise.mapSeries(photos, function(photo) {
let lastStamp = Date.now();
return deletePhoto(photo).then(function() {
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) {
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;