filer/src/node.js

169 lines
5.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

var path = require('./path.js');
var hash32 = require('./encoding.js').hash32;
var NODE_TYPE_FILE = require('./constants.js').NODE_TYPE_FILE;
var NODE_TYPE_DIRECTORY = require('./constants.js').NODE_TYPE_DIRECTORY;
var NODE_TYPE_SYMBOLIC_LINK = require('./constants.js').NODE_TYPE_SYMBOLIC_LINK;
var NODE_TYPE_META = require('./constants.js').NODE_TYPE_META;
var P9_QTFILE = require('./constants.js').P9.QTFILE;
var P9_QTDIR = require('./constants.js').P9.QTDIR;
var P9_QTSYMLINK = require('./constants.js').P9.QTSYMLINK;
var S_IFLNK = require('./constants.js').P9.S_IFLNK;
var S_IFDIR = require('./constants.js').P9.S_IFDIR;
var S_IFREG = require('./constants.js').P9.S_IFREG;
var ROOT_DIRECTORY_NAME = require('./constants.js').ROOT_DIRECTORY_NAME;
function getQType(type) {
switch(type) {
case NODE_TYPE_FILE:
return P9_QTFILE;
case NODE_TYPE_DIRECTORY:
return P9_QTDIR;
case NODE_TYPE_SYMBOLIC_LINK:
return P9_QTSYMLINK;
default:
return null;
}
}
function getPOSIXMode(type) {
switch(type) {
case NODE_TYPE_FILE:
return S_IFREG;
case NODE_TYPE_DIRECTORY:
return S_IFDIR;
case NODE_TYPE_SYMBOLIC_LINK:
return S_IFLNK;
default:
return null;
}
}
function Node(options) {
var now = Date.now();
this.id = options.id;
this.type = options.type || NODE_TYPE_FILE; // node type (file, directory, etc)
this.size = options.size || 0; // size (bytes for files, entries for directories)
this.atime = options.atime || now; // access time (will mirror ctime after creation)
this.ctime = options.ctime || now; // creation/change time
this.mtime = options.mtime || now; // modified time
this.flags = options.flags || []; // file flags
this.xattrs = options.xattrs || {}; // extended attributes
this.nlinks = options.nlinks || 0; // links count
this.version = options.version || 0; // node version
this.blksize = undefined; // block size
this.nblocks = 1; // blocks count
this.data = options.data; // id for data object
/**
* Plan 9 related metadata:
* https://web.archive.org/web/20170601072902/http://plan9.bell-labs.com/magic/man2html/5/0intro
*
* "The qid represents the server's unique identification for the file being
* accessed: two files on the same server hierarchy are the same if and only
* if their qids are the same. (The client may have multiple fids pointing to
* a single file on a server and hence having a single qid.) The thirteenbyte
* qid fields hold a onebyte type, specifying whether the file is a directory,
* appendonly file, etc., and two unsigned integers: first the fourbyte qid
* version, then the eightbyte qid path. The path is an integer unique among
* all files in the hierarchy. If a file is deleted and recreated with the same
* name in the same directory, the old and new path components of the qids
* should be different. The version is a version number for a file; typically,
* it is incremented every time the file is modified."
*/
options.p9 = options.p9 || {qid: {}};
this.p9 = {
qid: {
type: options.p9.qid.type || (getQType(this.type) || P9_QTFILE),
// use mtime for version info, since we already keep that updated
version: options.p9.qid.now || now,
// files have a unique `path` number, which takes into account files with same
// name but created at different times.
path: options.p9.qid.path || hash32(options.path + this.ctime)
},
// permissions and flags
// TODO: I don't think I'm doing this correctly yet...
mode: options.p9.mode || (getPOSIXMode(this.type) || S_IFREG),
// Name of file/dir. Must be / if the file is the root directory of the server
// TODO: do I need this or can I derive it from abs path?
name: options.p9.name || (options.path === ROOT_DIRECTORY_NAME ? ROOT_DIRECTORY_NAME : path.basename(options.path)),
uid: options.p9.uid || 0x0, // owner name
gid: options.p9.gid || 0x0, // group name
muid: options.p9.muid || 0x0 // name of the user who last modified the file
};
}
// When the node's path changes, update info that relates to it.
Node.prototype.updatePathInfo = function(newPath, ctime) {
// XXX: need to confirm that qid's path actually changes on rename.
this.p9.qid.path = hash32(newPath + (ctime || this.ctime));
this.p9.name = newPath === ROOT_DIRECTORY_NAME ? ROOT_DIRECTORY_NAME : path.basename(newPath);
};
// 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));
});
});
};
Node.fromObject = function(object) {
return new Node({
id: object.id,
type: object.type,
size: object.size,
atime: object.atime,
ctime: object.ctime,
mtime: object.mtime,
flags: object.flags,
xattrs: object.xattrs,
nlinks: object.nlinks,
data: object.data,
p9: {
qid: {
type: object.p9.qid.type,
version: object.p9.qid.version,
path: object.p9.qid.path
},
mode: object.p9.mode,
name: object.p9.name,
uid: object.p9.uid,
gid: object.p9.gid,
muid: object.p9.muid
}
});
};
module.exports = Node;