Skip to content

Commit 4aa8936

Browse files
committed
Complete file processing overhaul
1 parent 2bfc033 commit 4aa8936

File tree

4 files changed

+156
-42
lines changed

4 files changed

+156
-42
lines changed

lib/filehandler.js

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,18 @@ module.exports = function (middleware, options, callback) {
66
'Access-Control-Allow-Methods': options.accessControl.allowMethods
77
});
88
var UploadHandler = require('./uploadhandler')(options);
9-
var handler = new UploadHandler(req, res, callback || function (result, redirect) {
10-
files = {files: result};
9+
var handler = new UploadHandler(req, res, callback || function (result, files, redirect) {
10+
var data = { files: result };
1111
if (redirect) {
12-
res.redirect(redirect.replace(/%s/, encodeURIComponent(JSON.stringify(files))));
12+
res.redirect(redirect.replace(/%s/, encodeURIComponent(JSON.stringify(data))));
1313
} else {
1414
res.set({
1515
'Content-Type': (req.headers.accept || '').indexOf('application/json') !== -1
1616
? 'application/json'
1717
: 'text/plain'
1818
});
1919
if (req.method == 'HEAD') return res.send(200);
20-
res.json(200, files);
20+
res.json(200, data);
2121
}
2222
});
2323

@@ -36,6 +36,15 @@ module.exports = function (middleware, options, callback) {
3636
handler.on('delete', function (fileName) {
3737
middleware.emit('delete', fileName);
3838
});
39+
handler.on('file', function (fileInfo) {
40+
middleware.emit('file', fileInfo);
41+
});
42+
handler.on('image', function (fileInfo) {
43+
middleware.emit('image', fileInfo);
44+
});
45+
handler.on('processed', function (fileInfo, files) {
46+
middleware.emit('processed', fileInfo, files);
47+
});
3948

4049
switch (req.method) {
4150
case 'OPTIONS':

lib/fileinfo.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
var fs = require('fs'),
2-
_ = require('lodash');
2+
_ = require('lodash'),
3+
fileNameRegexp = /(?:(?:\-([\d]+))?(\.[^.]+))?$/;
34

45
module.exports = function (options) {
56

@@ -27,7 +28,7 @@ module.exports = function (options) {
2728
this.name = require('path').basename(this.name).replace(/^\.+/, '');
2829
// Prevent overwriting existing files:
2930
while (fs.existsSync(options.baseDir() + '/' + this.name)) {
30-
this.name = this.name.replace(/(?:(?:\-([\d]+))?(\.[^.]+))?$/, function (s, index, ext) {
31+
this.name = this.name.replace(fileNameRegexp, function (s, index, ext) {
3132
return '-' + ((parseInt(index, 10) || 0) + 1) + (ext || '');
3233
});
3334
}
@@ -37,6 +38,10 @@ module.exports = function (options) {
3738
var key = type ? type + '_url' : 'url';
3839
this[key] = baseUrl + '/' + encodeURIComponent(this.name);
3940
}
41+
42+
FileInfo.prototype.toResponse = function() {
43+
return _.omit(this, 'processedFiles', 'metadata');
44+
}
4045

4146
return FileInfo;
4247
};

lib/uploadhandler.js

Lines changed: 134 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,18 @@ var EventEmitter = require('events').EventEmitter,
44
formidable = require('formidable'),
55
imageMagick = require('imagemagick'),
66
mkdirp = require('mkdirp'),
7+
async = require('async'),
78
_ = require('lodash');
9+
10+
var convertArgs = [
11+
'srcPath', 'srcData', 'srcFormat',
12+
'dstPath', 'quality', 'format',
13+
'progressive', 'colorspace',
14+
'width', 'height',
15+
'strip', 'filter',
16+
'sharpening', 'customArgs',
17+
'timeout', 'gravity'
18+
];
819

920
module.exports = function (options) {
1021

@@ -59,17 +70,19 @@ module.exports = function (options) {
5970
tmpFiles = [],
6071
files = [],
6172
map = {},
62-
counter = 1,
6373
redirect,
64-
finish = _.bind(function () {
74+
counter = 1,
75+
finish = function() {
6576
if (!--counter) {
77+
var data = [];
6678
_.each(files, function (fileInfo) {
67-
this.initUrls(fileInfo);
79+
this.initUrls(fileInfo, true);
6880
this.emit('end', fileInfo);
81+
data.push(fileInfo.toResponse());
6982
}, this);
70-
this.callback(files, redirect);
83+
this.callback(data, files, redirect);
7184
}
72-
}, this);
85+
}.bind(this);
7386

7487
this.noCache();
7588

@@ -89,51 +102,36 @@ module.exports = function (options) {
89102
}
90103
})
91104
.on('file', function (name, file) {
92-
var fileInfo = map[path.basename(file.path)];
105+
var mapKey = path.basename(file.path);
106+
var fileInfo = map[mapKey];
93107
if (fs.existsSync(file.path)) {
94108
fileInfo.size = file.size;
95109
if (!fileInfo.validate()) {
96110
fs.unlink(file.path);
97111
return;
112+
} else {
113+
counter++;
98114
}
99-
100-
var generatePreviews = function () {
101-
if (options.imageTypes.test(fileInfo.name)) {
102-
_.each(options.imageVersions, function (value, version) {
103-
// creating directory recursive
104-
if (!fs.existsSync(options.uploadDir() + '/' + version + '/'))
105-
mkdirp.sync(options.uploadDir() + '/' + version + '/');
106-
107-
counter++;
108-
var opts = options.imageVersions[version];
109-
imageMagick.resize({
110-
width: opts.width,
111-
height: opts.height,
112-
srcPath: options.uploadDir() + '/' + fileInfo.name,
113-
dstPath: options.uploadDir() + '/' + version + '/' + fileInfo.name,
114-
customArgs: opts.imageArgs || ['-auto-orient']
115-
}, finish);
116-
});
117-
}
115+
116+
var handledFile = function(err, fileInfo, processedFiles) {
117+
fileInfo.processedFiles = processedFiles || [];
118+
finish();
118119
}
119120

120-
if (!fs.existsSync(options.uploadDir() + '/'))
121-
mkdirp.sync(options.uploadDir() + '/');
122-
123-
counter++;
121+
if (!fs.existsSync(options.uploadDir() + '/')) mkdirp.sync(options.uploadDir() + '/');
122+
124123
fs.rename(file.path, options.uploadDir() + '/' + fileInfo.name, function (err) {
125124
if (!err) {
126-
generatePreviews();
127-
finish();
125+
self.processFile(fileInfo, handledFile);
128126
} else {
129127
var is = fs.createReadStream(file.path);
130128
var os = fs.createWriteStream(options.uploadDir() + '/' + fileInfo.name);
131129
is.on('end', function (err) {
132130
if (!err) {
133131
fs.unlinkSync(file.path);
134-
generatePreviews();
132+
return self.processFile(fileInfo, handledFile);
135133
}
136-
finish();
134+
handledFile(fileInfo, []);
137135
});
138136
is.pipe(os);
139137
}
@@ -178,16 +176,117 @@ module.exports = function (options) {
178176
}
179177
};
180178

181-
UploadHandler.prototype.initUrls = function (fileInfo) {
179+
UploadHandler.prototype.initUrls = function (fileInfo, noCheck) {
182180
var baseUrl = (options.ssl ? 'https:' : 'http:') + '//' + (options.hostname || this.req.get('Host'));
183181
fileInfo.setUrl(null, baseUrl + options.uploadUrl());
184182
fileInfo.setUrl('delete', baseUrl + this.req.originalUrl);
185183
_.each(options.imageVersions, function (value, version) {
186-
if (fs.existsSync(options.uploadDir() + '/' + version + '/' + fileInfo.name)) {
184+
if (noCheck || fs.existsSync(options.uploadDir() + '/' + version + '/' + fileInfo.name)) {
187185
fileInfo.setUrl(version, baseUrl + options.uploadUrl() + '/' + version);
188186
}
189187
}, this);
190188
};
189+
190+
UploadHandler.prototype.processFile = function(fileInfo, processOpts, callback) {
191+
if (_.isFunction(processOpts)) {
192+
callback = processOpts;
193+
processOpts = _.extend({}, options); // use global options
194+
}
195+
var self = this;
196+
var files = [];
197+
var uploadDir = _.result(processOpts, 'uploadDir');
198+
var srcPath = uploadDir + '/' + fileInfo.name;
199+
var isImage = processOpts.imageTypes && processOpts.imageTypes.test(fileInfo.name);
200+
var commands = [];
201+
fileInfo.metadata = {};
202+
203+
// File metadata
204+
if (processOpts.identify) {
205+
if (isImage) {
206+
commands.push(function(next) {
207+
imageMagick.identify(srcPath, function(err, features) {
208+
fileInfo.metadata = err ? {} : features;
209+
fileInfo.metadata.fromOriginal = true;
210+
next();
211+
});
212+
});
213+
} // could add generic file identify fn here
214+
}
215+
216+
// Generic processing, after images have been processed
217+
_.each([].concat(processOpts.process || []), function(cmd) {
218+
commands.push(function(next) {
219+
cmd.call(null, fileInfo, srcPath, function(err, result) {
220+
var info = _.extend({}, fileInfo, { srcPath: srcPath, result: result });
221+
if (err && !info.error) info.error = err;
222+
if (_.isObject(result) && result instanceof FileInfo) {
223+
files.push(result);
224+
}
225+
next(info.error);
226+
});
227+
});
228+
});
229+
230+
// Image processing
231+
if (isImage) {
232+
commands.push(function(next) {
233+
async.mapSeries(_.keys(processOpts.imageVersions || {}), function (version, done) {
234+
var identify = processOpts.identify;
235+
var dstPath = uploadDir + '/' + version + '/' + fileInfo.name;
236+
var cb = function(err) {
237+
var args = arguments;
238+
var info = _.extend({}, fileInfo, {
239+
srcPath: srcPath, dstPath: dstPath, version: version
240+
});
241+
if (err) info.error = err;
242+
if (!err && identify) {
243+
imageMagick.identify(dstPath, function(err, features) {
244+
info.metadata = err ? {} : features;
245+
info.metadata.fromOriginal = false;
246+
files.push(info);
247+
done.apply(null, args);
248+
});
249+
} else {
250+
files.push(info);
251+
done.apply(null, args);
252+
}
253+
};
254+
255+
var process = function(err) {
256+
if (err) return cb(err);
257+
var opts = processOpts.imageVersions[version] || {};
258+
if (_.isObject(fileInfo.error)) {
259+
cb(fileInfo.error);
260+
} else if (_.isFunction(opts)) {
261+
opts.call(imageMagick, fileInfo, srcPath, dstPath, cb);
262+
} else if (_.isArray(opts)) { // raw imagemagick convert
263+
imageMagick.convert(opts, cb);
264+
} else if (_.isObject(opts)) {
265+
identify = (identify || opts.identify) && opts.identify !== false;
266+
var m = opts.crop ? 'crop' : 'resize';
267+
var args = _.pick(opts, convertArgs);
268+
args.srcPath = args.srcPath || srcPath;
269+
args.dstPath = args.dstPath || dstPath;
270+
args.customArgs = args.customArgs || opts.imageArgs || ['-auto-orient'];
271+
imageMagick[m](args, cb);
272+
} else {
273+
cb(new Error('Invalid image version config: ' + version));
274+
}
275+
}
276+
277+
var versionDir = uploadDir + '/' + version + '/';
278+
fs.exists(versionDir, function(exists) {
279+
exists ? process() : mkdirp(versionDir, process);
280+
});
281+
}, next);
282+
});
283+
}
284+
285+
async.series(commands, function(err) {
286+
if (!err) self.emit('processed', fileInfo, files);
287+
callback(err, fileInfo, files);
288+
});
289+
};
191290

192291
return UploadHandler;
193292
}

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
"formidable": ">=1.0.11",
1414
"imagemagick": ">=0.1.2",
1515
"lodash": ">= 0.9.2",
16-
"mkdirp": ">= 0.3.4"
16+
"mkdirp": ">= 0.3.4",
17+
"async": ">= 0.2.9"
1718
},
1819
"engines": {
1920
"node": ">= 0.8.8"

0 commit comments

Comments
 (0)