<commit_before>var fs = require('fs'),
  path = require('path'),
  async = require('../support/async.min.js'),
  exec = require('child_process').exec,
  spawn = require('child_process').spawn,
  Meta = require('./metadata.js');

exports = module.exports = function Processor(command) {
  // constant for timeout checks
  this.E_PROCESSTIMEOUT = -99;

  this.saveToFile = function(targetfile, callback) {

    this.options.outputfile = targetfile;
    var self = this;

    // parse options to command
    this._prepare(function(err, meta) {
      if (err) {
        callback(null, null, err);
      } else {
        var args = self.buildFfmpegArgs(false);

        // start conversion of file using spawn
        var ffmpegProc = self._spawnProcess(args);
        if (self.options.inputstream) {
            // pump input stream to stdin
            self.options.inputstream.resume();
            self.options.inputstream.pipe(ffmpegProc.stdin);
        }

        //handle timeout if set
        var processTimer;
        if (self.options.timeout) {
          processTimer = setTimeout(function() {
            ffmpegProc.removeAllListeners('exit');
            ffmpegProc.kill('SIGKILL');
            callback(self.E_PROCESSTIMEOUT, 'timeout');
          }, self.options.timeout);
        }

        var stdout = '';
        var stderr = '';
        ffmpegProc.on('exit', function(code) {
          if (processTimer) clearTimeout(processTimer);
          // check if we have to run flvtool2 to update flash video meta data
          if (self.options._updateFlvMetadata === true) {
            // check if flvtool2 is installed
            exec('which flvtool2', function(whichErr, whichStdOut, whichStdErr) {
              if (whichStdOut != '') {
                // update metadata in flash video
                exec('flvtool2 -U ' + self.options.outputfile, function(flvtoolErr, flvtoolStdout, flvtoolStderr) {
                  callback(stdout, stderr, null);
                });
              } else {
                callback(stdout, stderr, null);
              }
            });
          } else {
            callback(stdout, stderr, null);
          }
        });

        ffmpegProc.stdout.on('data', function (data) {
          stdout += data;
        });

        ffmpegProc.stderr.on('data', function (data) {
          stderr += data;
        });
      }
    });
  };

  this.writeToStream = function(stream, callback) {
    if (!this.options._isStreamable) {
      callback(null, new Error('selected output format is not streamable'));
    } else {
      var self = this;
      // parse options to command
      this._prepare(function(err, meta) {
        if (err) {
          callback(null, err);
        } else {
          var args = self.buildFfmpegArgs(true);
          // write data to stdout
          args.push('pipe:1');

          // start conversion of file using spawn
          var ffmpegProc = self._spawnProcess(args);

          if (self.options.inputstream) {
            // pump input stream to stdin
            self.options.inputstream.resume();
            self.options.inputstream.pipe(ffmpegProc.stdin);
          }

          //handle timeout if set
          var processTimer;
          if (self.options.timeout) {
            processTimer = setTimeout(function() {
              ffmpegProc.removeAllListeners('exit');
              ffmpegProc.kill('SIGKILL');
              callback(self.E_PROCESSTIMEOUT, 'timeout');
            }, self.options.timeout);
          }

          var stderr = '';

          ffmpegProc.stderr.on('data', function(data) {
            stderr += data;
          });

          ffmpegProc.stdout.on('data', function(chunk) {
          	stream.write(chunk);
          });

          ffmpegProc.on('exit', function(code, signal) {
            if (processTimer) clearTimeout(processTimer);
            // close file descriptor on outstream
            if(/http/.exec(self.options.inputfile)) {
            	callback(code, stderr);
            } else {
              var cb_ = function() {
                if (self.options.inputstream) {
                  fs.close(self.options.inputstream.fd, function() {
                    callback(code, stderr);
                  });
                }
                else {
                  callback(code, stderr);
                }
              };
              if ( !stream.fd ) {
                stream.end ? stream.end() : callback(code, "stream will not be closed");
                cb_();
              } else {
                fs.close(stream.fd, cb_);
              }
            }
          });
        }
      });
    }
  };

  this.takeScreenshot = function(config, callback) {
		if (!this.options.video.size) {
      callback(new Error("set size of thumbnails using 'withSize' method"));
    }

		var self = this;

    var input = Meta.escapedPath(this.options.inputfile);
    var output = Meta.escapedPath(config.outputfile);

		// build screenshot command
    var tnArgs = [
      '-ss', config.offset,
      '-i', input,
      '-vcodec', 'mjpeg',
      '-vframes', '1',
      '-an',
      '-f', 'rawvideo',
      '-s', self.options.video.size,
      '-y', output
    ];

		exec(process.env.FFMPEG_PATH || 'ffmpeg' + ' ' + tnArgs.join(' '), callback);
	}

  this.takeScreenshots = function(config, folder, callback) {
    var timemarks, screenshotcount;
    if (typeof config === 'object') {
      // use json object as config
      if (config.count)
        screenshotcount = config.count;
      if (config.timemarks)
        timemarks = config.timemarks;
    } else {
      // assume screenshot count as parameter
      screenshotcount = config;
      timemarks = null;
    }
    if (!this.options.video.size) {
      callback(new Error("set size of thumbnails using 'withSize' method"));
    }

    var self = this;
    // check target folder
    if (!fs.existsSync(folder)) {
      fs.mkdir(folder, '0755', function(err) {
        if (err !== null) {
          callback(err);
        } else {
          _screenShotInternal(callback);
        }
      });
    } else {
      _screenShotInternal(callback);
    }

    // read metadata from file
    function _screenShotInternal(callback) {
      // get correct dimensions
      self._prepare(function(err, meta) {
        if (meta.durationsec) {
          // check if all timemarks are inside duration
          if (timemarks !== null) {
            for (var i = 0; i < timemarks.length; i++) {
              if (parseInt(timemarks[i]) > (meta.durationsec * 0.9)) {
                // remove timemark from array
                timemarks.splice(i, 1);
              }
            }
            // if there are no more timemarks around, add one at end of the file
            if (timemarks.length == 0) {
              timemarks[0] = (meta.durationsec * 0.9);
            }
          }
          // get positions for screenshots (using duration of file minus 10% to remove fade-in/fade-out)
          var secondOffset = (meta.durationsec * 0.9) / screenshotcount;
          var donecount = 0;
          var series = [];

          var i = 1;

          // use async helper function to generate all screenshots and
          // fire callback just once after work is done
          async.until(
            function() {
              return i > screenshotcount;
            },
            function(taskcallback) {
              var offset;
              if (timemarks !== null) {
                // get timemark for current iteration
                offset = timemarks[(i - 1)];
              } else {
                offset = secondOffset * i;
              }
              var target = Meta.escapedPath(folder + '/tn_' + offset + 's.jpg');
              var input = Meta.escapedPath(self.options.inputfile);

              // build screenshot command
              var tnArgs = [
                '-ss', offset,
                '-i', input,
                '-vcodec', 'mjpeg',
                '-vframes', '1',
                '-an',
                '-f', 'rawvideo',
                '-s', self.options.video.size,
                '-y', target
              ];

              i++;

              if (self.options._nice.level) {
                // execute ffmpeg through nice
                exec('nice -n="' + self.options._nice.level + '" ffmpeg ' + tnArgs.join(' '), taskcallback);
              } else {
                exec(process.env.FFMPEG_PATH || 'ffmpeg' + ' ' + tnArgs.join(' '), taskcallback);
              }
            },
            function(err) {
              callback(err);
            }
          );
        } else {
          callback(new Error("meta data contains no duration, aborting screenshot creation"));
        }
      });
    }
  };

  this._spawnProcess = function(args, options) {
    var retProc = spawn(process.env.FFMPEG_PATH || 'ffmpeg', args, options);
    if (this.options._nice.level) {
      var niceLvl = (this.options._nice.level > 0 ? '+' + this.options._nice.level : this.options._nice.level);
      // renice the spawned process without waiting for callback
      exec('renice -n ' + niceLvl + ' -p ' + retProc.pid, function(err, stderr, stdout) {
        //console.log('successfully reniced process with pid ' + retProc.pid + ' to ' + niceLvl + ' niceness!');
      });
    }
    return retProc;
  };

  this.buildFfmpegArgs = function(overrideOutputCheck) {
    var args = [];

    // add startoffset and duration
    if (this.options.starttime) {
      args.push('-ss', this.options.starttime);
    }

    // add input file (if using fs mode)
    if (this.options.inputfile && !this.options.inputstream) {
      try
      {
        if(/http/.exec(this.options.inputfile)) {
          args.push('-i', this.options.inputfile.replace(' ', '%20'));
        } else {
          var fstats = fs.statSync(this.options.inputfile);
          if (fstats.isFile()) {
            // fix for spawn call with path containing spaces
            args.push('-i', this.options.inputfile.replace(' ', '\ '));
          } else {
            throw new Error('input file is not readable');
          }
        }
      } catch (err) {
        // handle error somehow
        throw err;
      }
    // check for input stream
    } else if (this.options.inputstream) {
      // push args to make ffmpeg read from stdin
      args.push('-i', '-');
    }

    if (this.options.duration) {
      args.push('-t', this.options.duration);
    }

    // add format
    if (this.options.format) {
      args.push('-f', this.options.format);
    }

    // add video options
    if (this.options.video.bitrate) {
      args.push('-b', this.options.video.bitrate + 'k');
      if (this.options._useConstantVideoBitrate) {
        // add parameters to ensure constant bitrate encoding
        args.push('-maxrate', this.options.video.bitrate + 'k');
        args.push('-minrate', this.options.video.bitrate + 'k');
        args.push('-bufsize', '3M');
      }
    } else {
      // use same quality for output as used in input
      args.push('-sameq');
    }
    if (this.options.video.codec) {
      args.push('-vcodec', this.options.video.codec);
    }
    if (this.options.video.fps) {
      args.push('-keyint_min', this.options.video.fps);
    }
    if (this.options.video.aspect) {
      args.push('-aspect', this.options.video.aspect);
    }

    // add video options
    if (this.options.audio.bitrate) {
      args.push('-ab', this.options.audio.bitrate + 'k');
    }
    if (this.options.audio.channels) {
      args.push('-ac', this.options.audio.channels);
    }
    if (this.options.audio.codec) {
      args.push('-acodec', this.options.audio.codec);
    }
    if (this.options.audio.frequency) {
      args.push('-ar', this.options.audio.frequency);
    }

    // add additional options
    if (this.options.additional) {
      if (this.options.additional.length > 0) {
        this.options.additional.forEach(function(el) {
          args.push(el);
        });
      }
    }
    if (this.options.video.pad) {
      // we have padding arguments, push
      args.push('-vf');
      args.push('pad=' + this.options.video.pad.w +
        ':' + this.options.video.pad.h +
        ':' + this.options.video.pad.x +
        ':' + this.options.video.pad.y +
        ':' + this.options.video.padcolor);
    }

    // add size and output file
    if (this.options.video.size) {
      args.push('-s', this.options.video.size);
    }

    if (this.options.outputfile) {
      args.push('-y', this.options.outputfile.replace(' ', '\ '));
    } else {
      if (!overrideOutputCheck) {
        throw new Error("no outputfile specified");
      }
    }

    return args;
  };
};
<commit_msg>should fix issue #30<commit_after>var fs = require('fs'),
  path = require('path'),
  async = require('../support/async.min.js'),
  exec = require('child_process').exec,
  spawn = require('child_process').spawn,
  Meta = require('./metadata.js');

