loopback-component-storage/lib/providers/filesystem/index.js

312 lines
8.4 KiB
JavaScript
Raw Normal View History

2016-05-03 23:18:18 +00:00
// Copyright IBM Corp. 2013,2015. All Rights Reserved.
// Node module: loopback-component-storage
// This file is licensed under the Artistic License 2.0.
// License text available at https://opensource.org/licenses/Artistic-2.0
'use strict';
2016-10-18 22:48:59 +00:00
2016-07-26 18:13:30 +00:00
// Globalization
var g = require('strong-globalize')();
2013-06-24 21:07:12 +00:00
/**
* File system based on storage provider
*/
var fs = require('fs'),
2014-01-10 19:34:37 +00:00
path = require('path'),
stream = require('stream'),
2014-01-10 19:34:37 +00:00
async = require('async'),
File = require('./file').File,
Container = require('./container').Container;
2013-06-24 21:07:12 +00:00
module.exports.storage = module.exports; // To make it consistent with pkgcloud
module.exports.File = File;
module.exports.Container = Container;
module.exports.Client = FileSystemProvider;
2016-10-18 22:06:55 +00:00
module.exports.createClient = function(options) {
2014-01-10 19:34:37 +00:00
return new FileSystemProvider(options);
};
2013-06-24 21:07:12 +00:00
function FileSystemProvider(options) {
2014-01-10 19:34:37 +00:00
options = options || {};
this.root = options.root;
var exists = fs.existsSync(this.root);
if (!exists) {
2016-07-26 18:13:30 +00:00
throw new Error(g.f('{{FileSystemProvider}}: Path does not exist: %s', this.root));
2014-01-10 19:34:37 +00:00
}
var stat = fs.statSync(this.root);
if (!stat.isDirectory()) {
2016-07-26 18:13:30 +00:00
throw new Error(g.f('{{FileSystemProvider}}: Invalid directory: %s', this.root));
2014-01-10 19:34:37 +00:00
}
2013-06-24 21:07:12 +00:00
}
var namePattern = new RegExp('[^' + path.sep + '/]+');
2017-02-08 03:21:18 +00:00
// To detect any file/directory containing dotdot paths
var containsDotDotPaths = /(^|[\\\/])\.\.([\\\/]|$)/;
2013-06-24 21:07:12 +00:00
function validateName(name, cb) {
2017-02-08 03:21:18 +00:00
if (!name || containsDotDotPaths.test(name)) {
2016-07-26 18:13:30 +00:00
cb && process.nextTick(cb.bind(null, new Error(g.f('Invalid name: %s', name))));
2014-01-10 19:34:37 +00:00
if (!cb) {
2016-07-26 18:13:30 +00:00
console.error(g.f('{{FileSystemProvider}}: Invalid name: %s', name));
2013-06-24 21:07:12 +00:00
}
2014-01-10 19:34:37 +00:00
return false;
}
var match = namePattern.exec(name);
if (match && match.index === 0 && match[0].length === name.length) {
return true;
} else {
2014-01-24 17:32:48 +00:00
cb && process.nextTick(cb.bind(null,
2016-07-26 18:13:30 +00:00
new Error(g.f('{{FileSystemProvider}}: Invalid name: %s', name))));
2014-01-10 19:34:37 +00:00
if (!cb) {
console.error(g.f('{{FileSystemProvider}}: Invalid name: %s', name));
2013-06-24 21:07:12 +00:00
}
2014-01-10 19:34:37 +00:00
return false;
}
2013-06-24 21:07:12 +00:00
}
function streamError(errStream, err, cb) {
process.nextTick(function() {
errStream.emit('error', err);
cb && cb(null, err);
});
return errStream;
}
var writeStreamError = streamError.bind(null, new stream.Writable());
var readStreamError = streamError.bind(null, new stream.Readable());
/*!
* Populate the metadata from file stat into props
* @param {fs.Stats} stat The file stat instance
* @param {Object} props The metadata object
*/
function populateMetadata(stat, props) {
for (var p in stat) {
switch (p) {
case 'size':
case 'atime':
case 'mtime':
case 'ctime':
props[p] = stat[p];
break;
}
}
}
2016-10-18 22:06:55 +00:00
FileSystemProvider.prototype.getContainers = function(cb) {
2014-01-10 19:34:37 +00:00
var self = this;
2016-10-18 22:06:55 +00:00
fs.readdir(self.root, function(err, files) {
2014-01-10 19:34:37 +00:00
var containers = [];
var tasks = [];
2016-10-18 22:06:55 +00:00
files.forEach(function(f) {
2014-01-24 17:32:48 +00:00
tasks.push(fs.stat.bind(fs, path.join(self.root, f)));
2014-01-10 19:34:37 +00:00
});
2016-10-18 22:06:55 +00:00
async.parallel(tasks, function(err, stats) {
2014-01-10 19:34:37 +00:00
if (err) {
cb && cb(err);
} else {
2016-10-18 22:06:55 +00:00
stats.forEach(function(stat, index) {
2014-01-10 19:34:37 +00:00
if (stat.isDirectory()) {
var name = files[index];
var props = {name: name};
populateMetadata(stat, props);
2014-01-10 19:34:37 +00:00
var container = new Container(self, props);
containers.push(container);
}
2013-06-24 21:07:12 +00:00
});
2014-01-10 19:34:37 +00:00
cb && cb(err, containers);
}
2013-06-24 21:07:12 +00:00
});
2014-01-10 19:34:37 +00:00
});
};
2013-06-24 21:07:12 +00:00
2016-10-18 22:06:55 +00:00
FileSystemProvider.prototype.createContainer = function(options, cb) {
2014-01-10 19:34:37 +00:00
var self = this;
var name = options.name;
var dir = path.join(this.root, name);
2016-10-18 22:06:55 +00:00
validateName(name, cb) && fs.mkdir(dir, options, function(err) {
if (err) {
return cb && cb(err);
}
2016-10-18 22:06:55 +00:00
fs.stat(dir, function(err, stat) {
var container = null;
if (!err) {
var props = {name: name};
populateMetadata(stat, props);
container = new Container(self, props);
}
cb && cb(err, container);
});
2014-01-10 19:34:37 +00:00
});
};
2013-06-24 21:07:12 +00:00
2016-10-18 22:06:55 +00:00
FileSystemProvider.prototype.destroyContainer = function(containerName, cb) {
2014-01-10 19:34:37 +00:00
if (!validateName(containerName, cb)) return;
2013-06-24 21:07:12 +00:00
2014-01-10 19:34:37 +00:00
var dir = path.join(this.root, containerName);
2016-10-18 22:06:55 +00:00
fs.readdir(dir, function(err, files) {
files = files || [];
2014-01-10 19:34:37 +00:00
var tasks = [];
2016-10-18 22:06:55 +00:00
files.forEach(function(f) {
2014-01-24 17:32:48 +00:00
tasks.push(fs.unlink.bind(fs, path.join(dir, f)));
2013-06-24 21:07:12 +00:00
});
2016-10-18 22:06:55 +00:00
async.parallel(tasks, function(err) {
2014-01-10 19:34:37 +00:00
if (err) {
cb && cb(err);
} else {
fs.rmdir(dir, cb);
}
2013-06-25 16:29:53 +00:00
});
2014-01-10 19:34:37 +00:00
});
};
2013-06-24 21:07:12 +00:00
2016-10-18 22:06:55 +00:00
FileSystemProvider.prototype.getContainer = function(containerName, cb) {
2014-01-10 19:34:37 +00:00
var self = this;
if (!validateName(containerName, cb)) return;
var dir = path.join(this.root, containerName);
2016-10-18 22:06:55 +00:00
fs.stat(dir, function(err, stat) {
2014-01-10 19:34:37 +00:00
var container = null;
if (!err) {
var props = {name: containerName};
populateMetadata(stat, props);
2014-01-10 19:34:37 +00:00
container = new Container(self, props);
}
cb && cb(err, container);
});
};
2013-06-24 21:07:12 +00:00
// File related functions
2016-10-18 22:06:55 +00:00
FileSystemProvider.prototype.upload = function(options, cb) {
2014-01-10 19:34:37 +00:00
var container = options.container;
if (!validateName(container)) {
return writeStreamError(
new Error(g.f('{{FileSystemProvider}}: Invalid name: %s', container)),
cb
);
}
2014-01-10 19:34:37 +00:00
var file = options.remote;
if (!validateName(file)) {
return writeStreamError(
new Error(g.f('{{FileSystemProvider}}: Invalid name: %s', file)),
cb
);
}
2014-01-10 19:34:37 +00:00
var filePath = path.join(this.root, container, file);
2013-06-24 21:07:12 +00:00
2014-01-24 17:32:48 +00:00
var fileOpts = {flags: options.flags || 'w+',
encoding: options.encoding || null,
mode: options.mode || parseInt('0666', 8),
2014-01-24 17:32:48 +00:00
};
2013-06-24 21:07:12 +00:00
2014-01-14 18:26:09 +00:00
try {
2017-03-09 16:22:03 +00:00
// simulate the success event in filesystem provider
// fixes: https://github.com/strongloop/loopback-component-storage/issues/58
2015-06-01 07:55:04 +00:00
// & #23 & #67
var stream = fs.createWriteStream(filePath, fileOpts);
2016-10-18 22:06:55 +00:00
stream.on('finish', function() {
2015-06-01 07:55:04 +00:00
stream.emit('success');
});
return stream;
2014-01-14 18:26:09 +00:00
} catch (e) {
return writeStreamError(e, cb);
2014-01-14 18:26:09 +00:00
}
2014-01-10 19:34:37 +00:00
};
2013-06-24 21:07:12 +00:00
2016-10-18 22:06:55 +00:00
FileSystemProvider.prototype.download = function(options, cb) {
2014-01-10 19:34:37 +00:00
var container = options.container;
if (!validateName(container, cb)) {
return readStreamError(
new Error(g.f('{{FileSystemProvider}}: Invalid name: %s', container)),
cb
);
}
2014-01-10 19:34:37 +00:00
var file = options.remote;
if (!validateName(file, cb)) {
return readStreamError(
new Error(g.f('{{FileSystemProvider}}: Invalid name: %s', file)),
cb
);
}
2013-06-24 21:07:12 +00:00
2014-01-10 19:34:37 +00:00
var filePath = path.join(this.root, container, file);
2013-06-24 21:07:12 +00:00
2014-01-10 19:34:37 +00:00
var fileOpts = {flags: 'r',
2016-10-18 22:06:55 +00:00
autoClose: true};
2015-11-24 01:16:48 +00:00
if (options.start) {
2016-10-18 22:06:55 +00:00
fileOpts.start = options.start;
fileOpts.end = options.end;
2015-11-24 01:16:48 +00:00
}
2013-06-24 21:07:12 +00:00
2014-01-14 18:26:09 +00:00
try {
return fs.createReadStream(filePath, fileOpts);
} catch (e) {
return readStreamError(e, cb);
2014-01-14 18:26:09 +00:00
}
2014-01-10 19:34:37 +00:00
};
2013-06-24 21:07:12 +00:00
2016-10-18 22:06:55 +00:00
FileSystemProvider.prototype.getFiles = function(container, options, cb) {
2014-04-08 01:19:35 +00:00
if (typeof options === 'function' && !(options instanceof RegExp)) {
cb = options;
options = false;
2014-01-10 19:34:37 +00:00
}
var self = this;
if (!validateName(container, cb)) return;
var dir = path.join(this.root, container);
2016-10-18 22:06:55 +00:00
fs.readdir(dir, function(err, entries) {
entries = entries || [];
2014-01-10 19:34:37 +00:00
var files = [];
var tasks = [];
2016-10-18 22:06:55 +00:00
entries.forEach(function(f) {
2014-01-24 17:32:48 +00:00
tasks.push(fs.stat.bind(fs, path.join(dir, f)));
2014-01-10 19:34:37 +00:00
});
2016-10-18 22:06:55 +00:00
async.parallel(tasks, function(err, stats) {
2014-01-10 19:34:37 +00:00
if (err) {
cb && cb(err);
} else {
2016-10-18 22:06:55 +00:00
stats.forEach(function(stat, index) {
2014-01-10 19:34:37 +00:00
if (stat.isFile()) {
var props = {container: container, name: entries[index]};
populateMetadata(stat, props);
2014-01-10 19:34:37 +00:00
var file = new File(self, props);
files.push(file);
}
2013-06-24 21:07:12 +00:00
});
2014-01-10 19:34:37 +00:00
cb && cb(err, files);
}
2013-06-24 21:07:12 +00:00
});
2014-01-10 19:34:37 +00:00
});
};
2013-06-24 21:07:12 +00:00
2016-10-18 22:06:55 +00:00
FileSystemProvider.prototype.getFile = function(container, file, cb) {
2014-01-10 19:34:37 +00:00
var self = this;
if (!validateName(container, cb)) return;
if (!validateName(file, cb)) return;
var filePath = path.join(this.root, container, file);
2016-10-18 22:06:55 +00:00
fs.stat(filePath, function(err, stat) {
2014-01-10 19:34:37 +00:00
var f = null;
if (!err) {
var props = {container: container, name: file};
populateMetadata(stat, props);
2014-01-10 19:34:37 +00:00
f = new File(self, props);
}
cb && cb(err, f);
});
};
2016-10-18 22:06:55 +00:00
FileSystemProvider.prototype.getUrl = function(options) {
2014-01-10 19:34:37 +00:00
options = options || {};
var filePath = path.join(this.root, options.container, options.path);
return filePath;
};
2013-06-24 21:07:12 +00:00
2016-10-18 22:06:55 +00:00
FileSystemProvider.prototype.removeFile = function(container, file, cb) {
2014-01-10 19:34:37 +00:00
if (!validateName(container, cb)) return;
if (!validateName(file, cb)) return;
2013-06-24 21:07:12 +00:00
2014-01-10 19:34:37 +00:00
var filePath = path.join(this.root, container, file);
fs.unlink(filePath, cb);
};