<commit_before>/**
 * Main module for analyze view
 * @param {type} $ jQuery module
 * @param {type} facebook Facebook Graph API module
 * @param {type} LastFMProxy LastFM API Wrapper module
 * @param {type} Aggregate Custom aggregation methods module
 * @returns {void}
 */
define (['jquery', 'facebook', 'LastFMProxy', 'StatsAnalyzer'], function ($, facebook, LastFMProxy, StatsAnalyzer) {

    // Stats object for every artist (associative array)
    var artistStats = [];

    // Stats for every friend who already used the app
    var friendStats = [];

    // Number of artists liked by the user
    var artistCount = 0;

    // Number of artist already analyzed
    var analyzedArtistCount = 0;

    /**
     * Try to login and get an array of musicians
     * @param {type} callback
     * @returns {undefined}
     */
    function getArtists(callback) {

        // Try to login after init is complete
        FB.getLoginStatus(function(response){
            FB.login(function () {
                FB.api(
                    "/me/music?fields=likes,id,name, category",
                    function (response) {
                          var likes = response.data || [];
                          var artists = [];

                          // Filter by  category
                          likes.forEach(function (like) {
                              if(like.category === "Musician/band") {
                                  artists.push(like);
                              }
                          });

                          callback(artists);

                });
            }, {scope: 'user_likes'});
        });

    }

    /**
     * Show the modal window with a given text
     * @param {type} title Window title to show
     * @param {type} text Text to show
     * @param {function} callback
     * @returns {undefined}
     */
    function showModal (title, text, callback) {
        $('#modalTitle').text(title);
        $('#modalText').text(text);
        $('#myModal').modal('show');
        if (callback) {
            $('#modalOk').click(callback);
            $('#modalClose').click(callback);
        }
    }

    /**
     * Update progress bar as analyzedArtistCound increases
     * @returns {undefined}
     */
    function updateProgress () {
        analyzedArtistCount++;
        var percent = analyzedArtistCount / artistCount;
        var progress = percent*100;
        $('#appProgress').css('width', progress + '%');
    }

    /**
     * Callback for showing status
     * @param {type} artist
     * @returns {undefined}
     */
    function artistCallback (artist) {
        var initialStatus = $('#initial-status').val();
        $('#status').text(initialStatus + ' ' + artist + '...');
    }
    /**
     * Callback for showing status
     * @param {type} artist
     * @returns {undefined}
     */
    function albumCallback (artist) {
         var initialStatus = $('#initial-status-album').val();
        $('#status').text(initialStatus + ' ' + artist + '...');
    }
    /**
     * Callback for showing status
     * @param {type} artist
     * @returns {undefined}
     */
    function fanCallback (artist) {
        var initialStatus = $('#initial-status-fan').val();
        $('#status').text(initialStatus + ' ' + artist + '...');
    }
    /**
     * Callback for showing status
     * @param {type} artist
     * @returns {undefined}
     */
    function tagCallback (artist) {
        var initialStatus = $('#initial-status-tag').val();
        $('#status').text(initialStatus + ' ' + artist + '...');
    }
    /**
     * Callback for showing status
     * @param {type} artist
     * @returns {undefined}
     */
    function similarCallback (artist) {
        var initialStatus = $('#initial-status-similar').val();
        $('#status').text(initialStatus + ' ' + artist + '...');
    }

    /**
     * Get stats one by one recursively and call a callback when finished
     * @param {array} artists
     * @param {function} callback
     * @returns {void}
     */
    function getStats (artists, callback) {

        // Race end: empty array
        if (artists.length === 0) {
            callback();
            return;
        }

        // Pop an artist from array
        var artist = artists.pop();

        // Get stats for the popped artist and then continue
        console.log("id del artista:" + artist.id);
        LastFMProxy.getStats(artist.id, artist.name, artistCallback, albumCallback, fanCallback, tagCallback, similarCallback, function (stats) {
            updateProgress();
            artistStats[artist.name] = JSON.parse(stats);
            getStats(artists, callback);

        });
    }

    /**
     * Fill and show the results template
     * @returns {void}
     */
    function showResults() {

        // Analyze the stats
        StatsAnalyzer.build(artistStats);

        // Get the results text
        var ageText = StatsAnalyzer.getAgeText();
        var epochText = StatsAnalyzer.getEpochText();
        var styleText = StatsAnalyzer.getStyleText();
        var similarText = StatsAnalyzer.getSimilarText();

        // Print the resutls
        $('#age_result').text(ageText);
        $('#epoch_result').text(epochText);
        $('#style_result').text(styleText);
        $('#similar_result').text(similarText);

        // Add sharing event handlers shareResult('prueba');
        $('#share_age').click(function () { shareResult(ageText); });
        $('#share_epoch').click(function () { shareResult(epochText); });
        $('#share_style').click(function () { shareResult(styleText); });
        $('#share_similar').click(function () { shareResult(similarText); });

        $('#process').hide();
        $('#results').removeClass('hidden');
        $('#results').animate({
            maxHeight: '1000000px',
            opacity:1
        },{
            duration: 1000
        });

        loadLikesChart();

    }

    function loadLikesChart () {
        $(function () {
        $('#likesChart').highcharts({
                chart: {
                    type: 'bar'
                },
                title: {
                    text: 'Musical likes'
                },
                xAxis: {
                    categories: ['Apples', 'Bananas', 'Oranges']
                },
                yAxis: {
                    title: {
                        text: '# likes'
                    }
                },
                series: [{
                    name: 'Jane',
                    data: [1, 0, 4]
                }]
            });
        });
    }

    /**
     * Select the n artits with the most likes
     * @param {type} artists
     * @param {type} n
     * @returns {@exp;artists@call;splice}
     */
    function selectTopRated (artists, n) {
        // Sort from more to less likes
        artists.sort(function (a, b) {
            return b.likes - a.likes;
        });
        return artists.splice(0,n);
    }

    /**
     * Share a result to the Facebook timeline
     * @param {type} message
     * @returns {undefined}
     */
    function shareResult (message) {
        var wallPost = {
            method: 'feed',
            picture: $('#share-image').val(),
            link: 'https://apps.facebook.com/music-analyzer',
            name: message,
            caption: "music-analyzer.herokuapp.com",
            description: $('#description').val()
        };
        FB.ui(wallPost);
    }

    /**
     * Start the analysis
     * @returns {undefined}
     */
    function startAnalyzing () {

        // Set title
        $('#title').text($('#analyze-title').val());

        // Get user artists
        getArtists(function (artists) {

            var MAX_ARTISTS = 10;

            // If no artist likes show message
            if (artists.length === 0) {
                showModal($('#error-title').val(), $('#error-status').val(), function () {
                    document.location = '/';
                });
            }
            // If too much musicians  select the top 10 sorting by likes count
            if (artists.length > MAX_ARTISTS) {
                artists = selectTopRated(artists, MAX_ARTISTS);
            }

            artistCount = artists.length || 0;

            // Else get stats one by one
            getStats(artists, function () {
                showResults();
            });

        });
    }

    /**
     * PUBLIC INTERFACE
     */
    return {
        startAnalyzing: startAnalyzing
    };

}); // End module<commit_msg>highchart 7<commit_after>/**
 * Main module for analyze view
 * @param {type} $ jQuery module
 * @param {type} facebook Facebook Graph API module
 * @param {type} LastFMProxy LastFM API Wrapper module
 * @param {type} Aggregate Custom aggregation methods module
 * @returns {void}
 */
