salix/back/models/image.js

195 lines
6.9 KiB
JavaScript

const fs = require('fs-extra');
const path = require('path');
const gm = require('gm');
const crypto = require('crypto');
const {models} = require('vn-loopback/server/server');
const PNG = 'png';
const FORMAT = PNG;
const DEFAULT_GRAVITY = 'Center';
const DEFAULT_QUALITY = 100;
const SIZE_UNIT = 'x';
const formatWidthHeight = (width, height) => `${width}${SIZE_UNIT}${height}`;
const parseSize = value => formatWidthHeight(value.width, value.height).split(SIZE_UNIT);
module.exports = Self => {
require('../methods/image/download')(Self);
require('../methods/image/upload')(Self);
require('../methods/image/scrub')(Self);
Self.handleFolderDestination = async fileName => {
// Insert image row
const {name} = path.parse(fileName);
const hash = crypto.createHash('sha1').update(name).digest('hex');
const pairs = hash.match(/(..?)/g);
const {dirLevels} = await models.ImageConfig.findOne({
fields: ['dirLevels'],
});
const dstDir = pairs.slice(0, dirLevels).reverse().join('/');
return {name, dstDir};
};
Self.getCollection = async collectionName => {
const collection = await models.ImageCollection.findOne({
fields: ['id', 'maxWidth', 'maxHeight', 'model', 'property'],
where: {name: collectionName},
include: {
relation: 'sizes',
scope: {
fields: ['width', 'height', 'crop'],
},
},
});
return collection;
};
Self.getCollectionDir = async collectionName => {
const container = await models.ImageContainer.container(collectionName);
const rootPath = container.client.root;
const collectionDir = path.join(rootPath, collectionName);
return collectionDir;
};
Self.getFullSizePath = (fileName, collectionDir, dstDir) => {
try {
const fullSizePath = path.join(collectionDir, `full/${dstDir}`);
const fullSizeOriginalPath = path.join(collectionDir, `full`);
const toFullSizePath = `${fullSizePath}/${fileName}`;
const toFullSizeOriginalPath = `${fullSizeOriginalPath}/${fileName}`;
return {
fullSizePath,
toFullSizePath,
toFullSizeOriginalPath
};
} catch (e) {
throw new Error(e);
}
};
Self.removeLink = async(child, parent = null) => {
try {
await fs.unlink(child);
await fs.symlink(parent, child);
} catch (e) {
throw new Error(e);
}
};
Self.createLink = async(parent, child = null) => {
try {
const exists = await fs.exists(parent);
exists && await fs.unlink(parent);
await fs.symlink(child, parent);
} catch (e) {
throw new Error(e);
}
};
Self.resize = async function({
collectionName,
srcFile,
fileName,
entityId,
}) {
const {name, dstDir} = await Self.handleFolderDestination(fileName);
try {
const baseUpsert = {
name,
collectionFk: collectionName,
};
await models.Image.upsertWithWhere(baseUpsert,
{
...baseUpsert,
updated: Date.vnNow() / 1000,
});
const collection = await Self.getCollection(collectionName);
const {maxWidth, maxHeight} = collection;
// Update entity image file name
const model = models[collection.model];
if (!model) throw new Error('No matching model found');
const entity = await model.findById(entityId);
if (entity)
await entity.updateAttribute(collection.property, name);
// Resize
const collectionDir = await Self.getCollectionDir(collectionName);
// To max size
const _fullSizePath = Self.getFullSizePath(fileName, collectionDir, dstDir);
const {fullSizePath, toFullSizePath, toFullSizeOriginalPath} = _fullSizePath;
await fs.mkdir(fullSizePath, {recursive: true});
const gmInstance = gm(srcFile);
let fileWidth = null;
let fileHeight = null;
gmInstance.size(function(err, size) {
if (err) throw new Error(err);
[fileWidth, fileHeight] = parseSize(size);
});
await new Promise((resolve, reject) => {
gmInstance
.resize(maxWidth, maxHeight, '>')
.setFormat(PNG)
.quality(DEFAULT_QUALITY)
.write(toFullSizePath + `.${FORMAT}`, function(err, data) {
if (err) reject(err);
if (!err) resolve(data);
});
});
await Self.createLink(toFullSizeOriginalPath, toFullSizePath);
// To collection sizes
for (const size of collection.sizes()) {
const [width, height] = parseSize(size);
const sizePath = path.join(
collectionDir,
`${formatWidthHeight(width, height)}/${dstDir}`
);
try {
await fs.mkdir(sizePath, {recursive: true});
} catch (e) {
throw new Error(e);
}
const toSizePath = `${sizePath}/${fileName}`;
if (+fileWidth < +width && +fileHeight < +height) {
await new Promise((resolve, reject) => {
if (size.crop) {
gmInstance
.resize(width, height, '^')
.gravity(DEFAULT_GRAVITY)
.crop(width, height).res(function(err, data) {
if (err) reject(err);
if (!err) resolve(data);
});
} else gmInstance.resize(width, height, '>');
gmInstance
.setFormat(PNG)
.quality(DEFAULT_QUALITY)
.write(toSizePath + `.${FORMAT}`, function(err, data) {
if (err) reject(err);
if (!err) resolve(data);
});
});
}
const sizeOriginalPath = path.join(
collectionDir,
formatWidthHeight(width, height)
);
const toSizeOriginalPath = `${sizeOriginalPath}/${fileName}`;
await Self.createLink(toSizeOriginalPath, toSizePath);
}
} catch (e) {
throw new Error(e);
}
};
};