Source: DataStructures/ContentStore.js

var debug = {}; debug.debug= require("debug")("ContentStore");
var lru = require("lru-cache");
var lruDebug = require("debug")("lru");

var Cache;

function pubKeyMatch (ar1, ar2){
  if (!ar1){
    return true;
  }
  for(var i = 0; i < ar1.length; i++ ){
    if (ar1[i] !== ar2[i]){
      return false;
    }
  }
  return true;
}


/** Default EntryClass for ContentStore
 *@constructor
 *@private
 *@param {Buffer} element the raw data packet.
 *@param {Data} data the ndn.Data object
 */
function csEntry (element, data){
  var freshnessPeriod = data.getMetaInfo().getFreshnessPeriod();
  this.name = data.name;
  this.freshnessPeriod = freshnessPeriod;
  this.uri = data.name.toUri();
  this.publisherPublicKeyDigest = data.signedInfo.publisher.publisherPublicKeyDigest;
  Cache.set(data.name.toUri(), {
    entry: this,
    element: element
  });
  return this;
}

/**
 *@property {String} type a type string describing the type of entry
 */
csEntry.type = "csEntry";

/** sync/async getter for the element
 *@private
 *@param {function} callback Recieves element as only argument
 *@returns {Buffer} element the raw data packet
 */
csEntry.prototype.getElement = function(callback){
  callback = callback || function(e){return e;};
  return callback(Cache.get(this.uri).element);
};

/**
 *@private
 *@param {NameTreeNode} node the node to remove this entry from
 *@returns {csEntry} entry the csEntry in case you want to do something other than throw it away
 */
csEntry.prototype.stale = function(node){
  delete node.csEntry;
  if (Cache.has(this.uri)){
    Cache.del(this.uri);
  }
  return this;
};

/**A ContentStore constructor for building cache's and database indexes
 *@constructor
 *@param {NameTree} nameTree the nameTree to build upon
 *@param {constructor} entryClass a constructor class conforming to the same API as {@link csEntry}.
 *@returns {ContentStore} - a new store
 */
var ContentStore = function ContentStore(nameTree, entryClass, maxMem){
  entryClass = entryClass || csEntry;
  debug.debug("ContentStore constructed with %s entry class", entryClass.type);
  if (entryClass.type === "csEntry"){
    Cache = lru({
      max: maxMem || 10000000
      , length: function(n){return n.element.length;}
      , dispose: function(key, value){
        lruDebug("evicting");
        delete value.entry.nameTreeNode.csEntry;
      }
    });
  }
  this.nameTree = nameTree;
  this.EntryClass = entryClass;
  return this;
};

/**check the ContentStore for data matching a given interest (including min/max suffix, exclude, publisherKey)
 *@param {ndn.Interest} interest the interest to match against
 *@param {function=} callback for asynchronous cases (like levelDB). recieves return value as only argument
 *@returns {Buffer | null}
 */
ContentStore.prototype.check = function(interest, callback, node, suffixCount, childTracker, stack){
debug.debug("checking for data matching interest : %s", interest.toUri());

  callback = callback || function(element){return element;};
  node = node || this.nameTree.lookup(interest.name);
  stack = stack || 1;
  stack++;

  if (stack++ > Object.keys(this.nameTree).length * 2){
  debug.debug("stack overflow, fix content store check!!!");
    return callback(null);
  }

  var self = this;

  if (node[this.EntryClass.type]
      && interest.matchesName(node[this.EntryClass.type].name)
      && pubKeyMatch(interest.publisherPublicKeyDigest, node[this.EntryClass.type].publisherPublicKeyDigest)
     )
     {
  debug.debug("check found data at %s", node.prefix.toUri() );
    return node[this.EntryClass.type].getElement(callback);
  }



  suffixCount = suffixCount || 0;
  childTracker = childTracker || [];

  var maxSuffix = interest.getMaxSuffixComponents()
    , minSuffix = interest.getMinSuffixComponents()
    , childSelector = interest.getChildSelector()
    , atMaxSuffix = (maxSuffix && (suffixCount === maxSuffix))
    , hasChildren = (node.children.length > 0);

  function hasMoreSiblings (node){
    var bool = (!!childTracker.length && !!node.parent && (node.parent.children.length > childTracker[childTracker.length - 1] + 1));
  debug.debug("checking if node %s has more siblings : ", bool);

    return bool;
  }

debug.debug("checker at node %s" , node.prefix.toUri());

  function toChild(node){
  debug.debug("checker moving to child");
    suffixCount++;
    childTracker.push(0);
    if (!childSelector){ //leftmost == 0 == falsey
      return self.check(interest, callback, node.children[0], suffixCount, childTracker , stack++);
    } else {

      return self.check(interest, callback, node.children[node.children.length - 1], suffixCount, childTracker, stack++);
    }
  }

  function toSibling(node){
  debug.debug("checker moving to sibling");
    childTracker[childTracker.length - 1]++;

    if (!childSelector){

      return self.check(interest, callback, node.parent.children[childTracker[childTracker.length - 1]], suffixCount, childTracker, stack++);
    } else {

      return self.check(interest, callback, node.parent.children[node.parent.children.length  + ~childTracker[childTracker.length - 1]], suffixCount, childTracker, stack++);
    }
  }

  function toAncestorSibling(node, stack){
  debug.debug("checker moving to ancestor sibling");
    suffixCount--;
    childTracker.pop();
    if (stack++ > 10000){
    debug.debug("stack over inside ancestorSibling!!!!!");
      return callback(null);
    }

    var hasParentSibling = (node.parent && node.parent.parent && node.parent.parent.children.length > childTracker[childTracker.length - 1] + 1);

    if (hasParentSibling){
      return toSibling(node.parent);
    } else if (childTracker.length >0) {
      return toAncestorSibling(node.parent, stack++);
    } else {
      return callback(null);
    }
  }

  if (childTracker.length === 1){
    if (interest.exclude.matches(node.prefix.get(-1))){
      if (hasMoreSiblings(node)){
        return toSibling(node);
      } else {
        return callback(null);
      }
    }
  }



  if (!node.prefix.size() ||(!atMaxSuffix && hasChildren)){
    return toChild(node);
  } else if (hasMoreSiblings(node)){
    return toSibling(node);
  } else if (childTracker.length > 1){
    return toAncestorSibling(node);
  } else{
    return callback(null);
  }
};

/**Insert a new entry into the contentStore
 *@constructor
 *@param {Buffer} element the raw data packet
 *@param {ndn.Data} data the ndn.Data object
 *@returns {ContentStore} - for chaining
 */
ContentStore.prototype.insert = function(element, data){
debug.debug("inserting %s", data.name.toUri());
  var Entry = this.EntryClass;
  var freshness = data.getMetaInfo().getFreshnessPeriod();
  var node = this.nameTree.lookup(data.name)
  , entry = new Entry(element, data);
  node[Entry.type] = entry;
  node[Entry.type].nameTreeNode = node;
debug.debug("inserting %s with freshness value of %s", data.name.toUri(), freshness);
  setTimeout(function(){
    if (node[Entry.type]) {node[Entry.type].stale(node);}
  }, freshness || 20 );

  return this;
};


module.exports = ContentStore;