<commit_before>// jshint ignore:start
var bidfactory = require('../bidfactory.js');
var bidmanager = require('../bidmanager.js');
var adloader = require('../adloader');

/**
 * Adapter for requesting bids from OpenX.
 *
 * @param {Object} options - Configuration options for OpenX
 * @param {string} options.pageURL - Current page URL to send with bid request
 * @param {string} options.refererURL - Referer URL to send with bid request
 *
 * @returns {{callBids: _callBids}}
 * @constructor
 */
var OpenxAdapter = function OpenxAdapter(options) {

  var opts = options || {};
  var scriptUrl;
  var bids;

  function _callBids(params) {
    bids = params.bids || [];
    for (var i = 0; i < bids.length; i++) {
      var bid = bids[i];

      //load page options from bid request
      if (bid.params.pageURL) {
        opts.pageURL = bid.params.pageURL;
      }

      if (bid.params.refererURL) {
        opts.refererURL = bid.params.refererURL;
      }

      if (bid.params.jstag_url) {
        scriptUrl = bid.params.jstag_url;
      }

      if (bid.params.pgid) {
        opts.pgid = bid.params.pgid;
      }
    }

    _requestBids();
  }

  function _requestBids() {

    if (scriptUrl) {
      adloader.loadScript(scriptUrl, function () {
        var i;
        var POX = OX();

        if (opts.pageURL) {
          POX.setPageURL(opts.pageURL);
        }

        if (opts.refererURL) {
          POX.setRefererURL(opts.refererURL);
        }

        if (opts.pgid) {
          POX.addPage(opts.pgid);
        }

        // Add each ad unit ID
        for (i = 0; i < bids.length; i++) {
          POX.addAdUnit(bids[i].params.unit);
        }

        POX.addHook(function (response) {
          var i;
          var bid;
          var adUnit;
          var adResponse;

          // Map each bid to its response
          for (i = 0; i < bids.length; i++) {
            bid = bids[i];

            // Get ad response
            adUnit = response.getOrCreateAdUnit(bid.params.unit);

            // If 'pub_rev' (CPM) isn't returned we got an empty response
            if (adUnit.get('pub_rev')) {
              adResponse = adResponse = bidfactory.createBid(1);

              adResponse.bidderCode = 'openx';
              adResponse.ad_id = adUnit.get('ad_id');
              adResponse.cpm = Number(adUnit.get('pub_rev')) / 1000;

              adResponse.ad = adUnit.get('html');
              adResponse.dealId = adUnit.get('auct_win_is_deal');

              // Add record/impression pixel to the creative HTML
              var recordPixel = OX.utils.template(response.getRecordTemplate(), {
                medium: OX.utils.getMedium(),
                rtype: OX.Resources.RI,
                txn_state: adUnit.get('ts')
              });
              adResponse.ad += '

';

              adResponse.adUrl = adUnit.get('ad_url');
              adResponse.width = adUnit.get('width');
              adResponse.height = adUnit.get('height');

              bidmanager.addBidResponse(bid.placementCode, adResponse);
            } else {
              // Indicate an ad was not returned
              adResponse = bidfactory.createBid(2);
              adResponse.bidderCode = 'openx';
              bidmanager.addBidResponse(bid.placementCode, adResponse);
            }
          }
        }, OX.Hooks.ON_AD_RESPONSE);

        // Make request
        POX.load();
      }, true);
    }
  }

  return {
    callBids: _callBids
  };
};

