<commit_before>(function() {

  var $ = jQuery; // Handle namespaced jQuery

  // The search box is responsible for managing the many facet views and input views.
  VS.ui.SearchBox = Backbone.View.extend({

    id: 'search',

    events: {
      'click .VS-cancel-search-box': 'clearSearch',
      'mousedown .VS-search-box': 'maybeFocusSearch',
      'dblclick .VS-search-box': 'highlightSearch',
      'click .VS-search-box': 'maybeTripleClick'
    },

    // Creating a new SearchBox registers handlers for re-rendering facets when necessary,
    // as well as handling typing when a facet is selected.
    initialize: function(options) {
      this.options = _.extend({}, this.options, options);

      this.app = this.options.app;
      this.flags = {
        allSelected: false
      };
      this.facetViews = [];
      this.inputViews = [];
      _.bindAll(this, 'renderFacets', '_maybeDisableFacets', 'disableFacets',
        'deselectAllFacets', 'addedFacet', 'removedFacet', 'changedFacet');
      this.app.searchQuery
        .bind('reset', this.renderFacets)
        .bind('add', this.addedFacet)
        .bind('remove', this.removedFacet)
        .bind('change', this.changedFacet);
      $(document).bind('keydown', this._maybeDisableFacets);
    },

    // Renders the search box, but requires placement on the page through `this.el`.
    render: function() {
      $(this.el).append(JST['search_box']({
        readOnly: this.app.options.readOnly
      }));
      $(document.body).setMode('no', 'search');

      return this;
    },

    // # Querying Facets #

    // Either gets a serialized query string or sets the faceted query from a query string.
    value: function(query) {
      if (query == null) return this.serialize();
      return this.setQuery(query);
    },

    // Uses the VS.app.searchQuery collection to serialize the current query from the various
    // facets that are in the search box.
    serialize: function() {
      var query = [];
      var inputViewsCount = this.inputViews.length;

      this.app.searchQuery.each(_.bind(function(facet, i) {
        query.push(this.inputViews[i].value());
        query.push(facet.serialize());
      }, this));

      if (inputViewsCount) {
        query.push(this.inputViews[inputViewsCount - 1].value());
      }

      return _.compact(query).join(' ');
    },

    // Returns any facet views that are currently selected. Useful for changing the value
    // callbacks based on what else is in the search box and which facet is being edited.
    selected: function() {
      return _.select(this.facetViews, function(view) {
        return view.modes.editing == 'is' || view.modes.selected == 'is';
      });
    },

    // Similar to `this.selected`, returns any facet models that are currently selected.
    selectedModels: function() {
      return _.pluck(this.selected(), 'model');
    },

    // Takes a query string and uses the SearchParser to parse and render it. Note that
    // `VS.app.SearchParser` refreshes the `VS.app.searchQuery` collection, which is bound
    // here to call `this.renderFacets`.
    setQuery: function(query) {
      this.currentQuery = query;
      VS.app.SearchParser.parse(this.app, query);
    },

    // Returns the position of a facet/input view. Useful when moving between facets.
    viewPosition: function(view) {
      var views = view.type == 'facet' ? this.facetViews : this.inputViews;
      var position = _.indexOf(views, view);
      if (position == -1) position = 0;
      return position;
    },

    // Used to launch a search. Hitting enter or clicking the search button.
    searchEvent: function(e) {
      var query = this.value(),
        isNew = this.$('.search_input input').val();

      this.focusSearch(e);
      this.value(query);

      if (!isNew || this.app.options.autosearch) {
        this.app.options.callbacks.search(query, this.app.searchQuery);
      }
    },

    // # Rendering Facets #

    // Add a new facet. Facet will be focused and ready to accept a value. Can also
    // specify position, in the case of adding facets from an inbetween input.
    addFacet: function(category, initialQuery, position) {
      category = VS.utils.inflector.trim(category);
      initialQuery = VS.utils.inflector.trim(initialQuery || '');
      if (!category) return;

      var model = new VS.model.SearchFacet({
        category: category,
        value: initialQuery || '',
        app: this.app
      });
      this.app.searchQuery.add(model, {at: position});
    },

    // Renders a newly added facet, and selects it.
    addedFacet: function(model) {
      this.renderFacets();
      var facetView = _.detect(this.facetViews, function(view) {
        if (view.model == model) return true;
      });

      _.defer(function() {
        facetView.enableEdit();
      });
    },

    // Changing a facet programmatically re-renders it.
    changedFacet: function() {
      this.renderFacets();
    },

    // When removing a facet, potentially do something. For now, the adjacent
    // remaining facet is selected, but this is handled by the facet's view,
    // since its position is unknown by the time the collection triggers this
    // remove callback.
    removedFacet: function(facet, query, options) {
      this.app.options.callbacks.removedFacet(facet, query, options);
    },

    // Renders each facet as a searchFacet view.
    renderFacets: function() {
      this.facetViews = [];
      this.inputViews = [];

      this.$('.VS-search-inner').empty();

      this.app.searchQuery.each(_.bind(this.renderFacet, this));

      // Add on an n+1 empty search input on the very end.
      this.renderSearchInput();
      this.renderPlaceholder();
    },

    // Render a single facet, using its category and query value.
    renderFacet: function(facet, position) {
      var view = new VS.ui.SearchFacet({
        app: this.app,
        model: facet,
        order: position,
        facetAutocompleteClassName: this.options.app.options.className.facetAutocomplete
      });

      // Input first, facet second.
      this.renderSearchInput();
      this.facetViews.push(view);
      this.$('.VS-search-inner').children().eq(position * 2).after(view.render().el);

      view.calculateSize();
      _.defer(_.bind(view.calculateSize, view));

      return view;
    },

    // Render a single input, used to create and autocomplete facets
    renderSearchInput: function() {
      var input = new VS.ui.SearchInput({
        position: this.inputViews.length,
        app: this.app,
        showFacets: this.options.showFacets,
        inputAutocompleteClassName: this.options.app.options.className.inputAutocomplete
      });
      this.$('.VS-search-inner').append(input.render().el);
      this.inputViews.push(input);
    },

    // Handles showing/hiding the placeholder text
    renderPlaceholder: function() {
      var $placeholder = this.$('.VS-placeholder');
      if (this.app.searchQuery.length) {
        $placeholder.addClass("VS-hidden");
      } else {
        $placeholder.removeClass("VS-hidden")
          .text(this.app.options.placeholder);
      }
    },

    // # Modifying Facets #

    // Clears out the search box. Command+A + delete can trigger this, as can a cancel button.
    //
    // If a `clearSearch` callback was provided, the callback is invoked and
    // provided with a function performs the actual removal of the data.  This
    // allows third-party developers to either clear data asynchronously, or
    // prior to performing their custom "clear" logic.
    clearSearch: function(e) {
      if (this.app.options.readOnly) return;
      var actualClearSearch = _.bind(function() {
        this.disableFacets();
        this.value('');
        this.flags.allSelected = false;
        this.searchEvent(e);
        this.focusSearch(e);
      }, this);

      if (this.app.options.callbacks.clearSearch != $.noop) {
        this.app.options.callbacks.clearSearch(actualClearSearch);
      } else {
        actualClearSearch();
      }
    },

    // Command+A selects all facets.
    selectAllFacets: function() {
      this.flags.allSelected = true;

      $(document).one('click.selectAllFacets', this.deselectAllFacets);

      _.each(this.facetViews, function(facetView, i) {
        facetView.selectFacet();
      });
      _.each(this.inputViews, function(inputView, i) {
        inputView.selectText();
      });
    },

    // Used by facets and input to see if all facets are currently selected.
    allSelected: function(deselect) {
      if (deselect) this.flags.allSelected = false;
      return this.flags.allSelected;
    },

    // After `selectAllFacets` is engaged, this method is bound to the entire document.
    // This immediate disables and deselects all facets, but it also checks if the user
    // has clicked on either a facet or an input, and properly selects the view.
    deselectAllFacets: function(e) {
      this.disableFacets();

      if (this.$(e.target).is('.category,input')) {
        var el = $(e.target).closest('.search_facet,.search_input');
        var view = _.detect(this.facetViews.concat(this.inputViews), function(v) {
          return v.el == el[0];
        });
        if (view.type == 'facet') {
          view.selectFacet();
        } else if (view.type == 'input') {
          _.defer(function() {
            view.enableEdit(true);
          });
        }
      }
    },

    // Disables all facets except for the passed in view. Used when switching between
    // facets, so as not to have to keep state of active facets.
    disableFacets: function(keepView) {
      _.each(this.inputViews, function(view) {
        if (view && view != keepView &&
          (view.modes.editing == 'is' || view.modes.selected == 'is')) {
          view.disableEdit();
        }
      });
      _.each(this.facetViews, function(view) {
        if (view && view != keepView &&
          (view.modes.editing == 'is' || view.modes.selected == 'is')) {
          view.disableEdit();
          view.deselectFacet();
        }
      });

      this.flags.allSelected = false;
      this.removeFocus();
      $(document).unbind('click.selectAllFacets');
    },

    // Resize all inputs to account for extra keystrokes which may be changing the facet
    // width incorrectly. This is a safety check to ensure inputs are correctly sized.
    resizeFacets: function(view) {
      _.each(this.facetViews, function(facetView, i) {
        if (!view || facetView == view) {
          facetView.resize();
        }
      });
    },

    // Handles keydown events on the document. Used to complete the Cmd+A deletion, and
    // blurring focus.
    _maybeDisableFacets: function(e) {
      var key = VS.app.hotkeys.key(e);

      if (this.flags.allSelected && (key === 'backspace' || key === 'delete')) {
        e.preventDefault();
        this.clearSearch(e);
        return false;
      } else if (this.flags.allSelected && VS.app.hotkeys.printable(e)) {
        this.clearSearch(e);
      }
    },

    // # Focusing Facets #

    // Move focus between facets and inputs. Takes a direction as well as many options
    // for skipping over inputs and only to facets, placement of cursor position in facet
    // (i.e. at the end), and selecting the text in the input/facet.
    focusNextFacet: function(currentView, direction, options) {
      options = options || {};
      var viewCount = this.facetViews.length;
      var viewPosition = options.viewPosition || this.viewPosition(currentView);

      if (!options.skipToFacet) {
        // Correct for bouncing between matching text and facet arrays.
        if (currentView.type == 'text' && direction > 0) direction -= 1;
        if (currentView.type == 'facet' && direction < 0) direction += 1;
      } else if (options.skipToFacet && currentView.type == 'text' &&
        viewCount == viewPosition && direction >= 0) {
        // Special case of looping around to a facet from the last search input box.
        return false;
      }
      var view, next = Math.min(viewCount, viewPosition + direction);

      if (currentView.type == 'text') {
        if (next >= 0 && next < viewCount) {
          view = this.facetViews[next];
        } else if (next == viewCount) {
          view = this.inputViews[this.inputViews.length - 1];
        }
        if (view && options.selectFacet && view.type == 'facet') {
          view.selectFacet();
        } else if (view) {
          view.enableEdit();
          view.setCursorAtEnd(direction || options.startAtEnd);
        }
      } else if (currentView.type == 'facet') {
        if (options.skipToFacet) {
          if (next >= viewCount || next < 0) {
            view = _.last(this.inputViews);
            view.enableEdit();
          } else {
            view = this.facetViews[next];
            view.enableEdit();
            view.setCursorAtEnd(direction || options.startAtEnd);
          }
        } else {
          view = this.inputViews[next];
          view.enableEdit();
        }
      }
      if (options.selectText) view.selectText();
      this.resizeFacets();

      return true;
    },

    maybeFocusSearch: function(e) {
      if (this.app.options.readOnly) return;
      if ($(e.target).is('.VS-search-box') ||
        $(e.target).is('.VS-search-inner') ||
        e.type == 'keydown') {
        this.focusSearch(e);
      }
    },

    // Bring focus to last input field.
    focusSearch: function(e, selectText) {
      if (this.app.options.readOnly) return;
      var view = this.inputViews[this.inputViews.length - 1];
      view.enableEdit(selectText);
      if (!selectText) view.setCursorAtEnd(-1);
      if (e.type == 'keydown') {
        view.keydown(e);
        view.box.trigger('keydown');
      }
      _.defer(_.bind(function() {
        if (!this.$('input:focus').length) {
          view.enableEdit(selectText);
        }
      }, this));
    },

    // Double-clicking on the search wrapper should select the existing text in
    // the last search input. Also start the triple-click timer.
    highlightSearch: function(e) {
      if (this.app.options.readOnly) return;
      if ($(e.target).is('.VS-search-box') ||
        $(e.target).is('.VS-search-inner') ||
        e.type == 'keydown') {
        var lastinput = this.inputViews[this.inputViews.length - 1];
        lastinput.startTripleClickTimer();
        this.focusSearch(e, true);
      }
    },

    maybeTripleClick: function(e) {
      var lastinput = this.inputViews[this.inputViews.length - 1];
      return lastinput.maybeTripleClick(e);
    },

    // Used to show the user is focused on some input inside the search box.
    addFocus: function() {
      if (this.app.options.readOnly) return;
      this.app.options.callbacks.focus();
      this.$('.VS-search-box').addClass('VS-focus');
    },

    // User is no longer focused on anything in the search box.
    removeFocus: function() {
      this.app.options.callbacks.blur();
      var focus = _.any(this.facetViews.concat(this.inputViews), function(view) {
        return view.isFocused();
      });
      if (!focus) this.$('.VS-search-box').removeClass('VS-focus');
    },

    // Show a menu which adds pre-defined facets to the search box. This is unused for now.
    showFacetCategoryMenu: function(e) {
      e.preventDefault();
      e.stopPropagation();
      if (this.facetCategoryMenu && this.facetCategoryMenu.modes.open == 'is') {
        return this.facetCategoryMenu.close();
      }

      var items = [
        {title: 'Account', onClick: _.bind(this.addFacet, this, 'account', '')},
        {title: 'Project', onClick: _.bind(this.addFacet, this, 'project', '')},
        {title: 'Filter', onClick: _.bind(this.addFacet, this, 'filter', '')},
        {title: 'Access', onClick: _.bind(this.addFacet, this, 'access', '')}
      ];

      var menu = this.facetCategoryMenu || (this.facetCategoryMenu = new dc.ui.Menu({
          items: items,
          standalone: true
        }));

      this.$('.VS-icon-search').after(menu.render().open().content);
      return false;
    }
  });
})();
<commit_msg>FIX review comments<commit_after>(function() {

  var $ = jQuery; // Handle namespaced jQuery

  // The search box is responsible for managing the many facet views and input views.
  VS.ui.SearchBox = Backbone.View.extend({

    id: 'search',

    events: {
      'click .VS-cancel-search-box': 'clearSearch',
      'mousedown .VS-search-box': 'maybeFocusSearch',
      'dblclick .VS-search-box': 'highlightSearch',
      'click .VS-search-box': 'maybeTripleClick'
    },

    // Creating a new SearchBox registers handlers for re-rendering facets when necessary,
    // as well as handling typing when a facet is selected.
    initialize: function(options) {
      this.options = _.extend({}, this.options, options);

      this.app = this.options.app;
      this.flags = {
        allSelected: false
      };
      this.facetViews = [];
      this.inputViews = [];
      _.bindAll(this, 'renderFacets', '_maybeDisableFacets', 'disableFacets',
        'deselectAllFacets', 'addedFacet', 'removedFacet', 'changedFacet');
      this.app.searchQuery
        .bind('reset', this.renderFacets)
        .bind('add', this.addedFacet)
        .bind('remove', this.removedFacet)
        .bind('change', this.changedFacet);
      $(document).bind('keydown', this._maybeDisableFacets);
    },

    // Renders the search box, but requires placement on the page through `this.el`.
    render: function() {
      $(this.el).append(JST['search_box']({
        readOnly: this.app.options.readOnly
      }));
      $(document.body).setMode('no', 'search');

      return this;
    },

    // # Querying Facets #

    // Either gets a serialized query string or sets the faceted query from a query string.
    value: function(query) {
      if (query == null) return this.serialize();
      return this.setQuery(query);
    },

    // Uses the VS.app.searchQuery collection to serialize the current query from the various
    // facets that are in the search box.
    serialize: function() {
      var query = [];
      var inputViewsCount = this.inputViews.length;

      this.app.searchQuery.each(_.bind(function(facet, i) {
        query.push(this.inputViews[i].value());
        query.push(facet.serialize());
      }, this));

      if (inputViewsCount) {
        query.push(this.inputViews[inputViewsCount - 1].value());
      }

      return _.compact(query).join(' ');
    },

    // Returns any facet views that are currently selected. Useful for changing the value
    // callbacks based on what else is in the search box and which facet is being edited.
    selected: function() {
      return _.select(this.facetViews, function(view) {
        return view.modes.editing == 'is' || view.modes.selected == 'is';
      });
    },

    // Similar to `this.selected`, returns any facet models that are currently selected.
    selectedModels: function() {
      return _.pluck(this.selected(), 'model');
    },

    // Takes a query string and uses the SearchParser to parse and render it. Note that
    // `VS.app.SearchParser` refreshes the `VS.app.searchQuery` collection, which is bound
    // here to call `this.renderFacets`.
    setQuery: function(query) {
      this.currentQuery = query;
      VS.app.SearchParser.parse(this.app, query);
    },

    // Returns the position of a facet/input view. Useful when moving between facets.
    viewPosition: function(view) {
      var views = view.type == 'facet' ? this.facetViews : this.inputViews;
      var position = _.indexOf(views, view);
      if (position == -1) position = 0;
      return position;
    },

    // Used to launch a search. Hitting enter or clicking the search button.
    searchEvent: function(e) {
      var query = this.value(),
        hasNewTextFacet = this.$('.search_input input').val();

      this.focusSearch(e);
      this.value(query);

      if (!hasNewTextFacet || this.app.options.autosearch) {
        this.app.options.callbacks.search(query, this.app.searchQuery);
      }
    },

    // # Rendering Facets #

    // Add a new facet. Facet will be focused and ready to accept a value. Can also
    // specify position, in the case of adding facets from an inbetween input.
    addFacet: function(category, initialQuery, position) {
      category = VS.utils.inflector.trim(category);
      initialQuery = VS.utils.inflector.trim(initialQuery || '');
      if (!category) return;

      var model = new VS.model.SearchFacet({
        category: category,
        value: initialQuery || '',
        app: this.app
      });
      this.app.searchQuery.add(model, {at: position});
    },

    // Renders a newly added facet, and selects it.
    addedFacet: function(model) {
      this.renderFacets();
      var facetView = _.detect(this.facetViews, function(view) {
        if (view.model == model) return true;
      });

      _.defer(function() {
        facetView.enableEdit();
      });
    },

    // Changing a facet programmatically re-renders it.
    changedFacet: function() {
      this.renderFacets();
    },

    // When removing a facet, potentially do something. For now, the adjacent
    // remaining facet is selected, but this is handled by the facet's view,
    // since its position is unknown by the time the collection triggers this
    // remove callback.
    removedFacet: function(facet, query, options) {
      this.app.options.callbacks.removedFacet(facet, query, options);
    },

    // Renders each facet as a searchFacet view.
    renderFacets: function() {
      this.facetViews = [];
      this.inputViews = [];

      this.$('.VS-search-inner').empty();

      this.app.searchQuery.each(_.bind(this.renderFacet, this));

      // Add on an n+1 empty search input on the very end.
      this.renderSearchInput();
      this.renderPlaceholder();
    },

    // Render a single facet, using its category and query value.
    renderFacet: function(facet, position) {
      var view = new VS.ui.SearchFacet({
        app: this.app,
        model: facet,
        order: position,
        facetAutocompleteClassName: this.options.app.options.className.facetAutocomplete
      });

      // Input first, facet second.
      this.renderSearchInput();
      this.facetViews.push(view);
      this.$('.VS-search-inner').children().eq(position * 2).after(view.render().el);

      view.calculateSize();
      _.defer(_.bind(view.calculateSize, view));

      return view;
    },

    // Render a single input, used to create and autocomplete facets
    renderSearchInput: function() {
      var input = new VS.ui.SearchInput({
        position: this.inputViews.length,
        app: this.app,
        showFacets: this.options.showFacets,
        inputAutocompleteClassName: this.options.app.options.className.inputAutocomplete
      });
      this.$('.VS-search-inner').append(input.render().el);
      this.inputViews.push(input);
    },

    // Handles showing/hiding the placeholder text
    renderPlaceholder: function() {
      var $placeholder = this.$('.VS-placeholder');
      if (this.app.searchQuery.length) {
        $placeholder.addClass("VS-hidden");
      } else {
        $placeholder.removeClass("VS-hidden")
          .text(this.app.options.placeholder);
      }
    },

    // # Modifying Facets #

    // Clears out the search box. Command+A + delete can trigger this, as can a cancel button.
    //
    // If a `clearSearch` callback was provided, the callback is invoked and
    // provided with a function performs the actual removal of the data.  This
    // allows third-party developers to either clear data asynchronously, or
    // prior to performing their custom "clear" logic.
    clearSearch: function(e) {
      if (this.app.options.readOnly) return;
      var actualClearSearch = _.bind(function() {
        this.disableFacets();
        this.value('');
        this.flags.allSelected = false;
        this.searchEvent(e);
        this.focusSearch(e);
      }, this);

      if (this.app.options.callbacks.clearSearch != $.noop) {
        this.app.options.callbacks.clearSearch(actualClearSearch);
      } else {
        actualClearSearch();
      }
    },

    // Command+A selects all facets.
    selectAllFacets: function() {
      this.flags.allSelected = true;

      $(document).one('click.selectAllFacets', this.deselectAllFacets);

      _.each(this.facetViews, function(facetView, i) {
        facetView.selectFacet();
      });
      _.each(this.inputViews, function(inputView, i) {
        inputView.selectText();
      });
    },

    // Used by facets and input to see if all facets are currently selected.
    allSelected: function(deselect) {
      if (deselect) this.flags.allSelected = false;
      return this.flags.allSelected;
    },

    // After `selectAllFacets` is engaged, this method is bound to the entire document.
    // This immediate disables and deselects all facets, but it also checks if the user
    // has clicked on either a facet or an input, and properly selects the view.
    deselectAllFacets: function(e) {
      this.disableFacets();

      if (this.$(e.target).is('.category,input')) {
        var el = $(e.target).closest('.search_facet,.search_input');
        var view = _.detect(this.facetViews.concat(this.inputViews), function(v) {
          return v.el == el[0];
        });
        if (view.type == 'facet') {
          view.selectFacet();
        } else if (view.type == 'input') {
          _.defer(function() {
            view.enableEdit(true);
          });
        }
      }
    },

    // Disables all facets except for the passed in view. Used when switching between
    // facets, so as not to have to keep state of active facets.
    disableFacets: function(keepView) {
      _.each(this.inputViews, function(view) {
        if (view && view != keepView &&
          (view.modes.editing == 'is' || view.modes.selected == 'is')) {
          view.disableEdit();
        }
      });
      _.each(this.facetViews, function(view) {
        if (view && view != keepView &&
          (view.modes.editing == 'is' || view.modes.selected == 'is')) {
          view.disableEdit();
          view.deselectFacet();
        }
      });

      this.flags.allSelected = false;
      this.removeFocus();
      $(document).unbind('click.selectAllFacets');
    },

    // Resize all inputs to account for extra keystrokes which may be changing the facet
    // width incorrectly. This is a safety check to ensure inputs are correctly sized.
    resizeFacets: function(view) {
      _.each(this.facetViews, function(facetView, i) {
        if (!view || facetView == view) {
          facetView.resize();
        }
      });
    },

    // Handles keydown events on the document. Used to complete the Cmd+A deletion, and
    // blurring focus.
    _maybeDisableFacets: function(e) {
      var key = VS.app.hotkeys.key(e);

      if (this.flags.allSelected && (key === 'backspace' || key === 'delete')) {
        e.preventDefault();
        this.clearSearch(e);
        return false;
      } else if (this.flags.allSelected && VS.app.hotkeys.printable(e)) {
        this.clearSearch(e);
      }
    },

    // # Focusing Facets #

    // Move focus between facets and inputs. Takes a direction as well as many options
    // for skipping over inputs and only to facets, placement of cursor position in facet
    // (i.e. at the end), and selecting the text in the input/facet.
    focusNextFacet: function(currentView, direction, options) {
      options = options || {};
      var viewCount = this.facetViews.length;
      var viewPosition = options.viewPosition || this.viewPosition(currentView);

      if (!options.skipToFacet) {
        // Correct for bouncing between matching text and facet arrays.
        if (currentView.type == 'text' && direction > 0) direction -= 1;
        if (currentView.type == 'facet' && direction < 0) direction += 1;
      } else if (options.skipToFacet && currentView.type == 'text' &&
        viewCount == viewPosition && direction >= 0) {
        // Special case of looping around to a facet from the last search input box.
        return false;
      }
      var view, next = Math.min(viewCount, viewPosition + direction);

      if (currentView.type == 'text') {
        if (next >= 0 && next < viewCount) {
          view = this.facetViews[next];
        } else if (next == viewCount) {
          view = this.inputViews[this.inputViews.length - 1];
        }
        if (view && options.selectFacet && view.type == 'facet') {
          view.selectFacet();
        } else if (view) {
          view.enableEdit();
          view.setCursorAtEnd(direction || options.startAtEnd);
        }
      } else if (currentView.type == 'facet') {
        if (options.skipToFacet) {
          if (next >= viewCount || next < 0) {
            view = _.last(this.inputViews);
            view.enableEdit();
          } else {
            view = this.facetViews[next];
            view.enableEdit();
            view.setCursorAtEnd(direction || options.startAtEnd);
          }
        } else {
          view = this.inputViews[next];
          view.enableEdit();
        }
      }
      if (options.selectText) view.selectText();
      this.resizeFacets();

      return true;
    },

    maybeFocusSearch: function(e) {
      if (this.app.options.readOnly) return;
      if ($(e.target).is('.VS-search-box') ||
        $(e.target).is('.VS-search-inner') ||
        e.type == 'keydown') {
        this.focusSearch(e);
      }
    },

    // Bring focus to last input field.
    focusSearch: function(e, selectText) {
      if (this.app.options.readOnly) return;
      var view = this.inputViews[this.inputViews.length - 1];
      view.enableEdit(selectText);
      if (!selectText) view.setCursorAtEnd(-1);
      if (e.type == 'keydown') {
        view.keydown(e);
        view.box.trigger('keydown');
      }
      _.defer(_.bind(function() {
        if (!this.$('input:focus').length) {
          view.enableEdit(selectText);
        }
      }, this));
    },

    // Double-clicking on the search wrapper should select the existing text in
    // the last search input. Also start the triple-click timer.
    highlightSearch: function(e) {
      if (this.app.options.readOnly) return;
      if ($(e.target).is('.VS-search-box') ||
        $(e.target).is('.VS-search-inner') ||
        e.type == 'keydown') {
        var lastinput = this.inputViews[this.inputViews.length - 1];
        lastinput.startTripleClickTimer();
        this.focusSearch(e, true);
      }
    },

    maybeTripleClick: function(e) {
      var lastinput = this.inputViews[this.inputViews.length - 1];
      return lastinput.maybeTripleClick(e);
    },

    // Used to show the user is focused on some input inside the search box.
    addFocus: function() {
      if (this.app.options.readOnly) return;
      this.app.options.callbacks.focus();
      this.$('.VS-search-box').addClass('VS-focus');
    },

    // User is no longer focused on anything in the search box.
    removeFocus: function() {
      this.app.options.callbacks.blur();
      var focus = _.any(this.facetViews.concat(this.inputViews), function(view) {
        return view.isFocused();
      });
      if (!focus) this.$('.VS-search-box').removeClass('VS-focus');
    },

    // Show a menu which adds pre-defined facets to the search box. This is unused for now.
    showFacetCategoryMenu: function(e) {
      e.preventDefault();
      e.stopPropagation();
      if (this.facetCategoryMenu && this.facetCategoryMenu.modes.open == 'is') {
        return this.facetCategoryMenu.close();
      }

      var items = [
        {title: 'Account', onClick: _.bind(this.addFacet, this, 'account', '')},
        {title: 'Project', onClick: _.bind(this.addFacet, this, 'project', '')},
        {title: 'Filter', onClick: _.bind(this.addFacet, this, 'filter', '')},
        {title: 'Access', onClick: _.bind(this.addFacet, this, 'access', '')}
      ];

      var menu = this.facetCategoryMenu || (this.facetCategoryMenu = new dc.ui.Menu({
          items: items,
          standalone: true
        }));

      this.$('.VS-icon-search').after(menu.render().open().content);
      return false;
    }
  });
})();
<|endoftext|>


