UNPKG

mediamonkeyserver

Version:
1,444 lines (1,153 loc) 30.9 kB
/*jslint node: true, sub: true, esversion: 6 */ 'use strict'; const assert = require('assert'); const Async = require('async'); const Item = require('./class/object.item'); const Semaphore = require('./util/semaphore'); const debugFactory = require('debug'); const debug = debugFactory('upnpserver:node'); const debugListChildren = debugFactory('upnpserver:node:listChildren'); const debugChildByTitle = debugFactory('upnpserver:node:childByName'); const logger = require('./logger'); const LIST_CHILDREN_LIMIT = 4; const ASSERT_CAN_NOT_LINK_A_CONTAINER = false; class Node { /** * */ constructor(service) { assert(service, 'Service is undefined !'); // assert(id !== undefined, "ID must be defined"); // this._id = id; Object.defineProperty(this, 'service', { enumerable: false, configurable: false, writable: false, value: service }); } /** * */ static createRef(linkedNode, name, callback) { if (ASSERT_CAN_NOT_LINK_A_CONTAINER && linkedNode.isUpnpContainer) { var error = new Error('Can not link a container (upnp limitation'); error.node = linkedNode; return callback(error); } var node = new Node(linkedNode.service); node.attributes = linkedNode.attributes; // LS: assign temporarily as this info is needed in allocateNodeId() linkedNode.service.allocateNodeId(node, (error) => { node.attributes = undefined; if (error) { return callback(error); } node.refId = linkedNode._id; if (name) { node.name = name; } if (debug.enabled) { debug('NewNodeRef id=#' + node._id + ' name=' + name + ' linkedName=' + linkedNode.name); } linkedNode.appendLink(node, (error) => { callback(error, node); }); }); } /** * */ static create(service, name, upnpClass, attributes, callback) { var node = new Node(service); if (name) node.name = name; if (attributes) node.attributes = attributes; assert(upnpClass instanceof Item, 'UpnpClass must be an item ' + upnpClass.name); node.upnpClass = upnpClass; service.allocateNodeId(node, (error) => { if (error) { return callback(error); } debug('NewNode id=#', node._id, 'name=', name, 'upnpClass=', upnpClass); callback(null, node); }); } /** * */ get id() { return this._id; } /** * */ get updateId() { return this._updateId || 0; } get contentURL() { if (this._contentURL !== undefined) { return this._contentURL; } var contentPath = this._contentPath; if (!contentPath) { this._contentURL = null; return null; } this._contentURL = this.service.newURL(contentPath); return this._contentURL; } set contentURL(url) { if (!url) { delete this._contentPath; delete this._contentURL; return; } if (!url.path) { throw new Error('Invalid parameter', url); } this._contentURL = url; this._contentPath = url.path; } registerRepository(repository, callback) { var key = repository.id; var repositories = this._repositories; if (repositories && (key in repositories)) { return callback(null, this); } this.takeLock('repositories', () => { if (!repositories) { repositories = {}; this._repositories = repositories; } else if (key in repositories) { this.leaveLock('repositories'); return callback(null, this); } repositories[key] = true; this.leaveLock('repositories'); this.service.saveNode(this, { $push: { repositories: key } }, (error) => { if (error) { logger.error('Can not save node #', this._id, error); return callback(error); } callback(null, this); }); }); } /** * */ removeChild(child, callback) { assert(child instanceof Node, 'Invalid child parameter'); assert(typeof(callback) === 'function', 'Invalid callback parameter'); debug('removeChild', 'Remove child #', child.id, ' of #', this.id); var childrenIds = this.childrenIds; if (!childrenIds) { let ex = new Error('The node has no children'); ex.node = this; ex.child = child; return callback(ex); } var idx = childrenIds.indexOf(child._id); if (idx < 0) { let ex = new Error('Can not find child #' + child._id); ex.node = this; ex.child = child; return callback(ex); } if (child.childrenIds && child.childrenIds.length) { let ex = new Error('Can not remove child #' + child._id + ' if its contains children'); ex.node = this; ex.child = child; return callback(ex); } if (this.childrenByContentPath && child.contentURL) this.childrenByContentPath[child.contentURL.path] = undefined; var service = this.service; this.takeLock('children', () => { idx = childrenIds.indexOf(child._id); if (idx < 0) { this.leaveLock('children'); return callback(); } this.childrenIds.splice(idx, 1); if (!this._updateId) { this._updateId = 0; } this._updateId++; delete child._path; delete child._parentId; service.saveNode(this, { updateId: this.updateId, $pull: { childrenIds: child._id } }, (error) => { this.leaveLock('children'); if (error) { logger.error('Can not save node #', this._id, error); return callback(error); } service.registerUpdate(this); var refId = child.refId; var unregisterNode = (callback) => { assert(typeof(callback) === 'function', 'Invalid callback parameter'); service.unregisterNode(child, (error) => { if (error) { logger.error('Can not unregister node #', child._id, error); return callback(error); } callback(null, this); }); }; var removeReferences = () => { if (!child.linkedIds) { return unregisterNode(callback); } child.removeAllLinks((error) => { if (error) { logger.error('Can not removeAllLinks node #', child._id, error); return callback(error); } unregisterNode(callback); }); }; if (!refId) { return removeReferences(); } service.getNodeById(refId, (error, refNode) => { if (error) { logger.error('Can not find linked node #', refId, error); return callback(error); } refNode.removeLink(child, removeReferences); }); }); }); } removeChildren(callback) { var _thisNode = this; var _callbackCalled; var _callback = function() { if (!_thisNode.hasChildren && !_callbackCalled) { callback(); _callbackCalled = true; } }; _thisNode.eachChild((child, link) => { if (child === link) // LS: to not remove linked (referenced) nodes (e.g. tracks referenced by playlist) child.removeChildren(() => { _thisNode.removeChild( child, _callback); }); }, _callback); } /** * */ removeAllLinks(callback) { assert.equal(typeof(callback), 'function', 'Invalid callback parameter'); debug('removeLink', 'Remove all links of #', this.id); this.takeLock('links', () => { var linkedIds = this.linkedIds; if (!linkedIds || !linkedIds.length) { this.leaveLock('links'); return callback(); } delete this.linkedIds; this.service.saveNode(this, { $unset: { linkedIds: true } }, (error) => { this.leaveLock('links'); if (error) { return callback(error); } var service = this.service; Async.eachSeries(linkedIds, (linkId, callback) => { service.getNodeById(linkId, (error, link) => { if (error) { logger.error('Can not find linked node #', linkId, error); return callback(error); } link._removeRef(callback); }); }, (error) => { callback(error, this); }); }); }); } _getLinkedParents(callback) { var parents = []; Async.eachSeries(this.linkedIds, (linkId, callback) => { this.service.getNodeById(linkId, (err, link) => { if (err) callback(); else this.service.getNodeById(link.parentId, (err, node) => { if (!err) parents.push(node); callback(); }); }); }, (/*err*/) => { callback(parents); }); } remove(remove_empty_parents, callback) { var node = this; var service = this.service; service.getNodeById(node.parentId, (err, parent) => { if (!err && parent) { node._getLinkedParents((linked_parents) => { parent.removeChild(node, // this also removes node.id from parent.childrenIds array and removes also all links () => { if (remove_empty_parents) { linked_parents.push(parent); Async.eachSeries(linked_parents, (parent, cbk) => { if (!parent.hasChildren) parent.remove(remove_empty_parents, cbk); else cbk(); }, callback); } else callback(); }); }); } else service.unregisterNode(node, callback); }); } _removeRef(callback) { assert.equal(typeof(callback), 'function', 'Invalid callback parameter'); delete this.refId; this.service.saveNode(this, { $unset: {refId: true} }, (error) => { if (error) { return callback(error); } this._updateId++; this.getParentNode((error, parent) => { if (error || !parent) { return callback(error); } parent.removeChild(this, callback); }); }); } /** * */ removeLink(child, callback) { assert(child instanceof Node, 'Invalid child parameter'); assert.equal(typeof(callback), 'function', 'Invalid callback parameter'); debug('removeLink', 'Remove link #', child.id, ' of #', this.id); this.takeLock('links', () => { var linkedIds = this.linkedIds; if (!linkedIds) { this.leaveLock('links'); return callback(); } var idx = linkedIds.indexOf(child._id); if (idx < 0) { this.leaveLock('links'); return callback(); // new Error("Can not find link")); } linkedIds.splice(idx, 1); this.service.saveNode(this, { $pull: { linkedIds: child._id } }, (error) => { this.leaveLock('links'); if (error) { return callback(error); } this._updateId++; this._removeRef((error) => { callback(error, this); }); }); }); } /** * */ appendLink(child, callback) { assert.equal(typeof(callback), 'function', 'Invalid callback parameter'); assert(child instanceof Node, 'Invalid child parameter'); this.takeLock('links', () => { var linkedIds = this.linkedIds; if (!linkedIds) { linkedIds = []; this.linkedIds = linkedIds; } linkedIds.push(child._id); this.service.saveNode(this, { $push: { linkedIds: child._id } }, (error) => { this.leaveLock('links'); callback(error, this); }); }); } /** * */ appendChild(child, callback) { assert.equal(typeof(callback), 'function', 'Invalid callback parameter'); assert(child instanceof Node, 'Invalid child parameter'); this.insertBefore(child, null, callback); } /** * */ insertBefore(child, before, callback) { assert.equal(typeof(callback), 'function', 'Invalid callback parameter'); assert(child instanceof Node, 'Invalid child parameter'); if (debug.enabled) { debug('InsertBefore parent=#', this._id, ' child=#', child._id, ' before=#', (before ? before._id : null)); } if (typeof (child._parentId) === 'number') { let ex = new Error('Can not add a child which has already a parent !'); ex.node = this; logger.error(ex); return callback(ex); } var service = this.service; // console.log("ENTER #"+this.id+ " #"+child.id); this.takeLock('children', () => { // console.log("ENTRED #"+this.id+ " #"+child.id); if (this._parentId === undefined) { let ex = new Error('Can not add a child to parent which is not connected !'); ex.node = this; logger.error(ex); return callback(ex); } var childrenIds = this.childrenIds || []; var idx = childrenIds.length; if (typeof (before) === 'number') { if (before > idx) { let ex = new Error('Before index overflow idx=' + before); ex.node = this; this.leaveLock('children'); logger.error(ex); return callback(ex); } idx = before; } else if (before) { idx = childrenIds.indexOf(before._id); if (idx < 0) { let ex = new Error('Before child #' + before._id + ' is not found'); ex.node = this; this.leaveLock('children'); logger.error(ex); return callback(ex); } } child._parentId = this._id; this.childrenIds = childrenIds; childrenIds.splice(idx, 0, child._id); if (!this._updateId) { this._updateId = 0; } this._updateId++; var childModifications = { parentId: child._parentId }; if (!this._path) { // Node is not connected to the root ! logger.error('**** Not connected to the root ? #' + this._id, 'name=', this.name, 'refId=', this.refId, 'attributes=', this.attributes, 'parentId=', this._parentId); } else { // Connected to root var ps = [this._path]; if (this._path !== '/') { ps.push('/'); } ps.push(child.name ? child.name : child._id); child._path = ps.join(''); childModifications.path = child._path; } var nodeModifications = { updateId: this.updateId }; if (before) { nodeModifications.childrenIds = childrenIds; } else { nodeModifications.$push = { childrenIds: child._id }; } service.saveNode(this, nodeModifications, (error) => { if (error) { logger.error('Can not save node #', this._id, error); this.leaveLock('children'); return callback(error); } service.saveNode(child, childModifications, (error) => { if (error) { logger.error('Can not save child node #', child._id, error); this.leaveLock('children'); return callback(error); } service.registerUpdate(this); this.leaveLock('children'); callback(null, this); }); }); }); } /** * */ toJSONObject() { var obj = { id: this._id }; if (this._parentId) { obj.parentId = this._parentId; } if (this.name) { obj.name = this.name; } if (this._path) { obj.path = this._path; } if (this.upnpClass) { obj.upnpClass = this.upnpClass.name; } if (this._updateId) { obj.updateId = this._updateId; } if (this.refId) { obj.refId = this.refId; } if (this.attributes && Object.keys(this.attributes).length) { obj.attributes = this.attributes; } if (this.childrenIds && this.childrenIds.length) { obj.childrenIds = this.childrenIds; } if (this.linkedIds && this.linkedIds.length) { obj.linkedIds = this.linkedIds; } if (this.repositories && Object.keys(this.repositories).length) { obj.repositories = this.repositories; } if (this._contentPath) { obj.contentURL = this._contentPath; if (this.contentTime) { obj.contentTime = this.contentTime; } } return obj; } /** * */ static fromJSONObject(service, obj) { var node = new Node(service); node._id = obj.id; if (obj.parentId !== undefined) { node._parentId = obj.parentId; } if (obj.name) { node.name = obj.name; } if (obj.upnpClass) { node.upnpClass = service.upnpClasses[obj.upnpClass]; if (!node.upnpClass) { assert(node.upnpClass, 'Unknown upnpClass \'' + obj.upnpClass + '\''); } } node.attributes = obj.attributes; // || {}; if (obj.updateId) { node._updateId = obj.updateId; } if (obj.refId !== undefined) { node.refId = obj.refId; } if (obj.path) { node._path = obj.path; } if (obj.childrenIds) { node.childrenIds = obj.childrenIds; } if (obj.linkedIds) { node.linkedIds = obj.linkedIds; } if (obj.repositories) { node.repositories = obj.repositories; } if (obj.contentURL) { node._contentPath = obj.contentURL; if (obj.contentTime) { node.contentTime = obj.contentTime; } } return node; } /** * */ get isUpnpContainer() { return this.upnpClass && this.upnpClass.isContainer; } /** * */ get hasChildren() { return this.childrenIds && this.childrenIds.length > 0; } /** * */ browseChildren(options, callback) { if (arguments.length === 1) { callback = options; options = undefined; } this.service.browseNode(this, options, (error) => { if (error) { logger.error('BrowseNode #' + this._id, error); return callback(error); } this.listChildren(options, callback); }); } /** * */ listChildren(options, callback) { if (arguments.length === 1) { callback = options; options = undefined; } if (!this.hasChildren) { // let error=new Error("Node.listChildren #" + this._id, "=> no children"); return callback(null, []); } var resolveLinks = options && options.resolveLinks; var canUseCache = !resolveLinks; var service = this.service; if (canUseCache) { var cache = service._childrenWeakHashmap.get(this._id, this); if (cache) { cache = cache.slice(0); // Clone list return callback(null, cache); } } var getNodeFunc = (id, callback) => service.getNodeById(id, callback); if (resolveLinks) { var old = getNodeFunc; getNodeFunc = (id, callback) => { old(id, (error, node) => { if (error) { return callback(error); } node.resolveLink(callback); }); }; } this.takeLock('children', () => { var childrenIds = this.childrenIds; if (!childrenIds || !childrenIds.length) { if (canUseCache) { service._childrenWeakHashmap.put(this, []); } this.leaveLock('children'); return callback(null, []); } if (debugListChildren.enabled) { debugListChildren('Node.listChildren #', this._id, '=> cached ids list: length=', childrenIds.length, 'list=', childrenIds); } Async.mapLimit(childrenIds, LIST_CHILDREN_LIMIT, (id, callback) => { getNodeFunc(id, (error, node) => { if (error) { logger.error('Can not get node #' + id, error, error.stack); } if (!node) { var mapError = new Error('Can not get node #' + id); mapError.error = error; return callback(mapError); } callback(null, node); }); }, (error, result) => { if (error) { logger.error('Can not map ids', error, error.stack); if (debugListChildren.enabled) { debugListChildren('Node.listChildren #', this._id, '=> map returs error=', error); } this.leaveLock('children'); return callback(error); } if (debugListChildren.enabled) { debugListChildren('listChildren #', this._id, '=> map returns', result); } if (canUseCache) { service._childrenWeakHashmap.put(this, result); } this.leaveLock('children'); callback(null, result); }); }); } /** * */ filterChildNodes(filter, callback) { Node.filterChildNodes(this, null, filter, callback); } /** * */ static filterChildNodes(parent, list, filter, callback) { if (!list) { list = []; } if (filter(parent)) { list.push(parent); } if (!parent.hasChildren) { return callback(null, list); } if (!parent.childrenIds) { if (!parent.refId) { // TODO follow links ? } return callback(null, list); } var service = parent.service; Async.eachSeries(parent.childrenIds, (childId, callback) => { service.getNodeById(childId, (error, child) => { if (error) { return callback(error); } if (!child) { return callback(null); } Node.filterChildNodes(child, list, filter, callback); }); }, (error) => { if (error) { logger.error('Filter childNodes error', error); } callback(error, list); }); } /** * */ get parentId() { return this._parentId; } /** * */ get path() { return this._path; } /** * */ get service() { return this.service; } /** * */ getParentNode(callback) { if (!this._parentId) { return callback(null, null); } var service = this.service; service.getNodeById(this._parentId, callback); } /** * */ getFirstVirtualChildByTitle(title, callback) { assert(typeof(callback) === 'function', 'Invalid callback parameter'); debugChildByTitle('getFirstVirtualChildByTitle: request for', title); this.listChildrenByTitle(title, (error, nodes) => { debugChildByTitle('getFirstVirtualChildByTitle: returns', nodes); if (error) { return callback(error); } for (var node of nodes) { if (node._contentPath) { continue; } debugChildByTitle('getFirstVirtualChildByTitle', 'Find a virtual title=', title, 'in #', this._id, '=> #', node._id); return callback(null, node); } debugChildByTitle('getFirstVirtualChildByTitle', 'Can not find virtual title=', title, 'in #', this._id); callback(null); }); } /** * */ listChildrenByTitle(title, callback) { assert(typeof(callback) === 'function', 'Invalid callback parameter'); debugChildByTitle('listChildrenByTitle', 'request for ', title); this._listChildrenByTitle(title, (error, nodesIds, nodes) => { if (error) { return callback(error); } if (nodes) { debugChildByTitle('listChildrenByTitle', 'DIRECT search of', title, 'return nodes.count=', nodes.length); return callback(null, nodes); } if (!nodesIds) { debugChildByTitle('listChildrenByTitle: DIRECT search of', title, 'return EMPTY'); return callback(null, []); } var service = this.service; Async.mapLimit(nodesIds, LIST_CHILDREN_LIMIT, (id, callback) => service.getNodeById(id, callback), (error, nodes) => { if (error) { return callback(error); } debugChildByTitle('listChildrenByTitle', 'Computed search of', title, 'return count=', nodes.length); callback(null, nodes); }); }); } /** * */ _listChildrenByTitle(title, callback) { assert(typeof(callback) === 'function', 'Invalid callback parameter'); debugChildByTitle('_listChildrenByTitle', 'request for ', title); var map = this.service._childrenByTitleWeakHashmap; var childrenByTitle = map.get(this._id, this); if (childrenByTitle) { var children = childrenByTitle[title]; debugChildByTitle('_listChildrenByTitle', 'HIT! #', this.id, 'title=', title, '=>', children); if (children) { return callback(null, children); } return callback(null, []); } this.takeLock('childrenByTitle', () => { this._mapChildrenByTitle(title, (node) => { return (node.attributes && node.attributes.title) || node.name; }, (error, childrenByTitle, nodes) => { this.leaveLock('childrenByTitle'); if (error) { return callback(error); } var children = childrenByTitle[title]; debugChildByTitle('_listChildrenByTitle', 'SyncedHIT! #', this.id, 'title=', title, '=>', children); if (children) { return callback(null, children, nodes); } callback(null, []); }); }); } /** * */ mapChildrenByTitle(callback) { debugChildByTitle('mapChildrenByTitle', 'request map ...'); var map = this.service._childrenByTitleWeakHashmap; var childrenByTitle = map.get(this._id, this); if (childrenByTitle) { debugChildByTitle('mapChildrenByTitle', 'map in cache'); return callback(null, childrenByTitle); } this.takeLock('childrenByTitle', () => { this._mapChildrenByTitle(null, (node) => { return (node.attributes && node.attributes.title) || node.name; }, (error, childrenByTitle) => { this.leaveLock('childrenByTitle'); if (error) { return callback(error); } callback(null, childrenByTitle); }); }); } /** * */ _mapChildrenByTitle(title, getTitleCallback, callback) { debugChildByTitle('_mapChildrenByTitle', 'request map title=', title); var map = this.service._childrenByTitleWeakHashmap; var childrenByTitle = map.get(this._id, this); if (childrenByTitle) { debugChildByTitle('_mapChildrenByTitle', 'map in cache !'); return callback(null, childrenByTitle); } childrenByTitle = {}; var nodes = []; var l; this.eachChild((node, link) => { var ntitle = getTitleCallback(node); if (ntitle) { l = childrenByTitle[ntitle]; if (!l) { l = [node._id]; childrenByTitle[ntitle] = l; } else { l.push(node._id); } if (ntitle === title) { nodes.push(node); } } if (!link || node === link) { return; } var linkTitle = getTitleCallback(link); if (!linkTitle || ntitle === linkTitle) { return; } l = childrenByTitle[linkTitle]; if (!l) { l = [node._id]; childrenByTitle[linkTitle] = l; } else { l.push(node._id); } if (linkTitle === title) { nodes.push(node); } }, (error) => { if (error) { logger.error('Can not create map', error); return callback(error); } debugChildByTitle('_mapChildrenByTitle', 'map=', childrenByTitle); map.put(this, childrenByTitle); callback(null, childrenByTitle, nodes); }); } /** * */ eachChild(testFunc, callback) { assert(typeof(testFunc) === 'function', 'Invalid testFunc parameter'); assert(typeof(callback) === 'function', 'Invalid callback parameter'); this.listChildren((error, children) => { if (error) { return callback(error); } var links = []; for (var child of children) { var test = testFunc(child, child); if (test) { return callback(null, child); } if (child.refId) { links.push(child); continue; } } if (!links.length) { if (debugChildByTitle) { debugChildByTitle('eachChild #', this._id, '=> NO RESULT (no links)'); } return callback(); } var loopError; Async.detectSeries(links, (link, callback) => { link.resolveLink((error, node) => { if (error) { logger.error('Can not resolve link #', link._id, error); loopError = error; return callback(true); // Stop immediately } var ret = testFunc(node, link); callback(ret); // True value stops the loop ! }); }, (result) => { if (loopError) { return callback(loopError); } if (result) { return callback(null, result); } if (debugChildByTitle) { debugChildByTitle('eachChild #', this._id, '=> NOT FOUND'); } callback(); }); }); } /** * */ resolveLink(callback) { if (!this.refId) { return callback(null, this); } this.service.getNodeById(this.refId, (error, child) => { if (error) { return callback(error); } if (child) { child.resolveLink(callback); return; } logger.error('Can not find refId #' + this.refId + ' of node #' + this.id + ' try to repair it !'); this.takeLock('links', () => { delete this.refId; this.service.saveNode(this, { $unset: {refId: true} }, (error) => { this.leaveLock('links'); callback(error, this); }); }); }); } /** * */ addSearchClass(searchClass, includeDerived) { var searchClasses = this.searchClasses; if (!searchClasses) { searchClasses = []; this.searchClasses = searchClasses; } for (var sc of searchClasses) { if (sc.name !== searchClass) { continue; } sc.includeDerived = sc.includeDerived || includeDerived; return; } searchClasses.push({ name: searchClass, includeDerived: includeDerived }); } /** * */ treeString(callback) { return this._treeString('', callback); } /** * */ _treeString(indent, callback) { // logger.debug("TreeString " + this); indent = indent || ''; var s = indent + '# ' + this + '\n'; if (!this.hasChildren) { return callback(null, s); } indent += ' '; if (!this.childrenIds) { if (!this.refId) { s += indent + '<Unknown children>\n'; } return callback(null, s); } var service = this.service; Async.eachSeries(this.childrenIds, (childId, callback) => { service.getNodeById(childId, (error, child) => { if (error) { return callback(error); } if (!child) { s += '<NULL>'; return callback(null); } child._treeString(indent, (error, s2) => { if (s2) { s += s2; } callback(null); }); }); }, (error) => callback(error, s)); } /** * */ toString() { var s = '[Node id=' + this._id; // s += " path=" + this.path; if (this.upnpClass) { s += ' upnpClass=\'' + this.upnpClass + '\''; } if (this.name) { s += ' name=\'' + this.name + '\''; } if (this.refId) { s += ' refId=' + this.refId; } if (this._contentPath) { s += ' contentPath=\'' + this._contentPath + '\''; } return s + ']'; } /** * */ takeLock(lockName, callback) { var semaphores = this._semaphores; if (!semaphores) { semaphores = {}; this._semaphores = semaphores; } var semaphore = semaphores[lockName]; if (!semaphore) { semaphore = new Semaphore('Node#' + this._id + ':' + lockName); semaphores[lockName] = semaphore; } semaphore.take(callback); } /** * */ leaveLock(lockName) { var semaphores = this._semaphores; if (!semaphores) { throw new Error('Invalid Semaphores context'); } var semaphore = semaphores[lockName]; if (!semaphore) { throw new Error('Invalid Semaphore context \'' + lockName + '\''); } semaphore.leave(); } /** * */ _isLocked() { var semaphores = this._semaphores; if (!semaphores) { return false; } for (var k in semaphores) { var semaphore = semaphores[k]; if (semaphore.current) { return k; } } return false; } } module.exports = Node;