blob: 0137f57fe073a5fd36ef51f15445e076d7dd6231 [file] [log] [blame]
// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
'use strict';
/**
* An object that connects a wam.binding.fs.FileSystem to an in-memory file
* system composed of objects derived from wam.jsfs.Entry.
*
* See wam.jsfs.Directory, wam.jsfs.Executable, wam.jsfs.RemoteFileSystem,
* and wam.jsfs.dom.FileSystem for examples of entries that can be used
* with one of these.
*
* @param {wam.jsfs.Directory} opt_rootDirectory An optional directory instance
* to use as the root.
*/
wam.jsfs.FileSystem = function(opt_rootDirectory) {
this.rootDirectory_ = opt_rootDirectory || new wam.jsfs.Directory();
this.defaultBinding = new wam.binding.fs.FileSystem();
this.addBinding(this.defaultBinding);
this.defaultBinding.ready();
};
/**
* Connect a file system binding to this file system implementation.
*
* We'll subscribe to events on the binding and provide the implementation for
* stat, unlink, list, execute, and open related functionality.
*
* @param {wam.binding.fs.FileSystem} binding
*/
wam.jsfs.FileSystem.prototype.addBinding = function(binding) {
binding.onStat.addListener(this.onStat_, this);
binding.onUnlink.addListener(this.onUnlink_, this);
binding.onList.addListener(this.onList_, this);
binding.onExecuteContextCreated.addListener(
this.onExecuteContextCreated_, this);
binding.onOpenContextCreated.addListener(
this.onOpenContextCreated_, this);
binding.onClose.addListener(this.removeBinding.bind(this, binding));
};
/**
* Remove a binding.
*
* @param {wam.binding.fs.FileSystem} binding
*/
wam.jsfs.FileSystem.prototype.removeBinding = function(binding) {
binding.onStat.removeListener(this.onStat_, this);
binding.onUnlink.removeListener(this.onUnlink_, this);
binding.onList.removeListener(this.onStat_, this);
binding.onExecuteContextCreated.removeListener(
this.onExecuteContextCreated_, this);
binding.onOpenContextCreated.removeListener(
this.onOpenContextCreated_, this);
};
/**
* Publish this file system on the given wam.Channel.
*
* If the other end of the channel offers a 'wam.Filesystem' handshake, we'll
* accept it on behalf of this file system.
*
* @param {wam.Channel} channel The channel to publish on.
* @param {string} name A short name to identify this file system to the other
* party. This is sent to the other party when we accept their handshake
* offer. There is currently no provision for selecting a file system by
* name as part of the handshake offer.
*/
wam.jsfs.FileSystem.prototype.publishOn = function(channel, name) {
var readyValue = name ? {name: name} : null;
channel.onHandshakeOffered.addListener(function(offerEvent) {
if (offerEvent.response ||
!wam.remote.fs.testOffer(offerEvent.inMessage)) {
return;
}
this.handshakeResponse = new wam.remote.fs.handshake.Response(
offerEvent.inMessage, this.defaultBinding);
this.handshakeResponse.sendReady(readyValue);
offerEvent.response = this.handshakeResponse;
}.bind(this));
};
/**
* Ensure that the given path exists.
*
* Any missing directories are created as wam.jsfs.Directory instances.
* The onSuccess handler will be passed the final directory instance. If
* an error occurs, the path may have been partially constructed.
*/
wam.jsfs.FileSystem.prototype.makePath = function(
path, onSuccess, onError) {
var makeNextPath = function(directoryEntry, pathList) {
if (pathList.length == 0) {
onSuccess(directoryEntry);
return;
}
var childDir = new wam.jsfs.Directory();
directoryEntry.addEntry(pathList.shift(),
childDir,
makeNextPath.bind(null, childDir, pathList),
onError);
};
this.partialResolve
(path,
function (prefixList, pathList, resolvedEntry) {
if (!resolvedEntry) {
onError(wam.mkerr('wam.FileSystem.Error.NotFound', [path]));
return;
}
if (!resolvedEntry.can('LIST')) {
onError(wam.mkerr('wam.FileSystem.Error.NotListable',
['/' + prefixList.join('/')]));
return;
}
if (pathList.length == 0) {
onSuccess(resolvedEntry);
return;
}
makeNextPath(resolvedEntry, pathList);
},
onError);
};
/**
* Call ..makePath sequentially, once for each path in pathList.
*
* If any path fails, stop the sequence and call onError.
*
* @param {Array<string>} pathList The list of paths to create.
* @param {function()} onSuccess The function to invoke if all paths are created
* successfully.
* @param {function(wam.Error)} onError The function to invoke if a path fails.
* Remaning paths will not be created.
*/
wam.jsfs.FileSystem.prototype.makePaths = function(
pathList, onSuccess, onError) {
var makeNextPath = function(i, directoryEntry) {
if (i == pathList.length) {
onSuccess(directoryEntry);
return;
}
this.makePath(pathList[i], makeNextPath.bind(null, i + 1), onError);
}.bind(this);
makeNextPath(0, null);
};
/**
* Add a wam.jsfs.Entry subclass to the file system at the specified path.
*
* If necessary, wam.jsfs.Directory entries will be created for missing
* path elements.
*
* @param {string} path The path to the entry.
* @param {wam.jsfs.Entry} entry The wam.jsfs.Entry subclass to place at the
* path.
* @param {function()} onSuccess The function to invoke on success.
* @param {function(wam.Error)} onError The function to invoke on error.
*/
wam.jsfs.FileSystem.prototype.makeEntry = function(
path, entry, onSuccess, onError) {
var dirName = wam.binding.fs.dirName(path);
var baseName = wam.binding.fs.baseName(path);
var map = {};
map[baseName] = entry;
this.makeEntries(dirName, map, onSuccess, onError);
};
/**
* Ensure that the given path exists, then add the given entries to it.
*
* @param {string} path The path to the parent directory for these entries.
* Will be created if necessary.
* @param {Object} entryMap A map of one or more {name: wam.jsfs.Entry}.
* @param {function()} onSuccess The function to invoke on success.
* @param {function(wam.Error)} onError The function to invoke on error.
*/
wam.jsfs.FileSystem.prototype.makeEntries = function(
path, entryMap, onSuccess, onError) {
var entryNames = Object.keys(entryMap);
var makeNextEntry = function(directoryEntry) {
if (entryNames.length == 0) {
onSuccess(directoryEntry);
return;
}
var name = entryNames.shift()
directoryEntry.addEntry(name, entryMap[name],
makeNextEntry.bind(null, directoryEntry),
onError);
};
this.makePath(path, makeNextEntry, onError);
};
/**
* Resolve the given path as far as possible.
*
* The success callback will receive three arguments:
* prefixList - An array of path names that were successfully resolved.
* pathList - The remaining path names, starting with the first that could not
* be found.
* entry - The entry instance that represents the final element of prefixList.
* This is not guaranteed to be a directory entry.
*
* If the partialResolve succeeds, it means that all of the path elements on the
* prefixList were found, and the elements on the pathList are yet-to-be
* resolved. The entry is the final wam.jsfs.Entry that was resolved.
*
* The meaning of this success depends on the context. If the resolved Entry
* can 'FORWARD', then this isn't necessarily a completed success or failure
* yet.
*
* @param {string} path The path to resolve.
* @param {function(Array, Array, wam.jsfs.Entry)} The function to invoke on
* success.
* @param {function(wam.Error)} onError The function to invoke on error.
*/
wam.jsfs.FileSystem.prototype.partialResolve = function(
path, onSuccess, onError) {
if (!onSuccess || !onError)
throw new Error('Missing onSuccess or onError');
if (!path || path == '/') {
wam.async(onSuccess, [null, [], [], this.rootDirectory_]);
return;
}
var ary = path.match(/^\/?([^/]+)(.*)/);
if (!ary) {
wam.async(onError,
[null, wam.mkerr('wam.FileSystem.Error.InvalidPath', [path])]);
return;
}
if (path.substr(0, 1) == '/')
path = path.substr(1);
this.rootDirectory_.partialResolve(
[], wam.binding.fs.splitPath(path),
onSuccess, onError);
};
/**
* Handle the onStat event for a wam.binding.fs.FileSystem.
*/
wam.jsfs.FileSystem.prototype.onStat_ = function(arg, onSuccess, onError) {
if (typeof arg.path != 'string') {
console.error('Missing argument: path');
wam.async(onError,
[null, wam.mkerr('wam.Error.MissingArgument', ['path'])]);
return;
}
var onPartialResolve = function(prefixList, pathList, entry) {
if (entry.can('FORWARD')) {
entry.forwardStat
({fullPath: arg.path, forwardPath: pathList.join('/')},
onSuccess, onError);
return;
}
if (pathList.length) {
onError(wam.mkerr('wam.FileSystem.Error.NotFound', [arg.path]));
return;
}
entry.getStat(onSuccess, onError);
};
this.partialResolve(arg.path, onPartialResolve, onError);
};
/**
* Handle the onUnlink event for a wam.binding.fs.FileSystem.
*/
wam.jsfs.FileSystem.prototype.onUnlink_ = function(arg, onSuccess, onError) {
if (typeof arg.path != 'string') {
console.error('Missing argument: path');
wam.async(onError,
[null, wam.mkerr('wam.Error.MissingArgument', ['path'])]);
return;
}
var onPartialResolve = function(prefixList, pathList, entry) {
if (entry.can('FORWARD')) {
entry.forwardUnlink
({fullPath: arg.path, forwardPath: pathList.join('/') + '/' + targetName},
onSuccess, onError);
return;
}
if (pathList.length) {
onError(wam.mkerr('wam.FileSystem.Error.NotFound', [parentPath]));
return;
}
if (!entry.can('LIST')) {
onError(wam.mkerr('wam.FileSystem.Error.NotListable', [parentPath]));
return;
}
entry.doUnlink(targetName, onSuccess, onError);
};
var parentPath = wam.binding.fs.dirName(arg.path);
var targetName = wam.binding.fs.baseName(arg.path);
this.partialResolve(parentPath, onPartialResolve, onError);
};
/**
* Handle the onList event for a wam.binding.fs.FileSystem.
*/
wam.jsfs.FileSystem.prototype.onList_ = function(arg, onSuccess, onError) {
if (!onSuccess || !onError)
throw new Error('Missing callback', onSuccess, onError);
if (typeof arg.path != 'string') {
console.error('Missing argument: path');
wam.async(onError,
[null, wam.mkerr('wam.FileSystem.Error.BadOrMissingArgument',
['path'])]);
return;
}
var onPartialResolve = function(prefixList, pathList, entry) {
if (entry.can('FORWARD')) {
entry.forwardList
({fullPath: arg.path, forwardPath: pathList.join('/')},
onSuccess, onError);
return;
}
if (pathList.length) {
onError(wam.mkerr('wam.FileSystem.Error.NotFound', [arg.path]));
return;
}
if (!entry.can('LIST')) {
onError(wam.mkerr('wam.FileSystem.Error.NotListable', [arg.path]));
return;
}
entry.listEntryStats(onSuccess);
};
this.partialResolve(arg.path, onPartialResolve, onError);
};
/**
* Handle the onExecuteContextCreated event for a wam.binding.fs.FileSystem.
*/
wam.jsfs.FileSystem.prototype.onExecuteContextCreated_ = function(
executeContext) {
new wam.jsfs.ExecuteContext(this, executeContext);
};
/**
* Handle the onOpenContextCreated event for a wam.binding.fs.FileSystem.
*/
wam.jsfs.FileSystem.prototype.onOpenContextCreated_ = function(openContext) {
new wam.jsfs.OpenContext(this, openContext);
};