<commit_before>var _ = require('lodash');
var debug = require('debug')('backbone-db-redis:query');

function objToJSON(data) {
  var obj = {};
  var keys = _.keys(data);
  var len = keys.length;
  for (var i = 0; i < len; ++i) {
    try {
      obj[keys[i]] = JSON.parse(data[keys[i]]);
    } catch (e) {
      obj[keys[i]] = data[keys[i]];
    }
  }
  return obj;
}

function toJSON(res) {
  if (res) {
    res = res.map(function(data) {
      if (_.isObject(data)) {
        return objToJSON(data);
      } else {
        return data && JSON.parse(data);
      }
    });
  }
  if (_.isArray(res) && _.every(res, function(m) {
    return !m;
  }))  {
    return [];
  }
  return res;
}

var DbQuery = function(dbOptions, filterOptions) {
  this.dbOptions = dbOptions;
  this.filterOptions = filterOptions;
  this.db = dbOptions.db;
  this.model = dbOptions.model;
  this.redis_type = dbOptions.redis_type;
  this.ModelClass = dbOptions.ModelClass;
  this.limit = this.filterOptions.limit ? this.filterOptions.limit : 50;
  this.offset = this.filterOptions.offset ? this.filterOptions.offset : 0;
};

_.extend(DbQuery.prototype, {
  execute: function(callback) {
    var self = this;
    this.multi = this.db.redis.multi();
    if (this.filterOptions.where) {
      this.filterModels();
    } else {
      this.fetchAll();
    }

    if (this.destinationSet) this.multi.del(this.destinationSet);
    if (this.ids) {
      return this.searchByIds(this.ids, callback);
    }
    debug('multi query:', this.multi.queue);
    this.multi.exec(function(err, results) {
      debug('results:', results);
      var ids = results[self.idQueryNr];
      self.searchByIds(ids, callback);
    });
  },

  filterModels: function() {
    var self = this;
    this.requiredIndexes = [];
    _.each(this.filterOptions.where, function(val, key) {
      if (_.isObject(val) && val.hasOwnProperty('$in')) {
        val = val.$in;
      }
      if (key === 'id') {
        // searching ids is a special case,
        // no other filters supported
        debug('search by id', val);
        this.ids = val;
      } else {
        var dbKey = this.db.getValueSetKey(this.dbOptions.model, key, val);
        this.requiredIndexes.push(dbKey);
      }
    }, this);

    if (this.ids) {
      // no need to query, since we know ids already
      return;
    }

    var query = _.clone(this.requiredIndexes);
    this.destinationSet = 'temp' + (Date.now() * Math.random());

    if (query.length > 1 || this.filterOptions.sort) {
      this.idQueryNr = 1;
      var sortOpts = this._getSortSetKeyAndOrder();
      if (sortOpts.sortKey) query.push(sortOpts.sortKey);
      query.unshift(query.length);
      query.unshift(this.destinationSet);
      this.multi.zinterstore(query);
      this._fetchSorted(this.destinationSet);
    } else {
      this.idQueryNr = 0;
      this.multi.smembers(query[0]);
    }

    debug('searching', this.filterOptions.where, 'from', this.requiredIndexes);
  },

  fetchAll: function() {
    if (!this.idQueryNr) this.idQueryNr = 0;
    if (!this.filterOptions.sort) {
      var collectionKey = this.dbOptions.collectionKey;
      this.multi.sort(collectionKey, 'BY', 'nosort');
    } else {
      this._fetchSorted();
    }
  },

  searchByIds: function(ids, callback) {
    if (!ids.length) return callback(null, []);
    //TODO: already apply limit & offset on query
    ids = ids.splice(this.offset, this.limit);
    var keys = _.map(ids, function(id) {
      // if indexed value is the hash key
      if (id.indexOf('h:') > -1) {
        var parts = id.split(':');
        id = parts[parts.length -1];
      }
      var attrs = {};
      attrs[this.ModelClass.prototype.idAttribute] = id;
      var model = new this.ModelClass(attrs);
      return this.db.getIdKey(model);
    }, this);

    if (!keys.length) {
      debug('nothing found');
      return callback(null, []);
    }
    // read hashes or strings
    if (this.dbOptions.redis_type === 'hash') {
      this.mgetHashes(keys, function(err, res) {
        res = _.filter(res, function(r) {
          return r !== null;
        });
        callback(err, toJSON(res));
      });
    } else {
      this.db.redis.mget(keys, function(err, res) {
        callback(err, toJSON(res));
      });
    }
  },

  mgetHashes: function(keys, callback) {
    var multi = this.db.redis.multi();
    _.each(keys, function(key) {
      multi.hgetall(key);
    });
    multi.exec(function(err, results) {
      callback(err, results);
    });
  },

  _fetchSorted: function(set) {
    var self = this;
    var sortOpts = this._getSortSetKeyAndOrder();
    set = set ? set : sortOpts.sortKey;

    var start = 0;
    var end = -1;
    debug('fetch sorted', sortOpts, set);
    /*if(this.filterOptions.after_id) {
      this.multi.zrank(set, this.filterOptions.after_id, function(err, rank) {
        start = rank;
      });
    }*/

    if (sortOpts.sortOrder === undefined || sortOpts.sortOrder === 1) {
      this.multi.zrange(set, start, end);
    } else {
      this.multi.zrevrange(set, 0, -1);
    }
  },

  _getSortSetKeyAndOrder: function() {
    if (!this.filterOptions.sort) return {};
    var sortProp = this.filterOptions.sort;
    var sortOrder = 1;
    if (sortProp && sortProp[0] === '-') {
      sortOrder = -1;
      sortProp = sortProp.substr(1);
    }
    var sortKey = this.db.getSortSetKey(this.model, sortProp);
    return {
      sortKey: sortKey,
      sortOrder: sortOrder
    };
  }
});