define (['jquery', 'facebook', 'LastFMProxy', 'StatsAnalyzer'], function ($, facebook, LastFMProxy, StatsAnalyzer) {

    // Stats object for every artist (associative array)
    var artistStats = [];

    // Stats for every friend who already used the app
    var friendStats = [];

    // Number of artists liked by the user
    var artistCount = 0;

    // Number of artist already analyzed
    var analyzedArtistCount = 0;

    /**
     * Try to login and get an array of musicians
     * @param {type} callback
     * @returns {undefined}
     */
    function getArtists(callback) {

        // Try to login after init is complete
        FB.getLoginStatus(function(response){
            FB.login(function () {
                FB.api(
                    "/me/music?fields=likes,id,name, category",
                    function (response) {
                          var likes = response.data || [];
                          var artists = [];

                          // Filter by  category
                          likes.forEach(function (like) {
                              if(like.category === "Musician/band") {
                                  artists.push(like);
                              }
                          });

                          callback(artists);

                });
            }, {scope: 'user_likes'});
        });

    }

    /**
     * Show the modal window with a given text
     * @param {type} title Window title to show
     * @param {type} text Text to show
     * @param {function} callback
     * @returns {undefined}
     */
    function showModal (title, text, callback) {
        $('#modalTitle').text(title);
        $('#modalText').text(text);
        $('#myModal').modal('show');
        if (callback) {
            $('#modalOk').click(callback);
            $('#modalClose').click(callback);
        }
    }

    /**
     * Update progress bar as analyzedArtistCound increases
     * @returns {undefined}
     */
    function updateProgress () {
        analyzedArtistCount++;
        var percent = analyzedArtistCount / artistCount;
        var progress = percent*100;
        $('#appProgress').css('width', progress + '%');
    }

    /**
     * Callback for showing status
     * @param {type} artist
     * @returns {undefined}
     */
    function artistCallback (artist) {
        var initialStatus = $('#initial-status').val();
        $('#status').text(initialStatus + ' ' + artist + '...');
    }
    /**
     * Callback for showing status
     * @param {type} artist
     * @returns {undefined}
     */
    function albumCallback (artist) {
         var initialStatus = $('#initial-status-album').val();
        $('#status').text(initialStatus + ' ' + artist + '...');
    }
    /**
     * Callback for showing status
     * @param {type} artist
     * @returns {undefined}
     */
    function fanCallback (artist) {
        var initialStatus = $('#initial-status-fan').val();
        $('#status').text(initialStatus + ' ' + artist + '...');
    }
    /**
     * Callback for showing status
     * @param {type} artist
     * @returns {undefined}
     */
    function tagCallback (artist) {
        var initialStatus = $('#initial-status-tag').val();
        $('#status').text(initialStatus + ' ' + artist + '...');
    }
    /**
     * Callback for showing status
     * @param {type} artist
     * @returns {undefined}
     */
    function similarCallback (artist) {
        var initialStatus = $('#initial-status-similar').val();
        $('#status').text(initialStatus + ' ' + artist + '...');
    }

    /**
     * Get stats one by one recursively and call a callback when finished
     * @param {array} artists
     * @param {function} callback
     * @returns {void}
     */
    function getStats (artists, callback) {

        // Race end: empty array
        if (artists.length === 0) {
            callback();
            return;
        }

        // Pop an artist from array
        var artist = artists.pop();

        // Get stats for the popped artist and then continue
        console.log("id del artista:" + artist.id);
        LastFMProxy.getStats(artist.id, artist.name, artistCallback, albumCallback, fanCallback, tagCallback, similarCallback, function (stats) {
            updateProgress();
            artistStats[artist.name] = JSON.parse(stats);
            getStats(artists, callback);

        });
    }

    /**
     * Fill and show the results template
     * @returns {void}
     */
    function showResults() {

        // Analyze the stats
        StatsAnalyzer.build(artistStats);

        // Get the results text
        var ageText = StatsAnalyzer.getAgeText();
        var epochText = StatsAnalyzer.getEpochText();
        var styleText = StatsAnalyzer.getStyleText();
        var similarText = StatsAnalyzer.getSimilarText();

        // Print the resutls
        $('#age_result').text(ageText);
        $('#epoch_result').text(epochText);
        $('#style_result').text(styleText);
        $('#similar_result').text(similarText);

        // Add sharing event handlers shareResult('prueba');
        $('#share_age').click(function () { shareResult(ageText); });
        $('#share_epoch').click(function () { shareResult(epochText); });
        $('#share_style').click(function () { shareResult(styleText); });
        $('#share_similar').click(function () { shareResult(similarText); });

        $('#process').hide();
        $('#results').removeClass('hidden');
        $('#results').animate({
            maxHeight: '1000000px',
            opacity:1
        },{
            duration: 1000
        });

        loadLikesChart();

    }

    function loadLikesChart () {
        $(function () {
        $('#likesChart').highcharts({
                chart: {
                    type: 'bar'
                },
                title: {
                    text: 'Musical likes'
                },
                xAxis: {
                    categories: ['Apples', 'Bananas', 'Oranges']
                },
                yAxis: {
                },
                series: [{
                    name: '# likes',
                    data: [1, 0, 4]
                }]
            });
        });
    }

    /**
     * Select the n artits with the most likes
     * @param {type} artists
     * @param {type} n
     * @returns {@exp;artists@call;splice}
     */
    function selectTopRated (artists, n) {
        // Sort from more to less likes
        artists.sort(function (a, b) {
            return b.likes - a.likes;
        });
        return artists.splice(0,n);
    }

    /**
     * Share a result to the Facebook timeline
     * @param {type} message
     * @returns {undefined}
     */
    function shareResult (message) {
        var wallPost = {
            method: 'feed',
            picture: $('#share-image').val(),
            link: 'https://apps.facebook.com/music-analyzer',
            name: message,
            caption: "music-analyzer.herokuapp.com",
            description: $('#description').val()
        };
        FB.ui(wallPost);
    }

    /**
     * Start the analysis
     * @returns {undefined}
     */
    function startAnalyzing () {

        // Set title
        $('#title').text($('#analyze-title').val());

        // Get user artists
        getArtists(function (artists) {

            var MAX_ARTISTS = 10;

            // If no artist likes show message
            if (artists.length === 0) {
                showModal($('#error-title').val(), $('#error-status').val(), function () {
                    document.location = '/';
                });
            }
            // If too much musicians  select the top 10 sorting by likes count
            if (artists.length > MAX_ARTISTS) {
                artists = selectTopRated(artists, MAX_ARTISTS);
            }

            artistCount = artists.length || 0;

            // Else get stats one by one
            getStats(artists, function () {
                showResults();
            });

        });
    }

    /**
     * PUBLIC INTERFACE
     */
    return {
        startAnalyzing: startAnalyzing
    };

}); // End module<|endoftext|>


<commit_before>/**
 * Frontend application definition.
 *
 * This is the main file for the 'Frontend' application.
 */
(function() {
  'use strict';

  // Create frontend module and specify dependencies for that
  angular.module('frontend', [
      'angular-spinkit',
      //'frontend-templates',
      'frontend.core',
      'frontend.dashboard',
      'frontend.settings',
      'frontend.upstreams',
      'frontend.info',
      'frontend.plugins',
      'frontend.certificates',
      'frontend.users',
      'frontend.consumers',
      'frontend.apis'
  ]);

  /**
   * Configuration for frontend application, this contains following main sections:
   *
   *  1) Configure $httpProvider and $sailsSocketProvider
   *  2) Set necessary HTTP and Socket interceptor(s)
   *  3) Turn on HTML5 mode on application routes
   *  4) Set up application routes
   */
  angular.module('frontend')
      .config(function($logProvider){
          $logProvider.debugEnabled(window.enableLogs);
      })
      .config(['$provide',function($provide) {
          $provide.decorator('$state', function($delegate) {
              var originalTransitionTo = $delegate.transitionTo;
              $delegate.transitionTo = function(to, toParams, options) {
                  return originalTransitionTo(to, toParams, angular.extend({
                      reload: true
                  }, options));
              };
              return $delegate;
          });
      }])
    .config([
      '$stateProvider', '$locationProvider', '$urlRouterProvider', '$httpProvider', '$sailsSocketProvider',
       'cfpLoadingBarProvider',
      'toastrConfig',
      'AccessLevels',
      function config(
        $stateProvider, $locationProvider, $urlRouterProvider, $httpProvider, $sailsSocketProvider,
         cfpLoadingBarProvider,
        toastrConfig,
        AccessLevels
      ) {
        $httpProvider.defaults.useXDomain = true;

        delete $httpProvider.defaults.headers.common['X-Requested-With'];

        // Add interceptors for $httpProvider and $sailsSocketProvider
        $httpProvider.interceptors.push('AuthInterceptor');
        $httpProvider.interceptors.push('ErrorInterceptor');
        // $httpProvider.interceptors.push('TemplateCacheInterceptor');
        $httpProvider.interceptors.push('KongaInterceptor');

        // Iterate $httpProvider interceptors and add those to $sailsSocketProvider
        angular.forEach($httpProvider.interceptors, function iterator(interceptor) {
          $sailsSocketProvider.interceptors.push(interceptor);
        });

        // Disable spinner from cfpLoadingBar
        cfpLoadingBarProvider.includeSpinner = false;
        cfpLoadingBarProvider.latencyThreshold = 200;

        // Extend default toastr configuration with application specified configuration
        angular.extend(
          toastrConfig,
          {
            allowHtml: true,
            closeButton: true,
            extendedTimeOut: 3000
          }
        );

        // Yeah we wanna to use HTML5 urls!
        $locationProvider
          .html5Mode({
            enabled: false, // disable html5 mode
            requireBase: false
          })
          .hashPrefix('!');

        // Routes that needs authenticated user
        //$stateProvider
        //  .state('profile', {
        //    abstract: true,
        //    template: '<ui-view/>',
        //    data: {
        //      access: AccessLevels.user
        //    }
        //  })
        //  .state('profile.edit', {
        //    url: '/profile',
        //    templateUrl: 'js/app/profile/profile.html',
        //    controller: 'ProfileController'
        //  })
        //;

        // Main state provider for frontend application
        $stateProvider
          .state('frontend', {
            abstract: true,
              data: {
                access : 1
              },
            views: {
              header: {
                templateUrl: 'js/app/core/layout/partials/header.html',
                controller: 'HeaderController'
              },
              footer: {
                templateUrl: 'js/app/core/layout/partials/footer.html',
                controller: 'FooterController'
              }
            }
          })
        ;

        // For any unmatched url, redirect to /dashboard
        $urlRouterProvider.otherwise('/dashboard');
      }
    ])
  ;

  /**
   * Frontend application run hook configuration. This will attach auth status
   * check whenever application changes URL states.
   */
  angular.module('frontend')
    .run([
      '$rootScope', '$state', '$injector',
      'editableOptions','editableThemes','$templateCache','NodesService',
      'AuthService','cfpLoadingBar',
      function run(
        $rootScope, $state, $injector,
        editableOptions,editableThemes,$templateCache,NodesService,
        AuthService,cfpLoadingBar
      ) {

          editableThemes.bs3.buttonsClass = 'btn-sm btn-link';

          $rootScope.moment = window.moment

          // Set usage of Bootstrap 3 CSS with angular-xeditable
          editableOptions.theme = 'bs3';

        /**
         * Route state change start event, this is needed for following:
         *  1) Check if user is authenticated to access page, and if not redirect user back to login page
         */
        $rootScope.$on('$stateChangeStart', function stateChangeStart(event, toState, params) {

            cfpLoadingBar.start();

            if(toState.name == 'auth.login' && AuthService.isAuthenticated()) {
                event.preventDefault();
                $state.go('dashboard', params, {location: 'replace'})
            }
            //
            //if (!AuthService.authorize(toState.data.access)) {
            //    event.preventDefault();
            //    $state.go('auth.login', params)
            //}
            //
            //if (toState.redirectTo) {
            //    event.preventDefault();
            //    $state.go(toState.redirectTo, params, {location: 'replace'})
            //}

        });

          $rootScope.$on('$stateChangeSuccess', function stateChangeStart(event, toState) {
              $rootScope.$state = toState
              cfpLoadingBar.complete()

          });

        $rootScope.isAuthenticated = function() {
            return AuthService.isAuthenticated()
        }

         //Check for state change errors.
        //$rootScope.$on('$stateChangeError', function stateChangeError(event, toState, toParams, fromState, fromParams, error) {
        //  event.preventDefault();
        //
        //  $injector.get('MessageService')
        //    .error('Error loading the page');
        //
        //  $state.get('error').error = {
        //    event: event,
        //    toState: toState,
        //    toParams: toParams,
        //    fromState: fromState,
        //    fromParams: fromParams,
        //    error: error
        //  };
        //
        //  return $state.go('error');
        //});
      }
    ])
      .controller('MainController',['$log','$scope','$rootScope','Settings','NodeModel',
          'UserService','InfoService','AuthService',
          function($log,$scope,$rootScope,Settings,NodeModel,
                   UserService,InfoService,AuthService){

              $rootScope.user = UserService.user()
              $rootScope.konga_version = window.konga_version
              $log.debug("MainController:User => ", $rootScope.user)

              // ToDo decide whether to use Gateway Info for getting active node version and stuff...
              $scope.$on('user.node.updated',function(ev,node){

                  $log.debug("MainController:onUserNodeUpdated => Fetching Gateway Info")
                  // Fetch and store Gateway Info
                  _fetchGatewayInfo()
              })

              $scope.$on('user.login',function(ev,user){
                    _fetchGatewayInfo()

              })

              if(AuthService.isAuthenticated()) {
                  _fetchGatewayInfo()
                  _fetchSettings()
              }

              function _fetchSettings() {
                  Settings.load()
                      .then(function(settings){
                          $log.debug("MainController:_fetchSettings: =>", settings )
                          $rootScope.konga_settings_id = settings.length ? settings[0].id : null
                          $rootScope.konga_settings = settings.length ? settings[0].data : {}
                      })
              }

              function _fetchGatewayInfo() {
                  InfoService.getInfo()
                      .then(function(response){
                        $rootScope.Gateway = response.data
                        $log.debug("MainController:onUserNodeUpdated:Gateway Info =>",$rootScope.Gateway)
                  })
              }

          }])
  ;
}());
<commit_msg>Added HttpTimeoutInterceptor<commit_after>/**
 * Frontend application definition.
 *
 * This is the main file for the 'Frontend' application.
 */
