File: //proc/1526/cwd/zaklada/html/node_modules/node-haste/lib/Haste.js
/**
 * Copyright 2013 Facebook, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
var EventEmitter = require('events').EventEmitter;
var inherits = require('util').inherits;
var loaders = require('./loaders');
var fs = require('fs');
var MapUpdateTask = require('./MapUpdateTask');
var ResourceMap = require('./ResourceMap');
var ResourceMapSerializer = require('./ResourceMapSerializer');
var FileFinder = require('./FileFinder');
var ProjectConfigurationLoader = loaders.ProjectConfigurationLoader;
/*
 *
 *                             ____________________________
 *                                                           .OOo
 *                                                           OOOOL
 *                                  __________________    .. JOOO?
 *                                                     .eSSSSSS**'
 *                                                   gSSSSSSSSSS    6.
 *                                                   oo  SSSSSSSs  .G
 *                                      ___________   oo SSSSSSSSSgG
 *                                                     *ISSSSSS  **
 *                                                      SSSSSSS
 *                                                    .oYSSSSSY.
 *                                                    OOOOOOOOOOOg
 *                                                   .OOOOT**TOOOOO.
 *                        ______________________    .OOOO'     'OOOOO.
 *                                                 .OOOO'         OOOO'
 *                                              .oOOOO*        .OOOO*
 *                                            .OOOOO''       .OOOOO
 *                                         .JOOOO*         .##OOO'
 *                                        C##?/             T##I
 *                                         Y#|                'V
 *    Node Haste ________________________   C
 *
 */
/**
 * @class Haste. A nice facade to node-haste system
 *
 * Running a node haste update task is a pretty complicated matter. You have
 * to manually create a FileFinder, a MapUpdateTask, a ResourceMapSerializer.
 * Haste class automates all of this providing a task oriented API with a
 * broad set of configuration options. The only 2 required parameters are
 * loaders and scanDirs
 *
 * @extends {EventEmitter}
 *
 * @example
 *   var Haste = require('node-haste/Haste');
 *   var loaders = require('node-haste/loaders');
 *
 *   var haste = new Haste(
 *   [
 *     new loaders.JSLoader({ networkSize: true }),
 *     new loaders.CSSLoader({ networkSize: true }),
 *     new ProjectConfigurationLoader(),
 *     new ResourceLoader()
 *   ],
 *   ['html']);
 *
 *   haste.update('.cache', function(map) {
 *     assert(map instanceof ResourceMap);
 *   });
 *
 *
 * @param {Array.<Loader>}  loaders              Preconfigured Loader instances
 * @param {Array.<String>}  scanDirs
 * @param {FileFinder|null} options.finder       Custom finder instance
 * @param {ResourceMapSerializer|null} options.serializer Custom serializer
 * @param {Number|null}   options.maxOpenFiles   Maximum number of loaders
 *                                               MapUpdateTask can use
 * @param {Number|null}   options.maxProcesses   Maximum number of loader forks
 *                                               MapUpdateTask can use
 * @param {Boolean|null}  options.useNativeFind  Whether to use native shell
 *                                               find command (faster) or node
 *                                               implementation (safer)
 * @param {function|null} options.ignorePaths    Function to reject paths
 * @param {String|null}   options.version        Version of the cache. If
 *                                               the version mismatches the
 *                                               cached on, cache will be
 *                                               ignored
 *
 */
function Haste(loaders, scanDirs, options) {
  EventEmitter.call(this);
  this.loaders = loaders;
  this.scanDirs = scanDirs;
  this.options = options || {};
  this.finder = this.options.finder || null;
  this.serializer = this.options.serializer || null;
}
inherits(Haste, EventEmitter);
/**
 * All in one function:
 *  1) load cache if exists
 *  2) compare to the existing files
 *  3) analyze changes,
 *  4) update map,
 *  5) write cache back to disk
 *  6) return map
 *
 * @param  {String}   path
 * @param  {Function} callback
 */
Haste.prototype.update = function(path, callback, options) {
  var map, files;
  var me = this;
  var run = function() {
    if (!map || !files) {
      return;
    }
    var task = me.createUpdateTask(files, map).on('complete', function(map) {
      // only store map if it's changed
      var mapChanged = task.changed.length > task.skipped.length;
      if (mapChanged) {
        me.storeMap(path, map, function() {
          me.emit('mapStored');
          callback(map, task.messages);
        });
      } else {
        callback(map, task.messages);
      }
    }).run();
  }
  this.getFinder().find(function(f) {
    files = f;
    me.emit('found', files);
    run();
  });
  if (options && options.forceRescan) {
    map = new ResourceMap();
  } else {
    this.loadOrCreateMap(path, function(m) {
      map = m;
      me.emit('mapLoaded');
      run();
    });
  }
};
/**
 * Same as update but will also rerun the update every time something changes
 *
 * TODO: (voloko) add support for inotify and FSEvent instead of constantly
 * running finder
 *
 * @param  {String}   path
 * @param  {Function} callback
 * @param  {Number}   options.timeout How often to rerun finder
 * @param  {Boolean}  options.forceRescan
 */