module.exports = OpenxAdapter;
<commit_msg>Openx adaptor deal update (#612)<commit_after>// jshint ignore:start
var bidfactory = require('../bidfactory.js');
var bidmanager = require('../bidmanager.js');
var adloader = require('../adloader');

/**
 * Adapter for requesting bids from OpenX.
 *
 * @param {Object} options - Configuration options for OpenX
 * @param {string} options.pageURL - Current page URL to send with bid request
 * @param {string} options.refererURL - Referer URL to send with bid request
 *
 * @returns {{callBids: _callBids}}
 * @constructor
 */
var OpenxAdapter = function OpenxAdapter(options) {

  var opts = options || {};
  var scriptUrl;
  var bids;

  function _callBids(params) {
    bids = params.bids || [];
    for (var i = 0; i < bids.length; i++) {
      var bid = bids[i];

      //load page options from bid request
      if (bid.params.pageURL) {
        opts.pageURL = bid.params.pageURL;
      }

      if (bid.params.refererURL) {
        opts.refererURL = bid.params.refererURL;
      }

      if (bid.params.jstag_url) {
        scriptUrl = bid.params.jstag_url;
      }

      if (bid.params.pgid) {
        opts.pgid = bid.params.pgid;
      }
    }

    _requestBids();
  }

  function _requestBids() {

    if (scriptUrl) {
      adloader.loadScript(scriptUrl, function () {
        var i;
        var POX = OX();

        if (opts.pageURL) {
          POX.setPageURL(opts.pageURL);
        }

        if (opts.refererURL) {
          POX.setRefererURL(opts.refererURL);
        }

        if (opts.pgid) {
          POX.addPage(opts.pgid);
        }

        // Add each ad unit ID
        for (i = 0; i < bids.length; i++) {
          POX.addAdUnit(bids[i].params.unit);
        }

        POX.addHook(function (response) {
          var i;
          var bid;
          var adUnit;
          var adResponse;

          // Map each bid to its response
          for (i = 0; i < bids.length; i++) {
            bid = bids[i];

            // Get ad response
            adUnit = response.getOrCreateAdUnit(bid.params.unit);
            // If 'pub_rev' (CPM) isn't returned we got an empty response
            if (adUnit.get('pub_rev')) {
              adResponse = adResponse = bidfactory.createBid(1);

              adResponse.bidderCode = 'openx';
              adResponse.ad_id = adUnit.get('ad_id');
              adResponse.cpm = Number(adUnit.get('pub_rev')) / 1000;

              adResponse.ad = adUnit.get('html');
              if(adUnit.get('deal_id') !== undefined) {
                adResponse.dealId = adUnit.get('deal_id');
              }

              // Add record/impression pixel to the creative HTML
              var recordPixel = OX.utils.template(response.getRecordTemplate(), {
                medium: OX.utils.getMedium(),
                rtype: OX.Resources.RI,
                txn_state: adUnit.get('ts')
              });
              adResponse.ad += '

';

              adResponse.adUrl = adUnit.get('ad_url');
              adResponse.width = adUnit.get('width');
              adResponse.height = adUnit.get('height');

              bidmanager.addBidResponse(bid.placementCode, adResponse);
            } else {
              // Indicate an ad was not returned
              adResponse = bidfactory.createBid(2);
              adResponse.bidderCode = 'openx';
              bidmanager.addBidResponse(bid.placementCode, adResponse);
            }
          }
        }, OX.Hooks.ON_AD_RESPONSE);

        // Make request
        POX.load();
      }, true);
    }
  }

  return {
    callBids: _callBids
  };
};

module.exports = OpenxAdapter;
<|endoftext|>


<commit_before>/**
 * Copyright 2015 CANAL+ Group
 *
 * 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.
 */

import { Subscription } from "rxjs/Subscription";
import { BehaviorSubject } from "rxjs/BehaviorSubject";
import { combineLatest } from "rxjs/observable/combineLatest";
import { only } from "../utils/rx-utils";
import { findBetterMatchIndex } from "../utils/languages";

import AverageBitrate from "./average-bitrate";

const DEFAULTS = {
  defaultAudioTrack: {
    language: "fra",
    audioDescription: false,
  },
  defaultTextTrack: null,
  // default buffer size in seconds
  defaultBufferSize: 30,
  // buffer threshold ratio used as a lower bound
  // margin to find the suitable representation
  defaultBufferThreshold: 0.3,
  initialVideoBitrate: 0,
  initialAudioBitrate: 0,
  maxVideoBitrate: Infinity,
  maxAudioBitrate: Infinity,
};

/**
 * Simple find function implementation.
 * @param {Array} array
 * @param {Function} predicate - The predicate. Will take as arguments:
 *   1. the current array element
 *   2. the array index
 *   3. the entire array
 * @returns {*} - null if not found
 */
function find(array, predicate) {
  for (let i = 0; i < array.length; i++) {
    if (predicate(array[i], i, array) === true) {
      return array[i];
    }
  }
  return null;
}

/**
 * Returns val if x is either not a Number type or inferior or equal to 0.
 * @param {Number} [x]
 * @param {*} val
 * @returns {*}
 */
function def(x, val) {
  return typeof x == "number" && x > 0 ? x : val;
}

/**
 * Get closest bitrate lower or equal to the bitrate wanted when the threshold
 * is equal to 0. You can add a security margin by setting the threshold between
 * 0 and 1.
 * @param {Array.<Number>} bitrates - all available bitrates.
 * @param {Number} btr - a chosen bitrate
 * @param {Number} [treshold=0]
 * @returns {Number}
 */
function getClosestBitrate(bitrates, btr, threshold=0) {
  for (let i = bitrates.length - 1; i >= 0; i--) {
    if ((bitrates[i] / btr) <= (1 - threshold)) {
      return bitrates[i];
    }
  }
  return bitrates[0];
}

/**
 * Get the highest bitrate from the representations having a width immediately
 * superior or equal to the given one.
 * @param {Array.<Object>} representations - The representations array
 * @param {Number} width
 * @returns {Number}
 */
function getMaxUsefulBitrateforWidth(representations, width) {
  const sortedRepsByWidth = representations.sort((a, b) => a.width - b.width);
  const maxWidth = sortedRepsByWidth.find(r => r.width >= width);

  if (maxWidth) {
    const filteredAdaptations = representations.filter(r => r.width <= maxWidth);
    if (filteredAdaptations.length) {
      return filteredAdaptations[filteredAdaptations.length - 1].bitrate;
    } else {
      return representations[0];
    }
  }

  return Infinity;
}

/**
 * Find first adaptation with the corresponding language.
 * @param {Array.<Object>} adaptations
 * @param {string} language
 * @returns {Object|null}
 */
function findAdaptationByLang(adaptations, language) {
  const languages = adaptations.map(a => a.language);

  const index = findBetterMatchIndex(languages, language);
  if (index >= 0) {
    return adaptations[index];
  }
  return null;
}

/**
 * @param {Array.<Object>} adaptations
 * @param {string} language
 * @param {Boolean} [audioDescription=false]
 * @returns {Object|null}
 */
function findAudioAdaptation(adaptations, language, audioDescription = false) {
  const filteredAdaptations = adaptations.filter(adaptation =>
    adaptation.isAudioDescription == audioDescription
  );
  return findAdaptationByLang(filteredAdaptations, language);
}

/**
 * @param {Array.<Object>} adaptations
 * @param {string} language
 * @param {Boolean} [closedCaption=false]
 * @returns {Object|null}
 */
function findTextAdaptation(adaptations, language, closedCaption = false) {
  const filteredAdaptations = adaptations.filter(adaptation =>
    adaptation.isClosedCaption == closedCaption
  );
  return findAdaptationByLang(filteredAdaptations, language);
}

/**
 * Filter the given observable/array to only keep the item with the selected
 * type.
 * @param {Observable|Array.<Object>} stream
 * @param {string} selectedType
 * @returns {Observable|Array.<Object>}
 */
function filterByType(stream, selectedType) {
  return stream.filter(({ type }) => type === selectedType);
}

export default function(metrics, deviceEvents, options={}) {
  Object.keys(options).forEach(key =>
    options[key] === undefined && delete options[key]
  );

  const {
    defaultAudioTrack,
    defaultTextTrack,
    defaultBufferSize,
    defaultBufferThreshold,
    initialVideoBitrate,
    initialAudioBitrate,
    maxVideoBitrate,
    maxAudioBitrate,
    limitVideoWidth,
    throttleWhenHidden,
  } = Object.assign({}, DEFAULTS, options);

  const { videoWidth, inBackground } = deviceEvents;

  const $languages = new BehaviorSubject(defaultAudioTrack);
  const $subtitles = new BehaviorSubject(defaultTextTrack);

  const $averageBitrates = {
    audio: new BehaviorSubject(initialAudioBitrate),
    video: new BehaviorSubject(initialVideoBitrate),
  };

  const averageBitratesConns = [
    AverageBitrate(filterByType(metrics, "audio"), { alpha: 0.6 })
      .multicast($averageBitrates.audio),
    AverageBitrate(filterByType(metrics, "video"), { alpha: 0.6 })
      .multicast($averageBitrates.video),
  ];

  let conns = new Subscription();
  averageBitratesConns.forEach((a) => conns.add(a.connect()));

  const $usrBitrates = {
    audio: new BehaviorSubject(Infinity),
    video: new BehaviorSubject(Infinity),
  };

  const $maxBitrates = {
    audio: new BehaviorSubject(maxAudioBitrate),
    video: new BehaviorSubject(maxVideoBitrate),
  };

  const $bufSizes = {
    audio: new BehaviorSubject(defaultBufferSize),
    video: new BehaviorSubject(defaultBufferSize),
    text:  new BehaviorSubject(defaultBufferSize),
  };

  /**
   * Returns an Observable emitting:
   *   - first, the current audio adaption
   *   - the new one each time it changes
   * @param {Array.<Object>} adaptations - The available audio adaptations
   * objects.
   * @returns {Observable}
   */
  function audioAdaptationChoice(adaptations) {
    return $languages.distinctUntilChanged()
      .map(({ language, audioDescription }) =>
        findAudioAdaptation(
          adaptations,
          language,
          audioDescription
        ) || adaptations[0]
      );
  }

  /**
   * Returns an Observable emitting:
   *   - first, the current text adaption
   *   - the new one each time it changes
   * @param {Array.<Object>} adaptations - The available text adaptations
   * objects.
   * @returns {Observable}
   */
  function textAdaptationChoice(adaptations) {
    return $subtitles.distinctUntilChanged()
      .map(arg =>
        arg ? findTextAdaptation(
          adaptations,
          arg.language,
          arg.closedCaption
        ) : null
      );
  }

  /**
   * Get the current and new adaptations each time it changes for all
   * types.
   * Mostly useful for audio languages and text subtitles to know which one
   * to choose first and when it changes.
   * @param {string} type - The adaptation type
   * @param {Array.<Object>} adaptations
   * @returns {Observable}
   */
  function getAdaptationsChoice(type, adaptations) {
    if (type == "audio") {
      return audioAdaptationChoice(adaptations);
    }

    if (type == "text") {
      return textAdaptationChoice(adaptations);
    }

    return only(adaptations[0]);
  }

  /**
   * Returns an object containing two observables:
   *   - representations: the chosen best representation for the adaptation
   *     (correlated from the user, max and average bitrates)
   *   - bufferSizes: the bufferSize chosen
   * @param {Object} adaptation
   * @returns {Object}
   */
  function getBufferAdapters(adaptation) {
    const { type, representations } = adaptation;
    const bitrates = adaptation.getAvailableBitrates();

    let representationsObservable;
    if (representations.length > 1) {
      const usrBitrates = $usrBitrates[type];
      let maxBitrates = $maxBitrates[type];

      const avrBitrates = $averageBitrates[type]
        .map((avrBitrate, count) => {
          // no threshold for the first value of the average bitrate
          // stream corresponding to the selected initial video bitrate
          let bufThreshold;
          if (count === 0) {
            bufThreshold = 0;
          } else {
            bufThreshold = defaultBufferThreshold;
          }

          return getClosestBitrate(bitrates, avrBitrate, bufThreshold);
        })
        .distinctUntilChanged()
        .debounceTime(2000)
        .startWith(getClosestBitrate(bitrates, $averageBitrates[type].getValue(), 0));

      if (type == "video") {
        // To compute the bitrate upper-bound for video
        // representations we need to combine multiple stream:
        //   - user-based maximum bitrate (subject)
        //   - maximum based on the video element width
        //   - maximum based on the application visibility (background tab)
        maxBitrates = combineLatest([maxBitrates, videoWidth, inBackground])
          .map(([maxSetBitrate, width, isHidden]) => {
            if (throttleWhenHidden && isHidden) {
              return bitrates[0];
            }

            const maxUsableBitrate = limitVideoWidth ?
              getMaxUsefulBitrateforWidth(representations, width) :
              bitrates[bitrates.length - 1];

            return Math.min(maxUsableBitrate, maxSetBitrate);
          });
      }

      representationsObservable = combineLatest(
        usrBitrates,
        maxBitrates,
        avrBitrates,
        (usr, max, avr) => {
          let btr;
          if (usr < Infinity) {
            btr = usr;
          } else if (max < Infinity) {
            btr = Math.min(max, avr);
          } else {
            btr = avr;
          }
          return find(representations, (rep) => rep.bitrate === getClosestBitrate(bitrates, btr));
        })
        .distinctUntilChanged((a, b) => a.id === b.id);
    }
    else { // representations.length <= 1
      representationsObservable = only(representations[0]);
    }

    return {
      representations: representationsObservable,
      bufferSizes: $bufSizes[type] || new BehaviorSubject(defaultBufferSize),
    };
  }

  return {
    setAudioTrack(track) { $languages.next(track); },
    setTextTrack(track)  { $subtitles.next(track); },
    getAudioTrack()      { return $languages.getValue(); },
    getTextTrack()       { return $subtitles.getValue(); },

    getAverageBitrates() { return $averageBitrates; },

    getAudioMaxBitrate() { return $maxBitrates.audio.getValue(); },
    getVideoMaxBitrate() { return $maxBitrates.video.getValue(); },
    getAudioBufferSize() { return $bufSizes.audio.getValue(); },
    getVideoBufferSize() { return $bufSizes.video.getValue(); },

    setAudioBitrate(x)    { $usrBitrates.audio.next(def(x, Infinity)); },
    setVideoBitrate(x)    { $usrBitrates.video.next(def(x, Infinity)); },
    setAudioMaxBitrate(x) { $maxBitrates.audio.next(def(x, Infinity)); },
    setVideoMaxBitrate(x) { $maxBitrates.video.next(def(x, Infinity)); },
    setAudioBufferSize(x) { $bufSizes.audio.next(def(x, defaultBufferSize)); },
    setVideoBufferSize(x) { $bufSizes.video.next(def(x, defaultBufferSize)); },

    getBufferAdapters,
    getAdaptationsChoice,

    unsubscribe() {
      if (conns) {
        conns.unsubscribe();
        conns = null;
      }
    },
  };
}
<commit_msg>adaptive: fix width limitation<commit_after>/**
 * Copyright 2015 CANAL+ Group
 *
 * 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.
 */

import { Subscription } from "rxjs/Subscription";
import { BehaviorSubject } from "rxjs/BehaviorSubject";
import { combineLatest } from "rxjs/observable/combineLatest";
import { only } from "../utils/rx-utils";
import { findBetterMatchIndex } from "../utils/languages";

import AverageBitrate from "./average-bitrate";

const DEFAULTS = {
  defaultAudioTrack: {
    language: "fra",
    audioDescription: false,
  },
  defaultTextTrack: null,
  // default buffer size in seconds
  defaultBufferSize: 30,
  // buffer threshold ratio used as a lower bound
  // margin to find the suitable representation
  defaultBufferThreshold: 0.3,
  initialVideoBitrate: 0,
  initialAudioBitrate: 0,
  maxVideoBitrate: Infinity,
  maxAudioBitrate: Infinity,
};

/**
 * Simple find function implementation.
 * @param {Array} array
 * @param {Function} predicate - The predicate. Will take as arguments:
 *   1. the current array element
 *   2. the array index
 *   3. the entire array
 * @returns {*} - null if not found
 */
function find(array, predicate) {
  for (let i = 0; i < array.length; i++) {
    if (predicate(array[i], i, array) === true) {
      return array[i];
    }
  }
  return null;
}

/**
 * Returns val if x is either not a Number type or inferior or equal to 0.
 * @param {Number} [x]
 * @param {*} val
 * @returns {*}
 */
function def(x, val) {
  return typeof x == "number" && x > 0 ? x : val;
}

/**
 * Get closest bitrate lower or equal to the bitrate wanted when the threshold
 * is equal to 0. You can add a security margin by setting the threshold between
 * 0 and 1.
 * @param {Array.<Number>} bitrates - all available bitrates.
 * @param {Number} btr - a chosen bitrate
 * @param {Number} [treshold=0]
 * @returns {Number}
 */
function getClosestBitrate(bitrates, btr, threshold=0) {
  for (let i = bitrates.length - 1; i >= 0; i--) {
    if ((bitrates[i] / btr) <= (1 - threshold)) {
      return bitrates[i];
    }
  }
  return bitrates[0];
}

/**
 * Get the highest bitrate from the representations having a width immediately
 * superior or equal to the given one.
 * @param {Array.<Object>} representations - The representations array
 * @param {Number} width
 * @returns {Number}
 */
function getMaxUsefulBitrateforWidth(representations, width) {
  const sortedRepsByWidth = representations.sort((a, b) => a.width - b.width);
  const firstSuperiorRepresentation =
    sortedRepsByWidth.find(r => r.width >= width);

  if (firstSuperiorRepresentation) {
    const filteredAdaptations = representations
      .filter(r => r.width <= firstSuperiorRepresentation.width);
    if (filteredAdaptations.length) {
      return filteredAdaptations[filteredAdaptations.length - 1].bitrate;
    } else {
      const firstRepresentation = representations[0];
      return firstRepresentation ? firstRepresentation.bitrate : void 0;
    }
  }

  return Infinity;
}

/**
 * Find first adaptation with the corresponding language.
 * @param {Array.<Object>} adaptations
 * @param {string} language
 * @returns {Object|null}
 */
function findAdaptationByLang(adaptations, language) {
  const languages = adaptations.map(a => a.language);

  const index = findBetterMatchIndex(languages, language);
  if (index >= 0) {
    return adaptations[index];
  }
  return null;
}

/**
 * @param {Array.<Object>} adaptations
 * @param {string} language
 * @param {Boolean} [audioDescription=false]
 * @returns {Object|null}
 */
function findAudioAdaptation(adaptations, language, audioDescription = false) {
  const filteredAdaptations = adaptations.filter(adaptation =>
    adaptation.isAudioDescription == audioDescription
  );
  return findAdaptationByLang(filteredAdaptations, language);
}

/**
 * @param {Array.<Object>} adaptations
 * @param {string} language
 * @param {Boolean} [closedCaption=false]
 * @returns {Object|null}
 */
function findTextAdaptation(adaptations, language, closedCaption = false) {
  const filteredAdaptations = adaptations.filter(adaptation =>
    adaptation.isClosedCaption == closedCaption
  );
  return findAdaptationByLang(filteredAdaptations, language);
}

/**
 * Filter the given observable/array to only keep the item with the selected
 * type.
 * @param {Observable|Array.<Object>} stream
 * @param {string} selectedType
 * @returns {Observable|Array.<Object>}
 */
function filterByType(stream, selectedType) {
  return stream.filter(({ type }) => type === selectedType);
}

export default function(metrics, deviceEvents, options={}) {
  Object.keys(options).forEach(key =>
    options[key] === undefined && delete options[key]
  );

  const {
    defaultAudioTrack,
    defaultTextTrack,
    defaultBufferSize,
    defaultBufferThreshold,
    initialVideoBitrate,
    initialAudioBitrate,
    maxVideoBitrate,
    maxAudioBitrate,
    limitVideoWidth,
    throttleWhenHidden,
  } = Object.assign({}, DEFAULTS, options);

  const { videoWidth, inBackground } = deviceEvents;

  const $languages = new BehaviorSubject(defaultAudioTrack);
  const $subtitles = new BehaviorSubject(defaultTextTrack);

  const $averageBitrates = {
    audio: new BehaviorSubject(initialAudioBitrate),
    video: new BehaviorSubject(initialVideoBitrate),
  };

  const averageBitratesConns = [
    AverageBitrate(filterByType(metrics, "audio"), { alpha: 0.6 })
      .multicast($averageBitrates.audio),
    AverageBitrate(filterByType(metrics, "video"), { alpha: 0.6 })
      .multicast($averageBitrates.video),
  ];

  let conns = new Subscription();
  averageBitratesConns.forEach((a) => conns.add(a.connect()));

  const $usrBitrates = {
    audio: new BehaviorSubject(Infinity),
    video: new BehaviorSubject(Infinity),
  };

  const $maxBitrates = {
    audio: new BehaviorSubject(maxAudioBitrate),
    video: new BehaviorSubject(maxVideoBitrate),
  };

  const $bufSizes = {
    audio: new BehaviorSubject(defaultBufferSize),
    video: new BehaviorSubject(defaultBufferSize),
    text:  new BehaviorSubject(defaultBufferSize),
  };

  /**
   * Returns an Observable emitting:
   *   - first, the current audio adaption
   *   - the new one each time it changes
   * @param {Array.<Object>} adaptations - The available audio adaptations
   * objects.
   * @returns {Observable}
   */
  function audioAdaptationChoice(adaptations) {
    return $languages.distinctUntilChanged()
      .map(({ language, audioDescription }) =>
        findAudioAdaptation(
          adaptations,
          language,
          audioDescription
        ) || adaptations[0]
      );
  }

  /**
   * Returns an Observable emitting:
   *   - first, the current text adaption
   *   - the new one each time it changes
   * @param {Array.<Object>} adaptations - The available text adaptations
   * objects.
   * @returns {Observable}
   */
  function textAdaptationChoice(adaptations) {
    return $subtitles.distinctUntilChanged()
      .map(arg =>
        arg ? findTextAdaptation(
          adaptations,
          arg.language,
          arg.closedCaption
        ) : null
      );
  }

  /**
   * Get the current and new adaptations each time it changes for all
   * types.
   * Mostly useful for audio languages and text subtitles to know which one
   * to choose first and when it changes.
   * @param {string} type - The adaptation type
   * @param {Array.<Object>} adaptations
   * @returns {Observable}
   */
  function getAdaptationsChoice(type, adaptations) {
    if (type == "audio") {
      return audioAdaptationChoice(adaptations);
    }

    if (type == "text") {
      return textAdaptationChoice(adaptations);
    }

    return only(adaptations[0]);
  }

  /**
   * Returns an object containing two observables:
   *   - representations: the chosen best representation for the adaptation
   *     (correlated from the user, max and average bitrates)
   *   - bufferSizes: the bufferSize chosen
   * @param {Object} adaptation
   * @returns {Object}
   */
  function getBufferAdapters(adaptation) {
    const { type, representations } = adaptation;
    const bitrates = adaptation.getAvailableBitrates();

    let representationsObservable;
    if (representations.length > 1) {
      const usrBitrates = $usrBitrates[type];
      let maxBitrates = $maxBitrates[type];

      const avrBitrates = $averageBitrates[type]
        .map((avrBitrate, count) => {
          // no threshold for the first value of the average bitrate
          // stream corresponding to the selected initial video bitrate
          let bufThreshold;
          if (count === 0) {
            bufThreshold = 0;
          } else {
            bufThreshold = defaultBufferThreshold;
          }

          return getClosestBitrate(bitrates, avrBitrate, bufThreshold);
        })
        .distinctUntilChanged()
        .debounceTime(2000)
        .startWith(getClosestBitrate(bitrates, $averageBitrates[type].getValue(), 0));

      if (type == "video") {
        // To compute the bitrate upper-bound for video
        // representations we need to combine multiple stream:
        //   - user-based maximum bitrate (subject)
        //   - maximum based on the video element width
        //   - maximum based on the application visibility (background tab)
        maxBitrates = combineLatest([maxBitrates, videoWidth, inBackground])
          .map(([maxSetBitrate, width, isHidden]) => {
            if (throttleWhenHidden && isHidden) {
              return bitrates[0];
            }

            const maxUsableBitrate = limitVideoWidth ?
              getMaxUsefulBitrateforWidth(representations, width) :
              bitrates[bitrates.length - 1];

            return Math.min(maxUsableBitrate, maxSetBitrate);
          });
      }

      representationsObservable = combineLatest(
        usrBitrates,
        maxBitrates,
        avrBitrates,
        (usr, max, avr) => {
          let btr;
          if (usr < Infinity) {
            btr = usr;
          } else if (max < Infinity) {
            btr = Math.min(max, avr);
          } else {
            btr = avr;
          }
          return find(representations, (rep) => rep.bitrate === getClosestBitrate(bitrates, btr));
        })
        .distinctUntilChanged((a, b) => a.id === b.id);
    }
    else { // representations.length <= 1
      representationsObservable = only(representations[0]);
    }

    return {
      representations: representationsObservable,
      bufferSizes: $bufSizes[type] || new BehaviorSubject(defaultBufferSize),
    };
  }

  return {
    setAudioTrack(track) { $languages.next(track); },
    setTextTrack(track)  { $subtitles.next(track); },
    getAudioTrack()      { return $languages.getValue(); },
    getTextTrack()       { return $subtitles.getValue(); },

    getAverageBitrates() { return $averageBitrates; },

    getAudioMaxBitrate() { return $maxBitrates.audio.getValue(); },
    getVideoMaxBitrate() { return $maxBitrates.video.getValue(); },
    getAudioBufferSize() { return $bufSizes.audio.getValue(); },
    getVideoBufferSize() { return $bufSizes.video.getValue(); },

    setAudioBitrate(x)    { $usrBitrates.audio.next(def(x, Infinity)); },
    setVideoBitrate(x)    { $usrBitrates.video.next(def(x, Infinity)); },
    setAudioMaxBitrate(x) { $maxBitrates.audio.next(def(x, Infinity)); },
    setVideoMaxBitrate(x) { $maxBitrates.video.next(def(x, Infinity)); },
    setAudioBufferSize(x) { $bufSizes.audio.next(def(x, defaultBufferSize)); },
    setVideoBufferSize(x) { $bufSizes.video.next(def(x, defaultBufferSize)); },

    getBufferAdapters,
    getAdaptationsChoice,

    unsubscribe() {
      if (conns) {
        conns.unsubscribe();
        conns = null;
      }
    },
  };
}
<|endoftext|>


<commit_before>(function(window, angular, undefined) {

    'use strict';

    angular.module('ksSwiper', [])
        .directive('ksSwiperContainer', SwiperContainer)
        .directive('ksSwiperSlide', SwiperSlide);

    function createUUID() {
        // http://www.ietf.org/rfc/rfc4122.txt
        var s = [];
        var hexDigits = "0123456789abcdef";
        for (var i = 0; i < 36; i++) {
            s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
        }
        s[14] = "4"; // bits 12-15 of the time_hi_and_version field to 0010
        s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1); // bits 6-7 of the clock_seq_hi_and_reserved to 01
        s[8] = s[13] = s[18] = s[23] = "-";

        var uuid = s.join("");
        return uuid;
    }

    /* @ngInject */
    function SwiperContainer($log) {
        return {
            restrict: 'E',
            transclude: true,
            scope: {
                onReady: '&',
                slidesPerView: '=',
                slidesPerColumn: '=',
                spaceBetween: '=',
                parallax: '=',
                parallaxTransition: '@',
                paginationIsActive: '=',
                paginationClickable: '=',
                showNavButtons: '=',
                loop: '=',
                autoplay: '=',
                initialSlide: '=',
                containerCls: '@',
                paginationCls: '@',
                slideCls: '@',
                direction: '@',
                swiper: '=',
                overrideParameters: '='
            },
            controller: function($scope, $element, $timeout) {
                var self = this;
                var uuid = createUUID();

                $scope.swiper_uuid = uuid;

                // directive defaults
                var params = {
                    slidesPerView: $scope.slidesPerView || 1,
                    slidesPerColumn: $scope.slidesPerColumn || 1,
                    spaceBetween: $scope.spaceBetween || 0,
                    direction: $scope.direction || 'horizontal',
                    loop: $scope.loop || false,
                    initialSlide: $scope.initialSlide || 0,
                    showNavButtons: false
                };

                if (!angular.isUndefined($scope.autoplay) && typeof $scope.autoplay === 'number') {
                    params = angular.extend({}, params, {
                        autoplay: $scope.autoplay
                    });
                }

                if ($scope.paginationIsActive === true) {
                    params = angular.extend({}, params, {
                        paginationClickable: $scope.paginationClickable || true,
                        pagination: '#paginator-' + $scope.swiper_uuid
                    });
                }

                if ($scope.showNavButtons === true) {
                    params.nextButton = '#nextButton-' + $scope.swiper_uuid;
                    params.prevButton = '#prevButton-' + $scope.swiper_uuid;
                }

                if ($scope.overrideParameters) {
                    params = angular.extend({}, params, $scope.overrideParameters);
                }

                var containerCls = $scope.containerCls || '';

                $timeout(function() {
                    var swiper = null;

                    if (angular.isObject($scope.swiper)) {
                        $scope.swiper = new Swiper($element[0].firstChild, params);
                        swiper = $scope.swiper;
                    } else {
                        swiper = new Swiper($element[0].firstChild, params);
                    }

                    //If specified, calls this function when the swiper object is available
                    if (!angular.isUndefined($scope.onReady)) {
                        $scope.onReady({
                            swiper: swiper
                        });
                    }
                });
            },

            link: function(scope, element, attrs) {

                var uuid = scope.swiper_uuid;

                var paginatorId = "paginator-" + uuid;
                var prevButtonId = "prevButton-" + uuid;
                var nextButtonId = "nextButton-" + uuid;

                var containerElement = element[0];

                angular.element(containerElement.querySelector('.swiper-pagination'))
                    .attr('id', paginatorId);

                angular.element(containerElement.querySelector('.swiper-button-next'))
                    .attr('id', nextButtonId);

                angular.element(containerElement.querySelector('.swiper-button-prev'))
                    .attr('id', prevButtonId);
            },

            template: '

'
        };
    }

    /* @ngInject */
    function SwiperSlide($timeout) {
        return {
            restrict: 'E',
            require: '^ksSwiperContainer',
            transclude: true,
            template: '

',
            replace: true
        };
    }

})(window, angular, undefined);<commit_msg>style - make the template of the directive more readable<commit_after>(function(window, angular, undefined) {

    'use strict';

    angular.module('ksSwiper', [])
        .directive('ksSwiperContainer', SwiperContainer)
        .directive('ksSwiperSlide', SwiperSlide);

    function createUUID() {
        // http://www.ietf.org/rfc/rfc4122.txt
        var s = [];
        var hexDigits = "0123456789abcdef";
        for (var i = 0; i < 36; i++) {
            s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
        }
        s[14] = "4"; // bits 12-15 of the time_hi_and_version field to 0010
        s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1); // bits 6-7 of the clock_seq_hi_and_reserved to 01
        s[8] = s[13] = s[18] = s[23] = "-";

        var uuid = s.join("");
        return uuid;
    }

    /* @ngInject */
    function SwiperContainer($log) {
        return {
            restrict: 'E',
            transclude: true,
            scope: {
                onReady: '&',
                slidesPerView: '=',
                slidesPerColumn: '=',
                spaceBetween: '=',
                parallax: '=',
                parallaxTransition: '@',
                paginationIsActive: '=',
                paginationClickable: '=',
                showNavButtons: '=',
                loop: '=',
                autoplay: '=',
                initialSlide: '=',
                containerCls: '@',
                paginationCls: '@',
                slideCls: '@',
                direction: '@',
                swiper: '=',
                overrideParameters: '='
            },
            controller: function($scope, $element, $timeout) {
                var self = this;
                var uuid = createUUID();

                $scope.swiper_uuid = uuid;

                // directive defaults
                var params = {
                    slidesPerView: $scope.slidesPerView || 1,
                    slidesPerColumn: $scope.slidesPerColumn || 1,
                    spaceBetween: $scope.spaceBetween || 0,
                    direction: $scope.direction || 'horizontal',
                    loop: $scope.loop || false,
                    initialSlide: $scope.initialSlide || 0,
                    showNavButtons: false
                };

                if (!angular.isUndefined($scope.autoplay) && typeof $scope.autoplay === 'number') {
                    params = angular.extend({}, params, {
                        autoplay: $scope.autoplay
                    });
                }

                if ($scope.paginationIsActive === true) {
                    params = angular.extend({}, params, {
                        paginationClickable: $scope.paginationClickable || true,
                        pagination: '#paginator-' + $scope.swiper_uuid
                    });
                }

                if ($scope.showNavButtons === true) {
                    params.nextButton = '#nextButton-' + $scope.swiper_uuid;
                    params.prevButton = '#prevButton-' + $scope.swiper_uuid;
                }

                if ($scope.overrideParameters) {
                    params = angular.extend({}, params, $scope.overrideParameters);
                }

                var containerCls = $scope.containerCls || '';

                $timeout(function() {
                    var swiper = null;

                    if (angular.isObject($scope.swiper)) {
                        $scope.swiper = new Swiper($element[0].firstChild, params);
                        swiper = $scope.swiper;
                    } else {
                        swiper = new Swiper($element[0].firstChild, params);
                    }

                    //If specified, calls this function when the swiper object is available
                    if (!angular.isUndefined($scope.onReady)) {
                        $scope.onReady({
                            swiper: swiper
                        });
                    }
                });
            },

            link: function(scope, element, attrs) {

                var uuid = scope.swiper_uuid;

                var paginatorId = "paginator-" + uuid;
                var prevButtonId = "prevButton-" + uuid;
                var nextButtonId = "nextButton-" + uuid;

                var containerElement = element[0];

                angular.element(containerElement.querySelector('.swiper-pagination'))
                    .attr('id', paginatorId);

                angular.element(containerElement.querySelector('.swiper-button-next'))
                    .attr('id', nextButtonId);

                angular.element(containerElement.querySelector('.swiper-button-prev'))
                    .attr('id', prevButtonId);
            },

            template: '
' +
                '

' +
                '

' +
                '

' +
                '

' +
                '

' +
                '
'
        };
    }

    /* @ngInject */
    function SwiperSlide($timeout) {
        return {
            restrict: 'E',
            require: '^ksSwiperContainer',
            transclude: true,
            template: '

',
            replace: true
        };
    }

})(window, angular, undefined);<|endoftext|>


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

