For my purposes, I also required the implementation to be 'pluggable' - that is, where the directory information and file content was sourced would be supplied by external objects.
I considered a few options, but opted finally for the simplest - using the composite pattern. So I have this trivial set of abstractions:
FileSystemResource
FileResource < FileSystemResource
DirectoryResource < FileSystemResource
where DirectoryResource (1) --- (*) FileSystemResource.
To provide a central point of access, there is a file system manager type, which in the example code only allows one to find a resource (directory/file) or change the current directory.
So here is the code in its entirety, and at the end of the post, some simple test statements. The first few lines are some helper bits and pieces, before starting with the definition of a simple permission.
jfs = {};
// Inheritance helper
jfs.extend = function (childClass, parentClass) {
childClass.prototype = new parentClass;
childClass.prototype.constructor = childClass;
childClass.prototype.parent = parentClass.prototype;
};
jfs.fsConfig = {
rootDirectory: '/',
pathDelimiter: '/',
parentDirectory: '..',
currentDirectory: '.'
};
// Util
Array.prototype.first = function (match, def) {
for (var i = 0; i < this.length; i++) {
if (match(this[i])) {
return this[i];
}
}
return def;
};
String.prototype.splitButRemoveEmptyEntries = function (delim) {
return this.split(delim).filter(function (e) { return e !== ' ' && e !== '' });
};
// Simple permissions group
jfs.fileSystemPermission = function (readable, writeable) {
this._readable = readable;
this._writeable = writeable;
};
jfs.fileSystemPermission.prototype.writeable = function () {
return this._writeable;
};
jfs.fileSystemPermission.prototype.readable = function () {
return this._readable;
};
jfs.fileSystemPermission.prototype.toString = function () {
return (this.readable() ? 'r' : '-').concat((this.writeable() ? 'w' : '-'), '-');
};
jfs.standardPermission = new jfs.fileSystemPermission(true, false);
// Base resource
jfs.fileSystemResource = function () {
this._parent = undefined;
this._tags = {};
};
jfs.fileSystemResource.prototype.init = function (name, permissions) {
this._name = name;
this._permissions = permissions;
return this;
};
// Return the contents of the receiver i.e. for cat purposes
jfs.fileSystemResource.prototype.contents = function (consumer) {
};
// Return the details of the receiver i.e. for listing purposes
jfs.fileSystemResource.prototype.details = function (consumer) {
return this.toString();
};
jfs.fileSystemResource.prototype.name = function () {
return this._name;
};
jfs.fileSystemResource.prototype.getParent = function () {
return this._parent;
};
jfs.fileSystemResource.prototype.getTags = function () {
return this._tags;
};
jfs.fileSystemResource.prototype.setParent = function (parent) {
this._parent = parent;
};
jfs.fileSystemResource.prototype.permissions = function () {
return this._permissions;
};
jfs.fileSystemResource.prototype.type = function () {
return '?';
};
jfs.fileSystemResource.prototype.find = function (comps, index) {
};
jfs.fileSystemResource.prototype.absolutePath = function () {
return !this._parent ? '' :
this._parent.absolutePath().concat(jfs.fsConfig.pathDelimiter, this.name());
};
jfs.fileSystemResource.prototype.toString = function () {
return this.type().concat(this._permissions.toString(), ' ', this._name);
};
// Directory
jfs.directoryResource = function () {
this.children = [];
};
jfs.extend(jfs.directoryResource, jfs.fileSystemResource);
jfs.directoryResource.prototype.contents = function (consumer) {
return '';
};
jfs.directoryResource.prototype.details = function (consumer) {
consumer('total 0');
this.applyToChildren(function (kids) { kids.forEach(function(e) { consumer(e.toString()); }) });
};
jfs.directoryResource.prototype.type = function () {
return 'd';
};
jfs.directoryResource.prototype.addChild = function (resource) {
this.children.push(resource);
resource.setParent(this);
return this;
};
jfs.directoryResource.prototype.applyToChildren = function (fn) {
return this._proxy && this.children.length == 0 ? this._proxy.obtainState(this, fn) : fn(this.children);
};
jfs.directoryResource.prototype.setProxy = function (proxy) {
this._proxy = proxy;
};
jfs.directoryResource.prototype.find = function (comps, index) {
var comp = comps[index];
var node = comp === '' || comp === jfs.fsConfig.currentDirectory ? this :
(comp === jfs.fsConfig.parentDirectory ? this.getParent() :
this.applyToChildren(function(kids) { return kids.first(function(e) { return e.name() === comp; }); }));
return !node || index === comps.length - 1 ? node : node.find(comps, index + 1);
};
// File
jfs.fileResource = function () {
};
jfs.extend(jfs.fileResource, jfs.fileSystemResource);
// consumer should understand:
// accept(obj) - accept content
// failed - producer failed, totally or partially
jfs.fileResource.prototype.contents = function (consumer) {
this._producer(this, consumer || this._autoConsumer);
};
jfs.fileResource.prototype.type = function () {
return '-';
};
jfs.fileResource.prototype.plugin = function (producer, autoConsumer) {
this._producer = producer;
this._autoConsumer = autoConsumer;
};
// FSM
jfs.fileSystemManager = function () {
this._root = new jfs.directoryResource();
this._root.init('', jfs.standardPermission);
this._currentDirectory = this._root;
};
jfs.fileSystemManager.prototype.find = function (path) {
var components = path.splitButRemoveEmptyEntries(jfs.fsConfig.pathDelimiter);
if (components.length === 0) components = [ '.' ];
return (path.substr(0, 1) === jfs.fsConfig.rootDirectory ? this._root : this._currentDirectory).find(components, 0);
};
jfs.fileSystemManager.prototype.currentDirectory = function () {
return this._currentDirectory;
};
jfs.fileSystemManager.prototype.root = function () {
return this._root;
};
jfs.fileSystemManager.prototype.changeDirectory = function (path) {
var resource = this.find(path);
if (resource) this._currentDirectory = resource;
return resource;
};
And the test code; it creates a directory under the root called 389, and adds a file (called TestFile) to that directory, plugging in an example 'producer' function (that knows how to get the content of this type of file object) and an auto consumer - that is, a default consumer attached to the object. It is possible to pass any object in when calling the contents function and to not use default consumers at all.
Finally, and for illustration only, we use the file system manager find function to get the actual resource denoted by the full path name, and ask it for its contents. As we have an auto consumer associated with the object, it executes. In this case, we would dump two lines to the console log; 'some' and 'content'.
1: var m = new jfs.fileSystemManager();
2: var d = new jfs.directoryResource();
3: d.init('389', jfs.standardPermission);
4: m.currentDirectory().addChild(d) ;
5: var f = new jfs.fileResource();
6: f.init('TestFile', jfs.standardPermission);
7: d.addChild(f);
8: f.plugin(function(fileResource, consumer) {
9: ['some', 'content'].forEach(function(e) { consumer(e) })
10: },
11: function(e) { console.log(e) });
12: var r = m.find('/389/TestFile');
13: r.contents();
14:
No comments:
Post a Comment