exports.queryModels = function(filterOptions, dbOptions, callback) {
  var query = new DbQuery(dbOptions, filterOptions);
  query.execute(callback);
};<commit_msg>query with custom indexes<commit_after>var _ = require('lodash');
var debug = require('debug')('backbone-db-redis:query');

function objToJSON(data) {
  var obj = {};
  var keys = _.keys(data);
  var len = keys.length;
  for (var i = 0; i < len; ++i) {
    try {
      obj[keys[i]] = JSON.parse(data[keys[i]]);
    } catch (e) {
      obj[keys[i]] = data[keys[i]];
    }
  }
  return obj;
}

function toJSON(res) {
  if (res) {
    res = res.map(function(data) {
      if (_.isObject(data)) {
        return objToJSON(data);
      } else {
        return data && JSON.parse(data);
      }
    });
  }
  if (_.isArray(res) && _.every(res, function(m) {
    return !m;
  }))  {
    return [];
  }
  return res;
}

var DbQuery = function(dbOptions, filterOptions) {
  this.dbOptions = dbOptions;
  this.filterOptions = filterOptions;
  this.db = dbOptions.db;
  this.model = dbOptions.model;
  this.redis_type = dbOptions.redis_type;
  this.ModelClass = dbOptions.ModelClass;
  this.limit = this.filterOptions.limit ? this.filterOptions.limit : 50;
  this.offset = this.filterOptions.offset ? this.filterOptions.offset : 0;
};

