Merge pull request #127 from RomanBurunkov/master

mv works across filesystems
dev v1.1.3-alpha.1
Roman Burunkov 5 years ago committed by GitHub
commit 8b32412f98
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 54
      lib/fileFactory.js
  2. 10
      lib/tempFileHandler.js
  3. 86
      lib/utilities.js
  4. 7
      package.json
  5. 70
      test/fileFactory.spec.js
  6. 290
      test/multipartUploads.spec.js
  7. 156
      test/utilities.spec.js

@ -1,27 +1,12 @@
'use strict';
const fs = require('fs');
const path = require('path');
const streamifier = require('streamifier');
/**
* Returns true if argument is function.
*/
const isFunc = func => func && func.constructor && func.call && func.apply;
/**
* Creates a folder for file specified in the path variable
* @param {Object} fileUploadOptions
* @param {String} filePath
*/
const checkAndMakeDir = function(fileUploadOptions, filePath){
if (fileUploadOptions && fileUploadOptions.createParentPath) {
const parentPath = path.dirname(filePath);
if (!fs.existsSync(parentPath)) {
fs.mkdirSync(parentPath);
}
}
};
const {
isFunc,
checkAndMakeDir,
copyFile,
saveBufferToFile
} = require('./utilities.js');
/**
* Returns Local function that moves the file to a different location on the filesystem
@ -33,12 +18,20 @@ const moveFromTemp = function(filePath, options) {
return function(successFunc, errorFunc){
// Set errorFunc to the same value as successFunc for callback mode.
errorFunc = isFunc(errorFunc) ? errorFunc : successFunc;
fs.rename(options.tempFilePath, filePath, function(err){
// Copy temporary file.
copyFile(options.tempFilePath, filePath, function(err){
if (err) {
errorFunc(err);
} else {
successFunc();
return;
}
// Delete temporary file.
fs.unlink(options.tempFilePath, (err) => {
if (err) {
errorFunc(err);
} else {
successFunc();
}
});
});
};
};
@ -53,13 +46,12 @@ const moveFromBuffer = function(filePath, options) {
return function(successFunc, errorFunc){
// Set errorFunc to the same value as successFunc for callback mode.
errorFunc = isFunc(errorFunc) ? errorFunc : successFunc;
const fstream = fs.createWriteStream(filePath);
streamifier.createReadStream(options.buffer).pipe(fstream);
fstream.on('error', function(error) {
errorFunc(error);
});
fstream.on('close', function() {
successFunc();
saveBufferToFile(options.buffer, filePath, function(err){
if (err) {
errorFunc(err);
} else {
successFunc();
}
});
};
};

@ -1,15 +1,15 @@
const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
const {checkAndMakeDir} = require('./utilities.js');
module.exports = function(options, fieldname, filename) {
const dir = path.normalize(options.tempFileDir || process.cwd() + '/tmp/');
let hash = crypto.createHash('md5');
const tempFilePath = path.join(dir, 'tmp' + Date.now());
checkAndMakeDir({createParentPath: true}, tempFilePath);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir);
}
let tempFilePath = path.join(dir, 'tmp' + Date.now());
let hash = crypto.createHash('md5');
let writeStream = fs.createWriteStream(tempFilePath);
let fileSize = 0; // eslint-disable-line

@ -0,0 +1,86 @@
'use strict';
const fs = require('fs');
const path = require('path');
const Readable = require('stream').Readable;
/**
* Returns true if argument is function.
*/
const isFunc = func => func && func.constructor && func.call && func.apply ? true: false;
/**
* Creates a folder for file specified in the path variable
* @param {Object} fileUploadOptions
* @param {String} filePath
*/
const checkAndMakeDir = function(fileUploadOptions, filePath){
//Check upload options were set.
if (!fileUploadOptions) return false;
if (!fileUploadOptions.createParentPath) return false;
//Check whether folder for the file exists.
if (!filePath) return false;
const parentPath = path.dirname(filePath);
//Create folder if it is not exists.
if (!fs.existsSync(parentPath)) fs.mkdirSync(parentPath);
return true;
};
/**
* Copy file via streams
* @param {String} src - Path to the source file
* @param {String} dst - Path to the destination file.
*/
const copyFile = function(src, dst, callback){
//cbCalled flag and runCb helps to run cb only once.
let cbCalled = false;
let runCb = (err) => {
if (cbCalled) return;
cbCalled = true;
callback(err);
};
//Create read stream
let readable = fs.createReadStream(src);
readable.on('error', runCb);
//Create write stream
let writable = fs.createWriteStream(dst);
writable.on('error', (err)=>{
readable.destroy();
runCb(err);
});
writable.on('close', () => runCb());
//Copy file via piping streams.
readable.pipe(writable);
};
/**
* Save buffer data to a file.
* @param {Buffer} buffer - buffer to save to a file.
* @param {String} filePath - path to a file.
*/
const saveBufferToFile = function(buffer, filePath, callback){
if (!Buffer.isBuffer(buffer)){
callback(new Error('buffer variable should be a Buffer!'));
return;
}
//Setup readable stream from buffer.
let streamData = buffer;
let readStream = Readable();
readStream._read = ()=>{
readStream.push(streamData);
streamData = null;
};
//Setup file system writable stream.
let fstream = fs.createWriteStream(filePath);
fstream.on('error', error => callback(error));
fstream.on('close', () => callback());
//Copy file via piping streams.
readStream.pipe(fstream);
};
module.exports = {
isFunc,
checkAndMakeDir,
copyFile,
saveBufferToFile
};

