Permalink
Browse files

brought up-to-spec with README and tested

  • Loading branch information...
1 parent 8a8d712 commit 75dd32f17d06acb77b6ebabc0b9edfc79e02ea2f @coolaj86 committed Nov 12, 2012
Showing with 147 additions and 65 deletions.
  1. +2 −5 README.md
  2. +3 −1 cryptostream.js
  3. +43 −23 example.js
  4. +98 −36 good-form.js → formaline.js
  5. +1 −0 test.sh
View
@@ -48,16 +48,13 @@ so change it if the system's default tmp is a different partition than your dest
An array of hashes that should be performed on each file (fields are excluded).
The hash will be attached to the file before the `end` event.
-##### fieldNames
-
-List the fields and or files you expect here and they'll be prepopulated with empty arrays if not submitted.
-
##### arrayFields
When `end` fires it hands back a map for both `fields` and `files`,
all of which are assumed to be arrays by default (as per the HTTP spec).
-However, if `arrayFields` is an array of field names, two special things happen:
+However, if `arrayFields` is an array of field names (or an empty array),
+two special things happen:
1. All of the fields listed will always return an array, even if it's empty
View
@@ -30,11 +30,13 @@
this._hash.update(chunk);
return true;
};
+ CryptoStream.prototype.digest = function (str) {
+ return this._hash.digest(str);
+ };
CryptoStream.prototype.end = function (bytes, enc) {
if (bytes) {
this.write(bytes, enc);
}
- this.digest = this._hash.digest('hex');
this.emit('end');
};
View
@@ -1,19 +1,29 @@
-/*jshint strict:true node:true es5:true onevar:true laxcomma:true laxbreak:true eqeqeq:true immed:true latedef:true*/
+/*jshint strict:true node:true es5:true onevar:true laxcomma:true laxbreak:true eqeqeq:true immed:true latedef:true unused:true*/
(function () {
"use strict";
var connect = require('connect')
- , GoodForm = require('./good-form').GoodForm
- , UUID = require('node-uuid')
+ , fs = require('fs')
+ , Formaline = require('./formaline').Formaline
, app
, server
;
+ /*
+ function hidePrivates(key, value) {
+ if ('_' === key[0]) {
+ return undefined;
+ }
+ return value;
+ }
+ */
+
app = connect.createServer()
.use(function (req, res, next) {
- var form = GoodForm.create(req)
- , fields = {}
- , files = []
+ var form = Formaline.create(req, {
+ hashes: ['md5', 'sha1']
+ , arrayFields: ['avatar']
+ })
;
if (!form) {
@@ -23,33 +33,43 @@
return;
}
- form.on('progress', function (bytes) {
- //form.total;
+ form.on('progress', function () {
+ console.log((100 * (form.loaded / form.total)).toFixed(2) + '%');
});
- form.on('field', function (key, value, headers) {
- // TODO php-style keyname[] ?
- if (fields.hasOwnProperty(key)) {
- if (!Array.isArray(fields[key])) {
- fields[key] = [fields[key], value];
- } else {
- fields[key].push(value);
- }
- }
+ form.on('field', function (key, value) {
+ console.log(key, value);
});
- form.on('file', function (key, file, headers) {
- // GoodForm will call req.pause() and req.resume()
- form.createPipe(file, '/tmp/' + UUID.v4());
+ form.on('file', function (key, file) {
+ console.log(key, file);
});
- form.on('end', function () {
+ form.on('end', function (fields, files) {
console.log(fields);
console.log(files);
- res.end(JSON.stringify({ "success": true }, null, ' '));
+
+ // TODO this should, of course, use forEachAsync
+ Object.keys(files).forEach(function (key) {
+ var arr = files[key]
+ ;
+
+ arr.forEach(function (file) {
+ fs.unlink(file.path);
+ });
+ });
+
+ res.setHeader('Content-Type', 'application/json');
+ res.end(JSON.stringify({
+ "success": true
+ , "result": {fields: fields, files: files}
+ //}, hidePrivate, ' ') + '\n');
+ }, null, ' ') + '\n');
});
})
;
- app.listen(process.argv[2] || 3000);
+ server = app.listen(process.argv[2] || 3000, function () {
+ console.log('Listening...', server.address());
+ });
}());
@@ -4,7 +4,7 @@
var os = require('os')
, PoorForm = require('poor-form')
- , EventEmitter = require('events').EventEmitter
+ , Stream = require('stream')
, util = require('util')
, cryptostream = require('./cryptostream')
, fs = require('fs')
@@ -17,63 +17,93 @@
};
function startHashing(file, hashes) {
+ //file._hashes = {};
hashes.forEach(function (algo) {
var cs = cryptostream.create(algo)
;
+ //file._hashes[algo] = cs;
file.pipe(cs);
- cs.on('end', function (digest) {
- file[algo] = digest;
+ cs.on('end', function () {
+ file[algo] = cs.digest('hex');
});
});
}
+ /*
function stopHashing(file) {
- file._hashes = undefined;
+ Object.keys(file._hashes, function (algo) {
+ var cs = file._hashes[algo]
+ ;
+
+ if (!file[algo]) {
+ //file.partial = true;
+ file[algo] = cs.digest('hex');
+ }
+ });
}
+ */
- function GoodFile(req, headers) {
- if (!(this instanceof GoodFile)) {
- return new GoodFile(req, headers);
+ function FormFile(req, headers) {
+ if (!(this instanceof FormFile)) {
+ return new FormFile(req, headers);
}
- EventEmitter.call(this);
-
- this._req = req;
+ Object.defineProperty(this, '_events', {
+ enumerable: false
+ , writable: true
+ });
+ Stream.call(this);
// W3C File API
this.size = 0;
- this.length = null; // alias of size
this.type = headers.type;
this.name = headers.filename;
- this.fieldname = headers.name;
this.lastModifiedDate = null;
+
+ //this._req = req;
+ Object.defineProperty(this, '_req', {
+ value: req
+ , enumerable: false
+ });
this.headers = headers;
+ this.fieldname = headers.name;
+ this.length = null; // alias of size
+ // TODO headers should subtract filename, name, type
+
+ // NodeJS Stream API
+ Object.defineProperty(this, 'readable', {
+ value: true
+ , enumerable: false
+ });
+ Object.defineProperty(this, 'writable', {
+ value: false
+ , enumerable: false
+ });
}
- util.inherits(GoodFile, EventEmitter);
- GoodFile.prototype.pause = function () {
+ util.inherits(FormFile, Stream);
+ FormFile.prototype.pause = function () {
this._req.pause();
};
- GoodFile.prototype.resume = function () {
+ FormFile.prototype.resume = function () {
this._req.resume();
};
- GoodFile.create = function (req, headers) {
- return new GoodFile(req, headers);
+ /*
+ FormFile.prototype.pipe = function () {
+
+ };
+ */
+ FormFile.create = function (req, headers) {
+ return new FormFile(req, headers);
};
- function GoodForm() {
+ function Formaline() {
}
- GoodForm.pump = function (file, pathname) {
- var tmpFile = fs.createWriteStream(pathname)
- ;
-
- file.pipe(tmpFile);
- };
- GoodForm.create = function (req, options) {
+ Formaline.create = function (req, options) {
var poorForm = PoorForm.create(req)
- , fieldsArr
- , filesArr
- , fieldsMap
- , filesMap
+ , fieldsArr = []
+ , filesArr = []
+ , fieldsMap = {}
+ , filesMap = {}
, curFile
, curField
;
@@ -83,9 +113,9 @@
}
options = options || {};
- options.fieldNames = options.fieldNames || [];
options.expectedFiles = options.expectedFiles || [];
options.hashes = options.hashes || [];
+ options.arrayFields = options.arrayFields || null;
// either not defined or undefined
if ('undefined' === typeof options.path) {
@@ -111,7 +141,7 @@
filesMap[headers.name] = filesMap[headers.name] || [];
curField = null;
- curFile = new GoodFile(req, headers);
+ curFile = new FormFile(req, headers);
filesMap[headers.name].push(curFile);
filesArr.push(curFile);
@@ -121,7 +151,8 @@
}
poorForm.emit('file', headers.name /*form name, not file name*/, curFile);
if (null !== options.path) {
- curFile.pipe(fs.createWriteStream(path.join(options.path, UUID.v4())));
+ curFile.path = path.join(options.path, UUID.v4());
+ curFile.pipe(fs.createWriteStream(curFile.path));
}
} else {
// probably a field, but maybe a file without a filename
@@ -134,7 +165,6 @@
, value: ""
};
- fieldsMap[headers.name].push(curField);
fieldsArr.push(curField);
}
});
@@ -156,17 +186,49 @@
curFile = null;
} else {
curField.value = decodeURIComponent(curField.value);
+ fieldsMap[curField.name].push(curField.value);
poorForm.emit('field', curField.name, curField.value);
curField = null;
}
});
poorForm.on('formend', function () {
- options.fieldNames.forEach(function (key) {
+ var arrayFields = options.arrayFields
+ ;
+
+ // It is useful for resumableness to know the sum of the uploaded chunk
+ // and the sum can be resumed without recalc server side
+ if (curFile) {
+ curFile.emit('end');
+ // which name? incomplete, complete, partial
+ curFile.incomplete = true;
+ // TODO unless options.keepIncomplete, fs.unlink
+ }
+ //filesArr.forEach(stopHashing);
+
+ if (!arrayFields) {
+ poorForm.emit('end', fieldsMap, filesMap);
+ return;
+ }
+
+ // make sure that any non-submitted fields are empty arrays
+ arrayFields.forEach(function (key) {
fieldsMap[key] = fieldsMap[key] || [];
filesMap[key] = filesMap[key] || [];
});
- filesArr.forEach(stopHashing);
+
+ // change remaining fields from arrays to single values
+ Object.keys(fieldsMap).forEach(function (key) {
+ if (-1 === arrayFields.indexOf(key)) {
+ fieldsMap[key] = fieldsMap[key][0];
+ }
+ });
+ Object.keys(filesMap).forEach(function (key) {
+ if (-1 === arrayFields.indexOf(key)) {
+ filesMap[key] = filesMap[key][0];
+ }
+ });
+
poorForm.emit('end', fieldsMap, filesMap);
});
@@ -178,5 +240,5 @@
return poorForm;
};
- module.exports.GoodForm = GoodForm;
+ module.exports.Formaline = Formaline;
}());
View
@@ -0,0 +1 @@
+curl -vvv localhost:3000 -X POST -F "avatar=@/Users/coolaj86/Pictures/coolaj86-2012-square-med.jpg" -F "avatar=@/Users/coolaj86/Pictures/coolaj86-2012-square-med.jpg" -F "name=AJ" -F "job=awesome" -F "dunnet=emacs"

0 comments on commit 75dd32f

Please sign in to comment.