Haste.prototype.watch = function(path, callback, options) {
  var timeout = options && options.timeout || 1000;
  var finder = this.getFinder();
  var map, files, task;
  var me = this;
  var firstRun = true;
  function find() {
    finder.find(function(f) {
      files = f;
      if (map) {
        update();
      }
    });
  }
  function updated(m) {
    map = m;
    var mapChanged = task.changed.length > task.skipped.length;
    // if changed, store the map and only then callback and schedule next run
    if (mapChanged) {
      me.storeMap(path, map, function() {
        callback(map, task.changed, task.messages);
        setTimeout(find, timeout);
      });
      return;
    }
    // callback on the first run even if the map is unchanged
    if (firstRun) {
      firstRun = false;
      callback(map, task.changed, task.messages);
    }
    setTimeout(find, timeout);
  }
  function update() {
    task = me.createUpdateTask(files, map).on('complete', updated).run();
  }
  if (options && options.forceRescan) {
    map = new ResourceMap();
  } else {
    this.loadOrCreateMap(path, function(m) {
      map = m;
      if (files) {
        update();
      }
    });
  }
  find();
};
/**
 * Updates a map using the configuration options from constructor
 * @param  {ResourceMap}   map
 * @param  {Function} callback
 */
Haste.prototype.updateMap = function(map, callback) {
  this.getFinder().find(function(files) {
     this.createUpdateTask(files, map).on('complete', callback).run();
  }.bind(this));
};
/**
 * Loads map from a file
 * @param  {String}   path
 * @param  {Function} callback
 */
Haste.prototype.loadMap = function(path, callback) {
  this.getSerializer().loadFromPath(path, callback);
};
/**
 * @param  {String}   path
 * @return  {ResourceMap|null}
 */
Haste.prototype.loadMapSync = function(path) {
  return this.getSerializer().loadFromPathSync(path);
};
/**
 * Loads map from a file or creates one if cache is not available
 * @param  {String}   path
 * @param  {Function} callback
 */
Haste.prototype.loadOrCreateMap = function(path, callback) {
  this.getSerializer().loadFromPath(path, function(err, map) {
    callback(map || new ResourceMap());
  });
};
/**
 * @param  {String}   path
 * @return  {ResourceMap}
 */
Haste.prototype.loadOrCreateMapSync = function(path) {
  return this.loadMapSync(path) || new ResourceMap();
};
/**
 * Stores the map cache
 * @param  {String}   path
 * @param  {ResourceMap}   map
 * @param  {Function} callback
 */
Haste.prototype.storeMap = function(path, map, callback) {
  this.getSerializer().storeToPath(path, map, callback);
};
/**
 * @protected
 * @param {ResourceMap} map
 * @return {MapUpdateTask}
 */
Haste.prototype.createUpdateTask = function(files, map) {
  var task = new MapUpdateTask(
    files,
    this.loaders,
    map,
    {
      maxOpenFiles: this.options.maxOpenFiles,
      maxProcesses: this.options.maxProcesses
    });
  var events =
    ['found', 'changed', 'analyzed', 'mapUpdated', 'postProcessed', 'complete'];
  var me = this;
  events.forEach(function(name) {
    task.on(name, function(value) {
      me.emit(name, value);
    });
  });
  return task;
};
/**
 * @protected
 * @return {FileFinder}
 */
Haste.prototype.getFinder = function() {
  if (!this.finder) {
    var ext = {};
    this.loaders.forEach(function(loader) {
      loader.getExtensions().forEach(function(e) {
        ext[e] = true;
      });
    });
    this.finder = new FileFinder({
      scanDirs: this.scanDirs,
      extensions: Object.keys(ext),
      useNative: this.options.useNativeFind,
      ignore: this.options.ignorePaths
    });
  }
  return this.finder;
};
/**
 * @protected
 * @return {ResourceMapSerializer}
 */
Haste.prototype.getSerializer = function() {
  return this.serializer || new ResourceMapSerializer(
    this.loaders,
    { version: this.options.version });
};
module.exports = Haste;