var db
  , server;

exports.name = "doctexts";

exports.init = function (_db, _server, cb)
{
  db = _db;
  server = _server;
  cb();
}

exports.getOne = function (res, req, answerRequest)
{
  var docname = req.params.entity;

  db.doc.getDoc(docname,
    function (err, doc)
    {
      if(err == "nodoc")
      {
        answerRequest(res, 404, "Doc not found", null);
      }
      else if(err)
      {
        answerRequest(res, 500, err, null);
      }
      else
      {
        server.documentTypes.getText(doc,
          function (err, text)
          {
            if(err)
              answerRequest(res, 500, err, null);
            else
              answerRequest(res, 200, "ok", text);
          });
      }
    });
}

exports.getMany = function (res, req, answerRequest)
{
  answerRequest(res, 405, "Method Not Allowed", null);
}

exports.createOne = function (res, req, answerRequest)
{
  answerRequest(res, 405, "Method Not Allowed", null);
}

exports.updateOne = function (res, req, answerRequest)
{
  var text = req.body;
  var docname = req.params.entity;

  db.doc.getDoc(docname,
    function (err, doc)
    {
      if(err == "nodoc")
      {
        answerRequest(res, 404, "Doc not found", null);
      }
      else if(err)
      {
        answerRequest(res, 500, err, null);
      }
      else
      {
        server.documentTypes.setText(doc, text,
          function (err)
          {
            if(err)
              answerRequest(res, 500, err, null);
            else
              answerRequest(res, 200, "ok", null);
          });
      }
    });
}

