const fs = require('fs-extra'); const path = require('path'); const UserError = require('vn-loopback/util/user-error'); module.exports = Self => { Self.remoteMethod('scrub', { description: 'Deletes images without database reference', accessType: 'WRITE', accepts: [ { arg: 'collection', type: 'string', description: 'The collection name', required: true }, { arg: 'remove', type: 'boolean', description: 'Delete instead of move images to trash' }, { arg: 'limit', type: 'integer', description: 'Maximum number of images to clean' }, { arg: 'dryRun', type: 'boolean', description: 'Simulate actions' }, { arg: 'skipLock', type: 'boolean', description: 'Wether to skip exclusive lock' } ], returns: { type: 'integer', root: true }, http: { path: `/scrub`, verb: 'POST' } }); Self.scrub = async function(collection, remove, limit, dryRun, skipLock) { const $ = Self.app.models; 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; let tx; let opts; const lockName = 'salix.Image.scrub'; if (!skipLock) { tx = await Self.beginTransaction({timeout: null}); opts = {transaction: tx}; const [row] = await Self.rawSql( `SELECT GET_LOCK(?, 10) hasLock`, [lockName], opts); if (!row.hasLock) throw new UserError('Cannot obtain exclusive lock'); } try { const now = Date.vnNew().toJSON(); const scrubDir = path.join(rootPath, '.scrub', now); const collectionDir = path.join(rootPath, collection); const sizes = await fs.readdir(collectionDir); let cleanCount = 0; mainLoop: for (const size of sizes) { const sizeDir = path.join(collectionDir, size); const scrubSizeDir = path.join(scrubDir, collection, size); const images = await fs.readdir(sizeDir); for (const image of images) { const imageName = path.parse(image).name; const count = await Self.count({ collectionFk: collection, name: imageName }, opts); const exists = count > 0; let scrubDirCreated = false; if (!exists) { const srcFile = path.join(sizeDir, image); if (remove !== true) { if (!scrubDirCreated) { if (!dryRun) await fs.mkdir(scrubSizeDir, {recursive: true}); scrubDirCreated = true; } const dstFile = path.join(scrubSizeDir, image); if (!dryRun) await fs.rename(srcFile, dstFile); } else { try { if (!dryRun) await fs.unlink(srcFile); } catch (err) { console.error(err.message); } } cleanCount++; if (limit && cleanCount == limit) break mainLoop; } } } return cleanCount; } finally { if (!skipLock) { try { await Self.rawSql(`DO RELEASE_LOCK(?)`, [lockName], opts); await tx.rollback(); } catch (err) { console.error(err.message); } } } }; };