'use strict';

var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };

var _path = require('path');

var _path2 = _interopRequireDefault(_path);

var _fs = require('fs');

var _fs2 = _interopRequireDefault(_fs);

var _bluebird = require('bluebird');

var _bluebird2 = _interopRequireDefault(_bluebird);

var _glob = require('glob');

var _glob2 = _interopRequireDefault(_glob);

var _get = require('lodash/get');

var _get2 = _interopRequireDefault(_get);

var _partition = require('lodash/partition');

var _partition2 = _interopRequireDefault(_partition);

var _mapValues = require('lodash/mapValues');

var _mapValues2 = _interopRequireDefault(_mapValues);

var _uniq = require('lodash/uniq');

var _uniq2 = _interopRequireDefault(_uniq);

var _transparent = require('./transparent');

var _transparent2 = _interopRequireDefault(_transparent);

var _util = require('./util');

var _util2 = require('../util');

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new _bluebird2.default(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return _bluebird2.default.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; }

_bluebird2.default.promisifyAll(_fs2.default);
const globAsync = _bluebird2.default.promisify(_glob2.default);

const REVISIONS_FILE_NAME = '.ghost-revisions';

module.exports = ({ logger, db, projectLocation, enabled }) => {
  if (!enabled) {
    return (0, _transparent2.default)({ logger, projectLocation });
  }

  const normalizeFolder = (0, _util.normalizeFolder)(projectLocation);

  const pendingRevisionsByFolder = {};
  const trackedFolders = [];
  const folderOptions = {};

  const upsert = ({ knex, tableName, where, data, idField = 'id', trx = null }) => {
    const prepareQuery = () => trx ? knex(tableName).transacting(trx) : knex(tableName);
    return prepareQuery().where(where).select(idField).then(res => {
      const id = (0, _get2.default)(res, '0.id');
      return id ? prepareQuery().where(idField, id).update(data).thenReturn(id) : prepareQuery().insert(_extends({}, where, data), 'id').then(([insertedId]) => insertedId);
    });
  };

  const recordFile = (() => {
    var _ref = _asyncToGenerator(function* (folderPath, folder, file, { isBinary = false } = {}) {
      const knex = yield db.get();
      const filePath = _path2.default.join(folderPath, file);
      const column = isBinary ? 'binary_content' : 'content';
      yield _fs2.default.readFileAsync(filePath, isBinary ? null : 'utf8').then(function (content) {
        return upsert({
          knex,
          tableName: 'ghost_content',
          where: { folder, file },
          data: { [column]: content }
        });
      });
    });

    return function recordFile(_x, _x2, _x3) {
      return _ref.apply(this, arguments);
    };
  })();

  const getPendingRevisions = (() => {
    var _ref2 = _asyncToGenerator(function* (normalizedFolderName) {
      const knex = yield db.get();

      return knex('ghost_revisions').join('ghost_content', 'ghost_content.id', '=', 'ghost_revisions.content_id').where('ghost_content.folder', normalizedFolderName).select('ghost_content.file', 'ghost_revisions.id', 'ghost_revisions.revision', 'ghost_revisions.created_on', 'ghost_revisions.created_by').orderBy('ghost_revisions.created_on', 'desc').then();
    });

    return function getPendingRevisions(_x4) {
      return _ref2.apply(this, arguments);
    };
  })();

  const addRootFolder = (() => {
    var _ref3 = _asyncToGenerator(function* (rootFolder, options = {}) {
      const { folderPath, normalizedFolderName } = normalizeFolder(rootFolder);
      const { isBinary = false, filesGlob = '**/*' } = options;

      logger.debug(`[Ghost Content Manager] adding folder ${normalizedFolderName}`);
      trackedFolders.push(normalizedFolderName);
      folderOptions[normalizedFolderName] = options;

      // read known revisions
      const revisionsFile = _path2.default.join(folderPath, REVISIONS_FILE_NAME);
      const fileRevisionsPromise = _fs2.default.readFileAsync(revisionsFile, 'utf8').catch({ code: 'ENOENT' }, function () {
        return '';
      }).then(function (content) {
        return content.trim().split('\n').map(function (s) {
          return s.trim();
        }).filter(function (s) {
          return !!s && !s.startsWith('#');
        }).reduce(function (acc, r) {
          acc[r] = true;
          return acc;
        }, {});
      });

      const [knownRevisions, dbRevisions] = yield _bluebird2.default.all([fileRevisionsPromise, getPendingRevisions(normalizedFolderName)]);

      const [revisionsToDelete, remainingRevisions] = (0, _partition2.default)(dbRevisions, function ({ revision }) {
        return knownRevisions[revision];
      });

      const knex = yield db.get();

      // cleanup known revisions
      if (revisionsToDelete.length) {
        logger.debug(`[Ghost Content Manager] ${normalizedFolderName}: deleting ${revisionsToDelete.length} known revision(s).`);
        yield knex('ghost_revisions').whereIn('id', revisionsToDelete.map(function ({ id }) {
          return id;
        })).del();
      }

      if (remainingRevisions.length) {
        logger.debug(`[Ghost Content Manager] ${normalizedFolderName}: ${remainingRevisions.length} pending revision(s).`);
        // record remaining revisions if any
        pendingRevisionsByFolder[normalizedFolderName] = remainingRevisions;
        return;
      }

      logger.debug(`[Ghost Content Manager] ${normalizedFolderName} has no pending revisions, updating DB from the file system.`);
      // otherwise update the content in the DB
      const files = yield globAsync(filesGlob, { cwd: folderPath });
      yield _bluebird2.default.map(files, function (file) {
        return recordFile(folderPath, normalizedFolderName, file, { isBinary });
      });
      // and also delete the files no longer in the FS
      yield knex('ghost_content').whereNotIn('file', files).andWhere('folder', normalizedFolderName).del().then();
    });

    return function addRootFolder(_x5) {
      return _ref3.apply(this, arguments);
    };
  })();

  const updatePendingForFolder = (() => {
    var _ref4 = _asyncToGenerator(function* (normalizedFolderName) {
      pendingRevisionsByFolder[normalizedFolderName] = yield getPendingRevisions(normalizedFolderName);

      if (!pendingRevisionsByFolder[normalizedFolderName].length) {
        delete pendingRevisionsByFolder[normalizedFolderName];
      }
    });

    return function updatePendingForFolder(_x6) {
      return _ref4.apply(this, arguments);
    };
  })();

  const updatePendingForAllFolders = () => _bluebird2.default.each(trackedFolders, updatePendingForFolder);

  const recordRevision = (knex, content_id, trx) => knex('ghost_revisions').transacting(trx).insert({ content_id, revision: (0, _util2.safeId)(), created_by: 'admin' });

  const upsertFile = (() => {
    var _ref5 = _asyncToGenerator(function* (rootFolder, file, content) {
      const knex = yield db.get();

      const folder = normalizeFolder(rootFolder).normalizedFolderName;
      const { isBinary } = folderOptions[folder];
      const column = isBinary ? 'binary_content' : 'content';

      if (yield knex('ghost_content').where({ folder, file, [column]: content }).count('id as count').then(function ([res]) {
        return Number(res.count) > 0;
      })) {
        return _bluebird2.default.resolve();
      }

      return knex.transaction(function (trx) {
        upsert({
          knex,
          tableName: 'ghost_content',
          where: { folder, file },
          data: { [column]: content },
          trx
        }).then(function (content_id) {
          return recordRevision(knex, content_id, trx);
        }).then(trx.commit).then(function () {
          return updatePendingForFolder(folder);
        }).catch(function (err) {
          logger.error('[Ghost Content Manager]', err);
          trx.rollback();
        });
      });
    });

    return function upsertFile(_x7, _x8, _x9) {
      return _ref5.apply(this, arguments);
    };
  })();

  const revertAllPendingChangesForFile = (() => {
    var _ref6 = _asyncToGenerator(function* (folder, file) {
      const knex = yield db.get();

      const { folderPath, normalizedFolderName } = normalizeFolder(folder);
      const filePath = _path2.default.join(folderPath, file);
      const { isBinary = false } = folderOptions[normalizedFolderName];

      yield knex('ghost_revisions').whereIn('id', function () {
        // Subquery because SQLite doesn't support delete with joins
        this.select('ghost_revisions.id').from('ghost_revisions').join('ghost_content', 'ghost_content.id', '=', 'ghost_revisions.content_id').where('folder', folder).andWhere('file', file);
      }).del();

      yield updatePendingForFolder(folder);

      if (_fs2.default.existsSync(filePath)) {
        // If the file exists on disk, this means it was initially source controlled
        recordFile(folderPath, folder, file, { isBinary });
      } else {
        yield knex('ghost_content').where('folder', folder).andWhere('file', file).del();
      }
    });

    return function revertAllPendingChangesForFile(_x10, _x11) {
      return _ref6.apply(this, arguments);
    };
  })();

  const readFile = (() => {
    var _ref7 = _asyncToGenerator(function* (rootFolder, file) {
      const knex = yield db.get();
      const { normalizedFolderName } = normalizeFolder(rootFolder);
      const { isBinary } = folderOptions[normalizedFolderName] || {};
      const column = isBinary ? 'binary_content' : 'content';

      return knex('ghost_content').select(column).where({ folder: normalizedFolderName, file }).then(function (results) {
        if (!results || !results.length) {
          return null;
        }
        const result = results[0];
        return result && result[column] || null;
      });
    });

    return function readFile(_x12, _x13) {
      return _ref7.apply(this, arguments);
    };
  })();

  const deleteFile = (() => {
    var _ref8 = _asyncToGenerator(function* (rootFolder, file) {
      const knex = yield db.get();
      const { normalizedFolderName } = normalizeFolder(rootFolder);

      const id = (0, _get2.default)((yield knex('ghost_content').where({ folder: normalizedFolderName, file, deleted: 0 }).select('id')), '0.id');

      if (!id) {
        throw new Error(`Can't delete file: ${file}: couldn't find it in folder: ${normalizedFolderName}`);
      }

      return knex.transaction(function (trx) {
        knex('ghost_content').transacting(trx).where({ id }).update({ deleted: 1, content: null, binary_content: null }).then(function () {
          return recordRevision(knex, id, trx);
        }).then(trx.commit).then(function () {
          return updatePendingForFolder(normalizedFolderName);
        }).catch(function (err) {
          logger.error('[Ghost Content Manager]', err);
          trx.rollback();
        });
      });
    });

    return function deleteFile(_x14, _x15) {
      return _ref8.apply(this, arguments);
    };
  })();

  const directoryListing = (() => {
    var _ref9 = _asyncToGenerator(function* (rootFolder, fileEndingPattern = '', pathsToOmit = []) {
      const knex = yield db.get();
      const { normalizedFolderName } = normalizeFolder(rootFolder);
      return knex('ghost_content').select('file').whereNotIn('file', pathsToOmit).andWhere({ folder: normalizedFolderName, deleted: 0 }).andWhere('file', 'like', `%${fileEndingPattern}`).then(function (res) {
        return res.map(function (row) {
          return row.file;
        });
      });
    });

    return function directoryListing(_x16) {
      return _ref9.apply(this, arguments);
    };
  })();

  const getPending = () => pendingRevisionsByFolder;

  const getPendingWithContentForFolder = ({ stringifyBinary = false }) => (() => {
    var _ref10 = _asyncToGenerator(function* (folderInfo, normalizedFolderName) {
      const revisions = folderInfo.map(function ({ revision }) {
        return revision;
      });
      const fileNames = (0, _uniq2.default)(folderInfo.map(function ({ file }) {
        return file;
      }));
      const { isBinary } = folderOptions[normalizedFolderName];
      const column = isBinary ? 'binary_content' : 'content';

      const knex = yield db.get();
      const files = yield knex('ghost_content').select('file', column, 'deleted').whereIn('file', fileNames).andWhere({ folder: normalizedFolderName });

      if (isBinary) {
        files.forEach(function (data) {
          data.content = stringifyBinary ? data.binary_content.toString('base64') : data.binary_content;
          delete data.binary_content;
        });
      }

      return {
        files,
        revisions,
        binary: isBinary
      };
    });

    return function (_x17, _x18) {
      return _ref10.apply(this, arguments);
    };
  })();

  const getPendingWithContent = ({ stringifyBinary = false } = {}) => _bluebird2.default.props((0, _mapValues2.default)(pendingRevisionsByFolder, getPendingWithContentForFolder({ stringifyBinary })));

  logger.info('[Ghost Content Manager] Initialized');

  return {
    addRootFolder,
    upsertFile,
    readFile,
    deleteFile,
    directoryListing,
    getPending,
    getPendingWithContent,
    revertAllPendingChangesForFile
  };
};

// TODO: switch to ES6 modules
module.exports.REVISIONS_FILE_NAME = REVISIONS_FILE_NAME;
//# sourceMappingURL=index.js.map