exports.deleteOne = function (res, req, answerRequest)
{
  answerRequest(res, 405, "Method Not Allowed", null);
}
<commit_msg>Support API: GET /api/1/doctexts (get all doctexts)<commit_after>var async = require('async');

var db
  , server;

exports.name = "doctexts";

exports.init = function (_db, _server, cb)
{
  db = _db;
  server = _server;
  cb();
}

exports.getOne = function (res, req, answerRequest)
{
  var docname = req.params.entity;

  db.doc.getDoc(docname,
    function (err, doc)
    {
      if(err == "nodoc")
      {
        answerRequest(res, 404, "Doc not found", null);
      }
      else if(err)
      {
        answerRequest(res, 500, err, null);
      }
      else
      {
        server.documentTypes.getText(doc,
          function (err, text)
          {
            if(err)
              answerRequest(res, 500, err, null);
            else
              answerRequest(res, 200, "ok", text);
          });
      }
    });
}

exports.getMany = function (res, req, answerRequest)
{
  async.waterfall(
    [
      // get all docs
      server.db.doc.getDocs,
      // get doc-texts
      function (docs, cb)
      {
        async.map(docs,
          function (doc, cb)
          {
            server.documentTypes.getText(doc,
              function (err, text)
              {
                if(err)
                  cb(null, { name: doc.docname, error: err, text: null });
                else
                  cb(null, { name: doc.docname, error: null, text: text });
              });
          }, cb
        );
      }
    ],
    // return doc-texts to client
    function (err, docs)
    {
      if(err)
        answerRequest(res, 500, err, null);
      else
        answerRequest(res, 200, "ok", docs);
    }
  );
}

exports.createOne = function (res, req, answerRequest)
{
  answerRequest(res, 405, "Method Not Allowed", null);
}

exports.updateOne = function (res, req, answerRequest)
{
  var text = req.body;
  var docname = req.params.entity;

  db.doc.getDoc(docname,
    function (err, doc)
    {
      if(err == "nodoc")
      {
        answerRequest(res, 404, "Doc not found", null);
      }
      else if(err)
      {
        answerRequest(res, 500, err, null);
      }
      else
      {
        server.documentTypes.setText(doc, text,
          function (err)
          {
            if(err)
              answerRequest(res, 500, err, null);
            else
              answerRequest(res, 200, "ok", null);
          });
      }
    });
}

exports.deleteOne = function (res, req, answerRequest)
{
  answerRequest(res, 405, "Method Not Allowed", null);
}
<|endoftext|>


<commit_before>import events from 'events';

class Feeder extends events.EventEmitter {
    state = {
        hold: false,
        holdReason: null,
        queue: [],
        pending: false,
        changed: false
    };
    dataFilter = null;

    // @param {object} [options] The options object.
    // @param {function} [options.dataFilter] A function to be used to handle the data. The function accepts two arguments: The data to be sent to the controller, and the context.
    constructor(options) {
        super();

        if (typeof options.dataFilter === 'function') {
            this.dataFilter = options.dataFilter;
        }

        this.on('change', () => {
            this.state.changed = true;
        });
    }
    toJSON() {
        return {
            hold: this.state.hold,
            holdReason: this.state.holdReason,
            queue: this.state.queue.length,
            pending: this.state.pending,
            changed: this.state.changed
        };
    }
    feed(data = [], context = {}) {
        data = [].concat(data);
        if (data.length > 0) {
            this.state.queue = this.state.queue.concat(data.map(command => {
                return { command: command, context: context };
            }));
            this.emit('change');
        }
    }
    hold(reason) {
        if (this.state.hold) {
            return;
        }
        this.state.hold = true;
        this.state.holdReason = reason;
        this.emit('hold');
        this.emit('change');
    }
    unhold() {
        if (!this.state.hold) {
            return;
        }
        this.state.hold = false;
        this.state.holdReason = null;
        this.emit('unhold');
        this.emit('change');
    }
    clear() {
        this.state.queue = [];
        this.state.pending = false;
        this.emit('change');
    }
    reset() {
        this.state.hold = false;
        this.state.holdReason = null;
        this.state.queue = [];
        this.state.pending = false;
        this.emit('change');
    }
    size() {
        return this.state.queue.length;
    }
    next() {
        if (this.state.queue.length === 0) {
            this.state.pending = false;
            return false;
        }

        while (!this.state.hold && this.state.queue.length > 0) {
            let { command, context } = this.state.queue.shift();

            if (this.dataFilter) {
                command = this.dataFilter(command, context) || '';
                if (!command) {
                    continue;
                }
            }

            this.state.pending = true;
            this.emit('data', command, context);
            this.emit('change');
            break;
        }

        return this.state.pending;
    }
    isPending() {
        return this.state.pending;
    }
    // Returns true if any state have changes
    peek() {
        const changed = this.state.changed;
        this.state.changed = false;
        return changed;
    }
}

export default Feeder;
<commit_msg>Fix a bug that cannot process pending requests when the queue is empty but it still remains in pending state<commit_after>import events from 'events';

class Feeder extends events.EventEmitter {
    state = {
        hold: false,
        holdReason: null,
        queue: [],
        pending: false,
        changed: false
    };
    dataFilter = null;

