Cache2File

Cache to File module for Node.js

It stores cache data in files that can expire. Stored data can be in any format Buffer supports (utf8 [default], ascii, binary).

Install

npm install cache2file

Usage

var Cache2File = require('cache2file'),
    // Path to store the cache files
    cachePath = './cache',
    // Timeout in milliseconds
    timeout = 60000,
    // Generate a new cache
    cache = new Cache2File(cachePath, timeout);

cache.set('cacheKey', doIntensiveStuff());

// ... some time later
cache.get('cacheKey', function (err, data) {
  if (!err) {
    // We have the data, do whatever we want.
  }
  else {
    // Cache timed out, or removed, so store it again.
    data = doIntensiveStuff();
    cache.set('cacheKey', data);
  }

  processData(data);
});

// Remove cached data.
cache.remove('cacheKey');

Cache2File uses it's own function Cache2File.generateKey to generate a hash for the filename to store data in. It can be replaced with your own filename generating algorithm if you wish. Hashing was generally required to only have ascii characters in filenames and no / characters, as there is no restriction for the characters in the key.

key's string value should be less then 200 characters so the filesystem can handle the filename.

To remove multiple cache files, use cache.removeAll(callback, keyCached, expired)

Where if keyCached is set to true, remove those whose key was touched in the lifetime of the cache object. If it is false (default) all cache files in the cache directory will be removed (those that has the extension .cache).<br> If expired is set to true (default is false) it will only remove expired cache files.<br> These filters can be combined.

TODO

Handle cache read / write concurrency.

cache2file

lib/cache2file.js
  • fileOverview: Cache2File module. Stores cache data in files that can expire. Stored data can be in any format Buffer supports (utf8 [default], ascii, binary).

  • author: Peter (Poetro) Galiba <poetro at poetro dot hu>

  • version: 0 .2.1

  • requires: fs

  • requires: path

  • requires: funk

var fs = require('fs'),
    path = require('path'),
    Funk = require('funk');

Manage key - value caching into files. - class: Cache2File

  • property: String path

    Path to the cache directory.

  • property: Number timeout

    Timeout of cache files in milliseconds.

  • property: Object keyCache

    Hash table for mapping generated file identifier to cache key.

Manage key - value caching into files. - constructor: Cache2File

  • param: String path

    Path to the cache directory.

  • param: Number timeout

    Timeout of cache files in milliseconds.

function Cache2File(path, timeout) {
  // Only allow calling as a constructor.
  if (!(this instanceof Cache2File)) return new Cache2File(path, timeout);
  this.path = path || './cache';
  this.timeout = timeout || e5;
  this.keyCache = {};
}

Function placeholder.

function noop() {}

Remove the files specified cache files.

If expired is true, only the expired cache files will be unlinked.

  • param: Array files

    List of files to delete.

  • param: Boolean expired

    Only delete the files, if it has expired.

  • param: Function callback

    The callback function will be called, when all files are processed. It will be called with passing in an object keyed by the file's name, if any errors occured, or null otherwise.

function removeFiles(files, expired, callback, timeout) {
  var funk = new Funk;
  if (expired) {
    funk.set('files', []);
  }
  else {
    funk.set('errors', {});
  }

  // Go through the files.
  files.forEach(function (cacheFile) {
    if (expired) {
      // Need to check for expired files.
      fs.stat(cacheFile, funk.add(function (err, stats) {
        if (!err &amp;&amp; Date.now() - Date.parse(stats.ctime) &gt; timeout) {
          this.files.push(cacheFile);
        }
      }));
    }
    else {
      // Remove all the files, and keep track of the errors.
      fs.unlink(cacheFile, funk.add(function (err) {
        if (err) {
          this.errors[cacheFile] = err;
        }
      }));
    }
  });

  // Run the callbacks parallel
  funk.parallel(function () {
    if (expired) {
      // There is a list of files, that are expired delete them.
      removeFiles(this.files, false, callback, timeout);
    }
    else {
      // Deletion completed, call the callback passing the errors.
      callback(Object.keys(this.errors).length ? this.errors : null);
    }
  });
}

Generate a hash from the key that will serve as the base for the generated file.

  • param: String key

    The key to generate hash for.

  • returns: String

    Hashed value.

