text: var fs = require('graceful-fs');
  var path = require('path');
  var async = require('async');
  var mkdirp = require('mkdirp');
  var LRU = require('lru-cache');
  var md5 = require('./md5');
  
  function Cache(dir, options) {
      options = options || {};
  
      this._dir = dir;
      this._options = options;
      this._cache = this.constructor._cache.get(this._dir);
  
      if (!this._cache) {
          this._cache = new LRU(options);
          this.constructor._cache.set(this._dir, this._cache);
      }
  
      if (dir) {
          mkdirp.sync(dir);
      }
  }
  
  Cache.prototype.get = function (key, callback) {
      var file;
      var json = this._cache.get(key);
  
      // Check in memory
      if (json) {
          if (this._hasExpired(json)) {
              this.del(key, callback);
          } else {
              callback(null, json.value);
          }
  
          return;
      }
  
      // Check in disk
      if (!this._dir) {
          return callback(null);
      }
  
      file = this._getFile(key);
      fs.readFile(file, function (err, contents) {
          var json;
  
          // Check if there was an error reading
          // Note that if the file does not exist then
          // we don't have its value
          if (err) {
              return callback(err.code === 'ENOENT' ? null : err);
          }
  
          // If there was an error reading the file as json
          // simply assume it doesn't exist
          try {
              json = JSON.parse(contents.toString());
          } catch (e) {
              return this.del(key, callback);  // If so, delete it
          }
  
          // Check if it has expired
          if (this._hasExpired(json)) {
              return this.del(key, callback);
          }
  
          this._cache.set(key, json);
          callback(null, json.value);
      }.bind(this));
  };
  
  Cache.prototype.set = function (key, value, maxAge, callback) {
      var file;
      var entry;
      var str;
  
      maxAge = maxAge != null ? maxAge : this._options.maxAge;
      entry = {
          expires: maxAge ? Date.now() + maxAge : null,
          value: value
      };
  
      // Store in memory
      this._cache.set(key, entry);
  
      // Store in disk
      if (!this._dir) {
          return callback(null);
      }
  
      // If there was an error generating the json
      // then there's some cyclic reference or some other issue
      try {
          str = JSON.stringify(entry);
      } catch (e) {
          return callback(e);
      }
  
      file = this._getFile(key);
      fs.writeFile(file, str, callback);
  };
  
  Cache.prototype.del = function (key, callback) {
      // Delete from memory
      this._cache.del(key);
  
      // Delete from disk
      if (!this._dir) {
          return callback(null);
      }
  
      fs.unlink(this._getFile(key), function (err) {
          if (err && err.code !== 'ENOENT') {
              return callback(err);
          }
  
          callback();
      });
  };
  
  Cache.prototype.clear = function (callback) {
      var dir = this._dir;
  
      // Clear in memory cache
      this._cache.reset();
  
      // Clear everything from the disk
      if (!dir) {
          return callback(null);
      }
  
      fs.readdir(dir, function (err, files) {
          if (err) {
              return callback(err);
          }
  
          // Delete every file in parallel
          async.forEach(files, function (file, next) {
              fs.unlink(path.join(dir, file), function (err) {
                  if (err && err.code !== 'ENOENT') {
                      return next(err);
                  }
  
                  next();
              });
          }, callback);
      });
  };
  
  Cache.prototype.reset = function () {
      this._cache.reset();
  };
  
  Cache.clearRuntimeCache = function () {
      // Note that _cache refers to the static _cache variable
      // that holds other caches per dir!
      // Do not confuse it with the instance cache
  
      // Clear cache of each directory
      this._cache.forEach(function (cache) {
          cache.reset();
      });
  
      // Clear root cache
      this._cache.reset();
  };
  
  //-------------------------------
  
  Cache.prototype._hasExpired = function (json) {
      var expires = json.expires;
  
      if (!expires || this._options.useStale) {
          return false;
      }
  
      // Check if the key has expired
      return Date.now() > expires;
  };
  
  Cache.prototype._getFile = function (key) {
      // Append a truncated md5 to the end of the file to solve case issues
      // on case insensitive file systems
      // See: https://github.com/bower/bower/issues/859
      return path.join(this._dir, encodeURIComponent(key) + '_' + md5(key).substr(0, 5));
  };
  
  Cache._cache = new LRU({
      max: 5,
      maxAge: 60 * 30 * 1000  // 30 minutes
  });
  
  module.exports = Cache;
  