_.extend(DbQuery.prototype, {
  execute: function(callback) {
    var self = this;
    this.multi = this.db.redis.multi();
    if (this.filterOptions.where) {
      this.filterModels();
    } else {
      this.fetchAll();
    }

    if (this.destinationSet) this.multi.del(this.destinationSet);
    if (this.ids) {
      return this.searchByIds(this.ids, callback);
    }
    debug('multi query:', this.multi.queue);
    this.multi.exec(function(err, results) {
      debug('results:', results);
      var ids = results[self.idQueryNr];
      self.searchByIds(ids, callback);
    });
  },

  filterModels: function() {
    var self = this;
    this.requiredIndexes = [];
    if (this.filterOptions.customIndexes) {
      // custom indexes override default query logic
      this.requiredIndexes = this.filterOptions.customIndexes;
    } else {
      _.each(this.filterOptions.where, function(val, key) {
        if (_.isObject(val) && val.hasOwnProperty('$in')) {
          val = val.$in;
        }
        if (key === 'id') {
          // searching ids is a special case,
          // no other filters supported
          debug('search by id', val);
          this.ids = val;
        } else {
          var dbKey = this.db.getValueSetKey(this.dbOptions.model, key, val);
          this.requiredIndexes.push(dbKey);
        }
      }, this);
    }

    if (this.ids) {
      // no need to query, since we know ids already
      return;
    }

    var query = _.clone(this.requiredIndexes);
    this.destinationSet = 'temp' + (Date.now() * Math.random());

    if (query.length > 1 || this.filterOptions.sort) {
      this.idQueryNr = 1;
      var sortOpts = this._getSortSetKeyAndOrder();
      if (sortOpts.sortKey) query.push(sortOpts.sortKey);
      query.unshift(query.length);
      query.unshift(this.destinationSet);
      this.multi.zinterstore(query);
      this._fetchSorted(this.destinationSet);
    } else {
      this.idQueryNr = 0;
      this.multi.smembers(query[0]);
    }

    debug('searching', this.filterOptions.where, 'from', this.requiredIndexes);
  },

  fetchAll: function() {
    if (!this.idQueryNr) this.idQueryNr = 0;
    if (!this.filterOptions.sort) {
      var collectionKey = this.dbOptions.collectionKey;
      this.multi.sort(collectionKey, 'BY', 'nosort');
    } else {
      this._fetchSorted();
    }
  },

  searchByIds: function(ids, callback) {
    if (!ids.length) return callback(null, []);
    //TODO: already apply limit & offset on query
    ids = ids.splice(this.offset, this.limit);
    var keys = _.map(ids, function(id) {
      // if indexed value is the hash key
      if (id.indexOf('h:') > -1) {
        var parts = id.split(':');
        id = parts[parts.length -1];
      }
      var attrs = {};
      attrs[this.ModelClass.prototype.idAttribute] = id;
      var model = new this.ModelClass(attrs);
      return this.db.getIdKey(model);
    }, this);

    if (!keys.length) {
      debug('nothing found');
      return callback(null, []);
    }
    // read hashes or strings
    if (this.dbOptions.redis_type === 'hash') {
      this.mgetHashes(keys, function(err, res) {
        res = _.filter(res, function(r) {
          return r !== null;
        });
        callback(err, toJSON(res));
      });
    } else {
      this.db.redis.mget(keys, function(err, res) {
        callback(err, toJSON(res));
      });
    }
  },

  mgetHashes: function(keys, callback) {
    var multi = this.db.redis.multi();
    _.each(keys, function(key) {
      multi.hgetall(key);
    });
    multi.exec(function(err, results) {
      callback(err, results);
    });
  },

  _fetchSorted: function(set) {
    var self = this;
    var sortOpts = this._getSortSetKeyAndOrder();
    set = set ? set : sortOpts.sortKey;

    var start = 0;
    var end = -1;
    debug('fetch sorted', sortOpts, set);
    /*if(this.filterOptions.after_id) {
      this.multi.zrank(set, this.filterOptions.after_id, function(err, rank) {
        start = rank;
      });
    }*/

    if (sortOpts.sortOrder === undefined || sortOpts.sortOrder === 1) {
      this.multi.zrange(set, start, end);
    } else {
      this.multi.zrevrange(set, 0, -1);
    }
  },

  _getSortSetKeyAndOrder: function() {
    if (!this.filterOptions.sort) return {};
    var sortProp = this.filterOptions.sort;
    var sortOrder = 1;
    if (sortProp && sortProp[0] === '-') {
      sortOrder = -1;
      sortProp = sortProp.substr(1);
    }
    var sortKey = this.db.getSortSetKey(this.model, sortProp);
    return {
      sortKey: sortKey,
      sortOrder: sortOrder
    };
  }
});

exports.queryModels = function(filterOptions, dbOptions, callback) {
  var query = new DbQuery(dbOptions, filterOptions);
  query.execute(callback);
};<|endoftext|>


<commit_before>'use strict';

var AWS             = require('aws-sdk');
var CoreObject      = require('core-object');
var RSVP            = require('rsvp');
var fs              = require('fs');
var readFile        = RSVP.denodeify(fs.readFile);
var mime            = require('mime-types');
var joinUriSegments = require('./util/join-uri-segments');

function headObject(client, params) {
  return new RSVP.Promise(function(resolve, reject) {
    client.headObject(params, function(err, data) {
      if (err && err.code === 'NotFound') {
        return resolve();
      }
      else if (err) {
        return reject(err);
      }
      else {
        return resolve(data);
      }
    });
  });
}

