You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
nodebb/src/posts/uploads.js

131 lines
4.1 KiB
JavaScript

'use strict';
var async = require('async');
var nconf = require('nconf');
var crypto = require('crypto');
var fs = require('fs');
var path = require('path');
var winston = require('winston');
var db = require('../database');
const image = require('../image');
module.exports = function (Posts) {
Posts.uploads = {};
const md5 = filename => crypto.createHash('md5').update(filename).digest('hex');
const pathPrefix = path.join(nconf.get('upload_path'), 'files');
7 years ago
const searchRegex = /\/assets\/uploads\/files\/([^\s")]+\.?[\w]*)/g;
Posts.uploads.sync = async function (pid) {
// Scans a post's content and updates sorted set of uploads
const [content, currentUploads] = await Promise.all([
Posts.getPostField(pid, 'content'),
Posts.uploads.list(pid),
]);
// Extract upload file paths from post content
let match = searchRegex.exec(content);
const uploads = [];
while (match) {
uploads.push(match[1].replace('-resized', ''));
match = searchRegex.exec(content);
}
// Create add/remove sets
const add = uploads.filter(path => !currentUploads.includes(path));
const remove = currentUploads.filter(path => !uploads.includes(path));
await Promise.all([
Posts.uploads.associate(pid, add),
Posts.uploads.dissociate(pid, remove),
]);
};
Posts.uploads.list = async function (pid) {
return await db.getSortedSetRange('post:' + pid + ':uploads', 0, -1);
};
Posts.uploads.listWithSizes = async function (pid) {
Async refactor in place (#7736) * feat: allow both callback&and await * feat: ignore async key * feat: callbackify and promisify in same file * Revert "feat: callbackify and promisify in same file" This reverts commit cea206a9b8e6d8295310074b18cc82a504487862. * feat: no need to store .callbackify * feat: change getTopics to async * feat: remove .async * fix: byScore * feat: rewrite topics/index and social with async/await * fix: rewrite topics/data.js fix issue with async.waterfall, only pass result if its not undefined * feat: add callbackify to redis/psql * feat: psql use await * fix: redis :volcano: * feat: less returns * feat: more await rewrite * fix: redis tests * feat: convert sortedSetAdd rewrite psql transaction to async/await * feat: :dog: * feat: test * feat: log client and query * feat: log bind * feat: more logs * feat: more logs * feat: check perform * feat: dont callbackify transaction * feat: remove logs * fix: main functions * feat: more logs * fix: increment * fix: rename * feat: remove cls * fix: remove console.log * feat: add deprecation message to .async usage * feat: update more dbal methods * fix: redis :voodoo: * feat: fix redis zrem, convert setObject * feat: upgrade getObject methods * fix: psql getObjectField * fix: redis tests * feat: getObjectKeys * feat: getObjectValues * feat: isObjectField * fix: add missing return * feat: delObjectField * feat: incrObjectField * fix: add missing await * feat: remove exposed helpers * feat: list methods * feat: flush/empty * feat: delete * fix: redis delete all * feat: get/set * feat: incr/rename * feat: type * feat: expire * feat: setAdd * feat: setRemove * feat: isSetMember * feat: getSetMembers * feat: setCount, setRemoveRandom * feat: zcard,zcount * feat: sortedSetRank * feat: isSortedSetMember * feat: zincrby * feat: sortedSetLex * feat: processSortedSet * fix: add mising await * feat: debug psql * fix: psql test * fix: test * fix: another test * fix: test fix * fix: psql tests * feat: remove logs * feat: user arrow func use builtin async promises * feat: topic bookmarks * feat: topic.delete * feat: topic.restore * feat: topics.purge * feat: merge * feat: suggested * feat: topics/user.js * feat: topics modules * feat: topics/follow * fix: deprecation msg * feat: fork * feat: topics/posts * feat: sorted/recent * feat: topic/teaser * feat: topics/tools * feat: topics/unread * feat: add back node versions disable deprecation notice wrap async controllers in try/catch * feat: use db directly * feat: promisify in place * fix: redis/psql * feat: deprecation message logs for psql * feat: more logs * feat: more logs * feat: logs again * feat: more logs * fix: call release * feat: restore travis, remove logs * fix: loops * feat: remove .async. usage
6 years ago
const paths = await Posts.uploads.list(pid);
const sizes = await db.getObjects(paths.map(path => 'upload:' + md5(path))) || [];
return sizes.map((sizeObj, idx) => ({
...sizeObj,
name: paths[idx],
}));
};
Posts.uploads.isOrphan = async function (filePath) {
const length = await db.sortedSetCard('upload:' + md5(filePath) + ':pids');
return length === 0;
};
Posts.uploads.getUsage = async function (filePaths) {
// Given an array of file names, determines which pids they are used in
if (!Array.isArray(filePaths)) {
filePaths = [filePaths];
}
const keys = filePaths.map(fileObj => 'upload:' + md5(fileObj.name.replace('-resized', '')) + ':pids');
return await Promise.all(keys.map(k => db.getSortedSetRange(k, 0, -1)));
};
Posts.uploads.associate = async function (pid, filePaths) {
// Adds an upload to a post's sorted set of uploads
filePaths = !Array.isArray(filePaths) ? [filePaths] : filePaths;
if (!filePaths.length) {
return;
}
filePaths = await async.filter(filePaths, function (filePath, next) {
// Only process files that exist
fs.access(path.join(pathPrefix, filePath), fs.constants.F_OK | fs.constants.R_OK, function (err) {
next(null, !err);
});
});
const now = Date.now();
const scores = filePaths.map(() => now);
const bulkAdd = filePaths.map(path => ['upload:' + md5(path) + ':pids', now, pid]);
await Promise.all([
db.sortedSetAdd('post:' + pid + ':uploads', scores, filePaths),
db.sortedSetAddBulk(bulkAdd),
Posts.uploads.saveSize(filePaths),
]);
};
Posts.uploads.dissociate = async function (pid, filePaths) {
// Removes an upload from a post's sorted set of uploads
filePaths = !Array.isArray(filePaths) ? [filePaths] : filePaths;
if (!filePaths.length) {
return;
}
const bulkRemove = filePaths.map(path => ['upload:' + md5(path) + ':pids', pid]);
await Promise.all([
db.sortedSetRemove('post:' + pid + ':uploads', filePaths),
db.sortedSetRemoveBulk(bulkRemove),
]);
};
Posts.uploads.dissociateAll = async (pid) => {
const current = await Posts.uploads.list(pid);
await Promise.all(current.map(async path => await Posts.uploads.dissociate(pid, path)));
};
Posts.uploads.saveSize = async (filePaths) => {
await Promise.all(filePaths.map(async function (fileName) {
try {
const size = await image.size(path.join(pathPrefix, fileName));
winston.verbose('[posts/uploads/' + fileName + '] Saving size');
await db.setObject('upload:' + md5(fileName), {
width: size.width,
height: size.height,
});
} catch (err) {
winston.error('[posts/uploads] Error while saving post upload sizes (' + fileName + '): ' + err.message);
}
}));
};
};