const axios = require('axios'); const uuid = require('uuid'); const fs = require('fs/promises'); const {createWriteStream} = require('fs'); const path = require('path'); const gm = require('gm'); module.exports = Self => { Self.remoteMethod('download', { description: 'Processes the image download queue', accessType: 'WRITE', http: { path: `/download`, verb: 'POST' } }); Self.download = async() => { const models = Self.app.models; const tempContainer = await models.TempContainer.container('salix-image'); const tempPath = path.join(tempContainer.client.root, tempContainer.name); const maxAttempts = 3; const collectionName = 'catalog'; const tx = await Self.beginTransaction({}); let tempFilePath; let queueRow; try { const myOptions = {transaction: tx}; queueRow = await Self.findOne({ fields: [ 'id', 'itemFk', 'url', 'attempts' ], where: { url: {neq: null}, attempts: { lt: maxAttempts } }, order: 'priority, attempts, updated' }, myOptions); if (!queueRow) return; const collection = await models.ImageCollection.findOne({ fields: [ 'id', 'maxWidth', 'maxHeight', 'model', 'property' ], where: {name: collectionName}, include: { relation: 'sizes', scope: { fields: ['width', 'height', 'crop'] } } }, myOptions); const fileName = `${uuid.v4()}.png`; tempFilePath = path.join(tempPath, fileName); // Insert image row await models.Image.create({ name: fileName, collectionFk: collectionName, updated: Date.vnNow() }, myOptions); // Update item const model = models[collection.model]; if (!model) throw new Error('No matching model found'); const item = await model.findById(queueRow.itemFk, null, myOptions); if (item) { await item.updateAttribute( collection.property, fileName, myOptions ); } // Download remote image const response = await axios.get(queueRow.url, { responseType: 'stream', }); const writeStream = createWriteStream(tempFilePath); await new Promise((resolve, reject) => { writeStream.on('open', () => response.data.pipe(writeStream)); writeStream.on('finish', () => resolve()); writeStream.on('error', error => reject(error)); }); // Resize const container = await models.ImageContainer.container(collectionName); const rootPath = container.client.root; const collectionDir = path.join(rootPath, collectionName); // To max size const {maxWidth, maxHeight} = collection; const fullSizePath = path.join(collectionDir, 'full'); const toFullSizePath = `${fullSizePath}/${fileName}`; await fs.mkdir(fullSizePath, {recursive: true}); await new Promise((resolve, reject) => { gm(tempFilePath) .resize(maxWidth, maxHeight, '>') .setFormat('png') .write(toFullSizePath, function(err) { if (err) reject(err); if (!err) resolve(); }); }); // To collection sizes for (const size of collection.sizes()) { const {width, height} = size; const sizePath = path.join(collectionDir, `${width}x${height}`); const toSizePath = `${sizePath}/${fileName}`; await fs.mkdir(sizePath, {recursive: true}); await new Promise((resolve, reject) => { const gmInstance = gm(tempFilePath); if (size.crop) { gmInstance .resize(width, height, '^') .gravity('Center') .crop(width, height); } if (!size.crop) gmInstance.resize(width, height, '>'); gmInstance .setFormat('png') .write(toSizePath, function(err) { if (err) reject(err); if (!err) resolve(); }); }); } if (await fs.stat(tempFilePath)) await fs.unlink(tempFilePath); await queueRow.destroy(myOptions); // Restart queue Self.download(); await tx.commit(); } catch (error) { await tx.rollback(); if (queueRow.attempts < maxAttempts) { await queueRow.updateAttributes({ error: error, attempts: queueRow.attempts + 1, updated: Date.vnNew() }); } try { await fs.unlink(tempFilePath); } catch (error) {} Self.download(); } }; };