module.exports = CoreObject.extend({
  init: function(options) {
    this._super();
    var plugin = options.plugin;
    var config = plugin.pluginConfig;

    this._plugin = plugin;
    this._client = plugin.readConfig('s3Client') || new AWS.S3(config);
  },

  upload: function(options) {
    var client                = this._client;
    var plugin                = this._plugin;
    var bucket                = options.bucket;
    var acl                   = options.acl;
    var cacheControl          = options.cacheControl;
    var allowOverwrite        = options.allowOverwrite;
    var key                   = options.filePattern + ":" + options.revisionKey;
    var revisionKey           = joinUriSegments(options.prefix, key);
    var putObject             = RSVP.denodeify(client.putObject.bind(client));
    var gzippedFilePaths      = options.gzippedFilePaths || [];
    var isGzipped             = gzippedFilePaths.indexOf(options.filePattern) !== -1;
    var serverSideEncryption  = options.serverSideEncryption;

    var params = {
      Bucket: bucket,
      Key: revisionKey,
      ACL: acl,
      ContentType: mime.lookup(options.filePath) || 'text/html',
      CacheControl: cacheControl
    };

    if (serverSideEncryption) {
      params.ServerSideEncryption = serverSideEncryption;
    }

    if (isGzipped) {
      params.ContentEncoding = 'gzip';
    }

    return this.fetchRevisions(options)
      .then(function(revisions) {
        var found = revisions.map(function(element) { return element.revision; }).indexOf(options.revisionKey);
        if (found >= 0 && !allowOverwrite) {
          return RSVP.reject("REVISION ALREADY UPLOADED! (set `allowOverwrite: true` if you want to support overwriting revisions)");
        }
        return RSVP.resolve();
      })
      .then(readFile.bind(this, options.filePath))
      .then(function(fileContents) {
        params.Body = fileContents;
        return putObject(params).then(function() {
          plugin.log('✔  ' + revisionKey, { verbose: true });
        });
    });
  },

  activate: function(options) {
    var plugin                = this._plugin;
    var client                = this._client;
    var bucket                = options.bucket;
    var acl                   = options.acl;
    var prefix                = options.prefix;
    var filePattern           = options.filePattern;
    var key                   = filePattern + ":" + options.revisionKey;
    var serverSideEncryption  =  options.serverSideEncryption;

    var revisionKey           = joinUriSegments(prefix, key);
    var indexKey              = joinUriSegments(prefix, filePattern);
    var copySource            = encodeURIComponent([bucket, revisionKey].join('/'));
    var copyObject            = RSVP.denodeify(client.copyObject.bind(client));

    var params = {
      Bucket: bucket,
      CopySource: copySource,
      Key: indexKey,
      ACL: acl,
    };

    if (serverSideEncryption) {
      params.ServerSideEncryption = serverSideEncryption;
    }

    return this.fetchRevisions(options).then(function(revisions) {
      var found = revisions.map(function(element) { return element.revision; }).indexOf(options.revisionKey);
      if (found >= 0) {
        return copyObject(params).then(function() {
          plugin.log('✔  ' + revisionKey + " => " + indexKey);
        });
      } else {
        return RSVP.reject("REVISION NOT FOUND!"); // see how we should handle a pipeline failure
      }
    });
  },

  fetchRevisions: function(options) {
    var client         = this._client;
    var bucket         = options.bucket;
    var prefix         = options.prefix;
    var revisionPrefix = joinUriSegments(prefix, options.filePattern + ":");
    var indexKey       = joinUriSegments(prefix, options.filePattern);
    var listObjects    = RSVP.denodeify(client.listObjects.bind(client));

    return RSVP.hash({
      revisions: listObjects({ Bucket: bucket, Prefix: revisionPrefix }),
      current: headObject(client, { Bucket: bucket, Key: indexKey }),
    })
    .then(function(data) {
      return data.revisions.Contents.sort(function(a, b) {
        return new Date(b.LastModified) - new Date(a.LastModified);
      }).map(function(d) {
        var revision = d.Key.substr(revisionPrefix.length);
        var active = data.current && d.ETag === data.current.ETag;
        return { revision: revision, timestamp: d.LastModified, active: active };
      });
    });
  },
});
<commit_msg>Add support for recusively listing all revisions in a bucket<commit_after>'use strict';

var AWS             = require('aws-sdk');
var CoreObject      = require('core-object');
var RSVP            = require('rsvp');
var fs              = require('fs');
var readFile        = RSVP.denodeify(fs.readFile);
var mime            = require('mime-types');
var joinUriSegments = require('./util/join-uri-segments');

function headObject(client, params) {
  return new RSVP.Promise(function(resolve, reject) {
    client.headObject(params, function(err, data) {
      if (err && err.code === 'NotFound') {
        return resolve();
      }
      else if (err) {
        return reject(err);
      }
      else {
        return resolve(data);
      }
    });
  });
}

module.exports = CoreObject.extend({
  init: function(options) {
    this._super();
    var plugin = options.plugin;
    var config = plugin.pluginConfig;

    this._plugin = plugin;
    this._client = plugin.readConfig('s3Client') || new AWS.S3(config);
  },

  upload: function(options) {
    var client                = this._client;
    var plugin                = this._plugin;
    var bucket                = options.bucket;
    var acl                   = options.acl;
    var cacheControl          = options.cacheControl;
    var allowOverwrite        = options.allowOverwrite;
    var key                   = options.filePattern + ":" + options.revisionKey;
    var revisionKey           = joinUriSegments(options.prefix, key);
    var putObject             = RSVP.denodeify(client.putObject.bind(client));
    var gzippedFilePaths      = options.gzippedFilePaths || [];
    var isGzipped             = gzippedFilePaths.indexOf(options.filePattern) !== -1;
    var serverSideEncryption  = options.serverSideEncryption;

    var params = {
      Bucket: bucket,
      Key: revisionKey,
      ACL: acl,
      ContentType: mime.lookup(options.filePath) || 'text/html',
      CacheControl: cacheControl
    };

    if (serverSideEncryption) {
      params.ServerSideEncryption = serverSideEncryption;
    }

    if (isGzipped) {
      params.ContentEncoding = 'gzip';
    }

    return this.fetchRevisions(options)
      .then(function(revisions) {
        var found = revisions.map(function(element) { return element.revision; }).indexOf(options.revisionKey);
        if (found >= 0 && !allowOverwrite) {
          return RSVP.reject("REVISION ALREADY UPLOADED! (set `allowOverwrite: true` if you want to support overwriting revisions)");
        }
        return RSVP.resolve();
      })
      .then(readFile.bind(this, options.filePath))
      .then(function(fileContents) {
        params.Body = fileContents;
        return putObject(params).then(function() {
          plugin.log('✔  ' + revisionKey, { verbose: true });
        });
    });
  },

  activate: function(options) {
    var plugin                = this._plugin;
    var client                = this._client;
    var bucket                = options.bucket;
    var acl                   = options.acl;
    var prefix                = options.prefix;
    var filePattern           = options.filePattern;
    var key                   = filePattern + ":" + options.revisionKey;
    var serverSideEncryption  =  options.serverSideEncryption;

    var revisionKey           = joinUriSegments(prefix, key);
    var indexKey              = joinUriSegments(prefix, filePattern);
    var copySource            = encodeURIComponent([bucket, revisionKey].join('/'));
    var copyObject            = RSVP.denodeify(client.copyObject.bind(client));

    var params = {
      Bucket: bucket,
      CopySource: copySource,
      Key: indexKey,
      ACL: acl,
    };

    if (serverSideEncryption) {
      params.ServerSideEncryption = serverSideEncryption;
    }

    return this.fetchRevisions(options).then(function(revisions) {
      var found = revisions.map(function(element) { return element.revision; }).indexOf(options.revisionKey);
      if (found >= 0) {
        return copyObject(params).then(function() {
          plugin.log('✔  ' + revisionKey + " => " + indexKey);
        });
      } else {
        return RSVP.reject("REVISION NOT FOUND!"); // see how we should handle a pipeline failure
      }
    });
  },

  fetchRevisions: function(options) {
    var client         = this._client;
    var bucket         = options.bucket;
    var prefix         = options.prefix;
    var revisionPrefix = joinUriSegments(prefix, options.filePattern + ":");
    var indexKey       = joinUriSegments(prefix, options.filePattern);

    return RSVP.hash({
      revisions: this.listAllObjects({ Bucket: bucket, Prefix: revisionPrefix }),
      current: headObject(client, { Bucket: bucket, Key: indexKey }),
    })
    .then(function(data) {
      return data.revisions.All.sort(function(a, b) {
        return new Date(b.LastModified) - new Date(a.LastModified);
      }).map(function(d) {
        var revision = d.Key.substr(revisionPrefix.length);
        var active = data.current && d.ETag === data.current.ETag;
        return { revision: revision, timestamp: d.LastModified, active: active };
      });
    });
  },

  listAllObjects: function(options) {
    var client         = this._client;
    var listObjects    = RSVP.denodeify(client.listObjects.bind(client));
    var allRevisions   = [];

    return new Promise(function(resolve, reject) {
      function listObjectRecursively(options) {
        listObjects(options).then(function(response) {
          [].push.apply(allRevisions, response.Contents);

          if (response.IsTruncated) {
            var nextMarker = response.NextMarker || response.Contents[response.Contents.length - 1].Key;
            options.Marker = nextMarker;
            listObjectRecursively(options);
          } else {
            response.All = allRevisions;
            resolve(response);
          }
        }).catch(reject);
      }

      listObjectRecursively(options);
    });

  }
});
<|endoftext|>


<commit_before>var _    = require('underscore');
var jrpc = require('jsonrpc-js');
var stats = require('./stats');
var prm   = require('./primitives');

var Methods = {
  ListDBs:       'list_dbs',
  GetSchema:     'get_schema',
  Transact:      'transact',
  Cancel:        'cancel',
  Monitor:       'monitor',
  Update:        'update',
  MonitorCancel: 'monitor_cancel',
  Lock:          'lock',
  Steal:         'steal',
  Unlock:        'unlock',
  Locked:        'locked',
  Stolen:        'stolen',
  Echo:          'echo'
};

function Transaction(peer, id) {
  this.peer = peer;
  this.id   = id;
}

Transaction.prototype.cancel = function() {
  this.peer.notify('cancel', [this.id]);
};

function Monitor(peer, id) {
  this.peer = peer;
  this.id   = id;
}

Monitor.prototype.cancel = function(callback) {
  this.peer.request('monitor_cancel', [this.id], callback);
};

function Lock(peer, name) {
  this.peer = peer;
  this.name = name;
}

Lock.prototype.unlock = function(callback) {
  this.peer.request('unlock', [this.name], callback);
};

function OVSDB(socket) {
  var that = this;
  this.peer = new jrpc.Peer(socket,
    // request callback/handler
    function(msg) {
      that.recvRequest(msg);
    },
    // notification callback/handler
    function(msg) {
      that.recvNotify(msg);
    },
    // unacknowledged request expiration age
    10000,
    // low-level catastrophic failure callback/handler
    function() {
      that.destroy();
    });

  this.stats = new stats.Stats();
}