(function() {
  'use strict';

  // Create frontend module and specify dependencies for that
  angular.module('frontend', [
      'angular-spinkit',
      //'frontend-templates',
      'frontend.core',
      'frontend.dashboard',
      'frontend.settings',
      'frontend.upstreams',
      'frontend.info',
      'frontend.plugins',
      'frontend.certificates',
      'frontend.users',
      'frontend.consumers',
      'frontend.apis'
  ]);

  /**
   * Configuration for frontend application, this contains following main sections:
   *
   *  1) Configure $httpProvider and $sailsSocketProvider
   *  2) Set necessary HTTP and Socket interceptor(s)
   *  3) Turn on HTML5 mode on application routes
   *  4) Set up application routes
   */
  angular.module('frontend')
      .config(function($logProvider){
          $logProvider.debugEnabled(window.enableLogs);
      })
      .config(['$provide',function($provide) {
          $provide.decorator('$state', function($delegate) {
              var originalTransitionTo = $delegate.transitionTo;
              $delegate.transitionTo = function(to, toParams, options) {
                  return originalTransitionTo(to, toParams, angular.extend({
                      reload: true
                  }, options));
              };
              return $delegate;
          });
      }])
    .config([
      '$stateProvider', '$locationProvider', '$urlRouterProvider', '$httpProvider', '$sailsSocketProvider',
       'cfpLoadingBarProvider',
      'toastrConfig',
      'AccessLevels',
      function config(
        $stateProvider, $locationProvider, $urlRouterProvider, $httpProvider, $sailsSocketProvider,
         cfpLoadingBarProvider,
        toastrConfig,
        AccessLevels
      ) {
        $httpProvider.defaults.useXDomain = true;

        delete $httpProvider.defaults.headers.common['X-Requested-With'];

        // Add interceptors for $httpProvider and $sailsSocketProvider
        $httpProvider.interceptors.push('AuthInterceptor');
        $httpProvider.interceptors.push('ErrorInterceptor');
        $httpProvider.interceptors.push('timeoutHttpIntercept');

        // $httpProvider.interceptors.push('TemplateCacheInterceptor');
        $httpProvider.interceptors.push('KongaInterceptor');

        // Iterate $httpProvider interceptors and add those to $sailsSocketProvider
        angular.forEach($httpProvider.interceptors, function iterator(interceptor) {
          $sailsSocketProvider.interceptors.push(interceptor);
        });

        // Disable spinner from cfpLoadingBar
        cfpLoadingBarProvider.includeSpinner = false;
        cfpLoadingBarProvider.latencyThreshold = 200;

        // Extend default toastr configuration with application specified configuration
        angular.extend(
          toastrConfig,
          {
            allowHtml: true,
            closeButton: true,
            extendedTimeOut: 3000
          }
        );

        // Yeah we wanna to use HTML5 urls!
        $locationProvider
          .html5Mode({
            enabled: false, // disable html5 mode
            requireBase: false
          })
          .hashPrefix('!');

        // Routes that needs authenticated user
        //$stateProvider
        //  .state('profile', {
        //    abstract: true,
        //    template: '<ui-view/>',
        //    data: {
        //      access: AccessLevels.user
        //    }
        //  })
        //  .state('profile.edit', {
        //    url: '/profile',
        //    templateUrl: 'js/app/profile/profile.html',
        //    controller: 'ProfileController'
        //  })
        //;

        // Main state provider for frontend application
        $stateProvider
          .state('frontend', {
            abstract: true,
              data: {
                access : 1
              },
            views: {
              header: {
                templateUrl: 'js/app/core/layout/partials/header.html',
                controller: 'HeaderController'
              },
              footer: {
                templateUrl: 'js/app/core/layout/partials/footer.html',
                controller: 'FooterController'
              }
            }
          })
        ;

        // For any unmatched url, redirect to /dashboard
        $urlRouterProvider.otherwise('/dashboard');
      }
    ])
  ;

  /**
   * Frontend application run hook configuration. This will attach auth status
   * check whenever application changes URL states.
   */
  angular.module('frontend')
    .run([
      '$rootScope', '$state', '$injector',
      'editableOptions','editableThemes','$templateCache','NodesService',
      'AuthService','cfpLoadingBar',
      function run(
        $rootScope, $state, $injector,
        editableOptions,editableThemes,$templateCache,NodesService,
        AuthService,cfpLoadingBar
      ) {

          editableThemes.bs3.buttonsClass = 'btn-sm btn-link';

          $rootScope.moment = window.moment

          // Set usage of Bootstrap 3 CSS with angular-xeditable
          editableOptions.theme = 'bs3';

        /**
         * Route state change start event, this is needed for following:
         *  1) Check if user is authenticated to access page, and if not redirect user back to login page
         */
        $rootScope.$on('$stateChangeStart', function stateChangeStart(event, toState, params) {

            cfpLoadingBar.start();

            if(toState.name == 'auth.login' && AuthService.isAuthenticated()) {
                event.preventDefault();
                $state.go('dashboard', params, {location: 'replace'})
            }
            //
            //if (!AuthService.authorize(toState.data.access)) {
            //    event.preventDefault();
            //    $state.go('auth.login', params)
            //}
            //
            //if (toState.redirectTo) {
            //    event.preventDefault();
            //    $state.go(toState.redirectTo, params, {location: 'replace'})
            //}

        });

          $rootScope.$on('$stateChangeSuccess', function stateChangeStart(event, toState) {
              $rootScope.$state = toState
              cfpLoadingBar.complete()

          });

        $rootScope.isAuthenticated = function() {
            return AuthService.isAuthenticated()
        }

         //Check for state change errors.
        //$rootScope.$on('$stateChangeError', function stateChangeError(event, toState, toParams, fromState, fromParams, error) {
        //  event.preventDefault();
        //
        //  $injector.get('MessageService')
        //    .error('Error loading the page');
        //
        //  $state.get('error').error = {
        //    event: event,
        //    toState: toState,
        //    toParams: toParams,
        //    fromState: fromState,
        //    fromParams: fromParams,
        //    error: error
        //  };
        //
        //  return $state.go('error');
        //});
      }
    ])
      .controller('MainController',['$log','$scope','$rootScope','Settings','NodeModel',
          'UserService','InfoService','AuthService',
          function($log,$scope,$rootScope,Settings,NodeModel,
                   UserService,InfoService,AuthService){

              $rootScope.user = UserService.user()
              $rootScope.konga_version = window.konga_version
              $log.debug("MainController:User => ", $rootScope.user)

              // ToDo decide whether to use Gateway Info for getting active node version and stuff...
              $scope.$on('user.node.updated',function(ev,node){

                  $log.debug("MainController:onUserNodeUpdated => Fetching Gateway Info")
                  // Fetch and store Gateway Info
                  _fetchGatewayInfo()
              })

              $scope.$on('user.login',function(ev,user){
                    _fetchGatewayInfo()

              })

              if(AuthService.isAuthenticated()) {
                  _fetchGatewayInfo()
                  _fetchSettings()
              }

              function _fetchSettings() {
                  Settings.load()
                      .then(function(settings){
                          $log.debug("MainController:_fetchSettings: =>", settings )
                          $rootScope.konga_settings_id = settings.length ? settings[0].id : null
                          $rootScope.konga_settings = settings.length ? settings[0].data : {}
                      })
              }

              function _fetchGatewayInfo() {
                  InfoService.getInfo()
                      .then(function(response){
                        $rootScope.Gateway = response.data
                        $log.debug("MainController:onUserNodeUpdated:Gateway Info =>",$rootScope.Gateway)
                  })
              }

          }])
  ;
}());
<|endoftext|>


