refs #5553 Image scrubing method improved
gitea/salix/pipeline/head This commit looks good Details

Remove & dryRun options, check that collection exists, performance improved, exclusive lock
This commit is contained in:
Juan Ferrer 2023-04-17 18:19:25 +02:00
parent 0423fac88c
commit 378400c6d2
2 changed files with 105 additions and 36 deletions

View File

@ -1,9 +1,10 @@
const fs = require('fs-extra'); const fs = require('fs-extra');
const path = require('path'); const path = require('path');
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => { module.exports = Self => {
Self.remoteMethod('scrub', { Self.remoteMethod('scrub', {
description: 'Cleans collection images directory', description: 'Deletes images without database reference',
accessType: 'WRITE', accessType: 'WRITE',
accepts: [ accepts: [
{ {
@ -11,14 +12,22 @@ module.exports = Self => {
type: 'string', type: 'string',
description: 'The collection name', description: 'The collection name',
required: true required: true
}, {
arg: 'remove',
type: 'boolean',
description: 'Delete instead of move images to trash'
}, { }, {
arg: 'limit', arg: 'limit',
type: 'number', type: 'integer',
description: 'Maximun number of image to clean' description: 'Maximum number of images to clean'
}, {
arg: 'dryRun',
type: 'boolean',
description: 'Simulate actions'
} }
], ],
returns: { returns: {
type: 'number', type: 'integer',
root: true root: true
}, },
http: { http: {
@ -27,13 +36,35 @@ module.exports = Self => {
} }
}); });
Self.scrub = async function(collection, limit) { Self.scrub = async function(collection, remove, limit, dryRun) {
const models = Self.app.models; const $ = Self.app.models;
const container = await models.ImageContainer.container(
collection const env = process.env.NODE_ENV;
); dryRun = dryRun || (env && env !== 'production');
const instance = await $.ImageCollection.findOne({
fields: ['id'],
where: {name: collection}
});
if (!instance)
throw new UserError('Collection does not exist');
const container = await $.ImageContainer.container(collection);
const rootPath = container.client.root; const rootPath = container.client.root;
const conn = await getConnection();
const lockName = 'salix.Image.scrub';
let lockObtained;
try {
const [row] = await query(conn,
`SELECT GET_LOCK(?, 0) hasLock`,
[lockName]
);
lockObtained = !!row.hasLock;
if (!lockObtained)
throw new UserError('Cannot obtain exclusive lock');
const now = Date.vnNew().toJSON(); const now = Date.vnNew().toJSON();
const scrubDir = path.join(rootPath, '.scrub', now); const scrubDir = path.join(rootPath, '.scrub', now);
@ -47,18 +78,29 @@ module.exports = Self => {
const images = await fs.readdir(sizeDir); const images = await fs.readdir(sizeDir);
for (const image of images) { for (const image of images) {
const imageName = path.parse(image).name; const imageName = path.parse(image).name;
const count = await models.Image.count({ const count = await Self.count({
collectionFk: collection, collectionFk: collection,
name: imageName name: imageName
}); });
const exists = count > 0; const exists = count > 0;
let scrubDirCreated = false;
if (!exists) { if (!exists) {
cleanCount++; const srcFile = path.join(sizeDir, image);
const srcDir = path.join(sizeDir, image); if (remove !== true) {
const dstDir = path.join(scrubSizeDir, image); if (!scrubDirCreated) {
if (!dryRun)
await fs.mkdir(scrubSizeDir, {recursive: true}); await fs.mkdir(scrubSizeDir, {recursive: true});
await fs.rename(srcDir, dstDir); scrubDirCreated = true;
}
const dstFile = path.join(scrubSizeDir, image);
if (!dryRun)
await fs.rename(srcFile, dstFile);
} else {
if (!dryRun)
await fs.unlink(srcFile);
}
cleanCount++;
if (limit && cleanCount == limit) if (limit && cleanCount == limit)
break mainLoop; break mainLoop;
} }
@ -66,5 +108,30 @@ module.exports = Self => {
} }
return cleanCount; return cleanCount;
} finally {
if (lockObtained)
await query(conn, `DO RELEASE_LOCK(?)`, [lockName]);
conn.release();
}
// Promisified datasource functions
function getConnection() {
return new Promise((resolve, reject) => {
Self.dataSource.connector.client.getConnection((err, conn) => {
if (err) return reject(err);
resolve(conn);
});
});
}
function query(conn, sql, params) {
return new Promise((resolve, reject) => {
conn.query(sql, params, (err, res) => {
if (err) return reject(err);
resolve(res);
});
});
}
}; };
}; };

View File

@ -271,5 +271,7 @@
"This locker has already been assigned": "Esta taquilla ya ha sido asignada", "This locker has already been assigned": "Esta taquilla ya ha sido asignada",
"Tickets with associated refunds": "No se pueden borrar tickets con abonos asociados. Este ticket está asociado al abono Nº {{id}}", "Tickets with associated refunds": "No se pueden borrar tickets con abonos asociados. Este ticket está asociado al abono Nº {{id}}",
"Not exist this branch": "La rama no existe", "Not exist this branch": "La rama no existe",
"This ticket cannot be signed because it has not been boxed": "Este ticket no puede firmarse porque no ha sido encajado" "This ticket cannot be signed because it has not been boxed": "Este ticket no puede firmarse porque no ha sido encajado",
"Collection does not exist": "La colección no existe",
"Cannot obtain exclusive lock": "No se puede obtener un bloqueo exclusivo"
} }