Cache2File.generateKey = function (key) {
  return (new Buffer(key.toString())).toString('base64')
            .slice(0, -2).replace(/\+/g, '-').replace(/\//g, '_');
};

Cache2File.prototype = {

Generate a hash key / return one from the already generated key cache, if exists.

  • param: String key

    The key to generate the hash for.

  • returns: String

    Hashed value of string. @see Cache2File.generateKey().

generateKey: function (key) {
    if (!(key in this.keyCache)) {
      this.keyCache[key] = Cache2File.generateKey(key);
    }

    return this.keyCache[key];
  },

Get a value for the specified key.

  • param: String key

    The key for the value to fetch.

  • param: Function callback

    The function to call with the fetched data with. The callback has 2 arguments: err, data, where data is the content if no errors occured and the cache hasn't expired.

  • param: String [encoding]

    The encoding to fetch the information in is optional.

get: function (key, callback, encoding_) {
    var encoding = typeof encoding_ === 'string' ? encoding_ : null,
        cacheFile = path.join(this.path, this.generateKey(key) + '.cache'),
        timeout = this.timeout;

    callback = (typeof callback === 'function' ? callback : noop);

    // Check for file cache
    fs.stat(cacheFile, function (err, stats) {
      var date, expired = true;
      if (!err) {
        // File exists, check if it expired
        if (Date.now() - Date.parse(stats.ctime) &lt; timeout) {
          expired = false;
        }
      }

      if (expired) {
        // File has expired call it with error.
        callback(true, null);
      } else {
        // Load the cache file...
        fs.readFile(cacheFile, encoding, function (err, data) {
          callback(err, data &amp;&amp; data.toString());
        });
      }
    });
  },

Get a value for the specified key.

  • param: String key

    The key for the value to fetch.

  • param: String | Buffer data

    Data to write to the cache.

  • param: String [encoding]

    The encoding to fetch the information in is optional.

  • param: Function [callback]

    The function to call with when the setting finished is optional. The callback has an err argument and stores the errors if any occured.

set: function (key, data, encoding_, callback) {
    var encoding = typeof encoding_ === 'string' ? encoding_ : null,
        cacheFile = path.join(this.path, this.generateKey(key) + '.cache');

    if (typeof data !== 'string' &amp;&amp; !(data instanceof Buffer)) {
      data = data.toString();
    }


    callback = arguments[arguments.length - 1];
    callback = (typeof callback === 'function' ? callback : noop);

    fs.writeFile(cacheFile, data, encoding, callback);
  },

Remove a cache file.

  • param: String key

    The key to remove the cache for.

  • param: Function callback

    The callback will be called after the file is unlinked passing the error if present. @see fs.unlink()

remove: function (key, callback) {
    var cacheFile = path.join(this.path, this.generateKey(key) + '.cache');

    callback = (typeof callback === 'function' ? callback : noop);

    fs.unlink(cacheFile, callback);
  },

Remove all (or a subset of) cache files.

The parameters keyCached and expired can be combined in any ways, they filter the set of removed files.

  • param: Function [callback]

    The callback function will be called, when all files are processed. It will be called with passing in an object keyed by the file's name, if any errors occured, or null otherwise.

  • param: Boolean [keyCached]

    Only remove the files, whose key is already cached (aka used in this Cache2File object already). Optional, default is false.

  • param: Boolean [expired]

    Only remove those that files are already expired. Optional, default is false.

removeAll: function (callback, keyCached, expired) {
    var timeout = this.timeout,
        cache = this;
    callback = (typeof callback === 'function' ? callback : noop);

    if (keyCached) {
      removeFiles(Object.keys(this.keyCache).map(function (key) {
        return this[key];
      }, this.keyCache), expired, callback, timeout);
    }
    else {
      fs.readdir(this.path, function (err, files) {
        var fileTest;
        if (!err) {
          fileTest = /\.cache$/;
          removeFiles(
            files.filter(function (file) {
              return fileTest.test(file);
            }).map(function (file) {
              return path.join(cache.path, file);
            }),
            expired,
            callback,
            timeout);
        }
        else {
          callback([err]);
        }
      });
    }
  }
};
  • exports: Cache2File

module.exports = Cache2File;