Merge pull request #137 from humphd/issue2

Update atime, ctime, mtime on fs operations. Fixes #2
This commit is contained in:
Alan K 2014-03-05 22:23:46 -05:00
commit 27262cdb67
12 changed files with 979 additions and 60 deletions

View File

@ -80,7 +80,10 @@ Accepts two arguments: an `options` object, and an optional `callback`. The `opt
object can specify a number of optional arguments, including: object can specify a number of optional arguments, including:
* `name`: the name of the file system, defaults to `'"local'` * `name`: the name of the file system, defaults to `'"local'`
* `flags`: one or more flags to use when creating/opening the file system. Use `'FORMAT'` to force Filer to format (i.e., erase) the file system * `flags`: an Array of one or more flags to use when creating/opening the file system:
*`'FORMAT'` to force Filer to format (i.e., erase) the file system
*`'NOCTIME'` to force Filer to not update `ctime` on nodes when metadata changes (i.e., for better performance)
*`'NOMTIME'` to force Filer to not update `mtime` on nodes when data changes (i.e., for better performance)
* `provider`: an explicit storage provider to use for the file system's database context provider. See the section on [Storage Providers](#providers). * `provider`: an explicit storage provider to use for the file system's database context provider. See the section on [Storage Providers](#providers).
The `callback` function indicates when the file system is ready for use. Depending on the storage provider used, this might The `callback` function indicates when the file system is ready for use. Depending on the storage provider used, this might
@ -98,7 +101,7 @@ function fsReady(err, fs) {
fs = new Filer.FileSystem({ fs = new Filer.FileSystem({
name: "my-filesystem", name: "my-filesystem",
flags: 'FORMAT', flags: [ 'FORMAT' ],
provider: new Filer.FileSystem.providers.Memory() provider: new Filer.FileSystem.providers.Memory()
}, fsReady); }, fsReady);
``` ```

View File

@ -33,8 +33,12 @@ define(function(require) {
ROOT_DIRECTORY_NAME: '/', // basename(normalize(path)) ROOT_DIRECTORY_NAME: '/', // basename(normalize(path))
// FS Mount Flags
FS_FORMAT: 'FORMAT', FS_FORMAT: 'FORMAT',
FS_NOCTIME: 'NOCTIME',
FS_NOMTIME: 'NOMTIME',
// FS File Open Flags
O_READ: O_READ, O_READ: O_READ,
O_WRITE: O_WRITE, O_WRITE: O_WRITE,
O_CREATE: O_CREATE, O_CREATE: O_CREATE,

243
src/fs.js
View File

@ -52,6 +52,8 @@ define(function(require) {
var O_FLAGS = require('src/constants').O_FLAGS; var O_FLAGS = require('src/constants').O_FLAGS;
var XATTR_CREATE = require('src/constants').XATTR_CREATE; var XATTR_CREATE = require('src/constants').XATTR_CREATE;
var XATTR_REPLACE = require('src/constants').XATTR_REPLACE; var XATTR_REPLACE = require('src/constants').XATTR_REPLACE;
var FS_NOMTIME = require('src/constants').FS_NOMTIME;
var FS_NOCTIME = require('src/constants').FS_NOCTIME;
var providers = require('src/providers/providers'); var providers = require('src/providers/providers');
var adapters = require('src/adapters/adapters'); var adapters = require('src/adapters/adapters');
@ -70,7 +72,8 @@ define(function(require) {
* OpenFileDescription * OpenFileDescription
*/ */
function OpenFileDescription(id, flags, position) { function OpenFileDescription(path, id, flags, position) {
this.path = path;
this.id = id; this.id = id;
this.flags = flags; this.flags = flags;
this.position = position; this.position = position;
@ -101,8 +104,8 @@ define(function(require) {
this.id = id || guid(); this.id = id || guid();
this.mode = mode || MODE_FILE; // node type (file, directory, etc) this.mode = mode || MODE_FILE; // node type (file, directory, etc)
this.size = size || 0; // size (bytes for files, entries for directories) this.size = size || 0; // size (bytes for files, entries for directories)
this.atime = atime || now; // access time this.atime = atime || now; // access time (will mirror ctime after creation)
this.ctime = ctime || now; // creation time this.ctime = ctime || now; // creation/change time
this.mtime = mtime || now; // modified time this.mtime = mtime || now; // modified time
this.flags = flags || []; // file flags this.flags = flags || []; // file flags
this.xattrs = xattrs || {}; // extended attributes this.xattrs = xattrs || {}; // extended attributes
@ -128,6 +131,46 @@ define(function(require) {
this.type = fileNode.mode; this.type = fileNode.mode;
} }
/*
* Update node times. Only passed times are modified (undefined times are ignored)
* and filesystem flags are examined in order to override update logic.
*/
function update_node_times(context, path, node, times, callback) {
// Honour mount flags for how we update times
var flags = context.flags;
if(_(flags).contains(FS_NOCTIME)) {
delete times.ctime;
}
if(_(flags).contains(FS_NOMTIME)) {
delete times.mtime;
}
// Only do the update if required (i.e., times are still present)
var update = false;
if(times.ctime) {
node.ctime = times.ctime;
// We don't do atime tracking for perf reasons, but do mirror ctime
node.atime = times.ctime;
update = true;
}
if(times.atime) {
// The only time we explicitly pass atime is when utimes(), futimes() is called.
// Override ctime mirror here if so
node.atime = times.atime;
update = true;
}
if(times.mtime) {
node.mtime = times.mtime;
update = true;
}
if(update) {
context.put(node.id, node, callback);
} else {
callback();
}
}
/* /*
* find_node * find_node
*/ */
@ -231,9 +274,19 @@ define(function(require) {
*/ */
function set_extended_attribute (context, path_or_fd, name, value, flag, callback) { function set_extended_attribute (context, path_or_fd, name, value, flag, callback) {
var path;
function set_xattr (error, node) { function set_xattr (error, node) {
var xattr = (node ? node.xattrs[name] : null); var xattr = (node ? node.xattrs[name] : null);
function update_time(error) {
if(error) {
callback(error);
} else {
update_node_times(context, path, node, { ctime: Date.now() }, callback);
}
}
if (error) { if (error) {
callback(error); callback(error);
} }
@ -245,14 +298,16 @@ define(function(require) {
} }
else { else {
node.xattrs[name] = value; node.xattrs[name] = value;
context.put(node.id, node, callback); context.put(node.id, node, update_time);
} }
} }
if (typeof path_or_fd == 'string') { if (typeof path_or_fd == 'string') {
path = path_or_fd;
find_node(context, path_or_fd, set_xattr); find_node(context, path_or_fd, set_xattr);
} }
else if (typeof path_or_fd == 'object' && typeof path_or_fd.id == 'string') { else if (typeof path_or_fd == 'object' && typeof path_or_fd.id == 'string') {
path = path_or_fd.path;
context.get(path_or_fd.id, set_xattr); context.get(path_or_fd.id, set_xattr);
} }
else { else {
@ -356,12 +411,21 @@ define(function(require) {
} }
} }
function update_time(error) {
if(error) {
callback(error);
} else {
var now = Date.now();
update_node_times(context, parentPath, parentDirectoryNode, { mtime: now, ctime: now }, callback);
}
}
function update_parent_directory_data(error) { function update_parent_directory_data(error) {
if(error) { if(error) {
callback(error); callback(error);
} else { } else {
parentDirectoryData[name] = new DirectoryEntry(directoryNode.id, MODE_DIRECTORY); parentDirectoryData[name] = new DirectoryEntry(directoryNode.id, MODE_DIRECTORY);
context.put(parentDirectoryNode.data, parentDirectoryData, callback); context.put(parentDirectoryNode.data, parentDirectoryData, update_time);
} }
} }
@ -429,9 +493,18 @@ define(function(require) {
} }
} }
function update_time(error) {
if(error) {
callback(error);
} else {
var now = Date.now();
update_node_times(context, parentPath, parentDirectoryNode, { mtime: now, ctime: now }, remove_directory_node);
}
}
function remove_directory_entry_from_parent_directory_node() { function remove_directory_entry_from_parent_directory_node() {
delete parentDirectoryData[name]; delete parentDirectoryData[name];
context.put(parentDirectoryNode.data, parentDirectoryData, remove_directory_node); context.put(parentDirectoryNode.data, parentDirectoryData, update_time);
} }
function remove_directory_node(error) { function remove_directory_node(error) {
@ -567,12 +640,21 @@ define(function(require) {
} }
} }
function update_time(error) {
if(error) {
callback(error);
} else {
var now = Date.now();
update_node_times(context, parentPath, directoryNode, { mtime: now, ctime: now }, handle_update_result);
}
}
function update_directory_data(error) { function update_directory_data(error) {
if(error) { if(error) {
callback(error); callback(error);
} else { } else {
directoryData[name] = new DirectoryEntry(fileNode.id, MODE_FILE); directoryData[name] = new DirectoryEntry(fileNode.id, MODE_FILE);
context.put(directoryNode.data, directoryData, handle_update_result); context.put(directoryNode.data, directoryData, update_time);
} }
} }
@ -596,11 +678,20 @@ define(function(require) {
} }
} }
function update_time(error) {
if(error) {
callback(error);
} else {
var now = Date.now();
update_node_times(context, ofd.path, fileNode, { mtime: now, ctime: now }, return_nbytes);
}
}
function update_file_node(error) { function update_file_node(error) {
if(error) { if(error) {
callback(error); callback(error);
} else { } else {
context.put(fileNode.id, fileNode, return_nbytes); context.put(fileNode.id, fileNode, update_time);
} }
} }
@ -615,7 +706,6 @@ define(function(require) {
ofd.position = length; ofd.position = length;
fileNode.size = length; fileNode.size = length;
fileNode.mtime = Date.now();
fileNode.version += 1; fileNode.version += 1;
context.put(fileNode.data, newData, update_file_node); context.put(fileNode.data, newData, update_file_node);
@ -637,11 +727,20 @@ define(function(require) {
} }
} }
function update_time(error) {
if(error) {
callback(error);
} else {
var now = Date.now();
update_node_times(context, ofd.path, fileNode, { mtime: now, ctime: now }, return_nbytes);
}
}
function update_file_node(error) { function update_file_node(error) {
if(error) { if(error) {
callback(error); callback(error);
} else { } else {
context.put(fileNode.id, fileNode, return_nbytes); context.put(fileNode.id, fileNode, update_time);
} }
} }
@ -663,7 +762,6 @@ define(function(require) {
} }
fileNode.size = newSize; fileNode.size = newSize;
fileNode.mtime = Date.now();
fileNode.version += 1; fileNode.version += 1;
context.put(fileNode.data, newData, update_file_node); context.put(fileNode.data, newData, update_file_node);
@ -702,6 +800,14 @@ define(function(require) {
} }
} }
function update_time(error) {
if(error) {
callback(error);
} else {
}
}
function read_file_data(error, result) { function read_file_data(error, result) {
if(error) { if(error) {
callback(error); callback(error);
@ -801,13 +907,21 @@ define(function(require) {
var newDirectoryData; var newDirectoryData;
var fileNode; var fileNode;
function update_time(error) {
if(error) {
callback(error);
} else {
update_node_times(context, newpath, fileNode, { ctime: Date.now() }, callback);
}
}
function update_file_node(error, result) { function update_file_node(error, result) {
if(error) { if(error) {
callback(error); callback(error);
} else { } else {
fileNode = result; fileNode = result;
fileNode.nlinks += 1; fileNode.nlinks += 1;
context.put(fileNode.id, fileNode, callback); context.put(fileNode.id, fileNode, update_time);
} }
} }
@ -881,7 +995,10 @@ define(function(require) {
callback(error); callback(error);
} else { } else {
delete directoryData[name]; delete directoryData[name];
context.put(directoryNode.data, directoryData, callback); context.put(directoryNode.data, directoryData, function(error) {
var now = Date.now();
update_node_times(context, parentPath, directoryNode, { mtime: now, ctime: now }, callback);
});
} }
} }
@ -902,7 +1019,9 @@ define(function(require) {
if(fileNode.nlinks < 1) { if(fileNode.nlinks < 1) {
context.delete(fileNode.id, delete_file_data); context.delete(fileNode.id, delete_file_data);
} else { } else {
context.put(fileNode.id, fileNode, update_directory_data); context.put(fileNode.id, fileNode, function(error) {
update_node_times(context, path, fileNode, { ctime: Date.now() }, update_directory_data);
});
} }
} }
} }
@ -1006,12 +1125,21 @@ define(function(require) {
context.put(fileNode.id, fileNode, update_directory_data); context.put(fileNode.id, fileNode, update_directory_data);
} }
function update_time(error) {
if(error) {
callback(error);
} else {
var now = Date.now();
update_node_times(context, parentPath, directoryNode, { mtime: now, ctime: now }, callback);
}
}
function update_directory_data(error) { function update_directory_data(error) {
if(error) { if(error) {
callback(error); callback(error);
} else { } else {
directoryData[name] = new DirectoryEntry(fileNode.id, MODE_SYMBOLIC_LINK); directoryData[name] = new DirectoryEntry(fileNode.id, MODE_SYMBOLIC_LINK);
context.put(directoryNode.data, directoryData, callback); context.put(directoryNode.data, directoryData, update_time);
} }
} }
} }
@ -1089,14 +1217,22 @@ define(function(require) {
} }
} }
function update_time(error) {
if(error) {
callback(error);
} else {
var now = Date.now();
update_node_times(context, path, fileNode, { mtime: now, ctime: now }, callback);
}
}
function update_file_node (error) { function update_file_node (error) {
if(error) { if(error) {
callback(error); callback(error);
} else { } else {
fileNode.size = length; fileNode.size = length;
fileNode.mtime = Date.now();
fileNode.version += 1; fileNode.version += 1;
context.put(fileNode.id, fileNode, callback); context.put(fileNode.id, fileNode, update_time);
} }
} }
@ -1133,14 +1269,21 @@ define(function(require) {
} }
} }
function update_time(error) {
if(error) {
callback(error);
} else {
var now = Date.now();
update_node_times(context, ofd.path, fileNode, { mtime: now, ctime: now }, callback);
}
}
function update_file_node (error) { function update_file_node (error) {
if(error) { if(error) {
callback(error); callback(error);
} else { } else {
fileNode.size = length; fileNode.size = length;
fileNode.mtime = Date.now();
fileNode.version += 1; fileNode.version += 1;
context.put(fileNode.id, fileNode, callback); context.put(fileNode.id, fileNode, update_time);
} }
} }
@ -1154,14 +1297,11 @@ define(function(require) {
function utimes_file(context, path, atime, mtime, callback) { function utimes_file(context, path, atime, mtime, callback) {
path = normalize(path); path = normalize(path);
function update_times (error, node) { function update_times(error, node) {
if (error) { if (error) {
callback(error); callback(error);
} } else {
else { update_node_times(context, path, node, { atime: atime, ctime: mtime, mtime: mtime }, callback);
node.atime = atime;
node.mtime = mtime;
context.put(node.id, node, callback);
} }
} }
@ -1181,11 +1321,8 @@ define(function(require) {
function update_times (error, node) { function update_times (error, node) {
if (error) { if (error) {
callback(error); callback(error);
} } else {
else { update_node_times(context, ofd.path, node, { atime: atime, ctime: mtime, mtime: mtime }, callback);
node.atime = atime;
node.mtime = mtime;
context.put(node.id, node, callback);
} }
} }
@ -1296,6 +1433,14 @@ define(function(require) {
function remove_xattr (error, node) { function remove_xattr (error, node) {
var xattr = (node ? node.xattrs : null); var xattr = (node ? node.xattrs : null);
function update_time(error) {
if(error) {
callback(error);
} else {
update_node_times(context, path, node, { ctime: Date.now() }, callback);
}
}
if (error) { if (error) {
callback(error); callback(error);
} }
@ -1304,7 +1449,7 @@ define(function(require) {
} }
else { else {
delete node.xattrs[name]; delete node.xattrs[name];
context.put(node.id, node, callback); context.put(node.id, node, update_time);
} }
} }
@ -1322,6 +1467,14 @@ define(function(require) {
function fremovexattr_file (context, ofd, name, callback) { function fremovexattr_file (context, ofd, name, callback) {
function remove_xattr (error, node) { function remove_xattr (error, node) {
function update_time(error) {
if(error) {
callback(error);
} else {
update_node_times(context, ofd.path, node, { ctime: Date.now() }, callback);
}
}
if (error) { if (error) {
callback(error); callback(error);
} }
@ -1330,7 +1483,7 @@ define(function(require) {
} }
else { else {
delete node.xattrs[name]; delete node.xattrs[name];
context.put(node.id, node, callback); context.put(node.id, node, update_time);
} }
} }
@ -1471,7 +1624,21 @@ define(function(require) {
// Open file system storage provider // Open file system storage provider
provider.open(function(err, needsFormatting) { provider.open(function(err, needsFormatting) {
function complete(error) { function complete(error) {
fs.provider = provider; // Wrap the provider so we can extend the context with fs flags.
// From this point forward we won't call open again, so drop it.
fs.provider = {
getReadWriteContext: function() {
var context = provider.getReadWriteContext();
context.flags = flags;
return context;
},
getReadOnlyContext: function() {
var context = provider.getReadOnlyContext();
context.flags = flags;
return context;
}
};
if(error) { if(error) {
fs.readyState = FS_ERROR; fs.readyState = FS_ERROR;
} else { } else {
@ -1520,7 +1687,7 @@ define(function(require) {
} else { } else {
position = 0; position = 0;
} }
var openFileDescription = new OpenFileDescription(fileNode.id, flags, position); var openFileDescription = new OpenFileDescription(path, fileNode.id, flags, position);
var fd = fs.allocDescriptor(openFileDescription); var fd = fs.allocDescriptor(openFileDescription);
callback(null, fd); callback(null, fd);
} }
@ -1671,7 +1838,7 @@ define(function(require) {
if(err) { if(err) {
return callback(err); return callback(err);
} }
var ofd = new OpenFileDescription(fileNode.id, flags, 0); var ofd = new OpenFileDescription(path, fileNode.id, flags, 0);
var fd = fs.allocDescriptor(ofd); var fd = fs.allocDescriptor(ofd);
fstat_file(context, ofd, function(err2, fstatResult) { fstat_file(context, ofd, function(err2, fstatResult) {
@ -1748,7 +1915,7 @@ define(function(require) {
if(err) { if(err) {
return callback(err); return callback(err);
} }
var ofd = new OpenFileDescription(fileNode.id, flags, 0); var ofd = new OpenFileDescription(path, fileNode.id, flags, 0);
var fd = fs.allocDescriptor(ofd); var fd = fs.allocDescriptor(ofd);
replace_data(context, ofd, data, 0, data.length, function(err2, nbytes) { replace_data(context, ofd, data, 0, data.length, function(err2, nbytes) {
@ -1783,7 +1950,7 @@ define(function(require) {
if(err) { if(err) {
return callback(err); return callback(err);
} }
var ofd = new OpenFileDescription(fileNode.id, flags, fileNode.size); var ofd = new OpenFileDescription(path, fileNode.id, flags, fileNode.size);
var fd = fs.allocDescriptor(ofd); var fd = fs.allocDescriptor(ofd);
write_data(context, ofd, data, 0, data.length, ofd.position, function(err2, nbytes) { write_data(context, ofd, data, 0, data.length, ofd.position, function(err2, nbytes) {
@ -2284,7 +2451,7 @@ define(function(require) {
var fs = this; var fs = this;
var error = fs.queueOrRun( var error = fs.queueOrRun(
function() { function() {
var context = fs.provider.getReadOnlyContext(); var context = fs.provider.getReadWriteContext();
_exists(context, fs.name, path, callback); _exists(context, fs.name, path, callback);
} }
); );

View File

@ -131,7 +131,13 @@ define(["Filer", "util"], function(Filer, util) {
}); });
}); });
it("should fail when trying to write on ReadOnlyContext", function(done) { /**
* With issue 123 (see https://github.com/js-platform/filer/issues/128) we had to
* start using readwrite contexts everywhere with IndexedDB. As such, we can't
* easily test this here, without knowing which provider we have. We test this
* in the actual providers, so this isn't really needed. Skipping for now.
*/
it.skip("should fail when trying to write on ReadOnlyContext", function(done) {
var provider = createProvider(); var provider = createProvider();
provider.open(function(error, firstAccess) { provider.open(function(error, firstAccess) {
if(error) throw error; if(error) throw error;

View File

@ -11,9 +11,9 @@ define(["Filer", "util"], function(Filer, util) {
it('should return false if path does not exist', function(done) { it('should return false if path does not exist', function(done) {
var fs = util.fs(); var fs = util.fs();
fs.exists('/tmp', function(result) { fs.exists('/tmp', function(result) {
expect(result).to.exist; expect(result).to.be.false;
expect(result).equals(false);
done(); done();
}); });
}); });
@ -22,30 +22,33 @@ define(["Filer", "util"], function(Filer, util) {
var fs = util.fs(); var fs = util.fs();
fs.open('/myfile', 'w', function(err, fd) { fs.open('/myfile', 'w', function(err, fd) {
if(err) throw err;
fs.close(fd, function(err) {
if(err) throw err; if(err) throw err;
fs.close(fd, function(err) {
if(err) throw err; fs.exists('/myfile', function(result) {
fs.exists('/myfile', function(result) { expect(result).to.be.true;
expect(result).to.exist; done();
expect(result).equals(true);
done();
});
}); });
}); });
}); });
});
it('should follow symbolic links and return true for the resulting path', function(done) { it('should follow symbolic links and return true for the resulting path', function(done) {
var fs = util.fs(); var fs = util.fs();
fs.open('/myfile', 'w', function(error, fd) { fs.open('/myfile', 'w', function(error, fd) {
if(error) throw error; if(error) throw error;
fs.close(fd, function(error) { fs.close(fd, function(error) {
if(error) throw error; if(error) throw error;
fs.symlink('/myfile', '/myfilelink', function(error) { fs.symlink('/myfile', '/myfilelink', function(error) {
if(error) throw error; if(error) throw error;
fs.exists('/myfilelink', function(result) { fs.exists('/myfilelink', function(result) {
expect(result).to.exist; expect(result).to.be.true;
expect(result).equals(true);
done(); done();
}); });
}); });

View File

@ -28,7 +28,10 @@ define(["Filer", "util"], function(Filer, util) {
fs.stat('/myotherfile', function(error, result) { fs.stat('/myotherfile', function(error, result) {
expect(error).not.to.exist; expect(error).not.to.exist;
expect(result.nlinks).to.equal(2); expect(result.nlinks).to.equal(2);
expect(result).to.deep.equal(_oldstats); expect(result.dev).to.equal(_oldstats.dev);
expect(result.node).to.equal(_oldstats.node);
expect(result.size).to.equal(_oldstats.size);
expect(result.type).to.equal(_oldstats.type);
done(); done();
}); });
}); });
@ -52,7 +55,10 @@ define(["Filer", "util"], function(Filer, util) {
var _linkstats = result; var _linkstats = result;
fs.lstat('/myotherfile', function (error, result) { fs.lstat('/myotherfile', function (error, result) {
expect(error).not.to.exist; expect(error).not.to.exist;
expect(result).to.deep.equal(_linkstats); expect(result.dev).to.equal(_linkstats.dev);
expect(result.node).to.equal(_linkstats.node);
expect(result.size).to.equal(_linkstats.size);
expect(result.type).to.equal(_linkstats.type);
expect(result.nlinks).to.equal(2); expect(result.nlinks).to.equal(2);
done(); done();
}); });

View File

@ -102,7 +102,6 @@ define(["Filer", "util"], function(Filer, util) {
fs.stat('/testfile', function (error, stat) { fs.stat('/testfile', function (error, stat) {
expect(error).not.to.exist; expect(error).not.to.exist;
expect(stat.atime).to.equal(atime);
expect(stat.mtime).to.equal(mtime); expect(stat.mtime).to.equal(mtime);
done(); done();
}); });
@ -125,7 +124,6 @@ define(["Filer", "util"], function(Filer, util) {
fs.fstat(ofd, function (error, stat) { fs.fstat(ofd, function (error, stat) {
expect(error).not.to.exist; expect(error).not.to.exist;
expect(stat.atime).to.equal(atime);
expect(stat.mtime).to.equal(mtime); expect(stat.mtime).to.equal(mtime);
done(); done();
}); });
@ -146,7 +144,6 @@ define(["Filer", "util"], function(Filer, util) {
fs.stat('/testdir', function (error, stat) { fs.stat('/testdir', function (error, stat) {
expect(error).not.to.exist; expect(error).not.to.exist;
expect(stat.atime).to.equal(atime);
expect(stat.mtime).to.equal(mtime); expect(stat.mtime).to.equal(mtime);
done(); done();
}); });

View File

@ -129,7 +129,11 @@ define(["Filer", "util"], function(Filer, util) {
}); });
}); });
it("should fail when trying to write on ReadOnlyContext", function(done) { /**
* With issue 123 (see https://github.com/js-platform/filer/issues/128) we had to
* start using readwrite contexts everywhere with IndexedDB. Skipping for now.
*/
it.skip("should fail when trying to write on ReadOnlyContext", function(done) {
var provider = _provider.provider; var provider = _provider.provider;
provider.open(function(error, firstAccess) { provider.open(function(error, firstAccess) {
if(error) throw error; if(error) throw error;

View File

@ -92,7 +92,6 @@ define(["Filer", "util"], function(Filer, util) {
getTimes(fs, '/newfile', function(times) { getTimes(fs, '/newfile', function(times) {
expect(times.mtime).to.equal(date); expect(times.mtime).to.equal(date);
expect(times.atime).to.equal(date);
done(); done();
}); });
}); });

View File

@ -0,0 +1,106 @@
define(["Filer", "util"], function(Filer, util) {
describe('node times (atime, mtime, ctime) with mount flags', function() {
var dirname = "/dir";
var filename = "/dir/file";
function memoryFS(flags, callback) {
var name = util.uniqueName();
var fs = new Filer.FileSystem({
name: name,
flags: flags || [],
provider: new Filer.FileSystem.providers.Memory(name)
}, callback);
}
function createTree(fs, callback) {
fs.mkdir(dirname, function(error) {
if(error) throw error;
fs.open(filename, 'w', function(error, fd) {
if(error) throw error;
fs.close(fd, callback);
});
});
}
function stat(fs, path, callback) {
fs.stat(path, function(error, stats) {
if(error) throw error;
callback(stats);
});
}
/**
* We test the actual time updates in times.spec.js, whereas these just test
* the overrides with the mount flags. The particular fs methods called
* are unimportant, but are known to affect the particular times being suppressed.
*/
it('should not update ctime when calling fs.rename() with NOCTIME', function(done) {
memoryFS(['NOCTIME'], function(error, fs) {
var newfilename = filename + '1';
createTree(fs, function() {
stat(fs, filename, function(stats1) {
fs.rename(filename, newfilename, function(error) {
if(error) throw error;
stat(fs, newfilename, function(stats2) {
expect(stats2.ctime).to.equal(stats1.ctime);
expect(stats2.mtime).to.equal(stats1.mtime);
expect(stats2.atime).to.equal(stats1.atime);
done();
});
});
});
});
});
});
it('should not update ctime, mtime, atime when calling fs.truncate() with NOCTIME, NOMTIME', function(done) {
memoryFS(['NOCTIME', 'NOMTIME'], function(error, fs) {
createTree(fs, function() {
stat(fs, filename, function(stats1) {
fs.truncate(filename, 5, function(error) {
if(error) throw error;
stat(fs, filename, function(stats2) {
expect(stats2.ctime).to.equal(stats1.ctime);
expect(stats2.mtime).to.equal(stats1.mtime);
expect(stats2.atime).to.equal(stats1.atime);
done();
});
});
});
});
});
});
it('should not update mtime when calling fs.truncate() with NOMTIME', function(done) {
memoryFS(['NOMTIME'], function(error, fs) {
createTree(fs, function() {
stat(fs, filename, function(stats1) {
fs.truncate(filename, 5, function(error) {
if(error) throw error;
stat(fs, filename, function(stats2) {
expect(stats2.ctime).to.be.above(stats1.ctime);
expect(stats2.mtime).to.equal(stats1.mtime);
expect(stats2.atime).to.be.above(stats1.atime);
done();
});
});
});
});
});
});
});
});

622
tests/spec/times.spec.js Normal file
View File

@ -0,0 +1,622 @@
define(["Filer", "util"], function(Filer, util) {
describe('node times (atime, mtime, ctime)', function() {
beforeEach(util.setup);
afterEach(util.cleanup);
var dirname = "/dir";
var filename = "/dir/file";
function createTree(callback) {
var fs = util.fs();
fs.mkdir(dirname, function(error) {
if(error) throw error;
fs.open(filename, 'w', function(error, fd) {
if(error) throw error;
fs.close(fd, callback);
});
});
}
function stat(path, callback) {
var fs = util.fs();
fs.stat(path, function(error, stats) {
if(error) throw error;
callback(stats);
});
}
it('should update ctime when calling fs.rename()', function(done) {
var fs = util.fs();
var newfilename = filename + '1';
createTree(function() {
stat(filename, function(stats1) {
fs.rename(filename, newfilename, function(error) {
if(error) throw error;
stat(newfilename, function(stats2) {
expect(stats2.ctime).to.be.above(stats1.ctime);
expect(stats2.mtime).to.equal(stats1.mtime);
expect(stats2.atime).to.be.above(stats1.atime);
done();
});
});
});
});
});
it('should update ctime, mtime, atime when calling fs.truncate()', function(done) {
var fs = util.fs();
createTree(function() {
stat(filename, function(stats1) {
fs.truncate(filename, 5, function(error) {
if(error) throw error;
stat(filename, function(stats2) {
expect(stats2.ctime).to.be.above(stats1.ctime);
expect(stats2.mtime).to.be.above(stats1.mtime);
expect(stats2.atime).to.be.above(stats1.atime);
done();
});
});
});
});
});
it('should update ctime, mtime, atime when calling fs.ftruncate()', function(done) {
var fs = util.fs();
createTree(function() {
stat(filename, function(stats1) {
fs.open(filename, 'w', function(error, fd) {
if(error) throw error;
fs.ftruncate(fd, 5, function(error) {
if(error) throw error;
stat(filename, function(stats2) {
expect(stats2.ctime).to.be.above(stats1.ctime);
expect(stats2.mtime).to.be.above(stats1.mtime);
expect(stats2.atime).to.be.above(stats1.atime);
fs.close(fd, done);
});
});
});
});
});
});
it('should make no change when calling fs.stat()', function(done) {
var fs = util.fs();
createTree(function() {
stat(filename, function(stats1) {
fs.stat(filename, function(error, stats2) {
if(error) throw error;
expect(stats2.ctime).to.equal(stats1.ctime);
expect(stats2.mtime).to.equal(stats1.mtime);
expect(stats2.atime).to.equal(stats1.atime);
done();
});
});
});
});
it('should make no change when calling fs.fstat()', function(done) {
var fs = util.fs();
createTree(function() {
stat(filename, function(stats1) {
fs.open(filename, 'w', function(error, fd) {
if(error) throw error;
fs.fstat(fd, function(error, stats2) {
if(error) throw error;
expect(stats2.ctime).to.equal(stats1.ctime);
expect(stats2.mtime).to.equal(stats1.mtime);
expect(stats2.atime).to.equal(stats1.atime);
fs.close(fd, done);
});
});
});
});
});
it('should make no change when calling fs.lstat()', function(done) {
var fs = util.fs();
createTree(function() {
fs.link(filename, '/link', function(error) {
if(error) throw error;
stat(filename, function(stats1) {
fs.lstat('/link', function(error, stats2) {
if(error) throw error;
expect(stats2.ctime).to.equal(stats1.ctime);
expect(stats2.mtime).to.equal(stats1.mtime);
expect(stats2.atime).to.equal(stats1.atime);
done();
});
});
});
});
});
it('should make no change when calling fs.exists()', function(done) {
var fs = util.fs();
createTree(function() {
stat(filename, function(stats1) {
fs.exists(filename, function(exists) {
expect(exists).to.be.true;
fs.stat(filename, function(error, stats2) {
if(error) throw error;
expect(stats2.ctime).to.equal(stats1.ctime);
expect(stats2.mtime).to.equal(stats1.mtime);
expect(stats2.atime).to.equal(stats1.atime);
done();
});
});
});
});
});
it('should update ctime, atime when calling fs.link()', function(done) {
var fs = util.fs();
createTree(function() {
stat(filename, function(stats1) {
fs.link(filename, '/link', function(error) {
if(error) throw error;
stat(filename, function(stats2) {
expect(stats2.ctime).to.be.above(stats1.ctime);
expect(stats2.mtime).to.equal(stats1.mtime);
expect(stats2.atime).to.be.above(stats1.atime);
done();
});
});
});
});
});
it('should make no change when calling fs.symlink()', function(done) {
var fs = util.fs();
createTree(function() {
stat(filename, function(stats1) {
fs.symlink(filename, '/link', function(error) {
if(error) throw error;
stat(filename, function(stats2) {
expect(stats2.ctime).to.equal(stats1.ctime);
expect(stats2.mtime).to.equal(stats1.mtime);
expect(stats2.atime).to.equal(stats1.atime);
done();
});
});
});
});
});
it('should make no change when calling fs.readlink()', function(done) {
var fs = util.fs();
createTree(function() {
fs.symlink(filename, '/link', function(error) {
if(error) throw error;
stat('/link', function(stats1) {
fs.readlink('/link', function(error, contents) {
if(error) throw error;
expect(contents).to.equal(filename);
stat('/link', function(stats2) {
expect(stats2.ctime).to.equal(stats1.ctime);
expect(stats2.mtime).to.equal(stats1.mtime);
expect(stats2.atime).to.equal(stats1.atime);
done();
});
});
});
});
});
});
it('should update ctime, atime, mtime of parent dir when calling fs.unlink()', function(done) {
var fs = util.fs();
createTree(function() {
stat(dirname, function(stats1) {
fs.unlink(filename, function(error) {
if(error) throw error;
stat(dirname, function(stats2) {
expect(stats2.ctime).to.be.above(stats1.ctime);
expect(stats2.mtime).to.be.above(stats1.mtime);
expect(stats2.atime).to.be.above(stats1.atime);
done();
});
});
});
});
});
it('should update ctime, atime, mtime of parent dir when calling fs.rmdir()', function(done) {
var fs = util.fs();
createTree(function() {
stat('/', function(stats1) {
fs.unlink(filename, function(error) {
if(error) throw error;
fs.rmdir(dirname, function(error) {
if(error) throw error;
stat('/', function(stats2) {
expect(stats2.ctime).to.be.above(stats1.ctime);
expect(stats2.mtime).to.be.above(stats1.mtime);
expect(stats2.atime).to.be.above(stats1.atime);
done();
});
});
});
});
});
});
it('should update ctime, atime, mtime of parent dir when calling fs.mkdir()', function(done) {
var fs = util.fs();
createTree(function() {
stat('/', function(stats1) {
fs.mkdir('/a', function(error) {
if(error) throw error;
stat('/', function(stats2) {
expect(stats2.ctime).to.be.above(stats1.ctime);
expect(stats2.mtime).to.be.above(stats1.mtime);
expect(stats2.atime).to.be.above(stats1.atime);
done();
});
});
});
});
});
it('should make no change when calling fs.close()', function(done) {
var fs = util.fs();
createTree(function() {
fs.open(filename, 'w', function(error, fd) {
if(error) throw error;
stat(filename, function(stats1) {
fs.close(fd, function(error) {
if(error) throw error;
stat(filename, function(stats2) {
expect(stats2.ctime).to.equal(stats1.ctime);
expect(stats2.mtime).to.equal(stats1.mtime);
expect(stats2.atime).to.equal(stats1.atime);
done();
});
});
});
});
});
});
it('should make no change when calling fs.open()', function(done) {
var fs = util.fs();
createTree(function() {
stat(filename, function(stats1) {
fs.open(filename, 'w', function(error, fd) {
if(error) throw error;
stat(filename, function(stats2) {
expect(stats2.ctime).to.equal(stats1.ctime);
expect(stats2.mtime).to.equal(stats1.mtime);
expect(stats2.atime).to.equal(stats1.atime);
fs.close(fd, done);
});
});
});
});
});
/**
* fs.utimes and fs.futimes are tested elsewhere already, skipping
*/
it('should update atime, ctime, mtime when calling fs.write()', function(done) {
var fs = util.fs();
var buffer = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]);
createTree(function() {
fs.open('/myfile', 'w', function(err, fd) {
if(err) throw error;
stat('/myfile', function(stats1) {
fs.write(fd, buffer, 0, buffer.length, 0, function(err, nbytes) {
if(err) throw error;
fs.close(fd, function(error) {
if(error) throw error;
stat('/myfile', function(stats2) {
expect(stats2.ctime).to.be.above(stats1.ctime);
expect(stats2.mtime).to.be.above(stats1.mtime);
expect(stats2.atime).to.be.above(stats1.atime);
done();
});
});
});
});
});
});
});
it('should make no change when calling fs.read()', function(done) {
var fs = util.fs();
var buffer = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]);
createTree(function() {
fs.open('/myfile', 'w', function(err, fd) {
if(err) throw err;
fs.write(fd, buffer, 0, buffer.length, 0, function(err, nbytes) {
if(err) throw err;
fs.close(fd, function(error) {
if(error) throw error;
fs.open('/myfile', 'r', function(error, fd) {
if(error) throw error;
stat('/myfile', function(stats1) {
var buffer2 = new Uint8Array(buffer.length);
fs.read(fd, buffer2, 0, buffer2.length, 0, function(err, nbytes) {
fs.close(fd, function(error) {
if(error) throw error;
stat('/myfile', function(stats2) {
expect(stats2.ctime).to.equal(stats1.ctime);
expect(stats2.mtime).to.equal(stats1.mtime);
expect(stats2.atime).to.equal(stats1.atime);
done();
});
});
});
});
});
});
});
});
});
});
it('should make no change when calling fs.readFile()', function(done) {
var fs = util.fs();
createTree(function() {
stat(filename, function(stats1) {
fs.readFile(filename, function(error, data) {
if(error) throw error;
stat(filename, function(stats2) {
expect(stats2.ctime).to.equal(stats1.ctime);
expect(stats2.mtime).to.equal(stats1.mtime);
expect(stats2.atime).to.equal(stats1.atime);
done();
});
});
});
});
});
it('should update atime, ctime, mtime when calling fs.writeFile()', function(done) {
var fs = util.fs();
createTree(function() {
stat(filename, function(stats1) {
fs.writeFile(filename, 'data', function(error) {
if(error) throw error;
stat(filename, function(stats2) {
expect(stats2.ctime).to.be.above(stats1.ctime);
expect(stats2.mtime).to.be.above(stats1.mtime);
expect(stats2.atime).to.be.above(stats1.atime);
done();
});
});
});
});
});
it('should update atime, ctime, mtime when calling fs.appendFile()', function(done) {
var fs = util.fs();
createTree(function() {
stat(filename, function(stats1) {
fs.appendFile(filename, '...more data', function(error) {
if(error) throw error;
stat(filename, function(stats2) {
expect(stats2.ctime).to.be.above(stats1.ctime);
expect(stats2.mtime).to.be.above(stats1.mtime);
expect(stats2.atime).to.be.above(stats1.atime);
done();
});
});
});
});
});
it('should update ctime, atime when calling fs.setxattr()', function(done) {
var fs = util.fs();
createTree(function() {
stat(filename, function(stats1) {
fs.setxattr(filename, 'extra', 'data', function(error) {
if(error) throw error;
stat(filename, function(stats2) {
expect(stats2.ctime).to.be.above(stats1.ctime);
expect(stats2.mtime).to.equal(stats1.mtime);
expect(stats2.atime).to.be.above(stats1.atime);
done();
});
});
});
});
});
it('should update ctime, atime when calling fs.fsetxattr()', function(done) {
var fs = util.fs();
createTree(function() {
fs.open(filename, 'w', function(error, fd) {
if(error) throw error;
stat(filename, function(stats1) {
fs.fsetxattr(fd, 'extra', 'data', function(error) {
if(error) throw error;
stat(filename, function(stats2) {
expect(stats2.ctime).to.be.above(stats1.ctime);
expect(stats2.mtime).to.equal(stats1.mtime);
expect(stats2.atime).to.be.above(stats1.atime);
done();
});
});
});
});
});
});
it('should make no change when calling fs.getxattr()', function(done) {
var fs = util.fs();
createTree(function() {
fs.setxattr(filename, 'extra', 'data', function(error) {
if(error) throw error;
stat(filename, function(stats1) {
fs.getxattr(filename, 'extra', function(error, value) {
if(error) throw error;
stat(filename, function(stats2) {
expect(stats2.ctime).to.equal(stats1.ctime);
expect(stats2.mtime).to.equal(stats1.mtime);
expect(stats2.atime).to.equal(stats1.atime);
done();
});
});
});
});
});
});
it('should make no change when calling fs.fgetxattr()', function(done) {
var fs = util.fs();
createTree(function() {
fs.open(filename, 'w', function(error, fd) {
if(error) throw error;
fs.fsetxattr(fd, 'extra', 'data', function(error) {
if(error) throw error;
stat(filename, function(stats1) {
fs.fgetxattr(fd, 'extra', function(error, value) {
if(error) throw error;
stat(filename, function(stats2) {
expect(stats2.ctime).to.equal(stats1.ctime);
expect(stats2.mtime).to.equal(stats1.mtime);
expect(stats2.atime).to.equal(stats1.atime);
done();
});
});
});
});
});
});
});
it('should update ctime, atime when calling fs.removexattr()', function(done) {
var fs = util.fs();
createTree(function() {
fs.setxattr(filename, 'extra', 'data', function(error) {
if(error) throw error;
stat(filename, function(stats1) {
fs.removexattr(filename, 'extra', function(error) {
if(error) throw error;
stat(filename, function(stats2) {
expect(stats2.ctime).to.be.above(stats1.ctime);
expect(stats2.mtime).to.equal(stats1.mtime);
expect(stats2.atime).to.be.above(stats1.atime);
done();
});
});
});
});
});
});
it('should update ctime, atime when calling fs.fremovexattr()', function(done) {
var fs = util.fs();
createTree(function() {
fs.open(filename, 'w', function(error, fd) {
if(error) throw error;
fs.fsetxattr(fd, 'extra', 'data', function(error) {
if(error) throw error;
stat(filename, function(stats1) {
fs.fremovexattr(fd, 'extra', function(error) {
if(error) throw error;
stat(filename, function(stats2) {
expect(stats2.ctime).to.be.above(stats1.ctime);
expect(stats2.mtime).to.equal(stats1.mtime);
expect(stats2.atime).to.be.above(stats1.atime);
done();
});
});
});
});
});
});
});
});
});

View File

@ -32,6 +32,8 @@ define([
"spec/fs.utimes.spec", "spec/fs.utimes.spec",
"spec/fs.xattr.spec", "spec/fs.xattr.spec",
"spec/path-resolution.spec", "spec/path-resolution.spec",
"spec/times.spec",
"spec/time-flags.spec",
// Filer.FileSystem.providers.* // Filer.FileSystem.providers.*
"spec/providers/providers.spec", "spec/providers/providers.spec",