exports = module.exports = function Processor(command) {
  // constant for timeout checks
  this.E_PROCESSTIMEOUT = -99;

  this._determineFfmpegPath = function() {
    if (process.env.FFMPEG_PATH) {
      return process.env.FFMPEG_PATH;
    }
    return 'ffmpeg';
  }

  this.saveToFile = function(targetfile, callback) {

    this.options.outputfile = targetfile;
    var self = this;

    // parse options to command
    this._prepare(function(err, meta) {
      if (err) {
        callback(null, null, err);
      } else {
        var args = self.buildFfmpegArgs(false);

        // start conversion of file using spawn
        var ffmpegProc = self._spawnProcess(args);
        if (self.options.inputstream) {
            // pump input stream to stdin
            self.options.inputstream.resume();
            self.options.inputstream.pipe(ffmpegProc.stdin);
        }

        //handle timeout if set
        var processTimer;
        if (self.options.timeout) {
          processTimer = setTimeout(function() {
            ffmpegProc.removeAllListeners('exit');
            ffmpegProc.kill('SIGKILL');
            callback(self.E_PROCESSTIMEOUT, 'timeout');
          }, self.options.timeout);
        }

        var stdout = '';
        var stderr = '';
        ffmpegProc.on('exit', function(code) {
          if (processTimer) clearTimeout(processTimer);
          // check if we have to run flvtool2 to update flash video meta data
          if (self.options._updateFlvMetadata === true) {
            // check if flvtool2 is installed
            exec('which flvtool2', function(whichErr, whichStdOut, whichStdErr) {
              if (whichStdOut != '') {
                // update metadata in flash video
                exec('flvtool2 -U ' + self.options.outputfile, function(flvtoolErr, flvtoolStdout, flvtoolStderr) {
                  callback(stdout, stderr, null);
                });
              } else {
                callback(stdout, stderr, null);
              }
            });
          } else {
            callback(stdout, stderr, null);
          }
        });

        ffmpegProc.stdout.on('data', function (data) {
          stdout += data;
        });

        ffmpegProc.stderr.on('data', function (data) {
          stderr += data;
        });
      }
    });
  };

  this.writeToStream = function(stream, callback) {
    if (!this.options._isStreamable) {
      callback(null, new Error('selected output format is not streamable'));
    } else {
      var self = this;
      // parse options to command
      this._prepare(function(err, meta) {
        if (err) {
          callback(null, err);
        } else {
          var args = self.buildFfmpegArgs(true);
          // write data to stdout
          args.push('pipe:1');

          // start conversion of file using spawn
          var ffmpegProc = self._spawnProcess(args);

          if (self.options.inputstream) {
            // pump input stream to stdin
            self.options.inputstream.resume();
            self.options.inputstream.pipe(ffmpegProc.stdin);
          }

          //handle timeout if set
          var processTimer;
          if (self.options.timeout) {
            processTimer = setTimeout(function() {
              ffmpegProc.removeAllListeners('exit');
              ffmpegProc.kill('SIGKILL');
              callback(self.E_PROCESSTIMEOUT, 'timeout');
            }, self.options.timeout);
          }

          var stderr = '';

          ffmpegProc.stderr.on('data', function(data) {
            stderr += data;
          });

          ffmpegProc.stdout.on('data', function(chunk) {
          	stream.write(chunk);
          });

          ffmpegProc.on('exit', function(code, signal) {
            if (processTimer) clearTimeout(processTimer);
            // close file descriptor on outstream
            if(/http/.exec(self.options.inputfile)) {
            	callback(code, stderr);
            } else {
              var cb_ = function() {
                if (self.options.inputstream) {
                  fs.close(self.options.inputstream.fd, function() {
                    callback(code, stderr);
                  });
                }
                else {
                  callback(code, stderr);
                }
              };
              if ( !stream.fd ) {
                stream.end ? stream.end() : callback(code, "stream will not be closed");
                cb_();
              } else {
                fs.close(stream.fd, cb_);
              }
            }
          });
        }
      });
    }
  };

  this.takeScreenshot = function(config, callback) {
		if (!this.options.video.size) {
      callback(new Error("set size of thumbnails using 'withSize' method"));
    }

		var self = this;

    var input = Meta.escapedPath(this.options.inputfile);
    var output = Meta.escapedPath(config.outputfile);

		// build screenshot command
    var tnArgs = [
      '-ss', config.offset,
      '-i', input,
      '-vcodec', 'mjpeg',
      '-vframes', '1',
      '-an',
      '-f', 'rawvideo',
      '-s', self.options.video.size,
      '-y', output
    ];

		exec(this._determineFfmpegPath() + ' ' + tnArgs.join(' '), callback);
	}

  this.takeScreenshots = function(config, folder, callback) {
    var timemarks, screenshotcount;
    if (typeof config === 'object') {
      // use json object as config
      if (config.count)
        screenshotcount = config.count;
      if (config.timemarks)
        timemarks = config.timemarks;
    } else {
      // assume screenshot count as parameter
      screenshotcount = config;
      timemarks = null;
    }
    if (!this.options.video.size) {
      callback(new Error("set size of thumbnails using 'withSize' method"));
    }

    var self = this;
    // check target folder
    if (!fs.existsSync(folder)) {
      fs.mkdir(folder, '0755', function(err) {
        if (err !== null) {
          callback(err);
        } else {
          _screenShotInternal(callback);
        }
      });
    } else {
      _screenShotInternal(callback);
    }

    // read metadata from file
    function _screenShotInternal(callback) {
      // get correct dimensions
      self._prepare(function(err, meta) {
        if (meta.durationsec) {
          // check if all timemarks are inside duration
          if (timemarks !== null) {
            for (var i = 0; i < timemarks.length; i++) {
              if (parseInt(timemarks[i]) > (meta.durationsec * 0.9)) {
                // remove timemark from array
                timemarks.splice(i, 1);
              }
            }
            // if there are no more timemarks around, add one at end of the file
            if (timemarks.length == 0) {
              timemarks[0] = (meta.durationsec * 0.9);
            }
          }
          // get positions for screenshots (using duration of file minus 10% to remove fade-in/fade-out)
          var secondOffset = (meta.durationsec * 0.9) / screenshotcount;
          var donecount = 0;
          var series = [];

          var i = 1;

          // use async helper function to generate all screenshots and
          // fire callback just once after work is done
          async.until(
            function() {
              return i > screenshotcount;
            },
            function(taskcallback) {
              var offset;
              if (timemarks !== null) {
                // get timemark for current iteration
                offset = timemarks[(i - 1)];
              } else {
                offset = secondOffset * i;
              }
              var target = Meta.escapedPath(folder + '/tn_' + offset + 's.jpg');
              var input = Meta.escapedPath(self.options.inputfile);

              // build screenshot command
              var tnArgs = [
                '-ss', offset,
                '-i', input,
                '-vcodec', 'mjpeg',
                '-vframes', '1',
                '-an',
                '-f', 'rawvideo',
                '-s', self.options.video.size,
                '-y', target
              ];

              i++;

              if (self.options._nice.level) {
                // execute ffmpeg through nice
                exec('nice -n="' + self.options._nice.level + '" ' + self._determineFfmpegPath() + ' ' + tnArgs.join(' '), taskcallback);
              } else {
                exec(self._determineFfmpegPath() + ' ' + tnArgs.join(' '), taskcallback);
              }
            },
            function(err) {
              callback(err);
            }
          );
        } else {
          callback(new Error("meta data contains no duration, aborting screenshot creation"));
        }
      });
    }
  };

  this._spawnProcess = function(args, options) {
    var retProc = spawn(this._determineFfmpegPath(), args, options);
    if (this.options._nice.level) {
      var niceLvl = (this.options._nice.level > 0 ? '+' + this.options._nice.level : this.options._nice.level);
      // renice the spawned process without waiting for callback
      exec('renice -n ' + niceLvl + ' -p ' + retProc.pid, function(err, stderr, stdout) {
        //console.log('successfully reniced process with pid ' + retProc.pid + ' to ' + niceLvl + ' niceness!');
      });
    }
    return retProc;
  };

  this.buildFfmpegArgs = function(overrideOutputCheck) {
    var args = [];

    // add startoffset and duration
    if (this.options.starttime) {
      args.push('-ss', this.options.starttime);
    }

    // add input file (if using fs mode)
    if (this.options.inputfile && !this.options.inputstream) {
      try
      {
        if(/http/.exec(this.options.inputfile)) {
          args.push('-i', this.options.inputfile.replace(' ', '%20'));
        } else {
          var fstats = fs.statSync(this.options.inputfile);
          if (fstats.isFile()) {
            // fix for spawn call with path containing spaces
            args.push('-i', this.options.inputfile.replace(' ', '\ '));
          } else {
            throw new Error('input file is not readable');
          }
        }
      } catch (err) {
        // handle error somehow
        throw err;
      }
    // check for input stream
    } else if (this.options.inputstream) {
      // push args to make ffmpeg read from stdin
      args.push('-i', '-');
    }

    if (this.options.duration) {
      args.push('-t', this.options.duration);
    }

    // add format
    if (this.options.format) {
      args.push('-f', this.options.format);
    }

    // add video options
    if (this.options.video.bitrate) {
      args.push('-b', this.options.video.bitrate + 'k');
      if (this.options._useConstantVideoBitrate) {
        // add parameters to ensure constant bitrate encoding
        args.push('-maxrate', this.options.video.bitrate + 'k');
        args.push('-minrate', this.options.video.bitrate + 'k');
        args.push('-bufsize', '3M');
      }
    } else {
      // use same quality for output as used in input
      args.push('-sameq');
    }
    if (this.options.video.codec) {
      args.push('-vcodec', this.options.video.codec);
    }
    if (this.options.video.fps) {
      args.push('-keyint_min', this.options.video.fps);
    }
    if (this.options.video.aspect) {
      args.push('-aspect', this.options.video.aspect);
    }

    // add video options
    if (this.options.audio.bitrate) {
      args.push('-ab', this.options.audio.bitrate + 'k');
    }
    if (this.options.audio.channels) {
      args.push('-ac', this.options.audio.channels);
    }
    if (this.options.audio.codec) {
      args.push('-acodec', this.options.audio.codec);
    }
    if (this.options.audio.frequency) {
      args.push('-ar', this.options.audio.frequency);
    }

    // add additional options
    if (this.options.additional) {
      if (this.options.additional.length > 0) {
        this.options.additional.forEach(function(el) {
          args.push(el);
        });
      }
    }
    if (this.options.video.pad) {
      // we have padding arguments, push
      args.push('-vf');
      args.push('pad=' + this.options.video.pad.w +
        ':' + this.options.video.pad.h +
        ':' + this.options.video.pad.x +
        ':' + this.options.video.pad.y +
        ':' + this.options.video.padcolor);
    }

    // add size and output file
    if (this.options.video.size) {
      args.push('-s', this.options.video.size);
    }

    if (this.options.outputfile) {
      args.push('-y', this.options.outputfile.replace(' ', '\ '));
    } else {
      if (!overrideOutputCheck) {
        throw new Error("no outputfile specified");
      }
    }

    return args;
  };
};
<|endoftext|>


