all files / lib/offshore/core/ cache.js

85.11% Statements 80/94
72% Branches 36/50
88.89% Functions 16/18
85.11% Lines 80/94
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152       12×                                                                     21×           180× 180× 180×     180×                                           179× 179× 179× 179× 179× 179×              
var fs = require('fs');
var path = require('path');
var os = require('os');
var _ = require('lodash');
 
var filepath = os.tmpdir();
var prefix = 'CACHE_';
var defaultCacheTime = 3600000;
 
var HEADER_SIZE = 20;
 
function fullpath(key) {
  return path.join(filepath, prefix + key);
}
;
 
var DefaultAdapter = {
  get: function(key, cb) {
    var self = this;
    var headerStream = fs.createReadStream(fullpath(key), {start: 0, end: HEADER_SIZE - 1});
    var header = '';
    headerStream.on('error', function(err) {
      headerStream.destroy();
      if (err.code === 'ENOENT') {
        return cb(self.errors.NO_CACHE);
      }
      cb(err);
    });
    headerStream.on('data', function(data) {
      header += data.toString();
    });
    headerStream.on('end', function() {
      var time = parseInt(header);
      // if cache is valid
      if (time >= new Date().getTime() || time === 0) {
        var content = '';
        var contentStream = fs.createReadStream(fullpath(key), {start: HEADER_SIZE});
        contentStream.on('error', function(err) {
          contentStream.destroy();
          cb(err);
        });
        contentStream.on('data', function(data) {
          content += data.toString();
        });
        contentStream.on('end', function() {
          var cache;
          Iif (content === 'UNDEFINED') {
            return cb(null);
          }
          try {
            cache = JSON.parse(content);
            cb(null, cache);
          } catch (e) {
            if (_.isUndefined(cache)) {
              cb(e);
            }
          }
        });
      } else {
        cb(self.errors.NO_CACHE);
      }
    });
  },
  set: function(key, value, time) {
    var valid = 0;
    if (_.isUndefined(time)) {
      valid = new Date().getTime() + defaultCacheTime;
    }
    else Eif (time > 0) {
      valid = new Date().getTime() + (time * 1000);
    }
    var content;
    Iif (_.isUndefined(value)) {
      content = 'UNDEFINED';
    } else {
      content = JSON.stringify(value);
    }
    var header = valid.toString();
    var pad = HEADER_SIZE - header.length;
    for (var i = 0; i < pad; i ++) {
      header += ' ';
    }
    fs.createWriteStream(fullpath(key)).write(header + content);
  }
};
 
module.exports = {
  initialize: function(options, cb) {
    var options = options || {};
    var self = this;
    options.defaultCacheTime = options.defaultCacheTime || defaultCacheTime;
 
    // if an adapter is specified
    if (options.adapter && options.adapter !== 'default') {
      // Detects if there is a `getDatastore` in the adapter. Exit if it not exists.
      Iif (! options.adapter.getDatastore) {
        throw new Error('Adapter does not support Datastore interface which is required for cache');
      }
      options.adapter.getDatastore(options, function(err, cache) {
        self.get = function(key, cb) {
          cache.get(key, function(err, value) {
            if (err && err.message === '404') {
              cb(self.errors.NO_CACHE);
            } else Iif (err) {
              cb(err);
            } else Eif (value.ttl >= new Date().getTime() || value.ttl === 0) {
              cb(null, value.data);
            } else {
              cb(self.errors.NO_CACHE);
            }
          });
        };
        self.set = function(key, value, time) {
          var valid = 0;
          if (_.isUndefined(time)) {
            valid = new Date().getTime() + defaultCacheTime;
          } else Eif (time > 0) {
            valid = new Date().getTime() + (time * 1000);
          }
          var data = {ttl: valid, data: value};
          cache.set(key, data, function() {
            if (time > 0) {
              setTimeout(function() {
                //check cache
                cache.get(key, function(err, value) {
                  Iif (err) {
                    return;
                  }
                  Eif (value.ttl >= new Date().getTime()) {
                    cache.remove(key, function() {});
                  }
                });
              }, time);
            }
          });
        };
        cb();
      });
    } else {
      filepath = options.path || filepath;
      prefix = options.prefix || prefix;
      defaultCacheTime = options.defaultCacheTime;
      this.get = DefaultAdapter.get;
      this.set = DefaultAdapter.set;
      cb();
    }
  },
  errors: {
    NO_CACHE: new Error("NO_CACHE")
  }
};