@ -1,6 +1,6 @@
{
"name": "express-fileupload",
"version": "1.1.2-alpha.1",
"version": "1.1.3-alpha.1",
"author": "Richard Girges <richardgirges@gmail.com>",
"description": "Simple express file upload middleware that wraps around Busboy",
"main": "./lib/index",
@ -10,11 +10,10 @@
"coveralls": "cat ./coverage/lcov.info | coveralls"
},
"dependencies": {
"busboy": "^0.2.14",
"streamifier": "^0.1.1"
"busboy": "^0.2.14"
},
"engines": {
"node": ">=4.0.0"
"node": ">=6.0.0"
},
"keywords": [
"express",

@ -5,14 +5,12 @@ const fs = require('fs');
const md5 = require('md5');
const path = require('path');
const fileFactory = require('../lib').fileFactory;
const {isFunc} = require('../lib/utilities.js');
const server = require('./server');
const mockBuffer = fs.readFileSync(path.join(server.fileDir, 'basketball.png'));
/**
* Returns true if argument is function.
*/
const isFunc = func => (func && func.constructor && func.call && func.apply) ? true : false;
const mockFile = path.join(server.fileDir, 'basketball.png');
const mockBuffer = fs.readFileSync(mockFile);
describe('Test of the fileFactory factory', function() {
beforeEach(function() {
@ -26,25 +24,6 @@ describe('Test of the fileFactory factory', function() {
}));
});
describe('File object behavior', function() {
const file = fileFactory({
name: 'basketball.png',
buffer: mockBuffer
});
it('move the file to the specified folder', function(done) {
file.mv(path.join(server.uploadDir, 'basketball.png'), function(err) {
assert.ifError(err);
done();
});
});
it('reject the mv if the destination does not exists', function(done) {
file.mv(path.join(server.uploadDir, 'unknown', 'basketball.png'), function(err) {
assert.ok(err);
done();
});
});
});
describe('Properties', function() {
it('contains the name property', function() {
assert.equal(fileFactory({
@ -87,4 +66,47 @@ describe('Test of the fileFactory factory', function() {
}).mv), true);
});
});
describe('File object behavior for in memory upload', function() {
const file = fileFactory({
name: 'basketball.png',
buffer: mockBuffer
});
it('move the file to the specified folder', function(done) {
file.mv(path.join(server.uploadDir, 'basketball.png'), function(err) {
assert.ifError(err);
done();
});
});
it('reject the mv if the destination does not exists', function(done) {
file.mv(path.join(server.uploadDir, 'unknown', 'basketball.png'), function(err) {
assert.ok(err);
done();
});
});
});
describe('File object behavior for upload into temporary file', function() {
const file = fileFactory({
name: 'basketball.png',
buffer: mockBuffer,
tempFilePath: mockFile
});
it('move the file to the specified folder', function(done) {
file.mv(path.join(server.uploadDir, 'basketball.png'), function(err) {
if (!err){
//Place back moved file
fs.renameSync(path.join(server.uploadDir, 'basketball.png'), mockFile);
}
assert.ifError(err);
done();
});
});
it('reject the mv if the destination does not exists', function(done) {
file.mv(path.join(server.uploadDir, 'unknown', 'basketball.png'), function(err) {
assert.ok(err);
done();
});
});
});
});

@ -5,7 +5,6 @@ const md5 = require('md5');
const path = require('path');
const request = require('supertest');
const server = require('./server');
const app = server.setup();
const clearUploadsDir = server.clearUploadsDir;
const fileDir = server.fileDir;
const uploadDir = server.uploadDir;
@ -25,14 +24,188 @@ let mockUser = {
describe('Test Directory Cleaning Method', function() {
it('emptied "uploads" directory', function(done) {
clearUploadsDir();
let filesFound = fs.readdirSync(uploadDir).length;
done(filesFound ? `Directory not empty. Found ${filesFound} files.` : null);
});
});
describe('Test Single File Upload', function() {
const app = server.setup();
for (let i = 0; i < mockFiles.length; i++) {
let fileName = mockFiles[i];
it(`upload ${fileName} with POST`, function(done) {
let filePath = path.join(fileDir, fileName);
let fileBuffer = fs.readFileSync(filePath);
let fileHash = md5(fileBuffer);
let fileStat = fs.statSync(filePath);
let uploadedFilePath = path.join(uploadDir, fileName);
clearUploadsDir();
request(app)
.post('/upload/single')
.attach('testFile', filePath)
.expect((res)=>{
res.body.uploadDir = '';
res.body.uploadPath = '';
})
.expect(200, {
name: fileName,
md5: fileHash,
size: fileStat.size,
uploadDir: '',
uploadPath: ''
})
.end(function(err) {
if (err) {
return done(err);
}
fs.stat(uploadedFilePath, done);
});
});
it(`upload ${fileName} with PUT`, function(done) {
let filePath = path.join(fileDir, fileName);
let fileBuffer = fs.readFileSync(filePath);
let fileHash = md5(fileBuffer);
let fileStat = fs.statSync(filePath);
let uploadedFilePath = path.join(uploadDir, fileName);
clearUploadsDir();
request(app)
.post('/upload/single')
.attach('testFile', filePath)
.expect((res)=>{
res.body.uploadDir = '';
res.body.uploadPath = '';
})
.expect(200, {
name: fileName,
md5: fileHash,
size: fileStat.size,
uploadDir: '',
uploadPath: ''
})
.end(function(err) {
if (err) {
return done(err);
}
fs.stat(uploadedFilePath, done);
});
});
}
it('fail when no files were attached', function(done) {
request(app)
.post('/upload/single')
.expect(400)
.end(done);
});
it('fail when using GET', function(done) {
let filePath = path.join(fileDir, mockFiles[0]);
request(app)
.get('/upload/single')
.attach('testFile', filePath)
.expect(400)
.end(done);
});
it('fail when using HEAD', function(done) {
let filePath = path.join(fileDir, mockFiles[0]);
request(app)
.head('/upload/single')
.attach('testFile', filePath)
.expect(400)
.end(done);
});
});
describe('Test Single File Upload w/ .mv()', function() {
const app = server.setup();
for (let i = 0; i < mockFiles.length; i++) {
let fileName = mockFiles[i];
it(`upload ${fileName} with POST w/ .mv()`, function(done) {
let filePath = path.join(fileDir, fileName);
let fileBuffer = fs.readFileSync(filePath);
let fileHash = md5(fileBuffer);
let fileStat = fs.statSync(filePath);
let uploadedFilePath = path.join(uploadDir, fileName);
clearUploadsDir();
request(app)
.post('/upload/single')
.attach('testFile', filePath)
.expect((res)=>{
res.body.uploadDir = '';
res.body.uploadPath = '';
})
.expect(200, {
name: fileName,
md5: fileHash,
size: fileStat.size,
uploadDir: '',
uploadPath: ''
})
.end(function(err) {
if (err) {
return done(err);
}
fs.stat(uploadedFilePath, done);
});
});
it(`upload ${fileName} with PUT w/ .mv()`, function(done) {
let filePath = path.join(fileDir, fileName);
let fileBuffer = fs.readFileSync(filePath);
let fileHash = md5(fileBuffer);
let fileStat = fs.statSync(filePath);
let uploadedFilePath = path.join(uploadDir, fileName);
clearUploadsDir();
request(app)
.post('/upload/single')
.attach('testFile', filePath)
.expect((res)=>{
res.body.uploadDir = '';
res.body.uploadPath = '';
})
.expect(200, {
name: fileName,
md5: fileHash,
size: fileStat.size,
uploadDir: '',
uploadPath: ''
})
.end(function(err) {
if (err) {
return done(err);
}
fs.stat(uploadedFilePath, done);
});
});
}
});
describe('Test Single File Upload with useTempFiles option set to true', function() {
const app = server.setup({
useTempFiles: true,
tempFileDir: '/tmp/'
});
for (let i = 0; i < mockFiles.length; i++) {
let fileName = mockFiles[i];
@ -130,6 +303,110 @@ describe('Test Single File Upload', function() {
});
describe('Test Single File Upload w/ .mv() Promise', function() {
const app = server.setup();
for (let i = 0; i < mockFiles.length; i++) {
let fileName = mockFiles[i];
it(`upload ${fileName} with POST w/ .mv() Promise`, function(done) {
let filePath = path.join(fileDir, fileName);
let fileBuffer = fs.readFileSync(filePath);
let fileHash = md5(fileBuffer);
let fileStat = fs.statSync(filePath);
let uploadedFilePath = path.join(uploadDir, fileName);
clearUploadsDir();
request(app)
.post('/upload/single/promise')
.attach('testFile', filePath)
.expect((res)=>{
res.body.uploadDir = '';
res.body.uploadPath = '';
})
.expect(200, {
name: fileName,
md5: fileHash,
size: fileStat.size,
uploadDir: '',
uploadPath: ''
})
.end(function(err) {
if (err) {
return done(err);
}
fs.stat(uploadedFilePath, done);
});
});
it(`upload ${fileName} with PUT w/ .mv() Promise`, function(done) {
let filePath = path.join(fileDir, fileName);
let fileBuffer = fs.readFileSync(filePath);
let fileHash = md5(fileBuffer);
let fileStat = fs.statSync(filePath);
let uploadedFilePath = path.join(uploadDir, fileName);
clearUploadsDir();
request(app)
.post('/upload/single/promise')
.attach('testFile', filePath)
.expect((res)=>{
res.body.uploadDir = '';
res.body.uploadPath = '';
})
.expect(200, {
name: fileName,
md5: fileHash,
size: fileStat.size,
uploadDir: '',
uploadPath: ''
})
.end(function(err) {
if (err) {
return done(err);
}
fs.stat(uploadedFilePath, done);
});
});
}
it('fail when no files were attached', function(done) {
request(app)
.post('/upload/single')
.expect(400)
.end(done);
});
it('fail when using GET', function(done) {
let filePath = path.join(fileDir, mockFiles[0]);
request(app)
.get('/upload/single')
.attach('testFile', filePath)
.expect(400)
.end(done);
});
it('fail when using HEAD', function(done) {
let filePath = path.join(fileDir, mockFiles[0]);
request(app)
.head('/upload/single')
.attach('testFile', filePath)
.expect(400)
.end(done);
});
});
describe('Test Single File Upload w/ .mv() Promise and useTempFiles set to true', function() {
const app = server.setup({
useTempFiles: true,
tempFileDir: '/tmp/'
});
for (let i = 0; i < mockFiles.length; i++) {
let fileName = mockFiles[i];
@ -226,7 +503,10 @@ describe('Test Single File Upload w/ .mv() Promise', function() {
});
});
describe('Test Multi-File Upload', function() {
const app = server.setup();
it('upload multiple files with POST', function(done) {
let req = request(app).post('/upload/multiple');
@ -282,6 +562,8 @@ describe('Test Multi-File Upload', function() {
});
describe('Test File Array Upload', function() {
const app = server.setup();
it('upload array of files with POST', function(done) {
let req = request(app).post('/upload/array');
@ -329,6 +611,8 @@ describe('Test File Array Upload', function() {
});
describe('Test Upload With Fields', function() {
const app = server.setup();
for (let i = 0; i < mockFiles.length; i++) {
let fileName = mockFiles[i];

@ -0,0 +1,156 @@
'use strict';
const assert = require('assert');
const path = require('path');
const fs = require('fs');
const md5 = require('md5');
const server = require('./server');
const fileDir = server.fileDir;
const uploadDir = server.uploadDir;
const {
isFunc,
checkAndMakeDir,
copyFile,
saveBufferToFile
} = require('../lib/utilities.js');
const mockFile = 'basketball.png';
const mockBuffer = fs.readFileSync(path.join(fileDir, mockFile));
const mockHash = md5(mockBuffer);
describe('Test of the utilities functions', function() {
beforeEach(function() {
server.clearUploadsDir();
});
//isFunc tests
describe('Test isFunc function', () => {
it('isFunc returns true if function passed', () => assert.equal(isFunc(()=>{}), true));
it('isFunc returns false if null passed', function() {
assert.equal(isFunc(null), false);
});
it('isFunc returns false if undefined passed', function() {
assert.equal(isFunc(undefined), false);
});
it('isFunc returns false if object passed', function() {
assert.equal(isFunc({}), false);
});
it('isFunc returns false if array passed', function() {
assert.equal(isFunc([]), false);
});
});
//checkAndMakeDir tests
describe('Test checkAndMakeDir function', () => {
//
it('checkAndMakeDir returns false if upload options object was not set', () => {
assert.equal(checkAndMakeDir(), false);
});
//
it('checkAndMakeDir returns false if upload option createParentPath was not set', () => {
assert.equal(checkAndMakeDir({}), false);
});
//
it('checkAndMakeDir returns false if filePath was not set', () => {
assert.equal(checkAndMakeDir({createParentPath: true}), false);
});
//
it('checkAndMakeDir return true if path to the file already exists', ()=>{
let dir = path.join(uploadDir, 'testfile');
assert.equal(checkAndMakeDir({createParentPath: true}, dir), true);
});
//
it('checkAndMakeDir creates a dir if path to the file not exists', ()=>{
let dir = path.join(uploadDir, 'testfolder', 'testfile');
assert.equal(checkAndMakeDir({createParentPath: true}, dir), true);
});
});
//saveBufferToFile tests
describe('Test saveBufferToFile function', function(){
beforeEach(function() {
server.clearUploadsDir();
});
it('Save buffer to a file', function(done) {
let filePath = path.join(uploadDir, mockFile);
saveBufferToFile(mockBuffer, filePath, function(err){
if (err) {
return done(err);
}
fs.stat(filePath, done);
});
});
it('Failed if not a buffer passed', function(done) {
let filePath = path.join(uploadDir, mockFile);
saveBufferToFile(undefined, filePath, function(err){
if (err) {
return done();
}
});
});
it('Failed if wrong path passed', function(done) {
let filePath = '';
saveBufferToFile(mockFile, filePath, function(err){
if (err) {
return done();
}
});
});
});
describe('Test copyFile function', function(){
beforeEach(function() {
server.clearUploadsDir();
});
it('Copy a file and check a hash', function(done) {
let srcPath = path.join(fileDir, mockFile);
let dstPath = path.join(uploadDir, mockFile);
copyFile(srcPath, dstPath, function(err){
if (err) {
return done(err);
}
fs.stat(dstPath, (err)=>{
if (err){
return done(err);
}
//Match source and destination files hash.
let fileBuffer = fs.readFileSync(dstPath);
let fileHash = md5(fileBuffer);
return (fileHash === mockHash) ? done() : done(err);
});
});
});
it('Failed if wrong source file path passed', function(done){
let srcPath = path.join(fileDir, 'unknown');
let dstPath = path.join(uploadDir, mockFile);
copyFile(srcPath, dstPath, function(err){
if (err) {
return done();
}
});
});
it('Failed if wrong destination file path passed', function(done){
let srcPath = path.join(fileDir, 'unknown');
let dstPath = path.join('unknown', 'unknown');
copyFile(srcPath, dstPath, function(err){
if (err) {
return done();
}
});
});
});
});
Loading…
Cancel
Save