OVSDB.prototype.recvRequest = function(msg) {
  switch(msg.method) {
    case Methods.ListDBs:
      this.rx_list_dbs(msg);
      break;
    case Methods.GetSchema:
      this.rx_get_schema(msg);
      break;
    case Methods.Transact:
      this.rx_transact(msg);
      break;
    case Methods.Monitor:
      this.rx_monitor(msg);
      break;
    case Methods.Lock:
      this.rx_lock(msg);
      break;
    case Methods.Steal:
      this.rx_steal(msg);
      break;
    case Methods.Unlock:
      this.rx_unlock(msg);
      break;
    case Methods.Echo:
      this.rx_echo(msg);
      break;
    default:
      this.unknown_request(msg);
      break;
  }
};

OVSDB.prototype.recvNotify = function(msg) {
  switch(msg.method) {
    case Methods.Cancel:
      this.rx_cancel(msg);
      break;
    case Methods.Update:
      this.rx_update(msg);
      break;
    case Methods.MonitorCancel:
      this.rx_monitor_cancel(msg);
      break;
    case Methods.Locked:
      this.rx_locked(msg);
      break;
    case Methods.Stolen:
      this.rx_stolen(msg);
      break;
    default:
      this.unknown_notify(msg);
      break;
  }
};

OVSDB.prototype.destroy = function() {
};

OVSDB.prototype.timer = function() {
  this.peer.timer();
};

OVSDB.prototype.unknown_request = function(msg) {
  console.log('unknown request: '+msg);
};

OVSDB.prototype.unknown_notify = function(msg) {
  console.log('unknown notify: '+msg);
};

OVSDB.prototype.rx_list_dbs = function(msg) {
  // Update the stats
  this.stats.rcvPegs.requests++;
  this.stats.rcvPegs.req.listDB++;
};

OVSDB.prototype.rx_get_schema = function(msg) {
  // Update the stats
  this.stats.rcvPegs.requests++;
  this.stats.rcvPegs.req.getSchema++;
};

OVSDB.prototype.rx_transact = function(msg) {
  // Update the stats
  this.stats.rcvPegs.requests++;
  this.stats.rcvPegs.req.transact++;
};

OVSDB.prototype.rx_cancel = function(msg) {
  // Update the stats
  this.stats.rcvPegs.notifications++;
  this.stats.rcvPegs.req.cancel++;
};

OVSDB.prototype.rx_montior = function(msg) {
  // Update the stats
  this.stats.rcvPegs.requests++;
  this.stats.rcvPegs.req.monitor++;
};

OVSDB.prototype.rx_update = function(msg) {
  // Update the stats
  this.stats.rcvPegs.notifications++;
  this.stats.rcvPegs.req.update++;
};

OVSDB.prototype.rx_monitor_cancel = function(msg) {
  // Update the stats
  this.stats.rcvPegs.notifications++;
  this.stats.rcvPegs.req.monitor_cancel++;
};

OVSDB.prototype.rx_lock = function(msg) {
  // Update the stats
  this.stats.rcvPegs.requests++;
  this.stats.rcvPegs.req.lock++;
};

OVSDB.prototype.rx_steal = function(msg) {
  // Update the stats
  this.stats.rcvPegs.requests++;
  this.stats.rcvPegs.req.steal++;
};

OVSDB.prototype.rx_unlock = function(msg) {
  // Update the stats
  this.stats.rcvPegs.requests++;
  this.stats.rcvPegs.req.unlock++;
};

OVSDB.prototype.rx_locked = function(msg) {
  // Update the stats
  this.stats.rcvPegs.notifications++;
  this.stats.rcvPegs.req.locked++;
};

OVSDB.prototype.rx_stolen = function(msg) {
  // Update the stats
  this.stats.rcvPegs.notifications++;
  this.stats.rcvPegs.req.stolen++;
};

OVSDB.prototype.rx_echo = function(msg) {
  // Update the stats
  this.stats.rcvPegs.requests++;
  this.stats.rcvPegs.reqs.echo++;
  this.peer.response(msg.method, msg.id);
};

OVSDB.prototype.list = function(callback) {
  this.peer.request('list_dbs', [], callback);
};

OVSDB.prototype.get = function(db_name, callback) {
  this.peer.request('get_schema', [db_name], callback);
};

function mkParams(db_name, operations){
  prm.assertArray('operations', operations);
  prm.assertString('db_name', db_name);
  return _.flatten([db_name, operations], true);
}

OVSDB.prototype.transact = function(db_name, operations, callback) {
  var params = mkParams(db_name, operations);
  var msg = this.peer.request('transact', params, callback);
  return new Transaction(this.peer, msg.id);
};

OVSDB.prototype.monitor = function(db_name, reqs, callback) {
  var id = uuid.v4();
  var that = this;
  this.peer.request('monitor', [db_name, id, reqs], function(err, result) {
    if(err) {
      callback(err);
    } else {
      callback(null, new Monitor(that.peer, id));
    }
  });
};

OVSDB.prototype.lock = function(lock_name, callback) {
  var that = this;
  this.peer.request('lock', [lock_name], function(err, result) {
    if(err) {
      callback(err);
    } else {
      callback(null, new Lock(that.peer, lock_name));
    }
  });
};

OVSDB.prototype.steal = function(lock_name, callback) {
  var that = this;
  this.peer.request('steal', [lock_name], function(err, result) {
    if(err) {
      callback(err);
    } else {
      callback(null, new Lock(that.peer, lock_name));
    }
  });
};

OVSDB.prototype.echo = function() {
  var that = this;
  this.peer.request('echo', [], function(err, res) {
    if(err) {
    } else {
    }
  });
};

exports.OVSDB = OVSDB;

<commit_msg>update<commit_after>var _    = require('underscore');
var uuid = require('uuid');
var jrpc = require('jsonrpc-js');
var stats = require('./stats');
var prm   = require('./primitives');

var Methods = {
  ListDBs:       'list_dbs',
  GetSchema:     'get_schema',
  Transact:      'transact',
  Cancel:        'cancel',
  Monitor:       'monitor',
  Update:        'update',
  MonitorCancel: 'monitor_cancel',
  Lock:          'lock',
  Steal:         'steal',
  Unlock:        'unlock',
  Locked:        'locked',
  Stolen:        'stolen',
  Echo:          'echo'
};

function Transaction(peer, id) {
  this.peer = peer;
  this.id   = id;
}

Transaction.prototype.cancel = function() {
  this.peer.notify('cancel', [this.id]);
};

function Monitor(peer, id) {
  this.peer = peer;
  this.id   = id;
}

Monitor.prototype.cancel = function(callback) {
  this.peer.request('monitor_cancel', [this.id], callback);
};

function Lock(peer, name) {
  this.peer = peer;
  this.name = name;
}

Lock.prototype.unlock = function(callback) {
  this.peer.request('unlock', [this.name], callback);
};

function OVSDB(socket) {
  var that = this;
  this.peer = new jrpc.Peer(socket,
    // request callback/handler
    function(msg) {
      that.recvRequest(msg);
    },
    // notification callback/handler
    function(msg) {
      that.recvNotify(msg);
    },
    // unacknowledged request expiration age
    10000,
    // low-level catastrophic failure callback/handler
    function() {
      that.destroy();
    });

  this.stats = new stats.Stats();
}

OVSDB.prototype.recvRequest = function(msg) {
  switch(msg.method) {
    case Methods.ListDBs:
      this.rx_list_dbs(msg);
      break;
    case Methods.GetSchema:
      this.rx_get_schema(msg);
      break;
    case Methods.Transact:
      this.rx_transact(msg);
      break;
    case Methods.Monitor:
      this.rx_monitor(msg);
      break;
    case Methods.Lock:
      this.rx_lock(msg);
      break;
    case Methods.Steal:
      this.rx_steal(msg);
      break;
    case Methods.Unlock:
      this.rx_unlock(msg);
      break;
    case Methods.Echo:
      this.rx_echo(msg);
      break;
    default:
      this.unknown_request(msg);
      break;
  }
};

OVSDB.prototype.recvNotify = function(msg) {
  switch(msg.method) {
    case Methods.Cancel:
      this.rx_cancel(msg);
      break;
    case Methods.Update:
      this.rx_update(msg);
      break;
    case Methods.MonitorCancel:
      this.rx_monitor_cancel(msg);
      break;
    case Methods.Locked:
      this.rx_locked(msg);
      break;
    case Methods.Stolen:
      this.rx_stolen(msg);
      break;
    default:
      this.unknown_notify(msg);
      break;
  }
};

OVSDB.prototype.destroy = function() {
};

OVSDB.prototype.timer = function() {
  this.peer.timer();
};

OVSDB.prototype.unknown_request = function(msg) {
  console.log('unknown request: '+msg);
};

OVSDB.prototype.unknown_notify = function(msg) {
  console.log('unknown notify: '+msg);
};

OVSDB.prototype.rx_list_dbs = function(msg) {
  // Update the stats
  this.stats.rcvPegs.requests++;
  this.stats.rcvPegs.req.listDB++;
};

OVSDB.prototype.rx_get_schema = function(msg) {
  // Update the stats
  this.stats.rcvPegs.requests++;
  this.stats.rcvPegs.req.getSchema++;
};

OVSDB.prototype.rx_transact = function(msg) {
  // Update the stats
  this.stats.rcvPegs.requests++;
  this.stats.rcvPegs.req.transact++;
};

OVSDB.prototype.rx_cancel = function(msg) {
  // Update the stats
  this.stats.rcvPegs.notifications++;
  this.stats.rcvPegs.req.cancel++;
};

OVSDB.prototype.rx_montior = function(msg) {
  // Update the stats
  this.stats.rcvPegs.requests++;
  this.stats.rcvPegs.req.monitor++;
};

OVSDB.prototype.rx_update = function(msg) {
  // Update the stats
  this.stats.rcvPegs.notifications++;
  this.stats.rcvPegs.req.update++;
};

OVSDB.prototype.rx_monitor_cancel = function(msg) {
  // Update the stats
  this.stats.rcvPegs.notifications++;
  this.stats.rcvPegs.req.monitor_cancel++;
};

OVSDB.prototype.rx_lock = function(msg) {
  // Update the stats
  this.stats.rcvPegs.requests++;
  this.stats.rcvPegs.req.lock++;
};

OVSDB.prototype.rx_steal = function(msg) {
  // Update the stats
  this.stats.rcvPegs.requests++;
  this.stats.rcvPegs.req.steal++;
};

OVSDB.prototype.rx_unlock = function(msg) {
  // Update the stats
  this.stats.rcvPegs.requests++;
  this.stats.rcvPegs.req.unlock++;
};

OVSDB.prototype.rx_locked = function(msg) {
  // Update the stats
  this.stats.rcvPegs.notifications++;
  this.stats.rcvPegs.req.locked++;
};