<commit_before>(function(window) {
  var numberOfJobs = 3;
  var path = '/jobs?include=company,category,hourly_pay'
  var pageParam = '&page' + encodeURIComponent('[') + 'size' + encodeURIComponent(']') + '=' + numberOfJobs;
  // var filterParam = '&filter' + encodeURIComponent('[') + 'hidden' + encodeURIComponent(']') + '=true';
  var filterParam = '';
  var sortParam = '&sort=-featured,filled,-job_date';

  var baseURL = 'https://api.justarrived.se/api/v1';
  JOBS_ENDPOINT = baseURL + path + pageParam + filterParam + sortParam;

  function getJobs(callback) {
  $.ajax({
      url: JOBS_ENDPOINT,
      type: 'GET',
      dataType: 'json',
      success: function(response) {
        callback(response.data, response.included);
      },
      error: function() {
        console.error('ja-jobs.js: API Request failed!');
      },
      beforeSend: function(xhr) {
        xhr.setRequestHeader('X-API-KEY-TRANSFORM', 'underscore');
        xhr.setRequestHeader('X-API-LOCALE', CURRENT_LOCALE);
        xhr.setRequestHeader('Content-Type', 'application/vnd.api+json');
      }
    });
  }

  function formatTemplate(template, name, value) {
    var regex = new RegExp(name, 'g');
    return template.replace(regex, value);
  }

  function getIncludedResource(type, id, includedData) {
    var attributes;
    includedData.forEach(function(resource) {
      if (resource.type === type && resource.id === id) {
        attributes = resource.attributes;
      }
    });
    return attributes;
  }

  function getCompanyName(includedData, id) {
    return getIncludedResource('companies', id, includedData);
  }

  function getCategoryName(includedData, id) {
    return getIncludedResource('categories', id, includedData);
  }

  function getHourlyPayValue(includedData, id) {
    return getIncludedResource('hourly_pays', id, includedData);
  }

  function relationId(type, data) {
    return data.relationships[type].data.id;
  }

  function formatJobTemplate(template, jobData, includedData) {
    var innerHTML = '';
    var jobAtrs = jobData.attributes;
    var jobID = jobData.id;

    var companyId = relationId('company', jobData);
    var categoryId = relationId('category', jobData);
    var hourlyPayId = relationId('hourly_pay', jobData);

    var company = getCompanyName(includedData, companyId);
    var category = getCategoryName(includedData, categoryId);
    var hourlyPay = getHourlyPayValue(includedData, hourlyPayId);
    var grossSalary = hourlyPay['gross_salary_with_unit'];

    var hours = jobAtrs.hours;
    var amount = jobAtrs['gross_amount_delimited'];
    var maxDescriptionLength = 100;
    var shortDesc = jobAtrs['translated_text']['short_description'] || jobAtrs['short_description'];
    var description = shortDesc || (jobAtrs['translated_text']['description'] || jobAtrs['description']);
    var name = jobAtrs['translated_text'].name || jobAtrs.name;
    description = truncate(description, maxDescriptionLength);

    innerHTML = formatTemplate(template, '%job_id%', jobID);
    innerHTML = formatTemplate(innerHTML, '%job_company%', company.name);
    innerHTML = formatTemplate(innerHTML, '%job_city%', company.city);
    innerHTML = formatTemplate(innerHTML, '%job_category%', category.name);
    innerHTML = formatTemplate(innerHTML, '%job_name%', name);
    innerHTML = formatTemplate(innerHTML, '%job_amount%', amount);
    innerHTML = formatTemplate(innerHTML, '%job_hours%', hours);
    innerHTML = formatTemplate(innerHTML, '%job_hourly_pay%', grossSalary);
    return formatTemplate(innerHTML, '%job_description%', description);
  }

  function truncate(text) {
    var truncationLength = 90;
    var truncatedText = text.substring(0, truncationLength) + '...';
    return truncatedText;
  }

  function createCards(targetSelector, templateSelector) {
    var $target = $(targetSelector);
    var $template = $(templateSelector);

    if ($target.length === 0 || $template.length === 0) {
      return;
    }

    var template = $template.html().toString();
    var html = '';

    getJobs(function(jobsData, includedData) {
      jobsData.forEach(function(jobData) {
        var jobId = jobData.id;
        html += formatJobTemplate(template, jobData, includedData, jobId);
      });

      $target.append(html);
    });
  }

  window.JAJobs = {
    createCards: createCards
  };
})(window);
<commit_msg>Optimize jobs API request to only include needed data<commit_after>(function(window) {
  var numberOfJobs = 3;
  var path = '/jobs?include=company,hourly_pay'
  var pageParam = '&page' + encodeURIComponent('[') + 'size' + encodeURIComponent(']') + '=' + numberOfJobs;
  var filterParam = '&filter' + encodeURIComponent('[') + 'hidden' + encodeURIComponent(']') + '=true';
  var filterParam = '';
  var sortParam = '&sort=-featured,filled,-job_date';
  var fieldsParam = [
    'fields' + encodeURIComponent('[') + 'company' + encodeURIComponent(']') + '=id,name,city',
    'fields' + encodeURIComponent('[') + 'jobs' + encodeURIComponent(']') + '=company,hourly_pay,id,name,city,short_description,description,translated_text,gross_amount_delimited',
    'fields' + encodeURIComponent('[') + 'hourly_pay' + encodeURIComponent(']') + '=id,gross_salary_with_unit'
  ].join('&');

  var baseURL = 'https://api.justarrived.se/api/v1';
  JOBS_ENDPOINT = baseURL + path + pageParam + filterParam + sortParam + fieldsParam;

  function getJobs(callback) {
  $.ajax({
      url: JOBS_ENDPOINT,
      type: 'GET',
      dataType: 'json',
      success: function(response) {
        callback(response.data, response.included);
      },
      error: function() {
        console.error('ja-jobs.js: API Request failed!');
      },
      beforeSend: function(xhr) {
        xhr.setRequestHeader('X-API-KEY-TRANSFORM', 'underscore');
        xhr.setRequestHeader('X-API-LOCALE', CURRENT_LOCALE);
        xhr.setRequestHeader('Content-Type', 'application/vnd.api+json');
      }
    });
  }

  function formatTemplate(template, name, value) {
    var regex = new RegExp(name, 'g');
    return template.replace(regex, value);
  }

  function getIncludedResource(type, id, includedData) {
    var attributes;
    includedData.forEach(function(resource) {
      if (resource.type === type && resource.id === id) {
        attributes = resource.attributes;
      }
    });
    return attributes;
  }

  function getCompanyName(includedData, id) {
    return getIncludedResource('companies', id, includedData);
  }

  function getCategoryName(includedData, id) {
    return getIncludedResource('categories', id, includedData);
  }

  function getHourlyPayValue(includedData, id) {
    return getIncludedResource('hourly_pays', id, includedData);
  }

  function relationId(type, data) {
    return data.relationships[type].data.id;
  }

  function formatJobTemplate(template, jobData, includedData) {
    var innerHTML = '';
    var jobAtrs = jobData.attributes;
    var jobID = jobData.id;

    var companyId = relationId('company', jobData);
    var hourlyPayId = relationId('hourly_pay', jobData);

    var company = getCompanyName(includedData, companyId);
    var hourlyPay = getHourlyPayValue(includedData, hourlyPayId);
    var grossSalary = hourlyPay['gross_salary_with_unit'];

    var hours = jobAtrs.hours;
    var amount = jobAtrs['gross_amount_delimited'];
    var maxDescriptionLength = 100;
    var shortDesc = jobAtrs['translated_text']['short_description'] || jobAtrs['short_description'];
    var description = shortDesc || (jobAtrs['translated_text']['description'] || jobAtrs['description']);
    var name = jobAtrs['translated_text'].name || jobAtrs.name;
    description = truncate(description, maxDescriptionLength);

    innerHTML = formatTemplate(template, '%job_id%', jobID);
    innerHTML = formatTemplate(innerHTML, '%job_company%', company.name);
    innerHTML = formatTemplate(innerHTML, '%job_city%', company.city);
    innerHTML = formatTemplate(innerHTML, '%job_name%', name);
    innerHTML = formatTemplate(innerHTML, '%job_amount%', amount);
    innerHTML = formatTemplate(innerHTML, '%job_hours%', hours);
    innerHTML = formatTemplate(innerHTML, '%job_hourly_pay%', grossSalary);
    return formatTemplate(innerHTML, '%job_description%', description);
  }

  function truncate(text) {
    var truncationLength = 90;
    var truncatedText = text.substring(0, truncationLength) + '...';
    return truncatedText;
  }

  function createCards(targetSelector, templateSelector) {
    var $target = $(targetSelector);
    var $template = $(templateSelector);

    if ($target.length === 0 || $template.length === 0) {
      return;
    }

    var template = $template.html().toString();
    var html = '';

    getJobs(function(jobsData, includedData) {
      jobsData.forEach(function(jobData) {
        var jobId = jobData.id;
        html += formatJobTemplate(template, jobData, includedData, jobId);
      });

      $target.append(html);
    });
  }

  window.JAJobs = {
    createCards: createCards
  };
})(window);
<|endoftext|>