<commit_before>/*jslint node: true */
'use strict';
var async     = require("async")
    , _       = require("lodash")
    , request = require("request")
    , utils   = require("./../utils")
    , commons = require("./../commons")
    , config
    , log;

/**
 * The raw headers as they were received are pass in the headers parameter, which is an array of [key, value, key2, value2, ...]
 * @param headers: array
 * @private
 */
function _getRawOriginalHeaders (rawHeaders) {
    rawHeaders = rawHeaders || [];
    var rawHeadersStr = "";
    for (var i=0, j=1; i< rawHeaders.length; i+=2, j+=2) {
        rawHeadersStr += rawHeaders[i] + ": " + rawHeaders[j];
        // for the last headers don't add '\n'
        rawHeadersStr += (i < rawHeaders.length-2) ? "\n" : "";
    }
    return rawHeadersStr;
}

/**
 * callback method to be called once request return from call
 * @param req
 * @param error
 * @param response
 * @param body
 * @param callback
 * @private
 */
function _process(req, error, response, body, callback) {
    log.debug("[processor] _process called");
    var _result = {};

    if (!response) {
        log.error("[processor] _process no response: " + response);
        _result = utils.builder.buildResponse(error.code || commons.CONST.RESPONSE_TYPE.ERROR_API_URL);
    }
    else {
        if (error) {
            log.error(commons.CONST.BODY_RESPONSE.ERROR_API_URL + " " + req.url +  " Error Code: " + error.code + ", IP: " + req.ip);
            _result = utils.builder.buildResponse(commons.CONST.RESPONSE_TYPE.ERROR_API_URL);
        } else {
            try {
                log.debug("[processor] _process body: " + body);
                _result.body = JSON.parse(body);
            } catch (err) {
                _result.body = body;
                log.debug(commons.CONST.BODY_RESPONSE.NO_JSON_OBJECT + ", IP: " + req.ip);
            }
        }

        // set the headers/status in the _result object
        _result.headers = _result.headers || response.headers;
        _result.statusCode = _result.statusCode || response.statusCode;
        _result.HTTPStatus = _result.HTTPStatus || response.statusCode;

        // if the flag originalHeader is true, add originalHeader to the response
        if (config.originalHeader) {
            _result.originalHeader = _getRawOriginalHeaders(response.rawHeaders);
        }

    }

    // callback of the async.parallel method (async.parallel(reqs, function (err, _results) {...))
    // in the callback expects two parameters - error and _results
    return callback(null, _result);
}

/**
 * method to be called in parallel from async.parallelLimit
 * @param req - request to threat
 * @returns {Function}
 * @private
 */
function _getReq(req) {
    return function (cb) {
        var body = req.body || req.data;

        try {
            // body - entity body for PATCH, POST and PUT requests. Must be a Buffer or String, unless json is true. If json is true, then body must be a JSON-serializable object.
            body = (body) ? JSON.stringify(body) : null;
            req = req || {};
            var options = {
                strictSSL: (typeof config.strictSSL !== "undefined") ? config.strictSSL : true,
                url: req.url,
                headers: req.headers || {},
                method: req.method,
                body: body,
                timeout: (req.timeout <= config.request_default_values.timeout) ? req.timeout : config.request_default_values.timeout,
                pool : {
                    maxSockets : 200
                }
            };

            // if the body is not and empty string, we pass it to the request
            if (body !== null) {
                options.body = body;
            }
            options.headers.HTTP_X_FORWARDED_FOR = req.ip;

            log.debug("[processor] Requesting URL: " + options.url + ", headers: " + options.headers + ", method: " + options.method + ", body(when POST method): " + options.body + ", timeout: "  + options.timeout);

            // request to the API host for every job
            request(options, function(error, response, body) {
                _process(req, error, response, body, cb);
            });
        }
        catch (e) {
            log.error("[processor] exception parsing the body or in request");
        }

    };
}

/**
 * configure object
 * @param cfg
 * @returns {*}
 */
exports.configure = function (cfg) {
    config = commons.helper.configure(cfg);
    utils.configure(cfg);
    log = config.logger || console;
    return config;
};

/**
 * entry point to start running jobs
 * @param job - job containing one or more requests
 * @param cb - callback method to call once finish processing job
 */
exports.run = function (job, cb) {

    log.debug("[processor] running processor ...");
    var _reqs = {};

    // creates the collection of 'requests' (holds a collection of getReq methods)
    _.forEach(job, function (cReq) {
        _reqs[cReq.name] =_getReq(cReq);
    });

    async.parallelLimit(_reqs, config.maxConcurrentJobs, function (err, results) {
        if (err) {
            log.error("[processor] Error in running the job, err: " + err);
            return cb(err);
        }
        else {
            return cb(null, results);
        }
    });

};
<commit_msg>add runner test for eterator + fix useImmediate implementation add caseSensitiveHeaders per request and return the headers with the first letter with Capital letter<commit_after>/*jslint node: true */
'use strict';
var async     = require("async")
    , _       = require("lodash")
    , request = require("request")
    , utils   = require("./../utils")
    , commons = require("./../commons")
    , config
    , log;

/**
 * The raw headers as they were received are pass in the headers parameter, which is an array of [key, value, key2, value2, ...]
 * @param headers: array
 * @private
 */
function _getRawOriginalHeaders (rawHeaders) {
    rawHeaders = rawHeaders || [];
    var rawHeadersStr = "";
    var rawHeadersObj = {};
    for (var i=0, j=1; i< rawHeaders.length; i+=2, j+=2) {
        rawHeadersStr += rawHeaders[i] + ": " + rawHeaders[j];
        rawHeadersObj[rawHeaders[i]] = rawHeaders[j];
        // for the last headers don't add '\n'
        rawHeadersStr += (i < rawHeaders.length-2) ? "\n" : "";
    }
    return {
        originalHeadersStr: rawHeadersStr,
        originalHeadersObj: rawHeadersObj
    };
}

/**
 * callback method to be called once request return from call
 * @param req
 * @param error
 * @param response
 * @param body
 * @param callback
 * @private
 */
function _process(req, error, response, body, callback) {
    log.debug("[processor] _process called");
    var _result = {};
    var rawHeaders;

    if (!response) {
        log.error("[processor] _process no response: " + response);
        _result = utils.builder.buildResponse(error.code || commons.CONST.RESPONSE_TYPE.ERROR_API_URL);
    }
    else {
        if (error) {
            log.error(commons.CONST.BODY_RESPONSE.ERROR_API_URL + " " + req.url +  " Error Code: " + error.code + ", IP: " + req.ip);
            _result = utils.builder.buildResponse(commons.CONST.RESPONSE_TYPE.ERROR_API_URL);
        } else {
            try {
                log.debug("[processor] _process body: " + body);
                _result.body = JSON.parse(body);
            } catch (err) {
                _result.body = body;
                log.debug(commons.CONST.BODY_RESPONSE.NO_JSON_OBJECT + ", IP: " + req.ip);
            }
        }

        // set the headers/status in the _result object
        _result.headers = _result.headers || response.headers;
        _result.statusCode = _result.statusCode || response.statusCode;
        _result.HTTPStatus = _result.HTTPStatus || response.statusCode;

        // if the flag originalHeader is true, add originalHeader to the response
        if (config.originalHeader || req.caseSensitiveHeaders) {

            // call to the method in case one of the options is true
            rawHeaders = _getRawOriginalHeaders(response.rawHeaders);

            if (config.originalHeader) {
                _result.originalHeader = rawHeaders.originalHeadersStr;
            }

            _result.headers = (req.caseSensitiveHeaders) ? rawHeaders.originalHeadersObj : _result.headers;

        }

    }

    // callback of the async.parallel method (async.parallel(reqs, function (err, _results) {...))
    // in the callback expects two parameters - error and _results
    return callback(null, _result);
}

/**
 * method to be called in parallel from async.parallelLimit
 * @param req - request to threat
 * @returns {Function}
 * @private
 */
function _getReq(req) {
    return function (cb) {
        var body = req.body || req.data;

        try {
            // body - entity body for PATCH, POST and PUT requests. Must be a Buffer or String, unless json is true. If json is true, then body must be a JSON-serializable object.
            body = (body) ? JSON.stringify(body) : null;
            req = req || {};
            var options = {
                strictSSL: (typeof config.strictSSL !== "undefined") ? config.strictSSL : true,
                url: req.url,
                headers: req.headers || {},
                method: req.method,
                body: body,
                timeout: (req.timeout <= config.request_default_values.timeout) ? req.timeout : config.request_default_values.timeout,
                pool : {
                    maxSockets : 200
                }
            };

            // if the body is not and empty string, we pass it to the request
            if (body !== null) {
                options.body = body;
            }
            options.headers.HTTP_X_FORWARDED_FOR = req.ip;

            log.debug("[processor] Requesting URL: " + options.url + ", headers: " + options.headers + ", method: " + options.method + ", body(when POST method): " + options.body + ", timeout: "  + options.timeout);

            // request to the API host for every job
            request(options, function(error, response, body) {
                _process(req, error, response, body, cb);
            });
        }
        catch (e) {
            log.error("[processor] exception parsing the body or in request");
        }

    };
}

