text: const config = require('../config');
  const Metadata = require('../metadata');
  const mozlog = require('../log');
  const createRedisClient = require('./redis');
  
  function getPrefix(seconds) {
    return Math.max(Math.floor(seconds / 86400), 1);
  }
  
  class DB {
    constructor(config) {
      let Storage = null;
      if (config.s3_bucket) {
        Storage = require('./s3');
      } else if (config.gcs_bucket) {
        Storage = require('./gcs');
      } else {
        Storage = require('./fs');
      }
      this.log = mozlog('send.storage');
  
      this.storage = new Storage(config, this.log);
  
      this.redis = createRedisClient(config);
      this.redis.on('error', err => {
        this.log.error('Redis:', err);
      });
    }
  
    async ttl(id) {
      const result = await this.redis.ttlAsync(id);
      return Math.ceil(result) * 1000;
    }
  
    async getPrefixedInfo(id) {
      const [prefix, dead, flagged] = await this.redis.hmgetAsync(
        id,
        'prefix',
        'dead',
        'flagged'
      );
      return {
        filePath: `${prefix}-${id}`,
        flagged,
        dead
      };
    }
  
    async length(id) {
      const { filePath } = await this.getPrefixedInfo(id);
      return this.storage.length(filePath);
    }
  
    async get(id) {
      const info = await this.getPrefixedInfo(id);
      if (info.dead || info.flagged) {
        throw new Error(info.flagged ? 'flagged' : 'dead');
      }
      const length = await this.storage.length(info.filePath);
      return { length, stream: this.storage.getStream(info.filePath) };
    }
  
    async set(id, file, meta, expireSeconds = config.default_expire_seconds) {
      const prefix = getPrefix(expireSeconds);
      const filePath = `${prefix}-${id}`;
      await this.storage.set(filePath, file);
      if (meta) {
        this.redis.hmset(id, { prefix, ...meta });
      } else {
        this.redis.hset(id, 'prefix', prefix);
      }
      this.redis.expire(id, expireSeconds);
    }
  
    setField(id, key, value) {
      this.redis.hset(id, key, value);
    }
  
    async incrementField(id, key, increment = 1) {
      return await this.redis.hincrbyAsync(id, key, increment);
    }
  
    async kill(id) {
      const { filePath, dead } = await this.getPrefixedInfo(id);
      if (!dead) {
        this.redis.hset(id, 'dead', 1);
        this.storage.del(filePath);
      }
    }
  
    async flag(id) {
      await this.kill(id);
      this.redis.hset(id, 'flagged', 1);
    }
  
    async del(id) {
      const { filePath } = await this.getPrefixedInfo(id);
      this.redis.del(id);
      this.storage.del(filePath);
    }
  
    async ping() {
      await this.redis.pingAsync();
      await this.storage.ping();
    }
  
    async metadata(id) {
      const result = await this.redis.hgetallAsync(id);
      return result && new Metadata({ id, ...result }, this);
    }
  }
  
  module.exports = new DB(config);
  