    // @param {object} [options] The options object.
    // @param {function} [options.dataFilter] A function to be used to handle the data. The function accepts two arguments: The data to be sent to the controller, and the context.
    constructor(options) {
        super();

        if (typeof options.dataFilter === 'function') {
            this.dataFilter = options.dataFilter;
        }

        this.on('change', () => {
            this.state.changed = true;
        });
    }
    toJSON() {
        return {
            hold: this.state.hold,
            holdReason: this.state.holdReason,
            queue: this.state.queue.length,
            pending: this.state.pending,
            changed: this.state.changed
        };
    }
    feed(data = [], context = {}) {
        // Clear pending state when the feeder queue is empty
        if (this.state.queue.length === 0) {
            this.state.pending = false;
        }

        data = [].concat(data);
        if (data.length > 0) {
            this.state.queue = this.state.queue.concat(data.map(command => {
                return { command: command, context: context };
            }));
            this.emit('change');
        }
    }
    hold(reason) {
        if (this.state.hold) {
            return;
        }
        this.state.hold = true;
        this.state.holdReason = reason;
        this.emit('hold');
        this.emit('change');
    }
    unhold() {
        if (!this.state.hold) {
            return;
        }
        this.state.hold = false;
        this.state.holdReason = null;
        this.emit('unhold');
        this.emit('change');
    }
    clear() {
        this.state.queue = [];
        this.state.pending = false;
        this.emit('change');
    }
    reset() {
        this.state.hold = false;
        this.state.holdReason = null;
        this.state.queue = [];
        this.state.pending = false;
        this.emit('change');
    }
    size() {
        return this.state.queue.length;
    }
    next() {
        while (!this.state.hold && this.state.queue.length > 0) {
            let { command, context } = this.state.queue.shift();

            if (this.dataFilter) {
                command = this.dataFilter(command, context) || '';
                if (!command) { // Ignore blank lines
                    continue;
                }
            }

            this.state.pending = true;
            this.emit('data', command, context);
            this.emit('change');
            break;
        }

        // Clear pending state when the feeder queue is empty
        if (this.state.queue.length === 0) {
            this.state.pending = false;
        }

        return this.state.pending;
    }
    isPending() {
        return this.state.pending;
    }
    // Returns true if any state have changes
    peek() {
        const changed = this.state.changed;
        this.state.changed = false;
        return changed;
    }
}

export default Feeder;
<|endoftext|>


<commit_before>/**
 * @file
 * File picker and finder for device storages on Firefox OS devices
 *
 * This library provides an easy-to-use asynchronous interface for other Firefox OS apps to search for files
 * on Firefox OS devices. The library is based on an event-based architecture, letting developers build
 * beautiful asynchronous API for their apps.
 *
 * The `Finder` library is best used by developers looking to pick a file from the `sdcard` for their apps.
 *
 * This library depends on [EventEmitter](https://github.com/Wolfy87/EventEmitter) by Wolfy87, included with the
 * package.
 *
 * @version 1.1.1
 * @license The MIT License (MIT)
 * @author Applait Technologies LLP
 * @copyright Copyright (c) 2014 Applait Technologies LLP
 */

/**
 * Core Applait namespace for Applait libraries.
 *
 * @namespace
 */
var Applait = Applait || {};

/**
 * Core `Finder` class. Provides the constructor for applications to instantiate.
 *
 * @memberOf Applait
 * @constructor
 *
 * @property {object} options - Easy access to `options` passed in constructor argument.
 * @property {string} type - The type of DeviceStorage specified.
 * @property {boolean} hidden - Boolean specifying whether hidden files will be included in search or not.
 * @property {boolean} casesensitive - Boolean specifying whether searches will be case sensitive or not.
 * @property {boolean} debugmode - Boolean activating or deactivating debug mode.
 * @property {array} storages - Array of device storage cursors based on `this.type`.
 * @property {number} searchcompletecount - Number of device storages searched through in latest search.
 * @property {number} filematchcount - Number of files found in latest search
 * @property {string} searchkey - Search term used in last search
 *
 * @param {object=} options - Default options for the `Finder` constructor. It can include any of the following
 * properties:
 *
 * - `type` : `{string}` : Can be one of `sdcard`, `music`, `pictures`, `videos`. Defaults to `sdcard`.
 * - `minSearchLength`: `{number}` : The minimum length of search string without which search will not be triggered.
 * Defaults to `3`.
 * - `hidden`: `{boolean}` : If set to `true`, searches hidden files as well. Defaults to `false`.
 * - `caseSensitive` : `{boolean}` : If set to `true`, searches will be case sensitive. Defaults to `false`.
 * - `debugMode`: `{boolean}` : If `true`, enables debug mode which logs all messages to the browser console. This
 * should be disabled in production mode to reduce memory footprint.
 *
 * @example
 * var finder = new Applait.Finder({ type: "sdcard", debugMode: true });
 */
Applait.Finder = function (options) {

    this.options = options || {};

    this.type = options.type || "sdcard";

    this.hidden = options.hidden || false;

    this.casesensitive = options.caseSensitive || false;

    this.minsearchlength = (options.minSearchLength && typeof options.minSearchLength === "number") ?
        options.minSearchLength : 3;

    this.debugmode = options.debugMode ? true : false;

    this.storages = navigator.getDeviceStorages && navigator.getDeviceStorages(this.type);

    this.searchcompletecount = 0;

    this.filematchcount = 0;

    this.searchkey = "";
};

/**
 * Make `Applait.Finder` inherit the `EventEmitter` prototype chain.
 */
Applait.Finder.prototype = new EventEmitter();

/**
 * Match hidden files based on settings
 *
 * @param {string} filename - The filename to test
 * @return {boolean} - `true` if file is a hidden file and if `hidden`
 * is `true` in constructor options.
 */
Applait.Finder.prototype.checkhidden = function (filename) {
    if ((filename.indexOf(".") === 0) && this.hidden !== true) {
        return false;
    }
    return true;
};

/**
 * Instantiate search
 *
 * @memberOf Applait.Finder
 * @param {string} needle - The string to match file names from the device storage.
 * @return {null} - Only if `needle` length is less than `minsearchlength` or if no DeviceStorages are found.
 */
Applait.Finder.prototype.search = function (needle) {

    var self = this;

    self.reset();
    self.searchkey = !self.casesensitive ? needle.trim().toLowerCase() : needle.trim();

    if (self.searchkey.length < self.minsearchlength) {
        self.log("searchCancelled",
                 ["Search string should be at least " + self.minsearchlength + " characters"]);
        self.emitEvent("searchCancelled",
                       ["Search string should be at least " + self.minsearchlength + " characters"]);
        return null;
    }

    if (self.storagecount() < 1) {
        self.log("empty", [self.searchkey]);
        self.emitEvent("empty", [self.searchkey]);
        return null;
    }

    self.log("searchBegin", [self.searchkey]);
    self.emitEvent("searchBegin", [self.searchkey]);

    self.storages.forEach(function (storage) {

        var cursor = storage.enumerate();

        self.log("storageSearchBegin", [storage.storageName, self.searchkey]);
        self.emitEvent("storageSearchBegin", [storage.storageName, self.searchkey]);

        cursor.onsuccess = function () {

            if (this.result) {

                var file = this.result;
                var fileinfo = self.splitname(file.name);

                if (self.matchname(fileinfo.name)) {
                    self.filematchcount++;
                    self.log("fileFound", [file, fileinfo, storage.storageName]);
                    self.emitEvent("fileFound", [file, fileinfo, storage.storageName]);
                }
                if (!this.done) {
                    this.continue();
                } else {
                    self.searchcompletecount++;
                    self.log("storageSearchComplete", [storage.storageName, self.searchkey]);
                    self.emitEvent("storageSearchComplete", [storage.storageName, self.searchkey]);
                }
            } else {
                self.searchcompletecount++;
                self.log("storageSearchComplete", [storage.storageName, self.searchkey]);
                self.emitEvent("storageSearchComplete", [storage.storageName, self.searchkey]);
            }

            if (self.searchcompletecount === self.storagecount()) {
                self.log("searchComplete", [self.searchkey, self.filematchcount]);
                self.emitEvent("searchComplete", [self.searchkey, self.filematchcount]);
            }

        };

        cursor.onerror = function () {
            self.log("error", ["Error accessing device storage '" + storage.storageName + "'", this.error]);
            self.emitEvent('error', ["Error accessing device storage '" + storage.storageName + "'",
                                     this.error]);
        };

    });
};

/**
 * Splits full file path into basename and path to directory.
 *
 * @memberOf Applait.Finder
 * @param {string} filename - Filename obtained for `File.filename`
 * @return {object} - An object with two keys:
 *
 * - `name` - the basename of the file with extension
 * - `path` - path to the file's directory.
 */
Applait.Finder.prototype.splitname = function (filename) {
    filename = filename.split(/[\\/]/);

    return { "name": filename.pop(), "path": filename.join("/") };
};

/**
 * Return the number of storages being used
 *
 * @memberOf Applait.Finder
 * @return {number} - The length of storages
 */
Applait.Finder.prototype.storagecount = function () {
    return this.storages && this.storages.length ? this.storages.length : 0;
};

/**
 * Reset internals
 *
 * @memberOf Applait.Finder
 */
Applait.Finder.prototype.reset = function () {
    this.filematchcount = 0;
    this.searchcompletecount = 0;
    this.searchkey = "";
};

/**
 * Generic logging method, dependent on debugMode
 *
 * @memberOf Applait.Finder
 * @param {string} message - A string identifying this log
 * @param {array} args - An array of stuff to print
 */
Applait.Finder.prototype.log = function (message, args) {
    if (this.debugmode) {
        console.log(message, args);
    }
};

/**
 * Match name
 *
 * @memberOf Applait.Finder
 * @param {string} name - Filename
 * @return {boolean}
 */
Applait.Finder.prototype.matchname = function (name) {
    name = !this.casesensitive ? name.trim().toLowerCase() : name.trim();
    return (name.indexOf(this.searchkey) > -1 && this.checkhidden(name));
};
<commit_msg>Fix lint issue - use double quote everywhere<commit_after>/**
 * @file
 * File picker and finder for device storages on Firefox OS devices
 *
 * This library provides an easy-to-use asynchronous interface for other Firefox OS apps to search for files
 * on Firefox OS devices. The library is based on an event-based architecture, letting developers build
 * beautiful asynchronous API for their apps.
 *
 * The `Finder` library is best used by developers looking to pick a file from the `sdcard` for their apps.
 *
 * This library depends on [EventEmitter](https://github.com/Wolfy87/EventEmitter) by Wolfy87, included with the
 * package.
 *
 * @version 1.1.1
 * @license The MIT License (MIT)
 * @author Applait Technologies LLP
 * @copyright Copyright (c) 2014 Applait Technologies LLP
 */

/**
 * Core Applait namespace for Applait libraries.
 *
 * @namespace
 */
var Applait = Applait || {};

/**
 * Core `Finder` class. Provides the constructor for applications to instantiate.
 *
 * @memberOf Applait
 * @constructor
 *
 * @property {object} options - Easy access to `options` passed in constructor argument.
 * @property {string} type - The type of DeviceStorage specified.
 * @property {boolean} hidden - Boolean specifying whether hidden files will be included in search or not.
 * @property {boolean} casesensitive - Boolean specifying whether searches will be case sensitive or not.
 * @property {boolean} debugmode - Boolean activating or deactivating debug mode.
 * @property {array} storages - Array of device storage cursors based on `this.type`.
 * @property {number} searchcompletecount - Number of device storages searched through in latest search.
 * @property {number} filematchcount - Number of files found in latest search
 * @property {string} searchkey - Search term used in last search
 *
 * @param {object=} options - Default options for the `Finder` constructor. It can include any of the following
 * properties:
 *
 * - `type` : `{string}` : Can be one of `sdcard`, `music`, `pictures`, `videos`. Defaults to `sdcard`.
 * - `minSearchLength`: `{number}` : The minimum length of search string without which search will not be triggered.
 * Defaults to `3`.
 * - `hidden`: `{boolean}` : If set to `true`, searches hidden files as well. Defaults to `false`.
 * - `caseSensitive` : `{boolean}` : If set to `true`, searches will be case sensitive. Defaults to `false`.
 * - `debugMode`: `{boolean}` : If `true`, enables debug mode which logs all messages to the browser console. This
 * should be disabled in production mode to reduce memory footprint.
 *
 * @example
 * var finder = new Applait.Finder({ type: "sdcard", debugMode: true });
 */