/**
 * configure object
 * @param cfg
 * @returns {*}
 */
exports.configure = function (cfg) {
    config = commons.helper.configure(cfg);
    utils.configure(cfg);
    log = config.logger || console;
    return config;
};

/**
 * entry point to start running jobs
 * @param job - job containing one or more requests
 * @param cb - callback method to call once finish processing job
 */
exports.run = function (job, cb) {

    log.debug("[processor] running processor ...");
    var _reqs = {};

    // creates the collection of 'requests' (holds a collection of getReq methods)
    _.forEach(job, function (cReq) {
        _reqs[cReq.name] =_getReq(cReq);
    });

    async.parallelLimit(_reqs, config.maxConcurrentJobs, function (err, results) {
        if (err) {
            log.error("[processor] Error in running the job, err: " + err);
            return cb(err);
        }
        else {
            return cb(null, results);
        }
    });

};
<|endoftext|>


<commit_before>var _ = require("lodash");
var assert = require("assert");
var normalizer = require("./normalize");
var yaml = require("./yaml");
var path = require("path");
var Plugins = require("./plugins");
var Context = require("./context");
var Promise = require("bluebird");
var verror = require("verror");
var semver = require("semver");

var logger = require("./logging").logger("hyperpotamus.index");

var default_options = {
	emit: console.log, // eslint-disable-line no-console
	safe: false,
	auto_load_plugins: false,
	sessionDefaults: {},
	response_defaults: { status: 200 }
};

function Processor(options) {
	var self = this;
	self.options = _.defaults({}, options, default_options);
	self.plugins = new Plugins();
	self.loadFile = loadFile;
	self.loadString = loadString;
	self.normalize = normalize;
	self.process = process;
	self.processFile = processFile;

	function loadFile(scriptPath) {
		var script = yaml.loadFile(scriptPath, self.options.safe);
		if (self.options.auto_load_plugins && !_.isNil(script.plugins)) {
			self.plugins.load(script.plugins, path.dirname(scriptPath));
		}
		return script;
	}

	function loadString(scriptString, basePath) {
		var script = yaml.load(scriptString, self.options.safe);
		if (self.options.auto_load_plugins && !_.isNil(script.plugins)) {
			self.plugins.load(script.plugins, basePath);
		}
		return script;
	}

	function normalize(script) {
		return normalizer(script, self.plugins);
	}

	function processFile(scriptPath, session, firstStep) {
		var script = loadFile(scriptPath);
		return process(script, session, firstStep);
	}

	// TODO - Refactoring needed
	// Eliminate the closure by encapsulating into the class state. I've intentionally tried to keep the
	// processor as a reusable object that can process multiple script instances (for plugin loading, options,
	// etc.) but that might be causing more trouble than it solves.
	function process(script, session, firstStep) {
		assert(!_.isNil(script), "script is null or empty");
		assert(_.isNil(session) || _.isObject(session), "If session is specified, it must be an object with name/value properties.");

		script = self.normalize(script);

		if (script.version) {
			var hyperpotamus_version = require("../package.json").version;
			if (!semver.satisfies(hyperpotamus_version, script.version)) {
				return Promise.reject(new verror.VError({
					name: "VersionMismatchError",
					info: {
						current_version: hyperpotamus_version,
						required_version: script.version
					}
				}));
			}
		}

		logger.debug("Initializing new session context.");
		var context = new Context(session, self, processAction, handleDirective);

		// Find the first step to process
		var step = firstStep ? _.find(script.steps, { name: firstStep }) : _.first(script.steps);
		if (!step) {
			throw new verror.VError({
				name: "StepNotFoundError",
				info: {
					goto: firstStep || "#1"
				}
			});
		}
		// And kick off the script with the first step
		return processStep(step, context);

		function processStep(step, context) {
			logger.debug(`Processing step - ${step.path}`);
			logger.trace(`Step is \n${yaml.dump(step, { noRefs: true })}`);
			context.current_step = step;
			return processAction(step.actions, context).then(() => {
				logger.debug("Step processed successfully.");
				var next_index = _.indexOf(script.steps, step) + 1;
				var next_step = _.nth(script.steps, next_index);
				return gotoNextStep(next_step, context);
			}).catch(err => {
				var jumpResult = verror.findCauseByName(err, "JumpResult");
				if (jumpResult !== null) {
					return handleDirective(verror.info(jumpResult), step);
				}
				// If it's not already an error object, let's wrap it in one
				if (!(err instanceof verror.VError)) {
					err = new verror.WError({
						name: "StepProcessingError",
						cause: err,
					}, "Unexpected error occurred during step processing");
				}
				err.jse_info.step = step;
				throw err;
			});
		}

		function handleDirective(directive, step) {
			var next_step;
			logger.debug("Action processing completed with goto directive for - " + directive.goto);

			// Special targets
			if (directive.goto === "END") {
				next_step = null;
			}
			else if (directive.goto === "SELF") { // Jump to the same request again
				logger.trace("Special case, jumping to 'SELF'");
				next_step = step;
			}
			else if (directive.goto === "NEXT") {
				next_step = _.nth(script.steps, _.indexOf(script.steps, step) + 1);
			}

			// Named target
			else {
				logger.trace("Next step will be " + directive.goto);
				next_step = _.find(script.steps, { name: directive.goto });
				if (!next_step) {
					logger.warn("Could not find next step from goto=" + directive.goto);
					var err = new verror.VError({
						name: "StepNotFoundError",
						info: {
							goto: directive.goto,
							step: step
						}
					});
					err.step = step;
					err.context = directive.context || context;
					throw err;
				}
			}
			return gotoNextStep(next_step, directive.context || context);
		}

		function gotoNextStep(next_step, context) {
			if (next_step) {
				logger.debug("Next step will be - " + (next_step.name || next_step.path));
				return processStep(next_step, context);
			}
			else {
				logger.debug("Reached the end of the script.");
				logger.debug("No more steps, script completed.");
				return context; // Reached the end of the script
			}
		}

		function processAction(action, context) {
			if (_.isArray(action)) {
				// Loop through each response validation step and verify
				return Promise.mapSeries(action, single_action => processAction(single_action, context)).then(results => {
					assert(_.filter(results, !_.isNil).length == 0, "Code: PM-001 - processAction array returned values"); // Should never happen
				});
			}

			logger.trace("Processing action - " + yaml.dump(action, { noRefs: true }));
			return promiseActionExecution(action, context).then(context => {
				if (action.on_success) {
					// Abort processing of the current series of actions and start processing on_success actions
					return processAction(action.on_success, context);
				}
				return context;
			}).catch(err => {
				// Action-processing error
				if (action.on_failure) {
					logger.info(`Handling error: now processing on_failure actions.\n Action: ${yaml.dump(action, { noRefs: true })}\n Error: ${err}`);
					context.error = err;
					context.session.error = err;
					// Abort processing of the current series of actions and start processing on_failure actions
					return processAction(action.on_failure, context).finally(() => {
						// Remove the error from the context once we've processed the on_failure
						delete context.error;
						delete context.session.error;
					});
				}
				else {
					var throwable;
					if (_.has(err, "goto")) {
						throwable = new verror.VError({
							name: "JumpResult",
							info: {
								goto: err.goto,
								context
							}
						});
					}
					else {
						if (_.isError(err)) {
							throwable = new verror.WError({
								name: "ActionProcessingError",
								cause: err,
								info: {
									action,
									session: context.session
								}
							}, "Error processing action - %s", action.path);
						}
						else {
							throwable = new verror.VError({
								name: "ActionProcessingError",
								info: {
									action,
									session: context.session,
									err
								}
							}, `Error processing action - ${err}`);
						}
					}
					throw throwable;
				}
			});
		}

		function promiseActionExecution(action, context) {
			try {
				var plugin = self.plugins.findPluginForAction(action);
				if (context.options.safe && !plugin.safe) {
					throw new verror.VError({
						name: "UnsafePluginError",
						info: {
							action: action,
							plugin: plugin.name
						}
					}, "Unsafe plugin cannot be executed in safe-mode");
				}
				if (plugin.manual_interpolation != true) {
					action = context.interpolate(action, _.union(["on_success", "on_failure"], _.castArray(plugin.manual_interpolation || []))); // Need to exclude these so we can defer the interpolation
				}
				if (plugin.process.length == 1) { // Synchronous plugin
					return Promise.try(() => {
						if (action.debugger) {
							debugger; // eslint-disable-line no-debugger
						}
						// Dear user: for debugging actions with hdb, step into the next line
						return plugin.process.call(action, context) || context;
					});
				}
				if (plugin.process.length == 2) { // Async plugin
					return new Promise((fulfill, reject) => {
						if (action.debugger) {
							debugger; // eslint-disable-line no-debugger
						}
						// Dear user: for debugging actions with hdb, step into the next line
						plugin.process.call(action, context, (err, result) => {
							if (err) {
								return reject(err);
							}
							return fulfill(result || context);
						});
					});
				}
				else {
					throw new verror.VError({
						name: "InvalidPluginError",
						info: {
							plugin: plugin.name
						}
					}, "Unexpected number of arguments for plugin.process() method. (Must be 1 or 2 arguments)");
				}
			}
			catch (err) {
				var toThrow = err;
				if (!(err instanceof verror.VError)) {
					toThrow = new verror.WError({
						name: "ActionProcessingError",
						cause: err,
					}, "Unexpected error occurred during action processing");
				}
				toThrow.jse_info.action = action;
				throw toThrow;
			}
		}
	}
}

module.exports = Processor;
<commit_msg>Formatting for log message<commit_after>var _ = require("lodash");
var assert = require("assert");
var normalizer = require("./normalize");
var yaml = require("./yaml");
var path = require("path");
var Plugins = require("./plugins");
var Context = require("./context");
var Promise = require("bluebird");
var verror = require("verror");
var semver = require("semver");

var logger = require("./logging").logger("hyperpotamus.index");

var default_options = {
	emit: console.log, // eslint-disable-line no-console
	safe: false,
	auto_load_plugins: false,
	sessionDefaults: {},
	response_defaults: { status: 200 }
};