OVSDB.prototype.rx_stolen = function(msg) {
  // Update the stats
  this.stats.rcvPegs.notifications++;
  this.stats.rcvPegs.req.stolen++;
};

OVSDB.prototype.rx_echo = function(msg) {
  // Update the stats
  this.stats.rcvPegs.requests++;
  this.stats.rcvPegs.reqs.echo++;
  this.peer.response(msg.method, msg.id);
};

OVSDB.prototype.list = function(callback) {
  this.peer.request('list_dbs', [], callback);
};

OVSDB.prototype.get = function(db_name, callback) {
  this.peer.request('get_schema', [db_name], callback);
};

function mkParams(db_name, operations){
  prm.assertArray('operations', operations);
  prm.assertString('db_name', db_name);
  return _.flatten([db_name, operations], true);
}

OVSDB.prototype.transact = function(db_name, operations, callback) {
  var params = mkParams(db_name, operations);
  var msg = this.peer.request('transact', params, callback);
  return new Transaction(this.peer, msg.id);
};

OVSDB.prototype.monitor = function(db_name, reqs, callback) {
  var id = uuid.v4();
  var that = this;
  this.peer.request('monitor', [db_name, id, reqs], function(err, result) {
    if(err) {
      callback(err);
    } else {
      callback(null, new Monitor(that.peer, id));
    }
  });
};

OVSDB.prototype.lock = function(lock_name, callback) {
  var that = this;
  this.peer.request('lock', [lock_name], function(err, result) {
    if(err) {
      callback(err);
    } else {
      callback(null, new Lock(that.peer, lock_name));
    }
  });
};

OVSDB.prototype.steal = function(lock_name, callback) {
  var that = this;
  this.peer.request('steal', [lock_name], function(err, result) {
    if(err) {
      callback(err);
    } else {
      callback(null, new Lock(that.peer, lock_name));
    }
  });
};

OVSDB.prototype.echo = function() {
  var that = this;
  this.peer.request('echo', [], function(err, res) {
    if(err) {
    } else {
    }
  });
};

exports.OVSDB = OVSDB;

<|endoftext|>


<commit_before>var dgram = require('dgram');

var Artnet = function (config) {

    if (!(this instanceof Artnet)) return new Artnet(config);

    var that = this;

    config = config || {};

    var host =     config.host                    || '255.255.255.255';
    var port =     parseInt(config.port)          || 6454;
    var refresh =  parseInt(config.refresh)       || 4000;

    var socket = dgram.createSocket("udp4");

    if (config.iface) {
        socket.bind(port, config.iface);
    }

    // index of the following arrays is the universe
    var data =          [];     // the 512 dmx channels
    var interval =      [];     // the intervals for the 4sec refresh
    var sendThrottle =  [];     // the timeouts
    var sendDelayed =   [];     // boolean flag indicating if data should be sent after sendThrottle timeout
    var dataChanged =   [];     // the highest channel number that had a change. mind that channel counting starts at 1!

    var startRefresh = function (universe) {
        interval[universe] = setInterval(function () {
            that.send(universe, 512);
        }, refresh);
    };

    // see http://www.artisticlicence.com/WebSiteMaster/User%20Guides/art-net.pdf page 45
    var artdmxPackage = function (universe, length) {

        length = parseInt(length, 10) || 2;
        if (length % 2) length += 1;

        var hUni = (universe >> 8) & 0xff;
        var lUni = universe & 0xff;

        var hLen = (length >> 8) & 0xff;
        var lLen = (length & 0xff);

        var header =   [65, 114, 116, 45, 78, 101, 116, 0, 0, 80, 0, 14, 0, 0, lUni, hUni, hLen, lLen];

        if (!data[universe]) data[universe] = Array.apply(null, new Array(512)).map(function () { return null; }, 0);
        return new Buffer(header.concat(data[universe].slice(0, (hLen * 256) + lLen)));

    };

    // if refresh is set to true all 512 channels will be sent, otherwise from channel 1 to the last changed channel
    this.send = function (universe, refresh, callback) {
        if (typeof refresh === 'function') {
            callback = refresh;
            refresh = false;
        }

        if (!interval[universe]) startRefresh(universe);

        if (sendThrottle[universe]) {
            sendDelayed[universe] = true;
            return;
        }

        clearTimeout(sendThrottle[universe]);
        sendThrottle[universe] = setTimeout(function () {
            sendThrottle[universe] = null;
            if (sendDelayed[universe]) {
                sendDelayed[universe] = false;
                that.send(universe, callback);
            }
        }, 25);

        var buf = artdmxPackage(universe, refresh ? 512 : dataChanged[universe]);
        dataChanged[universe] = 0;
        socket.send(buf, 0, buf.length, port, host, callback);
    };

    /* [ [ uint15 universe, ] uint9 channel, ] uint8 value [, function callback ] */
    /* [ [ uint15 universe, ] uint9 channel, ] array[uint8] values [, function callback ] */
    this.set = function () {

        var universe;
        var channel;
        var value;
        var callback;

        if (arguments.length === 4) {
            universe =      arguments[0];
            channel =       arguments[1];
            value =         arguments[2];
            callback =      arguments[3];
        } else if (arguments.length === 3) {
            if (typeof arguments[2] === 'function') {
                channel =   arguments[0];
                value =     arguments[1];
                callback =  arguments[2];
            } else {
                universe =  arguments[0];
                channel =   arguments[1];
                value =     arguments[2];
            }
        } else if (arguments.length === 2) {
            if (typeof arguments[1] === 'function') {
                channel =   1;
                value =     arguments[0];
                callback =  arguments[1];
            } else {
                channel =   arguments[0];
                value =     arguments[1];
            }
        } else if (arguments.length === 1) {
            channel =       1;
            value =         arguments[0];
        } else {
            return false;
        }

        universe = parseInt(universe, 10) || 0;

        //console.log('set universe=' + universe, 'channel=' + channel, 'value=' + value, 'typeof callback=' + typeof callback);

        if (!data[universe]) data[universe] = Array.apply(null, new Array(512)).map(function () { return null; }, 0);

        dataChanged[universe] = dataChanged[universe] || 0;

        var index;
        if (typeof value === 'object' && value.length) {
            for (var i = 0; i < value.length; i++) {
                index = channel + i - 1;
                if (typeof value[i] === 'number' && data[universe][index] !== value[i]) {
                    data[universe][index] = value[i];
                    if ((index + 1) > dataChanged[universe]) dataChanged[universe] = index + 1;
                }
            }
        } else {
            if (typeof value === 'number' && data[universe][channel - 1] !== value) {
                data[universe][channel - 1] = value;
                if (channel > dataChanged[universe]) dataChanged[universe] = channel;
            }
        }

        if (dataChanged[universe]) {
            that.send(universe, callback);
        } else {
            if (typeof callback === 'function') callback(null, null);
        }

        return true;
    };

    this.close = function () {
        for (var i = 0; i < interval.length; i++)
        clearInterval(interval[i]);
        socket.close();
    };

};

module.exports = Artnet;
<commit_msg>add host setting<commit_after>var dgram = require('dgram');

var Artnet = function (config) {

    if (!(this instanceof Artnet)) return new Artnet(config);

    var that = this;

    config = config || {};

    var host =     config.host                    || '255.255.255.255';
    var port =     parseInt(config.port)          || 6454;
    var refresh =  parseInt(config.refresh)       || 4000;

    var socket = dgram.createSocket("udp4");

    if (config.iface) {
        socket.bind(port, config.iface);
    }

    // index of the following arrays is the universe
    var data =          [];     // the 512 dmx channels
    var interval =      [];     // the intervals for the 4sec refresh
    var sendThrottle =  [];     // the timeouts
    var sendDelayed =   [];     // boolean flag indicating if data should be sent after sendThrottle timeout
    var dataChanged =   [];     // the highest channel number that had a change. mind that channel counting starts at 1!

    var startRefresh = function (universe) {
        interval[universe] = setInterval(function () {
            that.send(universe, 512);
        }, refresh);
    };

    // see http://www.artisticlicence.com/WebSiteMaster/User%20Guides/art-net.pdf page 45
    var artdmxPackage = function (universe, length) {

        length = parseInt(length, 10) || 2;
        if (length % 2) length += 1;

        var hUni = (universe >> 8) & 0xff;
        var lUni = universe & 0xff;

        var hLen = (length >> 8) & 0xff;
        var lLen = (length & 0xff);

        var header =   [65, 114, 116, 45, 78, 101, 116, 0, 0, 80, 0, 14, 0, 0, lUni, hUni, hLen, lLen];

        if (!data[universe]) data[universe] = Array.apply(null, new Array(512)).map(function () { return null; }, 0);
        return new Buffer(header.concat(data[universe].slice(0, (hLen * 256) + lLen)));

    };

    // if refresh is set to true all 512 channels will be sent, otherwise from channel 1 to the last changed channel
    this.send = function (universe, refresh, callback) {
        if (typeof refresh === 'function') {
            callback = refresh;
            refresh = false;
        }

        if (!interval[universe]) startRefresh(universe);

        if (sendThrottle[universe]) {
            sendDelayed[universe] = true;
            return;
        }

        clearTimeout(sendThrottle[universe]);
        sendThrottle[universe] = setTimeout(function () {
            sendThrottle[universe] = null;
            if (sendDelayed[universe]) {
                sendDelayed[universe] = false;
                that.send(universe, callback);
            }
        }, 25);

        var buf = artdmxPackage(universe, refresh ? 512 : dataChanged[universe]);
        dataChanged[universe] = 0;
        socket.send(buf, 0, buf.length, port, host, callback);
    };

    /* [ [ uint15 universe, ] uint9 channel, ] uint8 value [, function callback ] */
    /* [ [ uint15 universe, ] uint9 channel, ] array[uint8] values [, function callback ] */
    this.set = function () {

        var universe;
        var channel;
        var value;
        var callback;

        if (arguments.length === 4) {
            universe =      arguments[0];
            channel =       arguments[1];
            value =         arguments[2];
            callback =      arguments[3];
        } else if (arguments.length === 3) {
            if (typeof arguments[2] === 'function') {
                channel =   arguments[0];
                value =     arguments[1];
                callback =  arguments[2];
            } else {
                universe =  arguments[0];
                channel =   arguments[1];
                value =     arguments[2];
            }
        } else if (arguments.length === 2) {
            if (typeof arguments[1] === 'function') {
                channel =   1;
                value =     arguments[0];
                callback =  arguments[1];
            } else {
                channel =   arguments[0];
                value =     arguments[1];
            }
        } else if (arguments.length === 1) {
            channel =       1;
            value =         arguments[0];
        } else {
            return false;
        }

        universe = parseInt(universe, 10) || 0;

        //console.log('set universe=' + universe, 'channel=' + channel, 'value=' + value, 'typeof callback=' + typeof callback);

        if (!data[universe]) data[universe] = Array.apply(null, new Array(512)).map(function () { return null; }, 0);

        dataChanged[universe] = dataChanged[universe] || 0;

        var index;
        if (typeof value === 'object' && value.length) {
            for (var i = 0; i < value.length; i++) {
                index = channel + i - 1;
                if (typeof value[i] === 'number' && data[universe][index] !== value[i]) {
                    data[universe][index] = value[i];
                    if ((index + 1) > dataChanged[universe]) dataChanged[universe] = index + 1;
                }
            }
        } else {
            if (typeof value === 'number' && data[universe][channel - 1] !== value) {
                data[universe][channel - 1] = value;
                if (channel > dataChanged[universe]) dataChanged[universe] = channel;
            }
        }

        if (dataChanged[universe]) {
            that.send(universe, callback);
        } else {
            if (typeof callback === 'function') callback(null, null);
        }

        return true;
    };

    this.close = function () {
        for (var i = 0; i < interval.length; i++)
        clearInterval(interval[i]);
        socket.close();
    };

    this.setHost = function () {
        host = arguments[0];
    };

};