Applait.Finder = function (options) {

    this.options = options || {};

    this.type = options.type || "sdcard";

    this.hidden = options.hidden || false;

    this.casesensitive = options.caseSensitive || false;

    this.minsearchlength = (options.minSearchLength && typeof options.minSearchLength === "number") ?
        options.minSearchLength : 3;

    this.debugmode = options.debugMode ? true : false;

    this.storages = navigator.getDeviceStorages && navigator.getDeviceStorages(this.type);

    this.searchcompletecount = 0;

    this.filematchcount = 0;

    this.searchkey = "";
};

/**
 * Make `Applait.Finder` inherit the `EventEmitter` prototype chain.
 */
Applait.Finder.prototype = new EventEmitter();

/**
 * Match hidden files based on settings
 *
 * @param {string} filename - The filename to test
 * @return {boolean} - `true` if file is a hidden file and if `hidden`
 * is `true` in constructor options.
 */
Applait.Finder.prototype.checkhidden = function (filename) {
    if ((filename.indexOf(".") === 0) && this.hidden !== true) {
        return false;
    }
    return true;
};

/**
 * Instantiate search
 *
 * @memberOf Applait.Finder
 * @param {string} needle - The string to match file names from the device storage.
 * @return {null} - Only if `needle` length is less than `minsearchlength` or if no DeviceStorages are found.
 */
Applait.Finder.prototype.search = function (needle) {

    var self = this;

    self.reset();
    self.searchkey = !self.casesensitive ? needle.trim().toLowerCase() : needle.trim();

    if (self.searchkey.length < self.minsearchlength) {
        self.log("searchCancelled",
                 ["Search string should be at least " + self.minsearchlength + " characters"]);
        self.emitEvent("searchCancelled",
                       ["Search string should be at least " + self.minsearchlength + " characters"]);
        return null;
    }

    if (self.storagecount() < 1) {
        self.log("empty", [self.searchkey]);
        self.emitEvent("empty", [self.searchkey]);
        return null;
    }

    self.log("searchBegin", [self.searchkey]);
    self.emitEvent("searchBegin", [self.searchkey]);

    self.storages.forEach(function (storage) {

        var cursor = storage.enumerate();

        self.log("storageSearchBegin", [storage.storageName, self.searchkey]);
        self.emitEvent("storageSearchBegin", [storage.storageName, self.searchkey]);

        cursor.onsuccess = function () {

            if (this.result) {

                var file = this.result;
                var fileinfo = self.splitname(file.name);

                if (self.matchname(fileinfo.name)) {
                    self.filematchcount++;
                    self.log("fileFound", [file, fileinfo, storage.storageName]);
                    self.emitEvent("fileFound", [file, fileinfo, storage.storageName]);
                }
                if (!this.done) {
                    this.continue();
                } else {
                    self.searchcompletecount++;
                    self.log("storageSearchComplete", [storage.storageName, self.searchkey]);
                    self.emitEvent("storageSearchComplete", [storage.storageName, self.searchkey]);
                }
            } else {
                self.searchcompletecount++;
                self.log("storageSearchComplete", [storage.storageName, self.searchkey]);
                self.emitEvent("storageSearchComplete", [storage.storageName, self.searchkey]);
            }

            if (self.searchcompletecount === self.storagecount()) {
                self.log("searchComplete", [self.searchkey, self.filematchcount]);
                self.emitEvent("searchComplete", [self.searchkey, self.filematchcount]);
            }

        };

        cursor.onerror = function () {
            self.log("error", ["Error accessing device storage '" + storage.storageName + "'", this.error]);
            self.emitEvent("error", ["Error accessing device storage '" + storage.storageName + "'",
                                     this.error]);
        };

    });
};

/**
 * Splits full file path into basename and path to directory.
 *
 * @memberOf Applait.Finder
 * @param {string} filename - Filename obtained for `File.filename`
 * @return {object} - An object with two keys:
 *
 * - `name` - the basename of the file with extension
 * - `path` - path to the file's directory.
 */
Applait.Finder.prototype.splitname = function (filename) {
    filename = filename.split(/[\\/]/);

    return { "name": filename.pop(), "path": filename.join("/") };
};

/**
 * Return the number of storages being used
 *
 * @memberOf Applait.Finder
 * @return {number} - The length of storages
 */
Applait.Finder.prototype.storagecount = function () {
    return this.storages && this.storages.length ? this.storages.length : 0;
};

/**
 * Reset internals
 *
 * @memberOf Applait.Finder
 */
Applait.Finder.prototype.reset = function () {
    this.filematchcount = 0;
    this.searchcompletecount = 0;
    this.searchkey = "";
};

/**
 * Generic logging method, dependent on debugMode
 *
 * @memberOf Applait.Finder
 * @param {string} message - A string identifying this log
 * @param {array} args - An array of stuff to print
 */
Applait.Finder.prototype.log = function (message, args) {
    if (this.debugmode) {
        console.log(message, args);
    }
};

/**
 * Match name
 *
 * @memberOf Applait.Finder
 * @param {string} name - Filename
 * @return {boolean}
 */
Applait.Finder.prototype.matchname = function (name) {
    name = !this.casesensitive ? name.trim().toLowerCase() : name.trim();
    return (name.indexOf(this.searchkey) > -1 && this.checkhidden(name));
};
<|endoftext|>


<commit_before>// copied and modified from https://gist.github.com/dsully/1938283
console.log("Loading bootstrap-tab-history-custom.js");
$(function () {
    console.log("Document ready handler in bootstrap-tab-history-custom.js");

    function loadPage(url) {
        window.location.href = url;
    }

    // Handle tabs, page reloads & browser forward/back history.
    var History = window.History;

    if (!History.enabled) {
        return false;
    }

    var statechange = function () {
        var State = History.getState();
        var hash  = History.getHash();

        // Our default tab.
        if (!State.data || !State.data.tab) {
            if (hash) {
                State.data.tab = hash;
                window.location.hash = '';
            } else {
                State.data.tab = '';
            }
        }

        var link;
        if (State.data.tab) {
            link = $('a[data-toggle="tab"][href$="#' + State.data.tab + '"]');
        } else {
            // if you can't find it by hash, try matching the url
            // this is for first loading a page,
            // where State.data.tab won't be available
            link = $('a[data-toggle="tab"][href^="' + window.location.pathname + '"]');
            if (link.length !== 0) {
                // If multiple links match the current path - likely because this is the inital page load,
                // so the pathname, foo/bar/, will match all links (since they're of the form foo/bar/#baz,
                // foo/bar/#zap, etc.) - look for any link that has been marked as the default landing page.
                // If there's no default set, ultimately we'll fall back to whatever the first link is.
                if (link.length > 1) {
                    var defaultLink = link.filter("[data-default='1']");
                    if (defaultLink.length) {
                        link = defaultLink;
                    }
                }
                link = link.first();
                History.replaceState({
                    tab: link.attr('href').split('#')[1]
                }, null, State.url);
            }
        }
        link.parent().removeClass('active');    // force tab to load
        link.tab('show');
    };
    $(window).on('load', function() {
        console.log("Window ready handler in bootstrap-tab-history-custom.js");
        statechange();
    });
    History.Adapter.bind(window, 'statechange', statechange);
    History.Adapter.bind(window, 'statechange', function () {
        var State = History.getState();
        if (!State.data || !State.data.tab) {
            // just go to the url specified with a reload
            loadPage(window.location.href);
        }
    });

    $('a[data-toggle="tab"]').on('shown.bs.tab', function (event) {
        // Set the selected tab to be the current state. But don't update the URL.
        var url = event.target.href.split("#")[0];
        var tab = event.target.href.split("#")[1];
        var pageTitle = $(this).attr('data-pagetitle');
        if (pageTitle) {
            document.title = pageTitle;
        }

        var State = History.getState();

        if (url === window.location.href) {
            // for tabs that we don't want to change the url on
            return;
        }

        if ($('#' + tab).length === 0) {
            loadPage(url);
        }

        // Don't set the state if we haven't changed tabs.
        if (State.data.tab != tab) {
            var message = COMMCAREHQ.beforeUnloadCallback();
            var ask = message !== undefined && message !== undefined;
            if (!ask) {
                History.pushState({'tab': tab}, pageTitle, url);
            } else {
                // instead of using tabs, reload page
                // so as to trigger warning
                event.preventDefault();
                loadPage(url);
            }
        }
    });

    // Handle control-click, middle-click, etc. opening in a new window
    $("a[data-toggle='tab']").on("click", function(event) {
        if (event && (event.metaKey || event.ctrlKey || event.which === 2)) {
            window.open($(event.target).attr("href"), '_blank');

            // Prevent the tab from showing in the current window
            $(event.target).one("show.bs.tab", function() {
                return false;
            });
        }
    });
});
<commit_msg>Attempt to fix intermittent app manager blank screen<commit_after>// copied and modified from https://gist.github.com/dsully/1938283
$(function () {

    function loadPage(url) {
        window.location.href = url;
    }

    // Handle tabs, page reloads & browser forward/back history.
    var History = window.History;

    if (!History.enabled) {
        return false;
    }

    var statechange = function () {
        var State = History.getState();
        var hash  = History.getHash();

        // Our default tab.
        if (!State.data || !State.data.tab) {
            if (hash) {
                State.data.tab = hash;
                window.location.hash = '';
            } else {
                State.data.tab = '';
            }
        }

        var link;
        if (State.data.tab) {
            link = $('a[data-toggle="tab"][href$="#' + State.data.tab + '"]');
        } else {
            // if you can't find it by hash, try matching the url
            // this is for first loading a page,
            // where State.data.tab won't be available
            link = $('a[data-toggle="tab"][href^="' + window.location.pathname + '"]');
            if (link.length !== 0) {
                // If multiple links match the current path - likely because this is the inital page load,
                // so the pathname, foo/bar/, will match all links (since they're of the form foo/bar/#baz,
                // foo/bar/#zap, etc.) - look for any link that has been marked as the default landing page.
                // If there's no default set, ultimately we'll fall back to whatever the first link is.
                if (link.length > 1) {
                    var defaultLink = link.filter("[data-default='1']");
                    if (defaultLink.length) {
                        link = defaultLink;
                    }
                }
                link = link.first();
                History.replaceState({
                    tab: link.attr('href').split('#')[1]
                }, null, State.url);
            }
        }
        link.parent().removeClass('active');    // force tab to load
        link.tab('show');
    };
    $(window).on('load', function() {
        statechange();
    });
    if (document.readyState === "complete") {
        statechange();
    }
    History.Adapter.bind(window, 'statechange', statechange);
    History.Adapter.bind(window, 'statechange', function () {
        var State = History.getState();
        if (!State.data || !State.data.tab) {
            // just go to the url specified with a reload
            loadPage(window.location.href);
        }
    });

    $('a[data-toggle="tab"]').on('shown.bs.tab', function (event) {
        // Set the selected tab to be the current state. But don't update the URL.
        var url = event.target.href.split("#")[0];
        var tab = event.target.href.split("#")[1];
        var pageTitle = $(this).attr('data-pagetitle');
        if (pageTitle) {
            document.title = pageTitle;
        }

        var State = History.getState();

        if (url === window.location.href) {
            // for tabs that we don't want to change the url on
            return;
        }

        if ($('#' + tab).length === 0) {
            loadPage(url);
        }

        // Don't set the state if we haven't changed tabs.
        if (State.data.tab != tab) {
            var message = COMMCAREHQ.beforeUnloadCallback();
            var ask = message !== undefined && message !== undefined;
            if (!ask) {
                History.pushState({'tab': tab}, pageTitle, url);
            } else {
                // instead of using tabs, reload page
                // so as to trigger warning
                event.preventDefault();
                loadPage(url);
            }
        }
    });

    // Handle control-click, middle-click, etc. opening in a new window
    $("a[data-toggle='tab']").on("click", function(event) {
        if (event && (event.metaKey || event.ctrlKey || event.which === 2)) {
            window.open($(event.target).attr("href"), '_blank');

            // Prevent the tab from showing in the current window
            $(event.target).one("show.bs.tab", function() {
                return false;
            });
        }
    });
});
<|endoftext|>