function Processor(options) {
	var self = this;
	self.options = _.defaults({}, options, default_options);
	self.plugins = new Plugins();
	self.loadFile = loadFile;
	self.loadString = loadString;
	self.normalize = normalize;
	self.process = process;
	self.processFile = processFile;

	function loadFile(scriptPath) {
		var script = yaml.loadFile(scriptPath, self.options.safe);
		if (self.options.auto_load_plugins && !_.isNil(script.plugins)) {
			self.plugins.load(script.plugins, path.dirname(scriptPath));
		}
		return script;
	}

	function loadString(scriptString, basePath) {
		var script = yaml.load(scriptString, self.options.safe);
		if (self.options.auto_load_plugins && !_.isNil(script.plugins)) {
			self.plugins.load(script.plugins, basePath);
		}
		return script;
	}

	function normalize(script) {
		return normalizer(script, self.plugins);
	}

	function processFile(scriptPath, session, firstStep) {
		var script = loadFile(scriptPath);
		return process(script, session, firstStep);
	}

	// TODO - Refactoring needed
	// Eliminate the closure by encapsulating into the class state. I've intentionally tried to keep the
	// processor as a reusable object that can process multiple script instances (for plugin loading, options,
	// etc.) but that might be causing more trouble than it solves.
	function process(script, session, firstStep) {
		assert(!_.isNil(script), "script is null or empty");
		assert(_.isNil(session) || _.isObject(session), "If session is specified, it must be an object with name/value properties.");

		script = self.normalize(script);

		if (script.version) {
			var hyperpotamus_version = require("../package.json").version;
			if (!semver.satisfies(hyperpotamus_version, script.version)) {
				return Promise.reject(new verror.VError({
					name: "VersionMismatchError",
					info: {
						current_version: hyperpotamus_version,
						required_version: script.version
					}
				}));
			}
		}

		logger.debug("Initializing new session context.");
		var context = new Context(session, self, processAction, handleDirective);

		// Find the first step to process
		var step = firstStep ? _.find(script.steps, { name: firstStep }) : _.first(script.steps);
		if (!step) {
			throw new verror.VError({
				name: "StepNotFoundError",
				info: {
					goto: firstStep || "#1"
				}
			});
		}
		// And kick off the script with the first step
		return processStep(step, context);

		function processStep(step, context) {
			logger.debug(`Processing step - ${step.path}`);
			logger.trace(`Step is \n${yaml.dump(step, { noRefs: true })}`);
			context.current_step = step;
			return processAction(step.actions, context).then(() => {
				logger.debug("Step processed successfully.");
				var next_index = _.indexOf(script.steps, step) + 1;
				var next_step = _.nth(script.steps, next_index);
				return gotoNextStep(next_step, context);
			}).catch(err => {
				var jumpResult = verror.findCauseByName(err, "JumpResult");
				if (jumpResult !== null) {
					return handleDirective(verror.info(jumpResult), step);
				}
				// If it's not already an error object, let's wrap it in one
				if (!(err instanceof verror.VError)) {
					err = new verror.WError({
						name: "StepProcessingError",
						cause: err,
					}, "Unexpected error occurred during step processing");
				}
				err.jse_info.step = step;
				throw err;
			});
		}

		function handleDirective(directive, step) {
			var next_step;
			logger.debug("Action processing completed with goto directive for - " + directive.goto);

			// Special targets
			if (directive.goto === "END") {
				next_step = null;
			}
			else if (directive.goto === "SELF") { // Jump to the same request again
				logger.trace("Special case, jumping to 'SELF'");
				next_step = step;
			}
			else if (directive.goto === "NEXT") {
				next_step = _.nth(script.steps, _.indexOf(script.steps, step) + 1);
			}

			// Named target
			else {
				logger.trace("Next step will be " + directive.goto);
				next_step = _.find(script.steps, { name: directive.goto });
				if (!next_step) {
					logger.warn("Could not find next step from goto=" + directive.goto);
					var err = new verror.VError({
						name: "StepNotFoundError",
						info: {
							goto: directive.goto,
							step: step
						}
					});
					err.step = step;
					err.context = directive.context || context;
					throw err;
				}
			}
			return gotoNextStep(next_step, directive.context || context);
		}

		function gotoNextStep(next_step, context) {
			if (next_step) {
				logger.debug("Next step will be - " + (next_step.name || next_step.path));
				return processStep(next_step, context);
			}
			else {
				logger.debug("Reached the end of the script.");
				logger.debug("No more steps, script completed.");
				return context; // Reached the end of the script
			}
		}

		function processAction(action, context) {
			if (_.isArray(action)) {
				// Loop through each response validation step and verify
				return Promise.mapSeries(action, single_action => processAction(single_action, context)).then(results => {
					assert(_.filter(results, !_.isNil).length == 0, "Code: PM-001 - processAction array returned values"); // Should never happen
				});
			}

			logger.trace("Processing action - \n" + yaml.dump(action, { noRefs: true }));
			return promiseActionExecution(action, context).then(context => {
				if (action.on_success) {
					// Abort processing of the current series of actions and start processing on_success actions
					return processAction(action.on_success, context);
				}
				return context;
			}).catch(err => {
				// Action-processing error
				if (action.on_failure) {
					logger.info(`Handling error: now processing on_failure actions.\n Action: ${yaml.dump(action, { noRefs: true })}\n Error: ${err}`);
					context.error = err;
					context.session.error = err;
					// Abort processing of the current series of actions and start processing on_failure actions
					return processAction(action.on_failure, context).finally(() => {
						// Remove the error from the context once we've processed the on_failure
						delete context.error;
						delete context.session.error;
					});
				}
				else {
					var throwable;
					if (_.has(err, "goto")) {
						throwable = new verror.VError({
							name: "JumpResult",
							info: {
								goto: err.goto,
								context
							}
						});
					}
					else {
						if (_.isError(err)) {
							throwable = new verror.WError({
								name: "ActionProcessingError",
								cause: err,
								info: {
									action,
									session: context.session
								}
							}, "Error processing action - %s", action.path);
						}
						else {
							throwable = new verror.VError({
								name: "ActionProcessingError",
								info: {
									action,
									session: context.session,
									err
								}
							}, `Error processing action - ${err}`);
						}
					}
					throw throwable;
				}
			});
		}

		function promiseActionExecution(action, context) {
			try {
				var plugin = self.plugins.findPluginForAction(action);
				if (context.options.safe && !plugin.safe) {
					throw new verror.VError({
						name: "UnsafePluginError",
						info: {
							action: action,
							plugin: plugin.name
						}
					}, "Unsafe plugin cannot be executed in safe-mode");
				}
				if (plugin.manual_interpolation != true) {
					action = context.interpolate(action, _.union(["on_success", "on_failure"], _.castArray(plugin.manual_interpolation || []))); // Need to exclude these so we can defer the interpolation
				}
				if (plugin.process.length == 1) { // Synchronous plugin
					return Promise.try(() => {
						if (action.debugger) {
							debugger; // eslint-disable-line no-debugger
						}
						// Dear user: for debugging actions with hdb, step into the next line
						return plugin.process.call(action, context) || context;
					});
				}
				if (plugin.process.length == 2) { // Async plugin
					return new Promise((fulfill, reject) => {
						if (action.debugger) {
							debugger; // eslint-disable-line no-debugger
						}
						// Dear user: for debugging actions with hdb, step into the next line
						plugin.process.call(action, context, (err, result) => {
							if (err) {
								return reject(err);
							}
							return fulfill(result || context);
						});
					});
				}
				else {
					throw new verror.VError({
						name: "InvalidPluginError",
						info: {
							plugin: plugin.name
						}
					}, "Unexpected number of arguments for plugin.process() method. (Must be 1 or 2 arguments)");
				}
			}
			catch (err) {
				var toThrow = err;
				if (!(err instanceof verror.VError)) {
					toThrow = new verror.WError({
						name: "ActionProcessingError",
						cause: err,
					}, "Unexpected error occurred during action processing");
				}
				toThrow.jse_info.action = action;
				throw toThrow;
			}
		}
	}
}

module.exports = Processor;
<|endoftext|>


<commit_before>/*
  Calibrated Input Script.

  Description of the API / available functions:
  ( functionName(input values)(return values) - description )

    - acceleration:Vector3 - (Read Only) the current values that have been calibrated.
    - rangeHack:boolean    - If set to true, will "double" the physical angles for orientation.

	- StartCalibration()()          - Starts the calibration.
	- EndCalibration()()            - Ends the calibration.

	- UpdateZeroVector()()          - Updates the default orientation. (As current orientation, smooth)

	- GetInput()(Vector3)           - Returns current calibrated axis values.
	- GetRawInput()(Vector3)        - Returns raw, uncalibrated axis values.
	- GetZeroVector()(Vector3)      - Get the zero vector that's currently in use.
	- GetRatiosX()(Vector2)         - Get the X ratios.
	- GetRatiosY()(Vector2)         - Get the Y ratios.
	- GetRatiosZ()(Vector2)         - Get the Z ratios.

	- GetCalibrationTime()(float)      - Returns time in seconds of how long the calibration has been going on.
	- GetCalibrationSampleCount()(int) - Returns the count of calibration samples.

	- SetZeroVector(Vector3)()      - Set the zero vector manually.

	- SetClampingDelegate(function(Vector3))() - Set the clamping function. The function needs to return Vector3.

	- IsCalibrating()(boolean)      - Returns either true or false, depending on the current state.
*/

#pragma strict

// Used to store and pass around ratios
public class RatioInformation
{
	public var x:Vector2;
	public var y:Vector2;
	public var z:Vector2;

	public function RatioInformation()
	{
		x = Vector2( 1, 1 );
		y = Vector2( 1, 1 );
		z = Vector2( 1, 1 );
	}

	public function RatioInformation( X:Vector2, Y:Vector2, Z:Vector2 )
	{
		x = X;
		y = Y;
		z = Z;
	}
}

// Used as an interface for different calibration schemes
public class CalibrationScheme
{
	public function Start()
	{
		// Initialization of variables goes here
	}

	public function End():RatioInformation
	{
		return RatioInformation();
	}

	public function Step( currentInput:Vector3, zeroVector:Vector3 )
	{
	}
}

// The Zero Vector that indicates
// the default orientation of device
private var zeroVector:Vector3 = Vector3( 0.0, 0.0, 0.0 );

// Ratios which are used to calculate
// the values after calibration is done
private var ratiosX:Vector2 = Vector2( 1.0, 1.0 );
private var ratiosY:Vector2 = Vector2( 1.0, 1.0 );
private var ratiosZ:Vector2 = Vector2( 1.0, 1.0 );