<commit_before>// vim:ts=4:sw=4:et
// Documentation references:
// https://developers.google.com/identity/sign-in/web/reference
// https://developers.google.com/picker/docs/reference

var user;

function start() {
    console.log('start');

    gapi.load('auth2', function() {
        // TODO: avoid a global variable here.
        auth2 = gapi.auth2.init({
            client_id: clientID,
            // The “profile” and “email” scope are always requested.
            scope: 'https://www.googleapis.com/auth/drive',
        });
        auth2.then(function() {
            user = auth2.currentUser.get();
            var sub = $('#user-name').attr('data-sub');
            // If sub does not match the user id, we are logged in in the
            // browser, but not on the server side (e.g. because sessions were
            // deleted).
            if (auth2.isSignedIn.get() && user.getId() === sub) {
                $('#user-avatar').attr('src', user.getBasicProfile().getImageUrl());
                $('#user-name').text(user.getBasicProfile().getName());
                $('paper-fab').show();
                $('#signin').hide();
                $('#signout').show();
                $('#settings-button').show();
                // TODO: open settings button in case drive folder is not configured
            }
        }, function(err) {
	    var errorp = $('#error p');
	    errorp.text('Error ' + err.error + ': ' + err.details);
	    console.log('OAuth2 error', err);
        });
    });

    gapi.load('picker', function() {});

    $('#signinButton').click(function() {
        auth2.grantOfflineAccess({'redirect_uri': 'postmessage'}).then(signInCallback);
    });

    $('#signout').click(function(ev) {
        var auth2 = gapi.auth2.getAuthInstance();
        auth2.signOut().then(function() {
            $.ajax({
                type: 'POST',
                url: '/signout',
                success: function(result) {
                    // Reload the page.
                    window.location.href = window.location.href;
                },
                // TODO: error handling (deleting file failed, e.g. because of readonly file system)
            });
        });
        ev.preventDefault();
    });

    // TODO: #signin keypress
    $('#signin').click(function(ev) {
        auth2.grantOfflineAccess({'redirect_uri': 'postmessage'}).then(signInCallback);
        ev.preventDefault();
    });

    $('#select-drive-folder').click(function() {
        createPicker();
    });
}

function pollScan(name) {
    $.ajax({
        type: 'POST',
        url: '/scanstatus',
        contentType: 'application/json',
        success: function(data, textStatus, jqXHR) {
            $('#scan-progress-status').text(data.Status);
            console.log('result', data, 'textStatus', textStatus, 'jqXHR', jqXHR);
            if (jqXHR.status !== 200) {
                // TODO: show error message
                return;
            }
            if (data.Done) {
                $('#scan-dialog paper-spinner-lite').attr('active', null);
                $('paper-fab').attr('icon', 'hardware:scanner').attr('disabled', null);
                $('#scan-dialog').off('iron-overlay-canceled');
                var sub = user.getBasicProfile().getId();
                $('#scan-dialog .scan-thumb').css('background', 'url("scans_dir/' + sub + '/' + name + '/thumb.png")').css('background-size', 'cover');
            } else {
                setTimeout(function() { pollScan(name); }, 500);
            }
        },
        error: function(jqXHR, textStatus, errorThrown) {
            if (jqXHR.status === 404) {
                // Scan was not yet found because the directory rescan isn’t done.
                // Retry in a little while.
                setTimeout(function() { pollScan(name); }, 500);
            } else {
                $('#scan-progress-status').text('Error: ' + errorThrown);
                setTimeout(function() { pollScan(name); }, 500);
            }
        },
        processData: false,
        data: JSON.stringify({'Name':name}),
    });
}

function renameScan(name, newSuffix) {
    var newName = name + '-' + newSuffix;

    $.ajax({
        type: 'POST',
        url: '/renamescan',
        contentType: 'application/json',
        success: function(data, textStatus, jqXHR) {
            $('#scan-form paper-input iron-icon').show();
        },
        error: function(jqXHR, textStatus, errorThrown) {
            console.log(textStatus);
        },
        processData: false,
        data: JSON.stringify({
            Name: name,
            NewName: newName,
        }),
    });
}

function scan() {
    // Only one scan can be in progress at a time.
    $('paper-fab').attr('icon', 'hourglass-empty').attr('disabled', true);

    document.getElementById('scan-dialog').open();
    $('#scan-dialog').on('iron-overlay-canceled', function(ev) {
        // TODO: move status to toolbar instead of blocking close
        ev.preventDefault();
    });

    $.ajax({
        type: 'POST',
        url: '/startscan',
        success: function(data, textStatus, jqXHR) {
            $('#scan-dialog paper-input[name="name"] div[prefix]').text(data.Name + '-');
            var renameButton = $('#scan-form paper-button');
            renameButton.click(function(ev) {
                renameScan(data.Name, $('#scan-form paper-input').val());
            });
            pollScan(data.Name);
        },
        error: function(jqXHR, textStatus, errorThrown) {
            var exitStatus = jqXHR.getResponseHeader('X-Exit-Status');
            if (exitStatus === undefined) {
                console.log('TODO: error handler for non-scanimage internal server error');
                return;
            }
            // From sane-backends-1.0.25/include/sane/sane.h
            // SANE_STATUS_GOOD = 0,   /* everything A-OK */
            // SANE_STATUS_UNSUPPORTED,    /* operation is not supported */
            // SANE_STATUS_CANCELLED,  /* operation was cancelled */
            // SANE_STATUS_DEVICE_BUSY,    /* device is busy; try again later */
            // SANE_STATUS_INVAL,      /* data is invalid (includes no dev at open) */
            // SANE_STATUS_EOF,        /* no more data available (end-of-file) */
            // SANE_STATUS_JAMMED,     /* document feeder jammed */
            // SANE_STATUS_NO_DOCS,    /* document feeder out of documents */
            // SANE_STATUS_COVER_OPEN, /* scanner cover is open */
            // SANE_STATUS_IO_ERROR,   /* error during device I/O */
            // SANE_STATUS_NO_MEM,     /* out of memory */
            // SANE_STATUS_ACCESS_DENIED   /* access to resource has been denied */

            // TODO: butter bar with human-readable error code
            console.log('error ', exitStatus);
        },
    });
}

// callback has “loaded”, “cancel” and “picked”
function pickerCallback(data) {
    console.log('picker callback', data);
    if (data.action !== google.picker.Action.PICKED) {
        return;
    }
    if (data.docs.length !== 1) {
        // TODO: error handling
        return;
    }
    var picked = data.docs[0];
    // TODO: show a spinner
    $.ajax({
        type: 'POST',
        url: '/storedrivefolder',
        contentType: 'application/json',
        success: function(data, textStatus, jqXHR) {
            console.log('result', data, 'textStatus', textStatus, 'jqXHR', jqXHR);
            if (jqXHR.status !== 200) {
                // TODO: show error message
                return;
            }
            $('#drive-folder').html($('<img>').attr('src', picked.iconUrl).css('margin-right', '0.25em')).append($('<a />').attr('href', picked.url).text(picked.name));
        },
        data: JSON.stringify({
            'Id': picked.id,
            'IconUrl': picked.iconUrl,
            'Url': picked.url,
            'Name': picked.name,
        }),
    });
}

function createPicker() {
    if (!user) {
        // The picker requires an OAuth token.
        return;
    }

    var docsView = new google.picker.DocsView()
        .setIncludeFolders(true)
        .setMimeTypes('application/vnd.google-apps.folder')
        .setMode(google.picker.DocsViewMode.LIST)
        .setSelectFolderEnabled(true);

    var picker = new google.picker.PickerBuilder()
        .addView(docsView)
        .setCallback(pickerCallback)
        .setOAuthToken(user.getAuthResponse().access_token)
        .build();
    picker.setVisible(true);
}

function signInCallback(authResult) {
    if (authResult['code']) {
        // TODO: progress indicator, writing to disk and examining scans could take a while.
        $.ajax({
            type: 'POST',
            url: '/oauth',
            contentType: 'application/octet-stream; charset=utf-8',
            success: function(result) {
                // Reload the page.
                window.location.href = window.location.href;
            },
            error: function(jqXHR, textStatus, errorThrown) {
		if (jqXHR.status == 500) {
                    $('#error p').text('OAuth error: ' + jqXHR.responseText + '. Try revoking access on https://security.google.com/settings/u/0/security/permissions, then retry');
		} else {
                    $('#error p').text('Unknown OAuth error: ' + errorThrown);
                }
            },
            processData: false,
            data: authResult['code'],
        });
    } else {
        console.log('sth went wrong :|', authResult);
        // TODO: trigger logout, without server-side auth we are screwed
    }
}
<commit_msg>fix page reloads when URLs contain fragments<commit_after>// vim:ts=4:sw=4:et
// Documentation references:
// https://developers.google.com/identity/sign-in/web/reference
// https://developers.google.com/picker/docs/reference

var user;