<commit_before>'use strict';
angular.module('codeine').controller('projectStatusCtrl',['$scope','$rootScope','$log','$filter','$location','SelectedNodesService','Constants', function($scope,$rootScope,$log,$filter,$location,SelectedNodesService,Constants) {
    $scope.projectName = $scope.projectConfiguration.name;
    $scope.allNodesCount = 0;
    $log.debug('projectStatusCtrl: current project is ' + $scope.projectName);
    $log.debug('projectStatusCtrl: projectConfiguration = ' + angular.toJson($scope.projectConfiguration));
    $log.debug('projectStatusCtrl: projectStatus = ' + angular.toJson($scope.projectStatus));

    $scope.versionIsOpen = [];
    $scope.maxNodesToShow = 2;
    for (var i=0 ; i < $scope.projectStatus.nodes_for_version.length; i++) {
        $scope.versionIsOpen[i] = true;
    }
    $scope.collapseAll = function() {
        for (var i=0 ; i < $scope.versionIsOpen.length; i++) {
            $scope.versionIsOpen[i] = false;
        }
    };
    $scope.expandAll = function() {
        for (var i=0 ; i < $scope.versionIsOpen.length; i++) {
            $scope.versionIsOpen[i] = true;
        }
    };
    $scope.shouldShowClearFilters = function() {
        var search = $location.search();
        var noTagsOn = ((!angular.isDefined(search.tagsOn) || search.tagsOn.length === 0));
        var noTagsOff = ((!angular.isDefined(search.tagsOff) || search.tagsOff.length === 0));
        var noTags = noTagsOn && noTagsOff;
        return !(($scope.selectedMonitor === 'All Nodes') && ($scope.nodesFilter === '') && (noTags));
    };

    $scope.initValues = function() {
        $scope.selectedMonitor = 'All Nodes';
        $scope.nodesFilter = '';
    };

    $scope.clearFilters = function() {
        $location.search({});
        $scope.initValues();
        for (var j=0; j < $scope.projectStatus.tag_info.length ; j++) {
            $scope.projectStatus.tag_info[j].state = 0;
        }
        $scope.refreshFilters();
    };

    $scope.initValues();

    $scope.initFromQueryString = function() {
        var queryStringObject = $location.search();
        var shouldRefresh = false;
        if (angular.isDefined(queryStringObject.monitorFilter)) {
            shouldRefresh = true;
            $log.debug('projectStatusCtrl: Monitor filter init from query string - ' + queryStringObject.monitorFilter);
            $scope.selectedMonitor = queryStringObject.monitorFilter;
        }
        if (angular.isDefined(queryStringObject.tagsOn)) {
            shouldRefresh = true;
        }
        if (angular.isDefined(queryStringObject.tagsOff)) {
            shouldRefresh = true;
        }
        return shouldRefresh;
    };

    var tagsChangedHandler = $rootScope.$on(Constants.EVENTS.TAGS_CHANGED, function() {
        $scope.refreshFilters();
    });

    // Returns true if the node should be in the filtered array (Displayed)
    var isNodeFiltered = function(node) {
        return $filter('nodeFilter')(node,$scope.nodesFilter,$scope.selectedMonitor,$scope.projectStatus.tag_info);
    };

    var moveNodeToVisible = function(versionItem,node) {
        node.visible = true;
        if (!versionItem.visibleNodes) {
            versionItem.visibleNodes = [];
        }
        versionItem.visibleNodes.push(node);
    };

    for (var i1=0 ; i1 < $scope.projectStatus.nodes_for_version.length; i1++) {
        $scope.projectStatus.nodes_for_version[i1].filteredNodes = $scope.projectStatus.nodes_for_version[i1].nodes.slice();
        var maxNodesToShowHere = $scope.maxNodesToShow;
        if (maxNodesToShowHere > $scope.projectStatus.nodes_for_version[i1].filteredNodes.length || !$scope.projectStatus.more_nodes_enabled) {
            maxNodesToShowHere = $scope.projectStatus.nodes_for_version[i1].filteredNodes.length;
        }
        for (var j=0; j < maxNodesToShowHere; j++) {
            moveNodeToVisible($scope.projectStatus.nodes_for_version[i1],$scope.projectStatus.nodes_for_version[i1].filteredNodes[j]);
        }
    }

    for (var i2=0 ; i2 < $scope.projectStatus.nodes_for_version.length; i2++) {
        $scope.allNodesCount += $scope.projectStatus.nodes_for_version[i2].filteredNodes.length;
    }

    $scope.$watch("selectedMonitor",function( newName, oldName ) {
            if ( newName === oldName ) {
                return;
            }
            $log.debug('projectStatusCtrl: selectedMonitor was changed');
            $location.search('monitorFilter',newName);
            $scope.refreshFilters();
        }
    );

    $scope.$watch("nodesFilter",function( newName, oldName ) {
            if ( newName === oldName ) {
                return;
            }
            $log.debug('projectStatusCtrl: nodesFilter was changed');
            $scope.refreshFilters();
        }
    );

    $scope.showMore = function(count) {
        if ($scope.projectStatus.more_nodes_enabled) {
            return count < $scope.maxNodesToShow;
        }
        else {
            return true;
        }
    };
    $scope.refreshFilters = function() {
        var count = 0;
        for (var i=0 ; i < $scope.projectStatus.nodes_for_version.length; i++) {
            $scope.projectStatus.nodes_for_version[i].filteredNodes.splice(0,$scope.projectStatus.nodes_for_version[i].filteredNodes.length);
            $scope.projectStatus.nodes_for_version[i].visibleNodes.splice(0,$scope.projectStatus.nodes_for_version[i].visibleNodes.length);
            for (var j=0 ; j < $scope.projectStatus.nodes_for_version[i].nodes.length; j++) {
                $scope.projectStatus.nodes_for_version[i].nodes[j].visible = false;
                if (isNodeFiltered($scope.projectStatus.nodes_for_version[i].nodes[j])) {
                    $scope.projectStatus.nodes_for_version[i].filteredNodes.push($scope.projectStatus.nodes_for_version[i].nodes[j]);
                    count++;
                }
            }
            if ($scope.projectStatus.nodes_for_version[i].filteredNodes.length > 0) {
                for (var k=0; $scope.showMore(k) && k < $scope.projectStatus.nodes_for_version[i].filteredNodes.length; k++) {
                    moveNodeToVisible($scope.projectStatus.nodes_for_version[i],$scope.projectStatus.nodes_for_version[i].filteredNodes[k]);
                }
            }
        }
        $scope.allNodesCount = count;
    };

    if ($scope.initFromQueryString()) {
        $scope.refreshFilters();
    }

    $scope.loadMoreNodes = function(index) {
        var j = 0;
        if ($scope.projectStatus.nodes_for_version[index].filteredNodes.length === $scope.projectStatus.nodes_for_version[index].visibleNodes.length) {
            return;
        }
        for (var i=0; (i < $scope.projectStatus.nodes_for_version[index].filteredNodes.length) && (j < 10); i++) {
            if (!$scope.projectStatus.nodes_for_version[index].filteredNodes[i].visible) {
                moveNodeToVisible($scope.projectStatus.nodes_for_version[index],$scope.projectStatus.nodes_for_version[index].filteredNodes[i]);
                j++;
            }
        }
    };

    $scope.selectMonitor = function(monitor) {
        $scope.selectedMonitor = monitor;
    };

    $scope.runCommand = function(command) {
        $log.debug('projectStatusCtrl: will run command ' + command);
        var url = '/codeine/project/' + $scope.projectName + '/command/' + command + '/setup';
        SelectedNodesService.setSelectedNodes($scope.getAllSelectedNodes(),url);
        $location.path(url);
    };

    $scope.selectedCount = function(){
        var count = 0;
        for (var i=0 ; i < $scope.projectStatus.nodes_for_version.length; i++) {
            for (var j=0 ; j < $scope.projectStatus.nodes_for_version[i].filteredNodes.length; j++) {
                if ($scope.projectStatus.nodes_for_version[i].filteredNodes[j].checked){
                    count++;
                }
            }
        }
        return count;
    };
    $scope.checkboxClick = function(versionItem, event) {
        event.stopPropagation();
        angular.forEach(versionItem.filteredNodes, function(item) {
           item.checked = event.target.checked && item.user_can_command;
        });
    };

    $scope.isAnyNodeChecked = function() {
        for (var i=0 ; i < $scope.projectStatus.nodes_for_version.length; i++) {
            for (var j=0 ; j < $scope.projectStatus.nodes_for_version[i].filteredNodes.length; j++) {
                if ($scope.projectStatus.nodes_for_version[i].filteredNodes[j].checked) {
                    return true;
                }
            }
        }
        return false;
    };

    $scope.getAllSelectedNodes = function() {
        var res = [];
        for (var i=0 ; i < $scope.projectStatus.nodes_for_version.length; i++) {
            for (var j=0 ; j < $scope.projectStatus.nodes_for_version[i].filteredNodes.length; j++) {
                if ($scope.projectStatus.nodes_for_version[i].filteredNodes[j].checked) {
                    res.push($scope.projectStatus.nodes_for_version[i].filteredNodes[j]);
                }
            }
        }
        return res;
    };

    $scope.isAllNodesChecked = function() {
        if ($scope.allNodesCount === 0) {
            return false;
        }
        for (var i=0 ; i < $scope.projectStatus.nodes_for_version.length; i++) {
            for (var j=0 ; j < $scope.projectStatus.nodes_for_version[i].filteredNodes.length; j++) {
                if (!$scope.projectStatus.nodes_for_version[i].filteredNodes[j].checked) {
                    return false;
                }
            }
        }
        return true;
    };

    $scope.isVersionChecked = function(versionItem) {
        for (var i=0 ; i < versionItem.filteredNodes.length; i++) {
            if (!versionItem.filteredNodes[i].checked) {
                return false;
            }

        }
        return true;
    };

    $scope.doSelectAllNodes = function(event) {
        event.stopPropagation();
        angular.forEach($scope.projectStatus.nodes_for_version, function(versionItem) {
            angular.forEach(versionItem.filteredNodes, function(node) {
                node.checked = event.target.checked && node.user_can_command;
            });
        });
    };

    $scope.isNodeDisabled = function(node){
        return !node.user_can_command;
    };
    var unRegisterFunction = $rootScope.$on(Constants.EVENTS.BREADCRUMB_CLICKED, function () {
        $scope.clearFilters();
    });

    $scope.$on('$destroy',function() {
        $location.search({});
        unRegisterFunction();
        tagsChangedHandler();
    });
}]);<commit_msg>fixed #33 - add alert when no nodes selected<commit_after>'use strict';
angular.module('codeine').controller('projectStatusCtrl',['$scope','$rootScope','$log','$filter','$location','SelectedNodesService','Constants', 'AlertService', function($scope,$rootScope,$log,$filter,$location,SelectedNodesService,Constants,AlertService) {
    $scope.projectName = $scope.projectConfiguration.name;
    $scope.allNodesCount = 0;
    $log.debug('projectStatusCtrl: current project is ' + $scope.projectName);
    $log.debug('projectStatusCtrl: projectConfiguration = ' + angular.toJson($scope.projectConfiguration));
    $log.debug('projectStatusCtrl: projectStatus = ' + angular.toJson($scope.projectStatus));

    $scope.versionIsOpen = [];
    $scope.maxNodesToShow = 2;
    for (var i=0 ; i < $scope.projectStatus.nodes_for_version.length; i++) {
        $scope.versionIsOpen[i] = true;
    }
    $scope.collapseAll = function() {
        for (var i=0 ; i < $scope.versionIsOpen.length; i++) {
            $scope.versionIsOpen[i] = false;
        }
    };
    $scope.expandAll = function() {
        for (var i=0 ; i < $scope.versionIsOpen.length; i++) {
            $scope.versionIsOpen[i] = true;
        }
    };
    $scope.shouldShowClearFilters = function() {
        var search = $location.search();
        var noTagsOn = ((!angular.isDefined(search.tagsOn) || search.tagsOn.length === 0));
        var noTagsOff = ((!angular.isDefined(search.tagsOff) || search.tagsOff.length === 0));
        var noTags = noTagsOn && noTagsOff;
        return !(($scope.selectedMonitor === 'All Nodes') && ($scope.nodesFilter === '') && (noTags));
    };

    $scope.initValues = function() {
        $scope.selectedMonitor = 'All Nodes';
        $scope.nodesFilter = '';
    };

    $scope.clearFilters = function() {
        $location.search({});
        $scope.initValues();
        for (var j=0; j < $scope.projectStatus.tag_info.length ; j++) {
            $scope.projectStatus.tag_info[j].state = 0;
        }
        $scope.refreshFilters();
    };

    $scope.initValues();

    $scope.initFromQueryString = function() {
        var queryStringObject = $location.search();
        var shouldRefresh = false;
        if (angular.isDefined(queryStringObject.monitorFilter)) {
            shouldRefresh = true;
            $log.debug('projectStatusCtrl: Monitor filter init from query string - ' + queryStringObject.monitorFilter);
            $scope.selectedMonitor = queryStringObject.monitorFilter;
        }
        if (angular.isDefined(queryStringObject.tagsOn)) {
            shouldRefresh = true;
        }
        if (angular.isDefined(queryStringObject.tagsOff)) {
            shouldRefresh = true;
        }
        return shouldRefresh;
    };

    var tagsChangedHandler = $rootScope.$on(Constants.EVENTS.TAGS_CHANGED, function() {
        $scope.refreshFilters();
    });

    // Returns true if the node should be in the filtered array (Displayed)
    var isNodeFiltered = function(node) {
        return $filter('nodeFilter')(node,$scope.nodesFilter,$scope.selectedMonitor,$scope.projectStatus.tag_info);
    };

    var moveNodeToVisible = function(versionItem,node) {
        node.visible = true;
        if (!versionItem.visibleNodes) {
            versionItem.visibleNodes = [];
        }
        versionItem.visibleNodes.push(node);
    };

    for (var i1=0 ; i1 < $scope.projectStatus.nodes_for_version.length; i1++) {
        $scope.projectStatus.nodes_for_version[i1].filteredNodes = $scope.projectStatus.nodes_for_version[i1].nodes.slice();
        var maxNodesToShowHere = $scope.maxNodesToShow;
        if (maxNodesToShowHere > $scope.projectStatus.nodes_for_version[i1].filteredNodes.length || !$scope.projectStatus.more_nodes_enabled) {
            maxNodesToShowHere = $scope.projectStatus.nodes_for_version[i1].filteredNodes.length;
        }
        for (var j=0; j < maxNodesToShowHere; j++) {
            moveNodeToVisible($scope.projectStatus.nodes_for_version[i1],$scope.projectStatus.nodes_for_version[i1].filteredNodes[j]);
        }
    }

    for (var i2=0 ; i2 < $scope.projectStatus.nodes_for_version.length; i2++) {
        $scope.allNodesCount += $scope.projectStatus.nodes_for_version[i2].filteredNodes.length;
    }

    $scope.$watch("selectedMonitor",function( newName, oldName ) {
            if ( newName === oldName ) {
                return;
            }
            $log.debug('projectStatusCtrl: selectedMonitor was changed');
            $location.search('monitorFilter',newName);
            $scope.refreshFilters();
        }
    );

    $scope.$watch("nodesFilter",function( newName, oldName ) {
            if ( newName === oldName ) {
                return;
            }
            $log.debug('projectStatusCtrl: nodesFilter was changed');
            $scope.refreshFilters();
        }
    );

    $scope.showMore = function(count) {
        if ($scope.projectStatus.more_nodes_enabled) {
            return count < $scope.maxNodesToShow;
        }
        else {
            return true;
        }
    };
    $scope.refreshFilters = function() {
        var count = 0;
        for (var i=0 ; i < $scope.projectStatus.nodes_for_version.length; i++) {
            $scope.projectStatus.nodes_for_version[i].filteredNodes.splice(0,$scope.projectStatus.nodes_for_version[i].filteredNodes.length);
            $scope.projectStatus.nodes_for_version[i].visibleNodes.splice(0,$scope.projectStatus.nodes_for_version[i].visibleNodes.length);
            for (var j=0 ; j < $scope.projectStatus.nodes_for_version[i].nodes.length; j++) {
                $scope.projectStatus.nodes_for_version[i].nodes[j].visible = false;
                if (isNodeFiltered($scope.projectStatus.nodes_for_version[i].nodes[j])) {
                    $scope.projectStatus.nodes_for_version[i].filteredNodes.push($scope.projectStatus.nodes_for_version[i].nodes[j]);
                    count++;
                }
            }
            if ($scope.projectStatus.nodes_for_version[i].filteredNodes.length > 0) {
                for (var k=0; $scope.showMore(k) && k < $scope.projectStatus.nodes_for_version[i].filteredNodes.length; k++) {
                    moveNodeToVisible($scope.projectStatus.nodes_for_version[i],$scope.projectStatus.nodes_for_version[i].filteredNodes[k]);
                }
            }
        }
        $scope.allNodesCount = count;
    };

    if ($scope.initFromQueryString()) {
        $scope.refreshFilters();
    }

    $scope.loadMoreNodes = function(index) {
        var j = 0;
        if ($scope.projectStatus.nodes_for_version[index].filteredNodes.length === $scope.projectStatus.nodes_for_version[index].visibleNodes.length) {
            return;
        }
        for (var i=0; (i < $scope.projectStatus.nodes_for_version[index].filteredNodes.length) && (j < 10); i++) {
            if (!$scope.projectStatus.nodes_for_version[index].filteredNodes[i].visible) {
                moveNodeToVisible($scope.projectStatus.nodes_for_version[index],$scope.projectStatus.nodes_for_version[index].filteredNodes[i]);
                j++;
            }
        }
    };

    $scope.selectMonitor = function(monitor) {
        $scope.selectedMonitor = monitor;
    };

    $scope.runCommand = function(command) {
        $log.debug('projectStatusCtrl: will run command ' + command);
        if (!$scope.isAnyNodeChecked()) {
            $log.debug('projectStatusCtrl: no nodes selected, will do nothing');
            AlertService.addAlert('danger','No nodes selected to run the command.<br/>Please select nodes first.',6000);
            return;
        }
        var url = '/codeine/project/' + $scope.projectName + '/command/' + command + '/setup';
        SelectedNodesService.setSelectedNodes($scope.getAllSelectedNodes(),url);
        $location.path(url);
    };

    $scope.selectedCount = function(){
        var count = 0;
        for (var i=0 ; i < $scope.projectStatus.nodes_for_version.length; i++) {
            for (var j=0 ; j < $scope.projectStatus.nodes_for_version[i].filteredNodes.length; j++) {
                if ($scope.projectStatus.nodes_for_version[i].filteredNodes[j].checked){
                    count++;
                }
            }
        }
        return count;
    };
    $scope.checkboxClick = function(versionItem, event) {
        event.stopPropagation();
        angular.forEach(versionItem.filteredNodes, function(item) {
           item.checked = event.target.checked && item.user_can_command;
        });
    };

    $scope.isAnyNodeChecked = function() {
        for (var i=0 ; i < $scope.projectStatus.nodes_for_version.length; i++) {
            for (var j=0 ; j < $scope.projectStatus.nodes_for_version[i].filteredNodes.length; j++) {
                if ($scope.projectStatus.nodes_for_version[i].filteredNodes[j].checked) {
                    return true;
                }
            }
        }
        return false;
    };

    $scope.getAllSelectedNodes = function() {
        var res = [];
        for (var i=0 ; i < $scope.projectStatus.nodes_for_version.length; i++) {
            for (var j=0 ; j < $scope.projectStatus.nodes_for_version[i].filteredNodes.length; j++) {
                if ($scope.projectStatus.nodes_for_version[i].filteredNodes[j].checked) {
                    res.push($scope.projectStatus.nodes_for_version[i].filteredNodes[j]);
                }
            }
        }
        return res;
    };

    $scope.isAllNodesChecked = function() {
        if ($scope.allNodesCount === 0) {
            return false;
        }
        for (var i=0 ; i < $scope.projectStatus.nodes_for_version.length; i++) {
            for (var j=0 ; j < $scope.projectStatus.nodes_for_version[i].filteredNodes.length; j++) {
                if (!$scope.projectStatus.nodes_for_version[i].filteredNodes[j].checked) {
                    return false;
                }
            }
        }
        return true;
    };

    $scope.isVersionChecked = function(versionItem) {
        for (var i=0 ; i < versionItem.filteredNodes.length; i++) {
            if (!versionItem.filteredNodes[i].checked) {
                return false;
            }

        }
        return true;
    };

    $scope.doSelectAllNodes = function(event) {
        event.stopPropagation();
        angular.forEach($scope.projectStatus.nodes_for_version, function(versionItem) {
            angular.forEach(versionItem.filteredNodes, function(node) {
                node.checked = event.target.checked && node.user_can_command;
            });
        });
    };

    $scope.isNodeDisabled = function(node){
        return !node.user_can_command;
    };
    var unRegisterFunction = $rootScope.$on(Constants.EVENTS.BREADCRUMB_CLICKED, function () {
        $scope.clearFilters();
    });

    $scope.$on('$destroy',function() {
        $location.search({});
        unRegisterFunction();
        tagsChangedHandler();
    });
}]);<|endoftext|>