// Holds values from Input.acceleration.
//   This is just so that same values can be used
//   per update, without passing them all over the place.
private var sensorInput:Vector3;

// Holds the raw input.
private var _rawInput:Vector3;

// These are used when calibrating the zero vector.
private var zeroBuffer:Array = new Array();
private var zeroBufferSize:int = 20;

// We want to know if we're calibrating or not.
private var calibrating:boolean = false;

// The calibration scheme we will be using.
private var calibrationScheme:CalibrationScheme = ChaoticCalibration();

// The clamping function we will be using.
private var ClampingFunction:function( Vector3 ):Vector3;

// Count of calibration samples we have
private var calibrationSampleCount:int = 0;

// Time when the calibration started
private var calibrationStartTime:float = 0.0;

// Private copy of acceleration we will calculate
private var _acceleration:Vector3;

// Values that can be used outside this script
static var currentX:float = 0;
static var currentY:float = 0;
static var currentZ:float = 0;
static var acceleration:Vector3;

// Public flag to keep taps on if we should be using the range hack or not
static var rangeHack:boolean = false;

function Start()
{
	// We'll turn the calibrating flag on because
	// we don't have anything set up.
	//   (In reality we do, but this is just a little precaution.)
	calibrating = true;

	// Set up the default values.
	Initialize();

	// Now that we have at least some sort of default values,
	// we can turn the calibrating flag off and let the developer
	// set it up when the real calibrating takes place.
	calibrating = false;
}

function Update()
{
	// Read the gyro+acceleration info for this update.
	sensorInput = Input.acceleration;
	_rawInput   = sensorInput;

	// This is a hack to increase the physical ranges for X and Y.
	// Should work just fine, except when the device is used upside-down
	// (screen facing straing down). Making code tolerant for that would
	// require use of quaternions and I won't do that until I really have to.
	// - The software values are still kept in range [-1.0 ... 1.0] by default.
	if( rangeHack )
	{
		var angle     = Mathf.Acos( Vector3.Dot( Vector3( 0, 0, -1 ), sensorInput.normalized ));
		var distance  = angle / Mathf.PI;
		var direction = Vector2( sensorInput.x, sensorInput.y ).normalized;

		sensorInput.x = direction.x * distance;
		sensorInput.y = direction.y * distance;
	}

	if( IsCalibrating() )
	{
		calibrationScheme.Step( sensorInput, zeroVector );
		++calibrationSampleCount;
	}

	// Update
	CalculateNewValues();
}

// Public functions:
function StartCalibration()
{
	calibrating = true;
	calibrationStartTime = Time.time;

	ratiosX = Vector2( 1.0, 1.0 );
	ratiosY = Vector2( 1.0, 1.0 );
	ratiosZ = Vector2( 1.0, 1.0 );

	calibrationScheme.Start();
}

function EndCalibration()
{
	calibrating = false;
	calibrationSampleCount = 0;
	calibrationStartTime   = 0;

	var ratios:RatioInformation = calibrationScheme.End();
	ratiosX = ratios.x;
	ratiosY = ratios.y;
	ratiosZ = ratios.z;
}

function UpdateZeroVector()
{
	zeroBuffer.Clear();

	var averageVector:Vector3 = Vector3( 0.0, 0.0, 0.0 );
	while( zeroBuffer.length < zeroBufferSize )
	{
		UpdateBuffers();
		var tmpOrientation:Vector3 = zeroBuffer.Pop();
		averageVector += tmpOrientation;
		zeroBuffer.Push( tmpOrientation );
	}
	averageVector /= zeroBufferSize;
	zeroVector = averageVector;
}

function GetInput()
{
	return _acceleration;
}

function GetRawInput()
{
	return _rawInput;
}

function GetZeroVector()
{
	return zeroVector;
}

function GetRatiosX()
{
	return ratiosX;
}

function GetRatiosY()
{
	return ratiosY;
}

function GetCalibrationTime()
{
	if( !IsCalibrating() )
		return 0.0;

	return Time.time - calibrationStartTime;
}

function GetCalibrationSampleCount()
{
	return calibrationSampleCount;
}

function SetZeroVector( newZeroVector:Vector3 )
{
	zeroVector = newZeroVector;
}

function SetClampingDelegate( clamper:function( Vector3 ):Vector3 )
{
	ClampingFunction = clamper;
}

function IsCalibrating()
{
	return calibrating;
}

// Private implementation:
private function Initialize()
{
	zeroVector = Input.acceleration;
	UpdateZeroVector();
}

private function UpdateBuffers()
{
	// Update zeroBuffer
	zeroBuffer.Push( sensorInput );
	if( zeroBuffer.length > zeroBufferSize )
	{
		zeroBuffer.RemoveAt( 0 );
	}
}

private function CalculateNewValues()
{
	var zeroedInput = sensorInput - zeroVector;

	if( zeroedInput.x > 0 )
	{
		currentX = zeroedInput.x * ratiosX.x;
	}
	else
	{
		currentX = zeroedInput.x * ratiosX.y;
	}

	if( zeroedInput.y > 0 )
	{
		currentY = zeroedInput.y * ratiosY.x;
	}
	else
	{
		currentY = zeroedInput.y * ratiosY.y;
	}

	if( zeroedInput.z > 0 )
	{
		currentZ = zeroedInput.z * ratiosZ.x;
	}
	else
	{
		currentZ = zeroedInput.z * ratiosZ.y;
	}

	var vals = Vector3( currentX, currentY, currentZ );
	if( ClampingFunction )
	{
		vals = ClampingFunction( vals );
	}

	acceleration = vals;
	_acceleration = vals;
}

// Example calibration scheme.
// Chaotic calibration.
// - This is waiting to be moved to a new file.

public class ChaoticCalibration extends CalibrationScheme
{
	// Reference values, which are used to calculate the ratio.
	private var referenceValues:Vector3 = Vector3( 1.0, 1.0, 1.0 );

	// The reference values can't be more than
	// the maximum values, desided by the developer.
	private var maxValueLimits:Vector3  = Vector3( 1.0, 1.0, 1.0 );

	// These are used to hold the current maximum values
	private var maxValuesX:Vector2 = Vector2( 0.01, -0.01 );
	private var maxValuesY:Vector2 = Vector2( 0.01, -0.01 );
	private var maxValuesZ:Vector2 = Vector2( 0.01, -0.01 );

	// This is the start function, which is
	// called when new calibration takes place.
	public function Start()
	{
		maxValuesX = Vector2( 0.01, 0.01 );
		maxValuesY = Vector2( 0.01, 0.01 );
		maxValuesZ = Vector2( 0.01, 0.01 );
	}

	// And this is called when calibration process stops.
	// It has to return an object, containing ratios as
	// Vector2 types as x, y and z in an object.
	public function End():RatioInformation
	{
		// Calculate ratios
		var ratioPositiveX = referenceValues.x     / maxValuesX.x;
		var ratioNegativeX = (0-referenceValues.x) / maxValuesX.y;
		var ratioPositiveY = referenceValues.y     / maxValuesY.x;
		var ratioNegativeY = (0-referenceValues.y) / maxValuesY.y;
		var ratioPositiveZ = referenceValues.z     / maxValuesZ.x;
		var ratioNegativeZ = (0-referenceValues.z) / maxValuesZ.y;

		// We don't want zeros here.
		if( ratioPositiveX == 0 )
			ratioPositiveX = 0.01;
		if( ratioNegativeX == 0 )
			ratioNegativeX = 0.01;
		if( ratioPositiveY == 0 )
			ratioPositiveY = 0.01;
		if( ratioNegativeY == 0 )
			ratioNegativeY = 0.01;
		if( ratioPositiveZ == 0 )
			ratioPositiveZ = 0.01;
		if( ratioNegativeZ == 0 )
			ratioNegativeZ = 0.01;

		var ratiosX = Vector2( ratioPositiveX, ratioNegativeX );
		var ratiosY = Vector2( ratioPositiveY, ratioNegativeY );
		var ratiosZ = Vector2( ratioPositiveZ, ratioNegativeZ );

		// And we shall return an object, containing the ratios:
		var ratios:RatioInformation = RatioInformation(
			ratiosX,
			ratiosY,
			ratiosZ
		);

		return ratios;
	}

	public function Step( currentValues:Vector3, zeroVector:Vector3 )
	{
		var currX:float = currentValues.x;
		var currY:float = currentValues.y;
		var currZ:float = currentValues.z;

		// Check if the current X value goes over any of so far
		// observed maximum values but still remains below the hard limits.
		if( currX > maxValuesX.x &&
		    currX <= maxValueLimits.x )
		{
			maxValuesX.x = currX;
		}
		else if( currX < maxValuesX.y &&
		         currX >= 0-maxValueLimits.x)
		{
			maxValuesX.y = currX;
		}

		// Do the same for the Y value.
		if( currY > maxValuesY.x &&
		    currY <= maxValueLimits.y )
		{
			maxValuesY.x = currY;
		}
		else if( currY < maxValuesY.y &&
		         currY >= 0-maxValueLimits.y  )
		{
			maxValuesY.y = currY;
		}

		// And for the Z value.
		if( currZ > maxValuesZ.x &&
		    currZ <= maxValueLimits.z )
		{
			maxValuesZ.x = currZ;
		}
		else if( currZ < maxValuesY.y &&
		         currZ >= 0-maxValueLimits.z  )
		{
			maxValuesZ.y = currZ;
		}
	}
}
<commit_msg>Scheme can be set from outside now too. Fixed bug.<commit_after>/*
  Calibrated Input Script.

  Description of the API / available functions:
  ( functionName(input values)(return values) - description )

    - acceleration:Vector3 - (Read Only) the current values that have been calibrated.
    - rangeHack:boolean    - If set to true, will "double" the physical angles for orientation.

	- StartCalibration()()          - Starts the calibration.
	- EndCalibration()()            - Ends the calibration.

	- UpdateZeroVector()()          - Updates the default orientation. (As current orientation, smooth)

	- GetInput()(Vector3)           - Returns current calibrated axis values.
	- GetRawInput()(Vector3)        - Returns raw, uncalibrated axis values.
	- GetZeroVector()(Vector3)      - Get the zero vector that's currently in use.
	- GetRatiosX()(Vector2)         - Get the X ratios.
	- GetRatiosY()(Vector2)         - Get the Y ratios.
	- GetRatiosZ()(Vector2)         - Get the Z ratios.

	- GetCalibrationTime()(float)      - Returns time in seconds of how long the calibration has been going on.
	- GetCalibrationSampleCount()(int) - Returns the count of calibration samples.

	- SetZeroVector(Vector3)()                  - Set the zero vector manually.
	- SetCalibrationScheme(CalibrationScheme)() - Set the calibration scheme.
	- SetClampingDelegate(function(Vector3))()  - Set the clamping function. The function needs to return Vector3.

	- IsCalibrating()(boolean)      - Returns either true or false, depending on the current state.
*/

