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(); }); }); } try { await fs.unlink(tempFilePath); } catch (error) { } 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(); } }; };