module.exports = Artnet;
<|endoftext|>


<commit_before>var path = require('path');
var minimatch = require('minimatch');
var _ = require('underscore');
var glob = require('glob');
var fs = require('fs');
var aggre = require('akostream').aggre;
var mkdirp = require('mkdirp');
var File = require('./file.js');

// file path src
function FileSystem(base) {
	this.base = base;
	this.files = {};
	this.fileStack = [];

	process.nextTick(_.bind(function() {
		this.refresh();
	}, this));
}

FileSystem.prototype.pathResolve = function(filepath, dir) {
	var first = filepath[0],
		result;

	if(dir) {
		dir = this.pathResolve(dir);
	} else {
		dir = this.base
	}

	// 相对于dir的相对路径
	if(first == '.') {
		result = path.resolve(dir, filepath);
	} else if(first == path.sep) {
		result = path.resolve('/', filepath);
	} else if(first == '~') {
		filepath = filepath.replace(/^~+/, '');
		result = path.resolve(path.join(this.base, filepath));
	} else {
		result = path.resolve(dir, filepath);
	}

	result = result.split(path.sep).join('/');
	result = path.normalize(result);
	return result; // unix style
};

FileSystem.prototype.createReadStream = function(filePath, option) {
	option || (option = {});
	filePath = this.pathResolve(filePath);
	if(!this.existFile(filePath)) {
		throw new Error('file not exists: '+filePath);
	}
	var ReadStream = require('./ReadStream.js');
	var readStream = new ReadStream(filePath, {encoding: option.encoding});

	return readStream.toStream();
};

FileSystem.prototype.mkdir = function(dir) {
	dir = this.pathResolve(dir);
	mkdirp.sync(dir);
};

FileSystem.prototype.rm = function(dir) {
	dir = this.pathResolve(dir);
	if(fs.existsSync(dir)) {
		var stat = fs.statSync(dir);
		if(stat.isDirectory()) {
			var contain = fs.readdirSync(dir);
			_.each(contain, function(file) {
				this.rm(this.pathResolve(file, dir));
			}, this);
			fs.rmdirSync(dir);
		} else {
			fs.unlinkSync(dir);
		}
	}
};

FileSystem.prototype.createWriteStream = function(filePath, option) {
	option || (option = {});
	filePath = this.pathResolve(filePath);
	var dir = path.dirname(filePath);
	if(option.focus) {
		this.mkdir(dir);
	} else {
		if(!fs.existsSync(dir)) {
			throw new Error('fail to open '+dir);
		}
	}
	return fs.createWriteStream(filePath, option);
};

FileSystem.prototype.readFile = function(filePath, callback) {
	var buffer;
	var stream;

	try {
		stream = this.createReadStream(filePath);
		aggre().on('data', function(chunk) {
			buffer = chunk;
		}).on('end', function() {
			callback(null, buffer || new Buffer(0));
		});
	} catch(e) {
		callback(e);
	}
};

FileSystem.prototype.search = function(search, option) {
	option || (option = {});
	search = this.pathResolve(search);
	var result = minimatch.match(this.fileStack, search, {});

	if(!option.notFs) {
		var real = glob.sync(search);

		result = _.uniq(result.concat(real));
	}

	return result;
};

FileSystem.prototype.searchStack = function() {
	var stack = {};
	var files = _.keys(this.files);
	_.each(files, function(file) {
		stack[file] = true;
		while(true) {
			var dir = path.dirname(file);
			if(stack[dir]) break;
			if(dir == '/') break;
			if(dir == this.base) break;
			stack[dir] = true;
			file = dir;
		}
	}, this);
	this.fileStack = _.keys(stack);
};

FileSystem.prototype.register = function(file, opt) {
	if(_.has(this.files, file)) {
		// warn
		throw new Error('Register override:'+file);
	}

	this.files[file] = _.extend(opt, {dest: file});
};

FileSystem.prototype.refresh = function() {
	this.files = {};
	this.fileStack = [];
	File.setup();
	this.searchStack();
};

FileSystem.prototype.readDir = function(dirPath) {
	dirPath = this.pathResolve(dirPath);
	var result = this.search(path.join(dirPath, '*'));

	result = result.map(function(file) {
		return path.relative(dirPath, file);
	});
	return result;
};

FileSystem.prototype.existFile = function(filePath, option) {
	option || (option = {});

	filePath = this.pathResolve(filePath);

	if(filePath in this.files) {
		return true;
	} else {
		if(!option.notFs) {
			filePath = this.pathResolve(filePath);
			if(fs.existsSync(filePath)) {
				var stat = fs.statSync(filePath);

				return stat.isFile();
			}
		}
	}
	return false;
};

module.exports = FileSystem;<commit_msg>bug fixed<commit_after>var path = require('path');
var minimatch = require('minimatch');
var _ = require('underscore');
var glob = require('glob');
var fs = require('fs');
var aggre = require('akostream').aggre;
var mkdirp = require('mkdirp');
var File = require('./file.js');

// file path src
function FileSystem(base) {
	this.base = base;
	this.files = {};
	this.fileStack = [];

	process.nextTick(_.bind(function() {
		this.refresh();
	}, this));
}

FileSystem.prototype.pathResolve = function(filepath, dir) {
	var first = filepath[0],
		result;

	if(dir) {
		dir = this.pathResolve(dir);
	} else {
		dir = this.base
	}

	// 相对于dir的相对路径
	if(first == '.') {
		result = path.resolve(dir, filepath);
	} else if(first == path.sep) {
		result = path.resolve('/', filepath);
	} else if(first == '~') {
		filepath = filepath.replace(/^~+/, '');
		result = path.resolve(path.join(this.base, filepath));
	} else {
		result = path.resolve(dir, filepath);
	}

	result = result.split(path.sep).join('/');
	result = path.normalize(result);
	return result; // unix style
};

FileSystem.prototype.createReadStream = function(filePath, option) {
	option || (option = {});
	filePath = this.pathResolve(filePath);
	if(!this.existFile(filePath)) {
		throw new Error('file not exists: '+filePath);
	}
	var ReadStream = require('./ReadStream.js');
	var readStream = new ReadStream(filePath, {encoding: option.encoding});

	return readStream.toStream();
};

FileSystem.prototype.mkdir = function(dir) {
	dir = this.pathResolve(dir);
	mkdirp.sync(dir);
};

FileSystem.prototype.rm = function(dir) {
	dir = this.pathResolve(dir);
	if(fs.existsSync(dir)) {
		var stat = fs.statSync(dir);
		if(stat.isDirectory()) {
			var contain = fs.readdirSync(dir);
			_.each(contain, function(file) {
				this.rm(this.pathResolve(file, dir));
			}, this);
			fs.rmdirSync(dir);
		} else {
			fs.unlinkSync(dir);
		}
	}
};

FileSystem.prototype.createWriteStream = function(filePath, option) {
	option || (option = {});
	filePath = this.pathResolve(filePath);
	var dir = path.dirname(filePath);
	if(option.focus) {
		this.mkdir(dir);
	} else {
		if(!fs.existsSync(dir)) {
			throw new Error('fail to open '+dir);
		}
	}
	return fs.createWriteStream(filePath, option);
};

FileSystem.prototype.readFile = function(filePath, callback) {
	var buffer;
	var stream;

	try {
		stream = this.createReadStream(filePath);
		aggre(stream).on('data', function(chunk) {
			buffer = chunk;
		}).on('end', function() {
			callback(null, buffer || new Buffer(0));
		});
	} catch(e) {
		callback(e);
	}
};

FileSystem.prototype.search = function(search, option) {
	option || (option = {});
	search = this.pathResolve(search);
	var result = minimatch.match(this.fileStack, search, {});

	if(!option.notFs) {
		var real = glob.sync(search);

		result = _.uniq(result.concat(real));
	}

	return result;
};

FileSystem.prototype.searchStack = function() {
	var stack = {};
	var files = _.keys(this.files);
	_.each(files, function(file) {
		stack[file] = true;
		while(true) {
			var dir = path.dirname(file);
			if(stack[dir]) break;
			if(dir == '/') break;
			if(dir == this.base) break;
			stack[dir] = true;
			file = dir;
		}
	}, this);
	this.fileStack = _.keys(stack);
};

FileSystem.prototype.register = function(file, opt) {
	if(_.has(this.files, file)) {
		// warn
		throw new Error('Register override:'+file);
	}

	this.files[file] = _.extend(opt, {dest: file});
};

FileSystem.prototype.refresh = function() {
	this.files = {};
	this.fileStack = [];
	File.setup();
	this.searchStack();
};

FileSystem.prototype.readDir = function(dirPath) {
	dirPath = this.pathResolve(dirPath);
	var result = this.search(path.join(dirPath, '*'));

	result = result.map(function(file) {
		return path.relative(dirPath, file);
	});
	return result;
};

FileSystem.prototype.existFile = function(filePath, option) {
	option || (option = {});

	filePath = this.pathResolve(filePath);

	if(filePath in this.files) {
		return true;
	} else {
		if(!option.notFs) {
			filePath = this.pathResolve(filePath);
			if(fs.existsSync(filePath)) {
				var stat = fs.statSync(filePath);

				return stat.isFile();
			}
		}
	}
	return false;
};

module.exports = FileSystem;<|endoftext|>