function start() {
    console.log('start');

    gapi.load('auth2', function() {
        // TODO: avoid a global variable here.
        auth2 = gapi.auth2.init({
            client_id: clientID,
            // The “profile” and “email” scope are always requested.
            scope: 'https://www.googleapis.com/auth/drive',
        });
        auth2.then(function() {
            user = auth2.currentUser.get();
            var sub = $('#user-name').attr('data-sub');
            // If sub does not match the user id, we are logged in in the
            // browser, but not on the server side (e.g. because sessions were
            // deleted).
            if (auth2.isSignedIn.get() && user.getId() === sub) {
                $('#user-avatar').attr('src', user.getBasicProfile().getImageUrl());
                $('#user-name').text(user.getBasicProfile().getName());
                $('paper-fab').show();
                $('#signin').hide();
                $('#signout').show();
                $('#settings-button').show();
                // TODO: open settings button in case drive folder is not configured
            }
        }, function(err) {
	    var errorp = $('#error p');
	    errorp.text('Error ' + err.error + ': ' + err.details);
	    console.log('OAuth2 error', err);
        });
    });

    gapi.load('picker', function() {});

    $('#signinButton').click(function() {
        auth2.grantOfflineAccess({'redirect_uri': 'postmessage'}).then(signInCallback);
    });

    $('#signout').click(function(ev) {
        var auth2 = gapi.auth2.getAuthInstance();
        auth2.signOut().then(function() {
            $.ajax({
                type: 'POST',
                url: '/signout',
                success: function(result) {
                    // Reload the page.
                    window.location.href = window.location.origin;
                },
                // TODO: error handling (deleting file failed, e.g. because of readonly file system)
            });
        });
        ev.preventDefault();
    });

    // TODO: #signin keypress
    $('#signin').click(function(ev) {
        auth2.grantOfflineAccess({'redirect_uri': 'postmessage'}).then(signInCallback);
        ev.preventDefault();
    });

    $('#select-drive-folder').click(function() {
        createPicker();
    });
}

function pollScan(name) {
    $.ajax({
        type: 'POST',
        url: '/scanstatus',
        contentType: 'application/json',
        success: function(data, textStatus, jqXHR) {
            $('#scan-progress-status').text(data.Status);
            console.log('result', data, 'textStatus', textStatus, 'jqXHR', jqXHR);
            if (jqXHR.status !== 200) {
                // TODO: show error message
                return;
            }
            if (data.Done) {
                $('#scan-dialog paper-spinner-lite').attr('active', null);
                $('paper-fab').attr('icon', 'hardware:scanner').attr('disabled', null);
                $('#scan-dialog').off('iron-overlay-canceled');
                var sub = user.getBasicProfile().getId();
                $('#scan-dialog .scan-thumb').css('background', 'url("scans_dir/' + sub + '/' + name + '/thumb.png")').css('background-size', 'cover');
            } else {
                setTimeout(function() { pollScan(name); }, 500);
            }
        },
        error: function(jqXHR, textStatus, errorThrown) {
            if (jqXHR.status === 404) {
                // Scan was not yet found because the directory rescan isn’t done.
                // Retry in a little while.
                setTimeout(function() { pollScan(name); }, 500);
            } else {
                $('#scan-progress-status').text('Error: ' + errorThrown);
                setTimeout(function() { pollScan(name); }, 500);
            }
        },
        processData: false,
        data: JSON.stringify({'Name':name}),
    });
}

function renameScan(name, newSuffix) {
    var newName = name + '-' + newSuffix;

    $.ajax({
        type: 'POST',
        url: '/renamescan',
        contentType: 'application/json',
        success: function(data, textStatus, jqXHR) {
            $('#scan-form paper-input iron-icon').show();
        },
        error: function(jqXHR, textStatus, errorThrown) {
            console.log(textStatus);
        },
        processData: false,
        data: JSON.stringify({
            Name: name,
            NewName: newName,
        }),
    });
}

function scan() {
    // Only one scan can be in progress at a time.
    $('paper-fab').attr('icon', 'hourglass-empty').attr('disabled', true);

    document.getElementById('scan-dialog').open();
    $('#scan-dialog').on('iron-overlay-canceled', function(ev) {
        // TODO: move status to toolbar instead of blocking close
        ev.preventDefault();
    });

    $.ajax({
        type: 'POST',
        url: '/startscan',
        success: function(data, textStatus, jqXHR) {
            $('#scan-dialog paper-input[name="name"] div[prefix]').text(data.Name + '-');
            var renameButton = $('#scan-form paper-button');
            renameButton.click(function(ev) {
                renameScan(data.Name, $('#scan-form paper-input').val());
            });
            pollScan(data.Name);
        },
        error: function(jqXHR, textStatus, errorThrown) {
            var exitStatus = jqXHR.getResponseHeader('X-Exit-Status');
            if (exitStatus === undefined) {
                console.log('TODO: error handler for non-scanimage internal server error');
                return;
            }
            // From sane-backends-1.0.25/include/sane/sane.h
            // SANE_STATUS_GOOD = 0,   /* everything A-OK */
            // SANE_STATUS_UNSUPPORTED,    /* operation is not supported */
            // SANE_STATUS_CANCELLED,  /* operation was cancelled */
            // SANE_STATUS_DEVICE_BUSY,    /* device is busy; try again later */
            // SANE_STATUS_INVAL,      /* data is invalid (includes no dev at open) */
            // SANE_STATUS_EOF,        /* no more data available (end-of-file) */
            // SANE_STATUS_JAMMED,     /* document feeder jammed */
            // SANE_STATUS_NO_DOCS,    /* document feeder out of documents */
            // SANE_STATUS_COVER_OPEN, /* scanner cover is open */
            // SANE_STATUS_IO_ERROR,   /* error during device I/O */
            // SANE_STATUS_NO_MEM,     /* out of memory */
            // SANE_STATUS_ACCESS_DENIED   /* access to resource has been denied */

            // TODO: butter bar with human-readable error code
            console.log('error ', exitStatus);
        },
    });
}

// callback has “loaded”, “cancel” and “picked”
function pickerCallback(data) {
    console.log('picker callback', data);
    if (data.action !== google.picker.Action.PICKED) {
        return;
    }
    if (data.docs.length !== 1) {
        // TODO: error handling
        return;
    }
    var picked = data.docs[0];
    // TODO: show a spinner
    $.ajax({
        type: 'POST',
        url: '/storedrivefolder',
        contentType: 'application/json',
        success: function(data, textStatus, jqXHR) {
            console.log('result', data, 'textStatus', textStatus, 'jqXHR', jqXHR);
            if (jqXHR.status !== 200) {
                // TODO: show error message
                return;
            }
            $('#drive-folder').html($('<img>').attr('src', picked.iconUrl).css('margin-right', '0.25em')).append($('<a />').attr('href', picked.url).text(picked.name));
        },
        data: JSON.stringify({
            'Id': picked.id,
            'IconUrl': picked.iconUrl,
            'Url': picked.url,
            'Name': picked.name,
        }),
    });
}

function createPicker() {
    if (!user) {
        // The picker requires an OAuth token.
        return;
    }

    var docsView = new google.picker.DocsView()
        .setIncludeFolders(true)
        .setMimeTypes('application/vnd.google-apps.folder')
        .setMode(google.picker.DocsViewMode.LIST)
        .setSelectFolderEnabled(true);

    var picker = new google.picker.PickerBuilder()
        .addView(docsView)
        .setCallback(pickerCallback)
        .setOAuthToken(user.getAuthResponse().access_token)
        .build();
    picker.setVisible(true);
}

function signInCallback(authResult) {
    if (authResult['code']) {
        // TODO: progress indicator, writing to disk and examining scans could take a while.
        $.ajax({
            type: 'POST',
            url: '/oauth',
            contentType: 'application/octet-stream; charset=utf-8',
            success: function(result) {
                // Reload the page.
                window.location.href = window.location.origin;
            },
            error: function(jqXHR, textStatus, errorThrown) {
		if (jqXHR.status == 500) {
                    $('#error p').text('OAuth error: ' + jqXHR.responseText + '. Try revoking access on https://security.google.com/settings/u/0/security/permissions, then retry');
		} else {
                    $('#error p').text('Unknown OAuth error: ' + errorThrown);
                }
            },
            processData: false,
            data: authResult['code'],
        });
    } else {
        console.log('sth went wrong :|', authResult);
        // TODO: trigger logout, without server-side auth we are screwed
    }
}
<|endoftext|>