#pragma strict

// Used to store and pass around ratios
public class RatioInformation
{
	public var x:Vector2;
	public var y:Vector2;
	public var z:Vector2;

	public function RatioInformation()
	{
		x = Vector2( 1, 1 );
		y = Vector2( 1, 1 );
		z = Vector2( 1, 1 );
	}

	public function RatioInformation( X:Vector2, Y:Vector2, Z:Vector2 )
	{
		x = X;
		y = Y;
		z = Z;
	}
}

// Used as an interface for different calibration schemes
public class CalibrationScheme
{
	public function Start()
	{
		// Initialization of variables goes here
	}

	public function End():RatioInformation
	{
		return RatioInformation();
	}

	public function Step( currentInput:Vector3, zeroVector:Vector3 )
	{
	}
}

// The Zero Vector that indicates
// the default orientation of device
private var zeroVector:Vector3 = Vector3( 0.0, 0.0, 0.0 );

// Ratios which are used to calculate
// the values after calibration is done
private var ratiosX:Vector2 = Vector2( 1.0, 1.0 );
private var ratiosY:Vector2 = Vector2( 1.0, 1.0 );
private var ratiosZ:Vector2 = Vector2( 1.0, 1.0 );

// Holds values from Input.acceleration.
//   This is just so that same values can be used
//   per update, without passing them all over the place.
private var sensorInput:Vector3;

// Holds the raw input.
private var _rawInput:Vector3;

// These are used when calibrating the zero vector.
private var zeroBuffer:Array = new Array();
private var zeroBufferSize:int = 20;

// We want to know if we're calibrating or not.
private var calibrating:boolean = false;

// The calibration scheme we will be using.
private var calibrationScheme:CalibrationScheme = ChaoticCalibration();

// The clamping function we will be using.
private var ClampingFunction:function( Vector3 ):Vector3;

// Count of calibration samples we have
private var calibrationSampleCount:int = 0;

// Time when the calibration started
private var calibrationStartTime:float = 0.0;

// Private copy of acceleration we will calculate
private var _acceleration:Vector3;

// Values that can be used outside this script
static var currentX:float = 0;
static var currentY:float = 0;
static var currentZ:float = 0;
static var acceleration:Vector3;

// Public flag to keep taps on if we should be using the range hack or not
static var rangeHack:boolean = false;

function Start()
{
	// We'll turn the calibrating flag on because
	// we don't have anything set up.
	//   (In reality we do, but this is just a little precaution.)
	calibrating = true;

	// Set up the default values.
	Initialize();

	// Now that we have at least some sort of default values,
	// we can turn the calibrating flag off and let the developer
	// set it up when the real calibrating takes place.
	calibrating = false;
}

function Update()
{
	// Read the gyro+acceleration info for this update.
	sensorInput = Input.acceleration;
	_rawInput   = sensorInput;

	// This is a hack to increase the physical ranges for X and Y.
	// Should work just fine, except when the device is used upside-down
	// (screen facing straing down). Making code tolerant for that would
	// require use of quaternions and I won't do that until I really have to.
	// - The software values are still kept in range [-1.0 ... 1.0] by default.
	if( rangeHack )
	{
		var angle     = Mathf.Acos( Vector3.Dot( Vector3( 0, 0, -1 ), sensorInput.normalized ));
		var distance  = angle / Mathf.PI;
		var direction = Vector2( sensorInput.x, sensorInput.y ).normalized;

		sensorInput.x = direction.x * distance;
		sensorInput.y = direction.y * distance;
	}

	if( IsCalibrating() )
	{
		calibrationScheme.Step( sensorInput, zeroVector );
		++calibrationSampleCount;
	}

	// Update
	CalculateNewValues();
}

// Public functions:
function StartCalibration()
{
	calibrating = true;
	calibrationStartTime = Time.time;

	ratiosX = Vector2( 1.0, 1.0 );
	ratiosY = Vector2( 1.0, 1.0 );
	ratiosZ = Vector2( 1.0, 1.0 );

	calibrationScheme.Start();
}

function EndCalibration()
{
	calibrating = false;
	calibrationSampleCount = 0;
	calibrationStartTime   = 0;

	var ratios:RatioInformation = calibrationScheme.End();
	ratiosX = ratios.x;
	ratiosY = ratios.y;
	ratiosZ = ratios.z;
}

function UpdateZeroVector()
{
	zeroBuffer.Clear();

	var averageVector:Vector3 = Vector3( 0.0, 0.0, 0.0 );
	while( zeroBuffer.length < zeroBufferSize )
	{
		UpdateBuffers();
		var tmpOrientation:Vector3 = zeroBuffer.Pop();
		averageVector += tmpOrientation;
		zeroBuffer.Push( tmpOrientation );
	}
	averageVector /= zeroBufferSize;
	zeroVector = averageVector;
}

function GetInput()
{
	return _acceleration;
}

function GetRawInput()
{
	return _rawInput;
}

function GetZeroVector()
{
	return zeroVector;
}

function GetRatiosX()
{
	return ratiosX;
}

function GetRatiosY()
{
	return ratiosY;
}

function GetCalibrationTime()
{
	if( !IsCalibrating() )
		return 0.0;

	return Time.time - calibrationStartTime;
}

function GetCalibrationSampleCount()
{
	return calibrationSampleCount;
}

function SetZeroVector( newZeroVector:Vector3 )
{
	zeroVector = newZeroVector;
}

function SetCalibrationScheme( newScheme:CalibrationScheme )
{
	calibrationScheme = newScheme;
}

function SetClampingDelegate( clamper:function( Vector3 ):Vector3 )
{
	ClampingFunction = clamper;
}

function IsCalibrating()
{
	return calibrating;
}

// Private implementation:
private function Initialize()
{
	zeroVector = Input.acceleration;
	UpdateZeroVector();
}

private function UpdateBuffers()
{
	// Update zeroBuffer
	zeroBuffer.Push( sensorInput );
	if( zeroBuffer.length > zeroBufferSize )
	{
		zeroBuffer.RemoveAt( 0 );
	}
}

private function CalculateNewValues()
{
	var zeroedInput = sensorInput - zeroVector;

	if( zeroedInput.x > 0 )
	{
		currentX = zeroedInput.x * ratiosX.x;
	}
	else
	{
		currentX = zeroedInput.x * ratiosX.y;
	}

	if( zeroedInput.y > 0 )
	{
		currentY = zeroedInput.y * ratiosY.x;
	}
	else
	{
		currentY = zeroedInput.y * ratiosY.y;
	}

	if( zeroedInput.z > 0 )
	{
		currentZ = zeroedInput.z * ratiosZ.x;
	}
	else
	{
		currentZ = zeroedInput.z * ratiosZ.y;
	}

	var vals = Vector3( currentX, currentY, currentZ );
	if( ClampingFunction )
	{
		vals = ClampingFunction( vals );
	}

	acceleration = vals;
	_acceleration = vals;
}

// Example calibration scheme.
// Chaotic calibration: use the maximum values gotten during the
// calibration as the source to calculate ratios.
// - This is waiting to be moved to a new file.

public class ChaoticCalibration extends CalibrationScheme
{
	// Reference values, which are used to calculate the ratio.
	private var referenceValues:Vector3 = Vector3( 1.0, 1.0, 1.0 );

	// The reference values can't be more than
	// the maximum values, desided by the developer.
	private var maxValueLimits:Vector3  = Vector3( 1.0, 1.0, 1.0 );

	// These are used to hold the current maximum values
	private var maxValuesX:Vector2 = Vector2( 0.01, -0.01 );
	private var maxValuesY:Vector2 = Vector2( 0.01, -0.01 );
	private var maxValuesZ:Vector2 = Vector2( 0.01, -0.01 );

	// This is the start function, which is
	// called when new calibration takes place.
	public function Start()
	{
		maxValuesX = Vector2( 0.01, 0.01 );
		maxValuesY = Vector2( 0.01, 0.01 );
		maxValuesZ = Vector2( 0.01, 0.01 );
	}

	// And this is called when calibration process stops.
	// It has to return an object, containing ratios as
	// Vector2 types as x, y and z in an object.
	public function End():RatioInformation
	{
		// Calculate ratios
		var ratioPositiveX = referenceValues.x     / maxValuesX.x;
		var ratioNegativeX = (0-referenceValues.x) / maxValuesX.y;
		var ratioPositiveY = referenceValues.y     / maxValuesY.x;
		var ratioNegativeY = (0-referenceValues.y) / maxValuesY.y;
		var ratioPositiveZ = referenceValues.z     / maxValuesZ.x;
		var ratioNegativeZ = (0-referenceValues.z) / maxValuesZ.y;

		// We don't want zeros here.
		if( ratioPositiveX == 0 )
			ratioPositiveX = 0.01;
		if( ratioNegativeX == 0 )
			ratioNegativeX = 0.01;
		if( ratioPositiveY == 0 )
			ratioPositiveY = 0.01;
		if( ratioNegativeY == 0 )
			ratioNegativeY = 0.01;
		if( ratioPositiveZ == 0 )
			ratioPositiveZ = 0.01;
		if( ratioNegativeZ == 0 )
			ratioNegativeZ = 0.01;

		var ratiosX = Vector2( ratioPositiveX, ratioNegativeX );
		var ratiosY = Vector2( ratioPositiveY, ratioNegativeY );
		var ratiosZ = Vector2( ratioPositiveZ, ratioNegativeZ );

		// And we shall return an object, containing the ratios:
		var ratios:RatioInformation = RatioInformation(
			ratiosX,
			ratiosY,
			ratiosZ
		);

		return ratios;
	}

