Make Filer node guid pluggable, default to de-duping ids on generation.

This commit is contained in:
David Humphrey (:humph) david.humphrey@senecacollege.ca 2014-06-02 16:44:20 -04:00
parent b51957ea77
commit 5ab211d14d
5 changed files with 161 additions and 40 deletions

View File

@ -35,6 +35,7 @@ module.exports = {
FS_FORMAT: 'FORMAT', FS_FORMAT: 'FORMAT',
FS_NOCTIME: 'NOCTIME', FS_NOCTIME: 'NOCTIME',
FS_NOMTIME: 'NOMTIME', FS_NOMTIME: 'NOMTIME',
FS_NODUPEIDCHECK: 'FS_NODUPEIDCHECK',
// FS File Open Flags // FS File Open Flags
O_READ: O_READ, O_READ: O_READ,

View File

@ -152,9 +152,15 @@ function make_node(context, path, mode, callback) {
callback(error); callback(error);
} else { } else {
parentNodeData = result; parentNodeData = result;
node = new Node(undefined, mode); Node.create({guid: context.guid, mode: mode}, function(error, result) {
node.nlinks += 1; if(error) {
context.put(node.id, node, update_parent_node_data); callback(error);
return;
}
node = result;
node.nlinks += 1;
context.put(node.id, node, update_parent_node_data);
});
} }
} }
@ -339,8 +345,14 @@ function make_root_directory(context, callback) {
} else if(error && !(error instanceof Errors.ENOENT)) { } else if(error && !(error instanceof Errors.ENOENT)) {
callback(error); callback(error);
} else { } else {
superNode = new SuperNode(); SuperNode.create({guid: context.guid}, function(error, result) {
context.put(superNode.id, superNode, write_directory_node); if(error) {
callback(error);
return;
}
superNode = result;
context.put(superNode.id, superNode, write_directory_node);
});
} }
} }
@ -348,9 +360,15 @@ function make_root_directory(context, callback) {
if(error) { if(error) {
callback(error); callback(error);
} else { } else {
directoryNode = new Node(superNode.rnode, MODE_DIRECTORY); Node.create({guid: context.guid, id: superNode.rnode, mode: MODE_DIRECTORY}, function(error, result) {
directoryNode.nlinks += 1; if(error) {
context.put(directoryNode.id, directoryNode, write_directory_data); callback(error);
return;
}
directoryNode = result;
directoryNode.nlinks += 1;
context.put(directoryNode.id, directoryNode, write_directory_data);
});
} }
} }
@ -403,9 +421,15 @@ function make_directory(context, path, callback) {
callback(error); callback(error);
} else { } else {
parentDirectoryData = result; parentDirectoryData = result;
directoryNode = new Node(undefined, MODE_DIRECTORY); Node.create({guid: context.guid, mode: MODE_DIRECTORY}, function(error, result) {
directoryNode.nlinks += 1; if(error) {
context.put(directoryNode.id, directoryNode, write_directory_data); callback(error);
return;
}
directoryNode = result;
directoryNode.nlinks += 1;
context.put(directoryNode.id, directoryNode, write_directory_data);
});
} }
} }
@ -632,9 +656,15 @@ function open_file(context, path, flags, callback) {
} }
function write_file_node() { function write_file_node() {
fileNode = new Node(undefined, MODE_FILE); Node.create({guid: context.guid, mode: MODE_FILE}, function(error, result) {
fileNode.nlinks += 1; if(error) {
context.put(fileNode.id, fileNode, write_file_data); callback(error);
return;
}
fileNode = result;
fileNode.nlinks += 1;
context.put(fileNode.id, fileNode, write_file_data);
});
} }
function write_file_data(error) { function write_file_data(error) {
@ -1091,11 +1121,17 @@ function make_symbolic_link(context, srcpath, dstpath, callback) {
} }
function write_file_node() { function write_file_node() {
fileNode = new Node(undefined, MODE_SYMBOLIC_LINK); Node.create({guid: context.guid, mode: MODE_SYMBOLIC_LINK}, function(error, result) {
fileNode.nlinks += 1; if(error) {
fileNode.size = srcpath.length; callback(error);
fileNode.data = srcpath; return;
context.put(fileNode.id, fileNode, update_directory_data); }
fileNode = result;
fileNode.nlinks += 1;
fileNode.size = srcpath.length;
fileNode.data = srcpath;
context.put(fileNode.id, fileNode, update_directory_data);
});
} }
function update_time(error) { function update_time(error) {

View File

@ -9,6 +9,7 @@ var FS_FORMAT = Constants.FS_FORMAT;
var FS_READY = Constants.FS_READY; var FS_READY = Constants.FS_READY;
var FS_PENDING = Constants.FS_PENDING; var FS_PENDING = Constants.FS_PENDING;
var FS_ERROR = Constants.FS_ERROR; var FS_ERROR = Constants.FS_ERROR;
var FS_NODUPEIDCHECK = Constants.FS_NODUPEIDCHECK;
var providers = require('../providers/index.js'); var providers = require('../providers/index.js');
@ -16,13 +17,14 @@ var Shell = require('../shell/shell.js');
var Intercom = require('../../lib/intercom.js'); var Intercom = require('../../lib/intercom.js');
var FSWatcher = require('../fs-watcher.js'); var FSWatcher = require('../fs-watcher.js');
var Errors = require('../errors.js'); var Errors = require('../errors.js');
var defaultGuidFn = require('../shared.js').guid;
var STDIN = Constants.STDIN; var STDIN = Constants.STDIN;
var STDOUT = Constants.STDOUT; var STDOUT = Constants.STDOUT;
var STDERR = Constants.STDERR; var STDERR = Constants.STDERR;
var FIRST_DESCRIPTOR = Constants.FIRST_DESCRIPTOR; var FIRST_DESCRIPTOR = Constants.FIRST_DESCRIPTOR;
// The core fs operations live on impl // The core fs operations live on impl
var impl = require('./implementation.js'); var impl = require('./implementation.js');
// node.js supports a calling pattern that leaves off a callback. // node.js supports a calling pattern that leaves off a callback.
@ -55,6 +57,9 @@ function maybeCallback(callback) {
* can write one of their own and pass it in to be used. * can write one of their own and pass it in to be used.
* By default an IndexedDB provider is used. * By default an IndexedDB provider is used.
* *
* guid: a function for generating unique IDs for nodes in the filesystem.
* Use this to override the built-in UUID generation. (Used mainly for tests).
*
* callback: a callback function to be executed when the file system becomes * callback: a callback function to be executed when the file system becomes
* ready for use. Depending on the context provider used, this might * ready for use. Depending on the context provider used, this might
* be right away, or could take some time. The callback should expect * be right away, or could take some time. The callback should expect
@ -64,9 +69,10 @@ function maybeCallback(callback) {
*/ */
function FileSystem(options, callback) { function FileSystem(options, callback) {
options = options || {}; options = options || {};
callback = callback || nop; callback = callback || nop;
var flags = options.flags; var flags = options.flags;
var guid = options.guid ? options.guid : defaultGuidFn;
var provider = options.provider || new providers.Default(options.name || FILE_SYSTEM_NAME); var provider = options.provider || new providers.Default(options.name || FILE_SYSTEM_NAME);
// If we're given a provider, match its name unless we get an explicit name // If we're given a provider, match its name unless we get an explicit name
var name = options.name || provider.name; var name = options.name || provider.name;
@ -138,6 +144,36 @@ function FileSystem(options, callback) {
return watcher; return watcher;
}; };
// Deal with various approaches to node ID creation
function wrappedGuidFn(context) {
return function(callback) {
// Skip the duplicate ID check if asked to
if(_(flags).contains(FS_NODUPEIDCHECK)) {
callback(null, guid());
return;
}
// Otherwise (default) make sure this id is unused first
function guidWithCheck(callback) {
var id = guid();
context.get(id, function(err, value) {
if(err) {
callback(err);
return;
}
// If this id is unused, use it, otherwise find another
if(!value) {
callback(null, id);
} else {
guidWithCheck(callback);
}
});
}
guidWithCheck(callback);
};
}
// Let other instances (in this or other windows) know about // Let other instances (in this or other windows) know about
// any changes to this fs instance. // any changes to this fs instance.
function broadcastChanges(changes) { function broadcastChanges(changes) {
@ -158,6 +194,7 @@ function FileSystem(options, callback) {
var context = provider[methodName](); var context = provider[methodName]();
context.flags = flags; context.flags = flags;
context.changes = []; context.changes = [];
context.guid = wrappedGuidFn(context);
// When the context is finished, let the fs deal with any change events // When the context is finished, let the fs deal with any change events
context.close = function() { context.close = function() {
@ -201,6 +238,7 @@ function FileSystem(options, callback) {
} }
// otherwise format the fs first // otherwise format the fs first
var context = provider.getReadWriteContext(); var context = provider.getReadWriteContext();
context.guid = wrappedGuidFn(context);
context.clear(function(err) { context.clear(function(err) {
if(err) { if(err) {
complete(err); complete(err);

View File

@ -1,20 +1,53 @@
var MODE_FILE = require('./constants.js').MODE_FILE; var MODE_FILE = require('./constants.js').MODE_FILE;
var guid = require('./shared.js').guid;
module.exports = function Node(id, mode, size, atime, ctime, mtime, flags, xattrs, nlinks, version) { function Node(options) {
var now = Date.now(); var now = Date.now();
this.id = id || guid(); this.id = options.id;
this.mode = mode || MODE_FILE; // node type (file, directory, etc) this.mode = options.mode || MODE_FILE; // node type (file, directory, etc)
this.size = size || 0; // size (bytes for files, entries for directories) this.size = options.size || 0; // size (bytes for files, entries for directories)
this.atime = atime || now; // access time (will mirror ctime after creation) this.atime = options.atime || now; // access time (will mirror ctime after creation)
this.ctime = ctime || now; // creation/change time this.ctime = options.ctime || now; // creation/change time
this.mtime = mtime || now; // modified time this.mtime = options.mtime || now; // modified time
this.flags = flags || []; // file flags this.flags = options.flags || []; // file flags
this.xattrs = xattrs || {}; // extended attributes this.xattrs = options.xattrs || {}; // extended attributes
this.nlinks = nlinks || 0; // links count this.nlinks = options.nlinks || 0; // links count
this.version = version || 0; // node version this.version = options.version || 0; // node version
this.blksize = undefined; // block size this.blksize = undefined; // block size
this.nblocks = 1; // blocks count this.nblocks = 1; // blocks count
this.data = guid(); // id for data object this.data = options.data; // id for data object
}
// Make sure the options object has an id on property,
// either from caller or one we generate using supplied guid fn.
function ensureID(options, prop, callback) {
if(options[prop]) {
callback(null);
} else {
options.guid(function(err, id) {
options[prop] = id;
callback(err);
});
}
}
Node.create = function(options, callback) {
// We expect both options.id and options.data to be provided/generated.
ensureID(options, 'id', function(err) {
if(err) {
callback(err);
return;
}
ensureID(options, 'data', function(err) {
if(err) {
callback(err);
return;
}
callback(null, new Node(options));
});
});
}; };
module.exports = Node;

View File

@ -1,13 +1,26 @@
var Constants = require('./constants.js'); var Constants = require('./constants.js');
var guid = require('./shared.js').guid;
module.exports = function SuperNode(atime, ctime, mtime) { function SuperNode(options) {
var now = Date.now(); var now = Date.now();
this.id = Constants.SUPER_NODE_ID; this.id = Constants.SUPER_NODE_ID;
this.mode = Constants.MODE_META; this.mode = Constants.MODE_META;
this.atime = atime || now; this.atime = options.atime || now;
this.ctime = ctime || now; this.ctime = options.ctime || now;
this.mtime = mtime || now; this.mtime = options.mtime || now;
this.rnode = guid(); // root node id (randomly generated) // root node id (randomly generated)
this.rnode = options.rnode;
}
SuperNode.create = function(options, callback) {
options.guid(function(err, rnode) {
if(err) {
callback(err);
return;
}
options.rnode = options.rnode || rnode;
callback(null, new SuperNode(options));
});
}; };
module.exports = SuperNode;