<commit_before>module.exports = {
    users: [ // ADD YOUR USERNAME IN ITS ALPHABETICAL SLOT
        'avaynshtok',
        'andersos',
        'barretts',
        'bluetidepro',
        'bobnisco',
        'briansoule',
        'bwlang',
        'cgroner',
        'chall8908',
        'cpdean',
        'crandles',
        'crankeye',
        'd7p',
        'dcancel',
        'dck273',
        'dfjones',
        'dylnclrk',
        'eik3',
        'euank',
        'evsie001',
        'exadeci',
        'gwillen',
        'hemanth',
        'humd',
        'ianwalter',
        'isaaczafuta',
        'jackcarter',
        'jeffawang',
        'jensenbox',
        'jeromegn',
        'jmcelwain',
        'joewright',
        'jphatton',
        'julbaxter',
        'kalmi',
        'kasperlewau',
        'kennethrapp',
        'lablayers',
        'lgarron',
        'lukemiles',
        'LeandroLovisolo',
        'marco-fiset',
        'markbao',
        'mattclaw',
        'megamattron',
        'michaelnovakjr',
        'mreinhardt',
        'msied',
        'pents90',
        'pksjce',
        'qguv',
        'roryokane',
        'rtfeldman',
        'ryanwi',
        'shedd',
        'singularperturbation',
        'sorpaas',
        'sp1d3rx',
        'sriehl',
        'swelham',
        'thomascate',
        'tejohnso',
        'theprofessor117',
        'tito0224',
        'travishorn',
        'treygriffith',
        'utdemir',
        'williamle8300',
	'josephwegner'
    ]
};
<commit_msg>alphabetical user list<commit_after>module.exports = {
    users: [ // ADD YOUR USERNAME IN ITS ALPHABETICAL SLOT
        'avaynshtok',
        'andersos',
        'barretts',
        'bluetidepro',
        'bobnisco',
        'briansoule',
        'bwlang',
        'cgroner',
        'chall8908',
        'cpdean',
        'crandles',
        'crankeye',
        'd7p',
        'dcancel',
        'dck273',
        'dfjones',
        'dylnclrk',
        'eik3',
        'euank',
        'evsie001',
        'exadeci',
        'gwillen',
        'hemanth',
        'humd',
        'ianwalter',
        'isaaczafuta',
        'jackcarter',
        'jeffawang',
        'jensenbox',
        'jeromegn',
        'jmcelwain',
        'joewright',
        'josephwegner',
        'jphatton',
        'julbaxter',
        'kalmi',
        'kasperlewau',
        'kennethrapp',
        'lablayers',
        'lgarron',
        'lukemiles',
        'LeandroLovisolo',
        'marco-fiset',
        'markbao',
        'mattclaw',
        'megamattron',
        'michaelnovakjr',
        'mreinhardt',
        'msied',
        'pents90',
        'pksjce',
        'qguv',
        'roryokane',
        'rtfeldman',
        'ryanwi',
        'shedd',
        'singularperturbation',
        'sorpaas',
        'sp1d3rx',
        'sriehl',
        'swelham',
        'thomascate',
        'tejohnso',
        'theprofessor117',
        'tito0224',
        'travishorn',
        'treygriffith',
        'utdemir',
        'williamle8300'
    ]
};
<|endoftext|>


<commit_before>// -*- mode: js2; indent-tabs-mode: nil; -*-
define(function () {
  return window.io;
});
<commit_msg>use espresso-mode js<commit_after>// -*- mode: js; indent-tabs-mode: nil; -*-
define(function () {
  return window.io;
});
<|endoftext|>


<commit_before>var fs = require('fs'),
  level = require('level'),
  levelMultiply = require('level-multiply'),
  indexPeek = require('./indexing/indexPeek.js'),
  matcher = require('./matchers/matcher.js'),
  calibrater = require('./indexing/calibrater.js'),
  indexer = require('./indexing/indexer.js'),
  deleter = require('./indexing/deleter.js'),
  replicator = require('./indexing/replicator.js'),
  searcher = require('./mapreduce/searcher.js'),
  docGetter = require('./mapreduce/docGetter.js'),
  rmdir = require('rimraf');

var indexes = null, indexesMultiply = null;

var defaults = {
  indexPath: 'si'
};

exports.configure = function (options) {
  options = options || defaults;

  indexes = level(options.indexPath, {valueEncoding: 'json'}),
  indexesMultiply = levelMultiply(indexes);

  calibrater.getTotalDocs(indexes, function(totalDocs) {
    searcher.setTotalDocs(totalDocs);
  });
};

exports.del = function(docID, callback) {
  if (!indexes) exports.configure();

  deleter.deleteDoc(docID, indexes, indexesMultiply, function(msg) {
    callback(msg);
  });
};

exports.get = function(docID, callback) {
  if (!indexes) exports.configure();

  docGetter.getDoc(indexes, indexesMultiply, docID, function(msg) {
    callback(msg);
  });
};

exports.add = function(batchString, batchName, filters, callback) {
  if (!indexes) exports.configure();

  indexer.addDocToIndex(indexes, indexesMultiply, batchString, batchName, filters, function(msg) {
    callback(msg);
  });
};

exports.empty = function( callback) {
  if (!indexes) exports.configure();

  indexes.close(function(){
    rmdir('si', function(error){
      indexes = level('si', {valueEncoding: 'json'});
      indexesMultiply = levelMultiply(indexes);
      callback('emptied');
    });
  });
};

exports.search = function (q, callback) {
  if (!indexes) exports.configure();

  searcher.search(indexes, indexesMultiply, q, function(msg){
    callback(msg);
  });
};

exports.match = function(beginsWith, callback) {
  if (!indexes) exports.configure();

  matcher.matcher(indexes, beginsWith, function(msg) {
    callback(msg);
  });
};

exports.tellMeAboutMySearchIndex = function(callback) {
  if (!indexes) exports.configure();

  calibrater.getTotalDocs(indexes, function(totalDocs) {
    var metadata = {};
    metadata['totalDocs'] = totalDocs;
    callback(metadata);
  });
};

exports.replicate = function(readStream, callback) {
  if (!indexes) exports.configure();

  replicator.replicateFromSnapShot(readStream, indexes, function(msg) {
    callback(msg);
  });
};

exports.snapShot = function(callback) {
  if (!indexes) exports.configure();

  replicator.createSnapShot(indexes, function(msg) {
    callback(msg);
  });
};

//utility methods for testing and devlopment
//******************************************
exports.indexRange = function(start, stop, callback) {
  if (!indexes) exports.configure();

  indexPeek.indexRange(start, stop, indexes, function(msg) {
    callback(msg);
  });
};
exports.indexValue = function(key, callback) {
  if (!indexes) exports.configure();

  indexPeek.indexValue(key, indexes, function(msg) {
    callback(msg);
  });
};
//do a full recalibration of the index
exports.calibrate = function(callback) {
  if (!indexes) exports.configure();

  calibrater.calibrate(indexes, function(msg) {
    callback(msg);
  });
};

<commit_msg>Make search-index export a function that takes options instead of exporting a separate configure function<commit_after>var fs = require('fs'),
  level = require('level'),
  levelMultiply = require('level-multiply'),
  indexPeek = require('./indexing/indexPeek.js'),
  matcher = require('./matchers/matcher.js'),
  calibrater = require('./indexing/calibrater.js'),
  indexer = require('./indexing/indexer.js'),
  deleter = require('./indexing/deleter.js'),
  replicator = require('./indexing/replicator.js'),
  searcher = require('./mapreduce/searcher.js'),
  docGetter = require('./mapreduce/docGetter.js'),
  rmdir = require('rimraf');

var indexes = null, indexesMultiply = null;

var defaults = {
  indexPath: 'si'
};

var SearchIndex = module.exports = function (options) {
  options = options || defaults;

  indexes = level(options.indexPath, {valueEncoding: 'json'}),
  indexesMultiply = levelMultiply(indexes);

  calibrater.getTotalDocs(indexes, function(totalDocs) {
    searcher.setTotalDocs(totalDocs);
  });
};

SearchIndex.del = function(docID, callback) {
  if (!indexes) SearchIndex();

  deleter.deleteDoc(docID, indexes, indexesMultiply, function(msg) {
    callback(msg);
  });
};

SearchIndex.get = function(docID, callback) {
  if (!indexes) SearchIndex();

  docGetter.getDoc(indexes, indexesMultiply, docID, function(msg) {
    callback(msg);
  });
};

SearchIndex.add = function(batchString, batchName, filters, callback) {
  if (!indexes) SearchIndex();

  indexer.addDocToIndex(indexes, indexesMultiply, batchString, batchName, filters, function(msg) {
    callback(msg);
  });
};

SearchIndex.empty = function( callback) {
  if (!indexes) SearchIndex();

  indexes.close(function(){
    rmdir('si', function(error){
      indexes = level('si', {valueEncoding: 'json'});
      indexesMultiply = levelMultiply(indexes);
      callback('emptied');
    });
  });
};

SearchIndex.search = function (q, callback) {
  if (!indexes) SearchIndex();

  searcher.search(indexes, indexesMultiply, q, function(msg){
    callback(msg);
  });
};

SearchIndex.match = function(beginsWith, callback) {
  if (!indexes) SearchIndex();

  matcher.matcher(indexes, beginsWith, function(msg) {
    callback(msg);
  });
};

SearchIndex.tellMeAboutMySearchIndex = function(callback) {
  if (!indexes) SearchIndex();

  calibrater.getTotalDocs(indexes, function(totalDocs) {
    var metadata = {};
    metadata['totalDocs'] = totalDocs;
    callback(metadata);
  });
};

SearchIndex.replicate = function(readStream, callback) {
  if (!indexes) SearchIndex();

  replicator.replicateFromSnapShot(readStream, indexes, function(msg) {
    callback(msg);
  });
};

SearchIndex.snapShot = function(callback) {
  if (!indexes) SearchIndex();

  replicator.createSnapShot(indexes, function(msg) {
    callback(msg);
  });
};

//utility methods for testing and devlopment
//******************************************
SearchIndex.indexRange = function(start, stop, callback) {
  if (!indexes) SearchIndex();

  indexPeek.indexRange(start, stop, indexes, function(msg) {
    callback(msg);
  });
};
SearchIndex.indexValue = function(key, callback) {
  if (!indexes) SearchIndex();

  indexPeek.indexValue(key, indexes, function(msg) {
    callback(msg);
  });
};
//do a full recalibration of the index
SearchIndex.calibrate = function(callback) {
  if (!indexes) SearchIndex();

  calibrater.calibrate(indexes, function(msg) {
    callback(msg);
  });
};

<|endoftext|>


<commit_before>var bunyan = require('bunyan');
var _ = require('lodash');
var level = require('levelup');
var levelMultiply = require('level-multiply');
var log = bunyan.createLogger({name: 'search-index'});