	public function Step( currentValues:Vector3, zeroVector:Vector3 )
	{
		currentValues  -= zeroVector;
		var currX:float = currentValues.x;
		var currY:float = currentValues.y;
		var currZ:float = currentValues.z;

		// Check if the current X value goes over any of so far
		// observed maximum values but still remains below the hard limits.
		if( currX > maxValuesX.x &&
		    currX <= maxValueLimits.x )
		{
			maxValuesX.x = currX;
		}
		else if( currX < maxValuesX.y &&
		         currX >= 0-maxValueLimits.x)
		{
			maxValuesX.y = currX;
		}

		// Do the same for the Y value.
		if( currY > maxValuesY.x &&
		    currY <= maxValueLimits.y )
		{
			maxValuesY.x = currY;
		}
		else if( currY < maxValuesY.y &&
		         currY >= 0-maxValueLimits.y  )
		{
			maxValuesY.y = currY;
		}

		// And for the Z value.
		if( currZ > maxValuesZ.x &&
		    currZ <= maxValueLimits.z )
		{
			maxValuesZ.x = currZ;
		}
		else if( currZ < maxValuesY.y &&
		         currZ >= 0-maxValueLimits.z  )
		{
			maxValuesZ.y = currZ;
		}
	}
}
<|endoftext|>


<commit_before>Kwf.onElementReady('.kwcFulltextSearchBox', function(el, config) {
    new Kwc.FulltextSearch.Box.Component(el, config);
}, this, {
    priority: 0 //call *after* initializing kwcForm to have access to searchForm
});

Ext.ns('Kwc.FulltextSearch.Box');
Kwc.FulltextSearch.Box.Component = function(el, config) {
    this.el = el;
    this.config = config;

    if (this.config.hideSubmit) {
        this.el.select('.submitWrapper').remove();
    }

    Kwf.Utils.HistoryState.on('popstate', function() {
        if (Kwf.Utils.HistoryState.currentState.searchVisible) {
            if (this.searchMainContent) {
                this.showSearch();
            } else {
                this.searchForm.setValues(Kwf.Utils.HistoryState.currentState.searchBoxValues);
                this.loadSearch();
            }
        } else {
            if (!this.previousMainContent) {
                //we didn't load the search using ajax, so we don't have a main content to show -> reload
                location.href = location.href;
            } else {
                this.hideSearch();
            }
        }
    }, this);

    this.searchForm = Kwc.Form.findForm(el);

    if (location.protocol+'//'+location.host+location.pathname == config.searchUrl) {
        //we are already on search page; nothing to do
        this.searchMainContent = Ext.select('.kwfMainContent').first();
        Kwf.Utils.HistoryState.currentState.searchBoxValues = this.searchForm.getValues();
        Kwf.Utils.HistoryState.currentState.searchVisible = true;
        Kwf.Utils.HistoryState.updateState();
        return;
    }
    this.previousSearchFormValues = this.searchForm.getValues();

    this.searchForm.on('fieldChange', function(f) {
        if (f instanceof Kwf.FrontendForm.Field && f.getValue().length < 3) return; //minimum length
        this.doSearch();
    }, this, { buffer: 500 });

    this.searchForm.on('beforeSubmit', function(f) {
        this.doSearch();
        if (!this.config.useLiveSearch) { // fires event to submit value
            return true;
        } else {
            return false;
        }
    }, this);

    Kwf.Utils.HistoryState.currentState.searchVisible = false;
    this.previousMainContent = Ext.select('.kwfMainContent').first();
};

Kwc.FulltextSearch.Box.Component.prototype =
{
    doSearch: function()
    {
        if (!this.config.useLiveSearch) {
            return;
        }

        Kwf.Utils.HistoryState.currentState.searchBoxValues = this.searchForm.getValues();

        if (Kwf.Utils.HistoryState.currentState.searchVisible) return;

        var values = this.searchForm.getValues();
        var diffFound = false;
        for(var i in values) {
            if (values[i] != this.previousSearchFormValues[i]) {
                diffFound = true;
                break;
            }
        }

        if (!diffFound) {
            //don't show search if value didn't change
            return;
        }

        if (this.searchMainContent) {
            //search already loaded, just show it
            this.showSearch();
            return;
        }

        this.loadSearch({
            success: function() {
                Kwf.Utils.HistoryState.currentState.searchVisible = true;
                var ajaxViewEl = this.searchMainContent.child('.kwcDirectoriesListViewAjax');
                if (ajaxViewEl && ajaxViewEl.kwcViewAjax) {
                    ajaxViewEl.kwcViewAjax.pushSearchFormHistoryState();
                }
            },
            scope: this
        });
    },

    loadSearch: function(params)
    {
        this.previousMainContent = Ext.select('.kwfMainContent').first();
        this.previousMainContent.enableDisplayMode('block');
        this.previousMainContent.hide();
        this.loadingContent = this.el.createChild({
            cls: 'kwfMainContent loadingContent',
            tag: 'div',
            style: 'width: ' + this.previousMainContent.getStyle('width'),
            html: '

'
        }, this.previousMainContent);
        this.loadingContent.enableDisplayMode('block');

        var requestParams = this.searchForm.getValuesIncludingPost();
        requestParams.url = this.config.searchUrl;
        Ext.Ajax.request({
            params: requestParams,
            url: Kwf.getKwcRenderUrl(),
            success: function(response, options) {
                this.loadingContent.remove();
                this.searchMainContent = this.el.createChild({
                    cls: 'kwfMainContent',
                    tag: 'div',
                    style: 'width: ' + this.previousMainContent.getStyle('width'),
                    html: response.responseText
                }, this.previousMainContent);
                this.searchMainContent.enableDisplayMode('block');
                Kwf.callOnContentReady(this.searchMainContent);

                if (params && params.success) params.success.call(params.scope || this);
            },
            scope: this
        });
    },

    hideSearch: function() {
        if (this.searchMainContent) {
            this.searchMainContent.hide();
            this.previousMainContent.show();
            this.previousSearchFormValues = this.searchForm.getValues();
        }
    },
    showSearch: function() {
        this.searchMainContent.show();
        if (this.previousMainContent) this.previousMainContent.hide();
    }
};
<commit_msg>don't remove, hide<commit_after>Kwf.onElementReady('.kwcFulltextSearchBox', function(el, config) {
    new Kwc.FulltextSearch.Box.Component(el, config);
}, this, {
    priority: 0 //call *after* initializing kwcForm to have access to searchForm
});

Ext.ns('Kwc.FulltextSearch.Box');
Kwc.FulltextSearch.Box.Component = function(el, config) {
    this.el = el;
    this.config = config;

    if (this.config.hideSubmit) {
        this.el.child('.submitWrapper').enableDisplayMode();
        this.el.child('.submitWrapper').hide();
    }

    Kwf.Utils.HistoryState.on('popstate', function() {
        if (Kwf.Utils.HistoryState.currentState.searchVisible) {
            if (this.searchMainContent) {
                this.showSearch();
            } else {
                this.searchForm.setValues(Kwf.Utils.HistoryState.currentState.searchBoxValues);
                this.loadSearch();
            }
        } else {
            if (!this.previousMainContent) {
                //we didn't load the search using ajax, so we don't have a main content to show -> reload
                location.href = location.href;
            } else {
                this.hideSearch();
            }
        }
    }, this);

    this.searchForm = Kwc.Form.findForm(el);

    if (location.protocol+'//'+location.host+location.pathname == config.searchUrl) {
        //we are already on search page; nothing to do
        this.searchMainContent = Ext.select('.kwfMainContent').first();
        Kwf.Utils.HistoryState.currentState.searchBoxValues = this.searchForm.getValues();
        Kwf.Utils.HistoryState.currentState.searchVisible = true;
        Kwf.Utils.HistoryState.updateState();
        return;
    }
    this.previousSearchFormValues = this.searchForm.getValues();

    this.searchForm.on('fieldChange', function(f) {
        if (f instanceof Kwf.FrontendForm.Field && f.getValue().length < 3) return; //minimum length
        this.doSearch();
    }, this, { buffer: 500 });

    this.searchForm.on('beforeSubmit', function(f) {
        this.doSearch();
        if (!this.config.useLiveSearch) { // fires event to submit value
            return true;
        } else {
            return false;
        }
    }, this);

    Kwf.Utils.HistoryState.currentState.searchVisible = false;
    this.previousMainContent = Ext.select('.kwfMainContent').first();
};

Kwc.FulltextSearch.Box.Component.prototype =
{
    doSearch: function()
    {
        if (!this.config.useLiveSearch) {
            return;
        }

        Kwf.Utils.HistoryState.currentState.searchBoxValues = this.searchForm.getValues();

        if (Kwf.Utils.HistoryState.currentState.searchVisible) return;

        var values = this.searchForm.getValues();
        var diffFound = false;
        for(var i in values) {
            if (values[i] != this.previousSearchFormValues[i]) {
                diffFound = true;
                break;
            }
        }

        if (!diffFound) {
            //don't show search if value didn't change
            return;
        }

        if (this.searchMainContent) {
            //search already loaded, just show it
            this.showSearch();
            return;
        }

        this.loadSearch({
            success: function() {
                Kwf.Utils.HistoryState.currentState.searchVisible = true;
                var ajaxViewEl = this.searchMainContent.child('.kwcDirectoriesListViewAjax');
                if (ajaxViewEl && ajaxViewEl.kwcViewAjax) {
                    ajaxViewEl.kwcViewAjax.pushSearchFormHistoryState();
                }
            },
            scope: this
        });
    },

    loadSearch: function(params)
    {
        this.previousMainContent = Ext.select('.kwfMainContent').first();
        this.previousMainContent.enableDisplayMode('block');
        this.previousMainContent.hide();
        this.loadingContent = this.el.createChild({
            cls: 'kwfMainContent loadingContent',
            tag: 'div',
            style: 'width: ' + this.previousMainContent.getStyle('width'),
            html: '

'
        }, this.previousMainContent);
        this.loadingContent.enableDisplayMode('block');

        var requestParams = this.searchForm.getValuesIncludingPost();
        requestParams.url = this.config.searchUrl;
        Ext.Ajax.request({
            params: requestParams,
            url: Kwf.getKwcRenderUrl(),
            success: function(response, options) {
                this.loadingContent.remove();
                this.searchMainContent = this.el.createChild({
                    cls: 'kwfMainContent',
                    tag: 'div',
                    style: 'width: ' + this.previousMainContent.getStyle('width'),
                    html: response.responseText
                }, this.previousMainContent);
                this.searchMainContent.enableDisplayMode('block');
                Kwf.callOnContentReady(this.searchMainContent);

                if (params && params.success) params.success.call(params.scope || this);
            },
            scope: this
        });
    },

    hideSearch: function() {
        if (this.searchMainContent) {
            this.searchMainContent.hide();
            this.previousMainContent.show();
            this.previousSearchFormValues = this.searchForm.getValues();
        }
    },
    showSearch: function() {
        this.searchMainContent.show();
        if (this.previousMainContent) this.previousMainContent.hide();
    }
};
<|endoftext|>