module.exports = function (options) {
  var SearchIndex = {};
  //------------libs
  var defaults = {
    indexPath: 'si',
    logLevel: 'warn'
  };
  //initialize defaults options
  SearchIndex.options = _.defaults(options || {}, defaults);
  var calibrater = require('./indexing/calibrater.js');
  var deleter = require('./indexing/deleter.js');
  var docGetter = require('./search/docGetter.js');
  var indexPeek = require('./indexing/indexPeek.js');
  var indexer = require('./indexing/indexer.js');
  var matcher = require('./matchers/matcher.js');
  var replicator = require('./indexing/replicator.js');
  var searcher = require('./search/searcher.js');
  SearchIndex.indexes = (function () {
    if (SearchIndex.options && SearchIndex.options.db) {
    return level(SearchIndex.options.indexPath,
          {valueEncoding: 'json', db: SearchIndex.options.db}
          )
  } else {
    return level(SearchIndex.options.indexPath, {valueEncoding: 'json'});
  }
  })();
  var indexesMultiply = levelMultiply(SearchIndex.indexes);
  // TODO: get rid of this.
  indexer.setLogLevel(options.logLevel);
  deleter.setLogLevel(options.logLevel);
  calibrater.setLogLevel(options.logLevel);
  searcher.setLogLevel(options.logLevel);
  indexPeek.setLogLevel(options.logLevel);
  matcher.setLogLevel(options.logLevel);
  replicator.setLogLevel(options.logLevel);
  //is there a better way of doing this?
  calibrater.getTotalDocs(SearchIndex.indexes, function (err, totalDocs) {
    searcher.setTotalDocs(totalDocs);
  });

  SearchIndex.getOptions = function () {
    return SearchIndex.options;
  };

  SearchIndex.add = function (options, batch, callback) {
    var filters = options.filters || {};
    indexer.addDocToIndex(SearchIndex.indexes, batch, options.batchName,
                          filters, callback);
  };

  SearchIndex.del = function (docID, callback) {
    deleter.deleteBatch([docID], SearchIndex.indexes, callback);
  };

  SearchIndex.deleteBatch = function (batch, callback) {
    deleter.deleteBatch(batch, SearchIndex.indexes, callback);
  };

  SearchIndex.empty = function (callback) {
    var deleteOps = [];
    SearchIndex.indexes.createKeyStream({gte: '0', lte: '~~~~~~~~~~~~~~~~~~~'})
      .on('data', function (data) {
        deleteOps.push({type: 'del', key: data});
      })
      .on('error', function (err) {
        log.error(err, ' failed to empty index');
      })
      .on('end', function () {
        SearchIndex.indexes.batch(deleteOps, callback);
      });
  };

  SearchIndex.get = function (docID, callback) {
    docGetter.getDoc(SearchIndex.indexes, docID, callback);
  };

  SearchIndex.match = function (beginsWith, callback) {
    matcher.matcher(SearchIndex.indexes, beginsWith, callback);
  };

  //create a pipey and non-pipey version here. (non-pipey is for bundling with browserify)
  SearchIndex.replicate = function (readStream, callback) {
    replicator.replicateFromSnapShotStream(readStream, SearchIndex.indexes, callback);
  };

  SearchIndex.replicateBatch = function (serializedDB, callback) {
    replicator.replicateFromSnapShotBatch(serializedDB, SearchIndex.indexes, callback);
  };

  SearchIndex.search = function (q, callback) {
    searcher.search(SearchIndex.indexes, q, function (results) {
      //TODO: make error throwing real
      callback(null, results);
    });
  };

  SearchIndex.snapShotBatch = function (callback) {
    replicator.createSnapShotBatch(SearchIndex.indexes, callback);
  };

  SearchIndex.snapShot = function (callback) {
    replicator.createSnapShot(SearchIndex.indexes, callback);
  };

  SearchIndex.tellMeAboutMySearchIndex = function (callback) {
    calibrater.getTotalDocs(SearchIndex.indexes, function (err, totalDocs) {
      callback({totalDocs: totalDocs});
    });
  };

  SearchIndex.close = function (callback) {
    SearchIndex.indexes.close(function () {
      while (SearchIndex.indexes.isOpen()) {
        console.log('closing...');
      }
      callback(null);
    });
  };

  //utility methods for testing and devlopment
  //******************************************
  SearchIndex.indexRange = function (options, callback) {
    indexPeek.indexRange(options.start, options.stop, SearchIndex.indexes, callback);
  };

  SearchIndex.indexValue = function (options, callback) {
    indexPeek.indexValue(options.key, SearchIndex.indexes, callback);
  };
  //do a full recalibration of the index
  SearchIndex.calibrate = function (callback) {
    calibrater.calibrate(SearchIndex.indexes, callback);
  };
  return SearchIndex;
};
<commit_msg>Nicer level-instantiation<commit_after>var bunyan = require('bunyan');
var _ = require('lodash');
var level = require('levelup');
var levelMultiply = require('level-multiply');
var log = bunyan.createLogger({name: 'search-index'});

module.exports = function (options) {
  var SearchIndex = {};
  //------------libs
  var defaults = {
    indexPath: 'si',
    logLevel: 'warn'
  };
  //initialize defaults options
  SearchIndex.options = _.defaults(options || {}, defaults);
  var calibrater = require('./indexing/calibrater.js');
  var deleter = require('./indexing/deleter.js');
  var docGetter = require('./search/docGetter.js');
  var indexPeek = require('./indexing/indexPeek.js');
  var indexer = require('./indexing/indexer.js');
  var matcher = require('./matchers/matcher.js');
  var replicator = require('./indexing/replicator.js');
  var searcher = require('./search/searcher.js');
  SearchIndex.indexes = level(SearchIndex.options.indexPath, {
    valueEncoding: 'json',
    db: SearchIndex.options.db
  });
  var indexesMultiply = levelMultiply(SearchIndex.indexes);
  // TODO: get rid of this.
  indexer.setLogLevel(options.logLevel);
  deleter.setLogLevel(options.logLevel);
  calibrater.setLogLevel(options.logLevel);
  searcher.setLogLevel(options.logLevel);
  indexPeek.setLogLevel(options.logLevel);
  matcher.setLogLevel(options.logLevel);
  replicator.setLogLevel(options.logLevel);
  //is there a better way of doing this?
  calibrater.getTotalDocs(SearchIndex.indexes, function (err, totalDocs) {
    searcher.setTotalDocs(totalDocs);
  });

  SearchIndex.getOptions = function () {
    return SearchIndex.options;
  };

  SearchIndex.add = function (options, batch, callback) {
    var filters = options.filters || {};
    indexer.addDocToIndex(SearchIndex.indexes, batch, options.batchName,
                          filters, callback);
  };

  SearchIndex.del = function (docID, callback) {
    deleter.deleteBatch([docID], SearchIndex.indexes, callback);
  };

  SearchIndex.deleteBatch = function (batch, callback) {
    deleter.deleteBatch(batch, SearchIndex.indexes, callback);
  };

  SearchIndex.empty = function (callback) {
    var deleteOps = [];
    SearchIndex.indexes.createKeyStream({gte: '0', lte: '~~~~~~~~~~~~~~~~~~~'})
      .on('data', function (data) {
        deleteOps.push({type: 'del', key: data});
      })
      .on('error', function (err) {
        log.error(err, ' failed to empty index');
      })
      .on('end', function () {
        SearchIndex.indexes.batch(deleteOps, callback);
      });
  };

  SearchIndex.get = function (docID, callback) {
    docGetter.getDoc(SearchIndex.indexes, docID, callback);
  };

  SearchIndex.match = function (beginsWith, callback) {
    matcher.matcher(SearchIndex.indexes, beginsWith, callback);
  };

  //create a pipey and non-pipey version here. (non-pipey is for bundling with browserify)
  SearchIndex.replicate = function (readStream, callback) {
    replicator.replicateFromSnapShotStream(readStream, SearchIndex.indexes, callback);
  };

  SearchIndex.replicateBatch = function (serializedDB, callback) {
    replicator.replicateFromSnapShotBatch(serializedDB, SearchIndex.indexes, callback);
  };

  SearchIndex.search = function (q, callback) {
    searcher.search(SearchIndex.indexes, q, function (results) {
      //TODO: make error throwing real
      callback(null, results);
    });
  };

  SearchIndex.snapShotBatch = function (callback) {
    replicator.createSnapShotBatch(SearchIndex.indexes, callback);
  };

  SearchIndex.snapShot = function (callback) {
    replicator.createSnapShot(SearchIndex.indexes, callback);
  };

  SearchIndex.tellMeAboutMySearchIndex = function (callback) {
    calibrater.getTotalDocs(SearchIndex.indexes, function (err, totalDocs) {
      callback({totalDocs: totalDocs});
    });
  };

  SearchIndex.close = function (callback) {
    SearchIndex.indexes.close(function () {
      while (SearchIndex.indexes.isOpen()) {
        console.log('closing...');
      }
      callback(null);
    });
  };

  //utility methods for testing and devlopment
  //******************************************
  SearchIndex.indexRange = function (options, callback) {
    indexPeek.indexRange(options.start, options.stop, SearchIndex.indexes, callback);
  };

  SearchIndex.indexValue = function (options, callback) {
    indexPeek.indexValue(options.key, SearchIndex.indexes, callback);
  };
  //do a full recalibration of the index
  SearchIndex.calibrate = function (callback) {
    calibrater.calibrate(SearchIndex.indexes, callback);
  };
  return SearchIndex;
};
<|endoftext|>