<commit_before>/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2019
 */

import { COLUMN_TYPES } from './constants';
import { getRowColumnIndicatorValue } from '../../database/utilities/getIndicatorData';

const COLUMN_INDICATOR = {
  width: 1,
  sortable: false,
};

const COLUMN_INDICATOR_IMMUTABLE = {
  type: COLUMN_TYPES.STRING,
  editable: false,
  ...COLUMN_INDICATOR,
};

const COLUMN_INDICATOR_MUTABLE = {
  type: COLUMN_TYPES.EDITABLE_STRING,
  editable: true,
  ...COLUMN_INDICATOR,
};

const COLUMNS = {
  DESCRIPTION: {
    ...COLUMN_INDICATOR_IMMUTABLE,
    title: 'Description',
    key: 'description',
  },
  CODE: {
    ...COLUMN_INDICATOR_IMMUTABLE,
    title: 'Code',
    key: 'code',
  },
};

/**
 * Get data table row for indicator row attribute.
 * @param {IndicatorAttribute} row
 * @param {ProgramIndicator} indicator
 * @param {Period} period
 * @return {object}
 */
const mapIndicatorTableRow = (row, period) => {
  const { id, description, code, indicator } = row;
  const values = indicator.columns.reduce((acc, column) => {
    const { code: key } = column;
    const value = getRowColumnIndicatorValue(row, column, period);
    return { ...acc, [key]: value.value };
  }, {});
  return { id, description, code, ...values };
};

/**
 * Map indicator rows to data table row objects.
 * @param {Array.<IndicatorAttribute>} rows
 * @param {Period} period
 * @return {Array.<object>}
 */
const mapIndicatorTableRows = (rows, period) => {
  console.log(period);
  const tableRows = rows.map(row => mapIndicatorTableRow(row, period));
  console.log(tableRows);
  return tableRows;
};

/**
 * Get indicator data table row objects.
 * @param {ProgramIndicator} indicator
 * @param {Period} period
 * @return {Array.<object>}
 */
const getIndicatorTableRows = (indicator, period) => {
  if (!indicator) return [];
  return mapIndicatorTableRows(indicator.rows, period);
};

/**
 * Get data table column for indicator row attribute.
 * @param {IndicatorAttribute} column
 * @return {object}
 */
const mapIndicatorTableColumn = (column, isEditable) => {
  const tableColumn = isEditable ? COLUMN_INDICATOR_MUTABLE : COLUMN_INDICATOR_IMMUTABLE;
  const { description: title, code: key } = column;
  return {
    ...tableColumn,
    title,
    key,
  };
};

/**
 * Map indicator columns to data table column objects.
 * @param {Array.<IndicatorAttribute>} indicatorColumns
 * @oaram {bool} isEditable
 * @return {Array.<object>}
 */
const mapIndicatorTableColumns = (indicatorColumns, isEditable) => {
  const valueColumns = indicatorColumns.map(column => mapIndicatorTableColumn(column, isEditable));
  return [COLUMNS.DESCRIPTION, COLUMNS.CODE, ...valueColumns];
};

/**
 * Get indicator data table column objects.
 * @param {ProgramIndicator} indicator
 * @return {Array.<object>}
 */
const getIndicatorTableColumns = indicator => {
  if (!indicator) return [];
  return mapIndicatorTableColumns(indicator.columns);
};

/**
 * Get indicator data table rows and columns.
 * @param {ProgramIndicator} indicator
 * @param {Period} period
 * @returns {object}
 */
const getIndicatorTableData = (indicator, period) => {
  const columns = getIndicatorTableColumns(indicator);
  const rows = getIndicatorTableRows(indicator, period);
  return { columns, rows };
};

export {
  mapIndicatorTableRow,
  mapIndicatorTableRows,
  getIndicatorTableRows,
  mapIndicatorTableColumn,
  mapIndicatorTableColumns,
  getIndicatorTableColumns,
  getIndicatorTableData,
};
<commit_msg>Remove unneeded getIndicatorTableData methods<commit_after>/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2019
 */

import { COLUMN_TYPES } from './constants';
import { getRowColumnIndicatorValue } from '../../database/utilities/getIndicatorData';

const COLUMN_INDICATOR = {
  width: 1,
  sortable: false,
};

const COLUMN_INDICATOR_IMMUTABLE = {
  type: COLUMN_TYPES.STRING,
  editable: false,
  ...COLUMN_INDICATOR,
};

const COLUMN_INDICATOR_MUTABLE = {
  type: COLUMN_TYPES.EDITABLE_STRING,
  editable: true,
  ...COLUMN_INDICATOR,
};

const COLUMNS = {
  DESCRIPTION: {
    ...COLUMN_INDICATOR_IMMUTABLE,
    title: 'Description',
    key: 'description',
  },
  CODE: {
    ...COLUMN_INDICATOR_IMMUTABLE,
    title: 'Code',
    key: 'code',
  },
};

/**
 * Get data table row for indicator row attribute.
 * @param {Array.<IndicatorAttribute>} rows
 * @param {ProgramIndicator} indicator
 * @param {Period} period
 * @return {object}
 */
const mapIndicatorTableRows = (rows, period) =>
  rows.map(row => {
    const { id, description, code, indicator } = row;
    const values = indicator.columns.reduce((acc, column) => {
      const { code: key } = column;
      const value = getRowColumnIndicatorValue(row, column, period);
      return { ...acc, [key]: value.value };
    }, {});
    return { id, description, code, ...values };
  });

/**
 * Get data table column for indicator row attribute.
 * @param {IndicatorAttribute} column
 * @return {object}
 */
const mapIndicatorTableColumn = (column, isEditable) => {
  const tableColumn = isEditable ? COLUMN_INDICATOR_MUTABLE : COLUMN_INDICATOR_IMMUTABLE;
  const { description: title, code: key } = column;
  return {
    ...tableColumn,
    title,
    key,
  };
};

/**
 * Map indicator columns to data table column objects.
 * @param {Array.<IndicatorAttribute>} indicatorColumns
 * @oaram {bool} isEditable
 * @return {Array.<object>}
 */
const mapIndicatorTableColumns = (indicatorColumns, isEditable) => {
  const valueColumns = indicatorColumns.map(column => mapIndicatorTableColumn(column, isEditable));
  return [COLUMNS.DESCRIPTION, COLUMNS.CODE, ...valueColumns];
};

export { mapIndicatorTableRows, mapIndicatorTableColumns };
<|endoftext|>


<commit_before>/*
 * IoT - Industrial Internet Framework
 * Apache License Version 2.0, January 2004
 * Released as a part of Helsinki University
 * Software Engineering Lab in summer 2015
 */

var IIFramework = angular.module('IIFramework', [
    'ngRoute',
    'angular-flot',
    'informationSources',
    'configurations',
    'sysErrors'
]);

IIFramework.config(function($routeProvider) {
    $routeProvider.when('/', {
        controller: 'FrontController',
        templateUrl: 'ngviews/front.html'
    })
            .when('/visual/now', {
                controller: 'VisualRealtimeController',
                templateUrl: 'ngviews/visual_realtime.html'
            })
            .when('/visual/history', {
                controller: 'FrontController',
                templateUrl: 'ngviews/visual_history.html'
            })
            .when('/sources', {
                controller: 'InformationSourcesController',
                templateUrl: 'ngviews/information_sources.html'
            })
            .when('/sources/:informationsourceid', {
                controller: 'InformationSourceController',
                templateUrl: 'ngviews/information_source.html'
            })
            .when('/sensors/:sensorid', {
                controller: 'SensorController',
                templateUrl: 'ngviews/sensor.html'})
            .when('/configurations/add', {
                controller: 'AddInformationSourceController',
                templateUrl: 'ngviews/add_datasource.html'
            })
            .when('/syserrors', {
                controller: 'SysErrorsController',
                templateUrl: 'ngviews/syserrors.html'
            });
});
<commit_msg>fix route<commit_after>/*
 * IoT - Industrial Internet Framework
 * Apache License Version 2.0, January 2004
 * Released as a part of Helsinki University
 * Software Engineering Lab in summer 2015
 */

var IIFramework = angular.module('IIFramework', [
    'ngRoute',
    'angular-flot',
    'informationSources',
    'configurations',
    'sysErrors'
]);

IIFramework.config(function($routeProvider) {
    $routeProvider.when('/', {
        controller: 'FrontController',
        templateUrl: 'ngviews/front.html'
    })
            .when('/visual/now', {
                controller: 'VisualRealtimeController',
                templateUrl: 'ngviews/visual_realtime.html'
            })
            .when('/visual/history', {
                controller: 'FrontController',
                templateUrl: 'ngviews/visual_history.html'
            })
            .when('/sources', {
                controller: 'InformationSourcesController',
                templateUrl: 'ngviews/information_sources.html'
            })
            .when('/sources/:informationsourceid', {
                controller: 'InformationSourceController',
                templateUrl: 'ngviews/information_source.html'
            })
            .when('/sensors/:sensorid', {
                controller: 'SensorController',
                templateUrl: 'ngviews/sensor.html'})
            .when('/configurations/add', {
                controller: 'AddInformationSourceController',
                templateUrl: 'ngviews/add_information_source.html'
            })
            .when('/syserrors', {
                controller: 'SysErrorsController',
                templateUrl: 'ngviews/syserrors.html'
            });
});
<|endoftext|>


<commit_before>/**
    Implementation comments:

     * To support various compose windows we share the data hash between instances
       of ad, through the preferences service with SharedHash

Listener to:
 Event "Onchange of the dictionary button":
    -> save the language for the current recipients
 Event "Changed recipients list":
    -> deduce language for this recipients
    I DIDNT FIND ANY WAY TO GET THE EVENT. It will be kept as observer.

*/
if( typeof(AutomaticDictionary)=== "undefined" ){
    var AutomaticDictionary = {};
}

/* DEBUGGING METHOD
 *
 * This method is used all over the code to show debugging messages.
 *
 * Uncomment the return to show messages on console
 */
AutomaticDictionary.dump = function(msg){
    return; // DISABLED DUMP! COMMENT TO SHOW MESSAGES!
    if( typeof(msg) == "function"){
        msg = msg();
    }
    dump("AutomaticDictionary: ");
    dump(msg);
    dump("\n");
}

/**
 *   SharedHash is used to share preferences between compose windows in case you
 *   have more than one open window open.
 *   Features:
 *       * When a set is done, it will be available to all SharedHashes through preferences-service
 *       * To spread the changes we save a version number in another preference field
 *
 *   data stored expires by size in a LRU logic.
 *
 */
AutomaticDictionary.SharedHash = function( prefPath ){
    this.prefPath = prefPath;
    this.lockPath = prefPath + ".lock";
    this.prefManager = Components.classes["@mozilla.org/preferences-service;1"]
        .getService(Components.interfaces.nsIPrefBranch);
    //Taking into account that we will not init two SharedHashes at the same time
    this.id = (new Date()).getTime().toString() + Math.floor(Math.random()*1000000);

    return this;
}
AutomaticDictionary.SharedHash.prototype = {
    id: null,
    internal_version: 1,
    prefPath: null,
    prefManager: null,
    lockPath: null,
    data: null,
    version: null,
    maxSize: null, //Max number of keys.
    //The builder/constructor of the data strucutre (LRU HASH)
    dataConstructor: AutomaticDictionary.Lib.LRUHashV2,

    load: function(){
        this.maxSize = this.prefManager.getIntPref( this.prefPath + ".maxSize" );
        this.data = this.readData();
        this.version = this.readVersion();
    },
    readVersion: function(){
        return this.prefManager.getCharPref( this.prefPath + ".version" );
    },
    writeVersion: function( v ){
        return this.prefManager.setCharPref( this.prefPath + ".version", v);
    },
    readData:function(){
        var v = this.prefManager.getCharPref( this.prefPath );
        var hash = new this.dataConstructor();
        if( v ){
            try{
                hash.fromJSON( v );
            }catch(e){
                //Restore in case it gets wrong status
                this.log("Failed to load data with " + e.toString() );
                hash = new this.dataConstructor();
            }
        }
        //Override max_size
        hash.max_size = this.maxSize;
        this.log( hash.toJSON() );
        return hash;
    },
    writeData:function(){
        return this.prefManager.setCharPref(
            this.prefPath, this.data.toJSON() );
    },

    sincronized:function( func, force ){
        if( force ) this.log("WARN AutomaticDictionary.SharedHash#sincronized with force");
        try{
            this.log("lock is " + JSON.stringify(
                this.prefManager.getCharPref( this.lockPath )));
            var nestedSync = this.gotLock();
            if( force || nestedSync ||
                    this.prefManager.getCharPref( this.lockPath ).length == 0  ){
                this.prefManager.setCharPref( this.lockPath, this.id );
                this.log("2 - lock is " +
                    JSON.stringify(this.prefManager.getCharPref( this.lockPath )));
                if( force || this.gotLock() ){
                    this.log("executing on sync");
                    func();
                    if( !nestedSync ){
                        this.prefManager.setCharPref( this.lockPath, "" ); //Release lock
                        this.log("lock released is " +
                            this.prefManager.getCharPref( this.lockPath ));
                    }else{
                        this.log("nested lock not released");
                    }
                    return true;
                }else{
                    this.log("lock is not its own: " +
                        this.prefManager.getCharPref( this.lockPath ) + " != " + this.id);
                }
            }else{
                this.log("no force neither is empty");
            }
        }catch(e){
            this.log("ERROR Sincronized failed: " + e.message);
            this.log("INFO Releasing the lock");
            this.prefManager.setCharPref( this.lockPath, "" ); //Release the lock
            throw e;
        }
        this.log("syncronized is false");
        return false;
    },

    gotLock:function(){
        return this.prefManager.getCharPref( this.lockPath ).toString() == this.id;
    },

    /**
    * Function to try to do an operation in sync but gets tired after n retrires
    */
    sincronized_with_patience:function( func, times ){
        for(var t=0; t<times; t++){
            if(this.sincronized(func, t == (times-1))) return true;
            this.log("DEBUG AD.SharedHash#sincronized_with_patience it:"+t);
        }
        this.log("UNREACHABLE CODE REACHED. Internal error. Logs and debugging can be required.");
        return false; //It should not reach here never!
    },

    set: function(key, value){
        var next_version = this.id + this.internal_version++;
        var _this = this;
        this.sincronized_with_patience(
            function(){
                _this.refresh(); //Avoid to ask for sync again
                _this.data.set(key, value);
                _this.writeVersion( next_version );
                _this.writeData();
                _this.log("INFO AutomaticDictionary.SharedHash("+_this.id+") write data sucess");
            }, 10 );
    },

    get: function(key){
        this.refresh();
        return this.data.get(key);
    },
    //Updates to last version of data
    refresh:function(){
        if( this.version != this.readVersion() ){
            var _this = this;
            this.sincronized_with_patience(function(){
                _this.load()
            }, 10 );
        }
        this.log(this.data.toSource());
    },
    log: function(msg){
        AutomaticDictionary.dump(msg);
    }
}

AutomaticDictionary.Class = function(){
    this.log("ad: init");
    this.running = true;
    this.prefManager = Components.classes["@mozilla.org/preferences-service;1"]
    .getService(Components.interfaces.nsIPrefBranch);

    //Version migrations upgrade check
    this.migrate();

    this.iter = 0; //ObserveRecipients execution counter
    this.data = new AutomaticDictionary.SharedHash( this.ADDRESS_INFO_PREF );
    this.setListeners();
    this.initialized = true;
    this.start();
    return this;
}

AutomaticDictionary.Class.prototype = {
    //Constants
    ADDRESS_INFO_PREF:"extensions.automatic_dictionary.addressesInfo",
    PREFERENCE_SCOPE: "extensions.automatic_dictionary",
    POLLING_DELAY: 3000, //Miliseconds

    //Attributes
    initialized: false,
    running: false, //Stopped
    data: null,
    last_timeout: null, //Timer object of the next poll
    instance_number: -1,
    prefManager: null,

    stop: function(){
        if( !this.running ) return; //Already stopped
        this.log("ad: stop");
        this.changeLabel("");
        this.running = false;
        if( this.last_timeout ) window.clearTimeout( this.last_timeout );
        this.last_timeout = null;
    },

    start: function(){
        if( this.running ) return; //Already started
        this.log("ad: start");
        this.running = true;
        this.observeRecipients();
    },

    observeRecipients: function(){
        this.log("ad: observeRecipients");
        if( !this.running ) return;
        this.log("ad: observeRecipients - running");
        this.iter++;
        try{
            this.deduceLanguage();
            //Queue next call
            if( this.running ){
                var _this = this;
                this.last_timeout = setTimeout(function(){
                    _this.observeRecipients();
                }, this.POLLING_DELAY );
            }
        }catch(e){
            this.changeLabel( e.toString());
            throw e;
        }
    },

    //Called when the user changes the language of the dictionary (event based func)
    languageChanged: function( event ){
        this.log("languageChanged by event");
        var current_lang = event.target.value;
        var arr = this.getRecipients();
        if( arr.length > 0 ){
            //The user has set the language for the recipients
            //We update the assignment of language for those recipients
            if( arr.length > 1 ){
                var group = this.stringifyRecipientsGroup( arr );
                this.data.set( group, current_lang );

                for( var i in arr){
                    // Save the lang only if it has no lang setted!
                    if( !this.data.get( arr[i] ) ){
                        this.data.set(arr[i], current_lang);
                    }
                }
            }else{
                this.data.set(arr[0], current_lang);
            }

            this.changeLabel(
                this.ft( "savedForRecipients",
                    [ current_lang, arr.length ] )
                );
        }
    },

    // Updates the interface with the lang deduced from the recipients
    /*
        How we search the lang:
         1. The recipients in "To" all together.
                This allows to recover specific language when the recipients
                are from diferent languages.
         2. The recipients alone in order of appearence.
    */
    deduceLanguage: function(){
        var recipients = this.getRecipients();
        if( recipients.length == 0 ){
            return;
        }
        this.log("Deducing language for: " + recipients.toSource());
        var target_lang = null;
        //Check if all them have a specific language. We want them ordered to maximize hits
        //Clone array and sort it
        var alltogether_key = this.stringifyRecipientsGroup( recipients );
        var lang = this.getLangFor( alltogether_key );

        // It returns the first recipient that has a language.
        // That is useful but maybe it's not the most convenient way.
        if( !lang ){
            for( var idx in recipients ){
                lang = this.getLangFor( recipients[idx] );
                if( lang ){
                    break;
                }
            }
        }
        if(lang){
            var worked = false;
            try{
                this.setCurrentLang( lang );
                worked = true;
            }catch( e ){
                this.changeLabel( this.ft("errorSettingSpellLanguage", [lang] ));
                throw e;
            }
            if(worked) this.changeLabel( this.ft("deducedLang", [lang]))
        }else{
            this.changeLabel(this.t( "noLangForRecipients" ));
        }
    },

    // It returns a string representing the array of recipients not caring about the order
    stringifyRecipientsGroup: function( arr ){
        var sorted = [];
        for( var k in arr ){
            sorted.push( arr[k] );
        }
        sorted.sort();
        return sorted.toString();
    },

    setCurrentLang: function( target ){
        var fake_event = {
            target: {
                value: target
            },
            stopPropagation: function(){}
        };
        ChangeLanguage( fake_event );
    },

    getLangFor: function( addr ){
        return this.data.get(addr);
    },

    getRecipients: function(){
        var fields = Components.classes["@mozilla.org/messengercompose/composefields;1"]
            .createInstance(Components.interfaces.nsIMsgCompFields);
        Recipients2CompFields( fields );
        dump(" FTO");
        dump(fields.to);
        var nsIMsgRecipientArrayInstance = {
            length:0
        };
        if( fields.to ){

            nsIMsgRecipientArrayInstance = fields.splitRecipients( fields.to, true, {} );
        }
        var arr = [];
        if(nsIMsgRecipientArrayInstance.length > 0){
            for(var i=0; i< nsIMsgRecipientArrayInstance.length; i++){
                arr.push(nsIMsgRecipientArrayInstance[i].toString());
            }
        }
        this.log("recipients found: " + arr.toSource());
        return arr;
    },

    setListeners: function(){
        if( window ){
            var _this = this;
            window.addEventListener("compose-window-close", function(){
                _this.stop()
                }, true);
            window.addEventListener('compose-window-reopen', function(){
                _this.start()
                }, true);
            //Observe when the dict changes
            document.getElementById("languageMenuList").addEventListener("command",
                function(event){
                    _this.languageChanged(event);
                },false);

            window.addEventListener("blur", function(){ _this.stop(); } , true);
            window.addEventListener("focus", function(){ _this.start(); }, true );

            this.log("events seem to be registered");
        }else{
            this.changeLabel(this.t("settingListenersError"));
            this.log("no window found");
        }
    },

    getLabel: function(){
        return document.getElementById("automatic-dictionary-panel");
    },

    changeLabel: function( str ){
        var x = this.getLabel();
        if(x)
            x.label = str;
        else
            this.log("no label found");
    },

    getStrBundle: function(){
        return document.getElementById("automaticdictionarystrings");
    },

    //Translation (i18n) helper functions
    t: function( key ){
        return this.getStrBundle().getString(key);
    },

    ft: function(key, values){
        return this.getStrBundle().getFormattedString(key, values);
    },

    //Log function
    log:function( msg ){
        AutomaticDictionary.dump( msg );
    },

    /* Migrations section */

    // Upgrades plugin data to current release version
    migrate: function(){
        // Get current migrations applied
        var pref_key = this.PREFERENCE_SCOPE + ".migrations_applied";

        var migrations_applied = [];
        var raw_data = this.prefManager.getCharPref( pref_key );
        if( raw_data !== "" ){
            migrations_applied = JSON.parse( raw_data );
        }

        // Apply all data migrations required
        // Get ordered migrations (by key size)
        var available_migrations = [];
        for( key in this.migrations ){
            available_migrations.push( key )
        }

        //Iterate over migration keys and apply them if needed
        available_migrations.sort();
        for( idx in available_migrations ){
            var migration_key = available_migrations[ idx ];
            if( migrations_applied.indexOf( migration_key ) < 0 ){
                //apply migration
                this.log("applying migration "+ migration_key);
                this.migrations[ migration_key ](this);
                migrations_applied.push( migration_key );
                this.log("migration "+ migration_key + " applied successfully");
            }
        }
        this.prefManager.setCharPref( pref_key, JSON.stringify( migrations_applied ) );
    },

    //Ordered migrations
    migrations: {
        //Key is date
        "201101010000": function(self){
            self.log("running base migration");
        },
        "201102130000": function(self){
            //Adpat data structure to new one
            // Steps: 1. Load old data. 2. Save as new data
            var prefPath = self.ADDRESS_INFO_PREF;
            var v = self.prefManager.getCharPref( prefPath );
            if( v ){
                try{
                    v = JSON.parse( v );
                }catch(e){
                    self.log("Failed the read of the old preferences. Maybe they were empty.");
                    return; // Nothing to migrate.
                }
            }else{ return; }
            // Save data as new format!
            var maxSize = self.prefManager.getIntPref( prefPath + ".maxSize");
            var lru = new AutomaticDictionary.Lib.LRUHashV2( v, {size: maxSize} );
            self.prefManager.setCharPref(prefPath, lru.toJSON());
        }
    }
}

var automatic_dictionary_instance = new AutomaticDictionary.Class();<commit_msg>remove debug messages<commit_after>/**
    Implementation comments:

     * To support various compose windows we share the data hash between instances
       of ad, through the preferences service with SharedHash

Listener to:
 Event "Onchange of the dictionary button":
    -> save the language for the current recipients
 Event "Changed recipients list":
    -> deduce language for this recipients
    I DIDNT FIND ANY WAY TO GET THE EVENT. It will be kept as observer.

*/
if( typeof(AutomaticDictionary)=== "undefined" ){
    var AutomaticDictionary = {};
}

/* DEBUGGING METHOD
 *
 * This method is used all over the code to show debugging messages.
 *
 * Uncomment the return to show messages on console
 */
AutomaticDictionary.dump = function(msg){
    return; // DISABLED DUMP! COMMENT TO SHOW MESSAGES!
    if( typeof(msg) == "function"){
        msg = msg();
    }
    dump("AutomaticDictionary: ");
    dump(msg);
    dump("\n");
}

/**
 *   SharedHash is used to share preferences between compose windows in case you
 *   have more than one open window open.
 *   Features:
 *       * When a set is done, it will be available to all SharedHashes through preferences-service
 *       * To spread the changes we save a version number in another preference field
 *
 *   data stored expires by size in a LRU logic.
 *
 */
AutomaticDictionary.SharedHash = function( prefPath ){
    this.prefPath = prefPath;
    this.lockPath = prefPath + ".lock";
    this.prefManager = Components.classes["@mozilla.org/preferences-service;1"]
        .getService(Components.interfaces.nsIPrefBranch);
    //Taking into account that we will not init two SharedHashes at the same time
    this.id = (new Date()).getTime().toString() + Math.floor(Math.random()*1000000);

    return this;
}
AutomaticDictionary.SharedHash.prototype = {
    id: null,
    internal_version: 1,
    prefPath: null,
    prefManager: null,
    lockPath: null,
    data: null,
    version: null,
    maxSize: null, //Max number of keys.
    //The builder/constructor of the data strucutre (LRU HASH)
    dataConstructor: AutomaticDictionary.Lib.LRUHashV2,

    load: function(){
        this.maxSize = this.prefManager.getIntPref( this.prefPath + ".maxSize" );
        this.data = this.readData();
        this.version = this.readVersion();
    },
    readVersion: function(){
        return this.prefManager.getCharPref( this.prefPath + ".version" );
    },
    writeVersion: function( v ){
        return this.prefManager.setCharPref( this.prefPath + ".version", v);
    },
    readData:function(){
        var v = this.prefManager.getCharPref( this.prefPath );
        var hash = new this.dataConstructor();
        if( v ){
            try{
                hash.fromJSON( v );
            }catch(e){
                //Restore in case it gets wrong status
                this.log("Failed to load data with " + e.toString() );
                hash = new this.dataConstructor();
            }
        }
        //Override max_size
        hash.max_size = this.maxSize;
        this.log( hash.toJSON() );
        return hash;
    },
    writeData:function(){
        return this.prefManager.setCharPref(
            this.prefPath, this.data.toJSON() );
    },

    sincronized:function( func, force ){
        if( force ) this.log("WARN AutomaticDictionary.SharedHash#sincronized with force");
        try{
            this.log("lock is " + JSON.stringify(
                this.prefManager.getCharPref( this.lockPath )));
            var nestedSync = this.gotLock();
            if( force || nestedSync ||
                    this.prefManager.getCharPref( this.lockPath ).length == 0  ){
                this.prefManager.setCharPref( this.lockPath, this.id );
                this.log("2 - lock is " +
                    JSON.stringify(this.prefManager.getCharPref( this.lockPath )));
                if( force || this.gotLock() ){
                    this.log("executing on sync");
                    func();
                    if( !nestedSync ){
                        this.prefManager.setCharPref( this.lockPath, "" ); //Release lock
                        this.log("lock released is " +
                            this.prefManager.getCharPref( this.lockPath ));
                    }else{
                        this.log("nested lock not released");
                    }
                    return true;
                }else{
                    this.log("lock is not its own: " +
                        this.prefManager.getCharPref( this.lockPath ) + " != " + this.id);
                }
            }else{
                this.log("no force neither is empty");
            }
        }catch(e){
            this.log("ERROR Sincronized failed: " + e.message);
            this.log("INFO Releasing the lock");
            this.prefManager.setCharPref( this.lockPath, "" ); //Release the lock
            throw e;
        }
        this.log("syncronized is false");
        return false;
    },

    gotLock:function(){
        return this.prefManager.getCharPref( this.lockPath ).toString() == this.id;
    },

    /**
    * Function to try to do an operation in sync but gets tired after n retrires
    */
    sincronized_with_patience:function( func, times ){
        for(var t=0; t<times; t++){
            if(this.sincronized(func, t == (times-1))) return true;
            this.log("DEBUG AD.SharedHash#sincronized_with_patience it:"+t);
        }
        this.log("UNREACHABLE CODE REACHED. Internal error. Logs and debugging can be required.");
        return false; //It should not reach here never!
    },

    set: function(key, value){
        var next_version = this.id + this.internal_version++;
        var _this = this;
        this.sincronized_with_patience(
            function(){
                _this.refresh(); //Avoid to ask for sync again
                _this.data.set(key, value);
                _this.writeVersion( next_version );
                _this.writeData();
                _this.log("INFO AutomaticDictionary.SharedHash("+_this.id+") write data sucess");
            }, 10 );
    },

    get: function(key){
        this.refresh();
        return this.data.get(key);
    },
    //Updates to last version of data
    refresh:function(){
        if( this.version != this.readVersion() ){
            var _this = this;
            this.sincronized_with_patience(function(){
                _this.load()
            }, 10 );
        }
        this.log(this.data.toSource());
    },
    log: function(msg){
        AutomaticDictionary.dump(msg);
    }
}

AutomaticDictionary.Class = function(){
    this.log("ad: init");
    this.running = true;
    this.prefManager = Components.classes["@mozilla.org/preferences-service;1"]
    .getService(Components.interfaces.nsIPrefBranch);

    //Version migrations upgrade check
    this.migrate();

    this.iter = 0; //ObserveRecipients execution counter
    this.data = new AutomaticDictionary.SharedHash( this.ADDRESS_INFO_PREF );
    this.setListeners();
    this.initialized = true;
    this.start();
    return this;
}

AutomaticDictionary.Class.prototype = {
    //Constants
    ADDRESS_INFO_PREF:"extensions.automatic_dictionary.addressesInfo",
    PREFERENCE_SCOPE: "extensions.automatic_dictionary",
    POLLING_DELAY: 3000, //Miliseconds

    //Attributes
    initialized: false,
    running: false, //Stopped
    data: null,
    last_timeout: null, //Timer object of the next poll
    instance_number: -1,
    prefManager: null,

    stop: function(){
        if( !this.running ) return; //Already stopped
        this.log("ad: stop");
        this.changeLabel("");
        this.running = false;
        if( this.last_timeout ) window.clearTimeout( this.last_timeout );
        this.last_timeout = null;
    },

    start: function(){
        if( this.running ) return; //Already started
        this.log("ad: start");
        this.running = true;
        this.observeRecipients();
    },

    observeRecipients: function(){
        this.log("ad: observeRecipients");
        if( !this.running ) return;
        this.log("ad: observeRecipients - running");
        this.iter++;
        try{
            this.deduceLanguage();
            //Queue next call
            if( this.running ){
                var _this = this;
                this.last_timeout = setTimeout(function(){
                    _this.observeRecipients();
                }, this.POLLING_DELAY );
            }
        }catch(e){
            this.changeLabel( e.toString());
            throw e;
        }
    },

    //Called when the user changes the language of the dictionary (event based func)
    languageChanged: function( event ){
        this.log("languageChanged by event");
        var current_lang = event.target.value;
        var arr = this.getRecipients();
        if( arr.length > 0 ){
            //The user has set the language for the recipients
            //We update the assignment of language for those recipients
            if( arr.length > 1 ){
                var group = this.stringifyRecipientsGroup( arr );
                this.data.set( group, current_lang );

                for( var i in arr){
                    // Save the lang only if it has no lang setted!
                    if( !this.data.get( arr[i] ) ){
                        this.data.set(arr[i], current_lang);
                    }
                }
            }else{
                this.data.set(arr[0], current_lang);
            }

            this.changeLabel(
                this.ft( "savedForRecipients",
                    [ current_lang, arr.length ] )
                );
        }
    },

    // Updates the interface with the lang deduced from the recipients
    /*
        How we search the lang:
         1. The recipients in "To" all together.
                This allows to recover specific language when the recipients
                are from diferent languages.
         2. The recipients alone in order of appearence.
    */
    deduceLanguage: function(){
        var recipients = this.getRecipients();
        if( recipients.length == 0 ){
            return;
        }
        this.log("Deducing language for: " + recipients.toSource());
        var target_lang = null;
        //Check if all them have a specific language. We want them ordered to maximize hits
        //Clone array and sort it
        var alltogether_key = this.stringifyRecipientsGroup( recipients );
        var lang = this.getLangFor( alltogether_key );

        // It returns the first recipient that has a language.
        // That is useful but maybe it's not the most convenient way.
        if( !lang ){
            for( var idx in recipients ){
                lang = this.getLangFor( recipients[idx] );
                if( lang ){
                    break;
                }
            }
        }
        if(lang){
            var worked = false;
            try{
                this.setCurrentLang( lang );
                worked = true;
            }catch( e ){
                this.changeLabel( this.ft("errorSettingSpellLanguage", [lang] ));
                throw e;
            }
            if(worked) this.changeLabel( this.ft("deducedLang", [lang]))
        }else{
            this.changeLabel(this.t( "noLangForRecipients" ));
        }
    },

    // It returns a string representing the array of recipients not caring about the order
    stringifyRecipientsGroup: function( arr ){
        var sorted = [];
        for( var k in arr ){
            sorted.push( arr[k] );
        }
        sorted.sort();
        return sorted.toString();
    },

    setCurrentLang: function( target ){
        var fake_event = {
            target: {
                value: target
            },
            stopPropagation: function(){}
        };
        ChangeLanguage( fake_event );
    },

    getLangFor: function( addr ){
        return this.data.get(addr);
    },

    getRecipients: function(){
        var fields = Components.classes["@mozilla.org/messengercompose/composefields;1"]
            .createInstance(Components.interfaces.nsIMsgCompFields);
        Recipients2CompFields( fields );
        var nsIMsgRecipientArrayInstance = {
            length:0
        };
        if( fields.to ){

            nsIMsgRecipientArrayInstance = fields.splitRecipients( fields.to, true, {} );
        }
        var arr = [];
        if(nsIMsgRecipientArrayInstance.length > 0){
            for(var i=0; i< nsIMsgRecipientArrayInstance.length; i++){
                arr.push(nsIMsgRecipientArrayInstance[i].toString());
            }
        }
        this.log("recipients found: " + arr.toSource());
        return arr;
    },

    setListeners: function(){
        if( window ){
            var _this = this;
            window.addEventListener("compose-window-close", function(){
                _this.stop()
                }, true);
            window.addEventListener('compose-window-reopen', function(){
                _this.start()
                }, true);
            //Observe when the dict changes
            document.getElementById("languageMenuList").addEventListener("command",
                function(event){
                    _this.languageChanged(event);
                },false);

            window.addEventListener("blur", function(){ _this.stop(); } , true);
            window.addEventListener("focus", function(){ _this.start(); }, true );

            this.log("events seem to be registered");
        }else{
            this.changeLabel(this.t("settingListenersError"));
            this.log("no window found");
        }
    },

    getLabel: function(){
        return document.getElementById("automatic-dictionary-panel");
    },

    changeLabel: function( str ){
        var x = this.getLabel();
        if(x)
            x.label = str;
        else
            this.log("no label found");
    },

    getStrBundle: function(){
        return document.getElementById("automaticdictionarystrings");
    },

    //Translation (i18n) helper functions
    t: function( key ){
        return this.getStrBundle().getString(key);
    },

    ft: function(key, values){
        return this.getStrBundle().getFormattedString(key, values);
    },

    //Log function
    log:function( msg ){
        AutomaticDictionary.dump( msg );
    },

    /* Migrations section */

    // Upgrades plugin data to current release version
    migrate: function(){
        // Get current migrations applied
        var pref_key = this.PREFERENCE_SCOPE + ".migrations_applied";

        var migrations_applied = [];
        var raw_data = this.prefManager.getCharPref( pref_key );
        if( raw_data !== "" ){
            migrations_applied = JSON.parse( raw_data );
        }

        // Apply all data migrations required
        // Get ordered migrations (by key size)
        var available_migrations = [];
        for( key in this.migrations ){
            available_migrations.push( key )
        }

        //Iterate over migration keys and apply them if needed
        available_migrations.sort();
        for( idx in available_migrations ){
            var migration_key = available_migrations[ idx ];
            if( migrations_applied.indexOf( migration_key ) < 0 ){
                //apply migration
                this.log("applying migration "+ migration_key);
                this.migrations[ migration_key ](this);
                migrations_applied.push( migration_key );
                this.log("migration "+ migration_key + " applied successfully");
            }
        }
        this.prefManager.setCharPref( pref_key, JSON.stringify( migrations_applied ) );
    },

    //Ordered migrations
    migrations: {
        //Key is date
        "201101010000": function(self){
            self.log("running base migration");
        },
        "201102130000": function(self){
            //Adpat data structure to new one
            // Steps: 1. Load old data. 2. Save as new data
            var prefPath = self.ADDRESS_INFO_PREF;
            var v = self.prefManager.getCharPref( prefPath );
            if( v ){
                try{
                    v = JSON.parse( v );
                }catch(e){
                    self.log("Failed the read of the old preferences. Maybe they were empty.");
                    return; // Nothing to migrate.
                }
            }else{ return; }
            // Save data as new format!
            var maxSize = self.prefManager.getIntPref( prefPath + ".maxSize");
            var lru = new AutomaticDictionary.Lib.LRUHashV2( v, {size: maxSize} );
            self.prefManager.setCharPref(prefPath, lru.toJSON());
        }
    }
}

var automatic_dictionary_instance = new AutomaticDictionary.Class();<|endoftext|>


<commit_before>// Copyright (c) 2015 Uber Technologies, Inc.

// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

'use strict';

module.exports.VERSION = 2;

var Types = {};
module.exports.Types = Types;

var RetryFlags = require('../retry-flags.js');
var Frame = require('./frame');
var LazyFrame = require('./lazy_frame');

module.exports.CallFlags = require('./call_flags');

var init = require('./init');
Types.InitRequest = init.Request.TypeCode;
Types.InitResponse = init.Response.TypeCode;
Frame.Types[Types.InitRequest] = init.Request;
Frame.Types[Types.InitResponse] = init.Response;
module.exports.InitRequest = init.Request;
module.exports.InitResponse = init.Response;

var call = require('./call');
Types.CallRequest = call.Request.TypeCode;
Types.CallResponse = call.Response.TypeCode;
Frame.Types[Types.CallRequest] = call.Request;
Frame.Types[Types.CallResponse] = call.Response;
module.exports.CallRequest = call.Request;
module.exports.CallResponse = call.Response;
module.exports.MaxArg1Size = 0x4000;

var Cancel = require('./cancel');
Types.Cancel = Cancel.TypeCode;
Frame.Types[Types.Cancel] = Cancel;
module.exports.Cancel = Cancel;

var cont = require('./cont');
Types.CallRequestCont = cont.RequestCont.TypeCode;
Types.CallResponseCont = cont.ResponseCont.TypeCode;
Frame.Types[Types.CallRequestCont] = cont.RequestCont;
Frame.Types[Types.CallResponseCont] = cont.ResponseCont;
module.exports.CallRequestCont = cont.RequestCont;
module.exports.CallResponseCont = cont.ResponseCont;

var Claim = require('./claim');
Types.Claim = Claim.TypeCode;
Frame.Types[Types.Claim] = Claim;
module.exports.Claim = Claim;

var ping = require('./ping');
Types.PingRequest = ping.Request.TypeCode;
Types.PingResponse = ping.Response.TypeCode;
Frame.Types[Types.PingRequest] = ping.Request;
Frame.Types[Types.PingResponse] = ping.Response;
module.exports.PingRequest = ping.Request;
module.exports.PingResponse = ping.Response;

var ErrorResponse = require('./error_response');
Types.ErrorResponse = ErrorResponse.TypeCode;
Frame.Types[Types.ErrorResponse] = ErrorResponse;
module.exports.ErrorResponse = ErrorResponse;

module.exports.Checksum = require('./checksum');

module.exports.Frame = Frame;
module.exports.LazyFrame = LazyFrame;

module.exports.parseRetryFlags = function parseRetryFlags(val) {
    val = val || 'c';
    var never = val.indexOf('n') > -1;
    var onConnectionError = !never && val.indexOf('c') > -1;
    var onTimeout = !never && val.indexOf('t') > -1;

    return new RetryFlags(
        never, onConnectionError, onTimeout
    );
};

module.exports.encodeRetryFlags = function encodeRetryFlags(retryFlags) {
    if (!retryFlags) return '';
    var re = '';
    if (retryFlags.never) {
        re += 'n';
    } else {
        if (retryFlags.onConnectionError) re += 'c';
        if (retryFlags.onTimeout) re += 't';
    }
    return re;
};

module.exports.Handler = require('./handler');
<commit_msg>v2: export Tracing<commit_after>// Copyright (c) 2015 Uber Technologies, Inc.

// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

'use strict';

module.exports.VERSION = 2;

var Types = {};
module.exports.Types = Types;

var RetryFlags = require('../retry-flags.js');
var Frame = require('./frame');
var LazyFrame = require('./lazy_frame');

module.exports.CallFlags = require('./call_flags');

var init = require('./init');
Types.InitRequest = init.Request.TypeCode;
Types.InitResponse = init.Response.TypeCode;
Frame.Types[Types.InitRequest] = init.Request;
Frame.Types[Types.InitResponse] = init.Response;
module.exports.InitRequest = init.Request;
module.exports.InitResponse = init.Response;

var call = require('./call');
Types.CallRequest = call.Request.TypeCode;
Types.CallResponse = call.Response.TypeCode;
Frame.Types[Types.CallRequest] = call.Request;
Frame.Types[Types.CallResponse] = call.Response;
module.exports.CallRequest = call.Request;
module.exports.CallResponse = call.Response;
module.exports.MaxArg1Size = 0x4000;

var Cancel = require('./cancel');
Types.Cancel = Cancel.TypeCode;
Frame.Types[Types.Cancel] = Cancel;
module.exports.Cancel = Cancel;

var cont = require('./cont');
Types.CallRequestCont = cont.RequestCont.TypeCode;
Types.CallResponseCont = cont.ResponseCont.TypeCode;
Frame.Types[Types.CallRequestCont] = cont.RequestCont;
Frame.Types[Types.CallResponseCont] = cont.ResponseCont;
module.exports.CallRequestCont = cont.RequestCont;
module.exports.CallResponseCont = cont.ResponseCont;

var Claim = require('./claim');
Types.Claim = Claim.TypeCode;
Frame.Types[Types.Claim] = Claim;
module.exports.Claim = Claim;

var ping = require('./ping');
Types.PingRequest = ping.Request.TypeCode;
Types.PingResponse = ping.Response.TypeCode;
Frame.Types[Types.PingRequest] = ping.Request;
Frame.Types[Types.PingResponse] = ping.Response;
module.exports.PingRequest = ping.Request;
module.exports.PingResponse = ping.Response;

var ErrorResponse = require('./error_response');
Types.ErrorResponse = ErrorResponse.TypeCode;
Frame.Types[Types.ErrorResponse] = ErrorResponse;
module.exports.ErrorResponse = ErrorResponse;

module.exports.Checksum = require('./checksum');

module.exports.Frame = Frame;
module.exports.LazyFrame = LazyFrame;

module.exports.parseRetryFlags = function parseRetryFlags(val) {
    val = val || 'c';
    var never = val.indexOf('n') > -1;
    var onConnectionError = !never && val.indexOf('c') > -1;
    var onTimeout = !never && val.indexOf('t') > -1;

    return new RetryFlags(
        never, onConnectionError, onTimeout
    );
};

module.exports.encodeRetryFlags = function encodeRetryFlags(retryFlags) {
    if (!retryFlags) return '';
    var re = '';
    if (retryFlags.never) {
        re += 'n';
    } else {
        if (retryFlags.onConnectionError) re += 'c';
        if (retryFlags.onTimeout) re += 't';
    }
    return re;
};

module.exports.Handler = require('./handler');

module.exports.Tracing = require('./tracing.js');
<|endoftext|>


<commit_before>"use strict";

window.cls || (window.cls = {});

/**
 * @constructor
 * @extends ViewBase
 */
cls.ResourceDetailView = function(id, name, container_class, html, default_handler, resource_inspector)
{
  this.createView = function(container)
  {
    if (this.resource && this.resource.data)
      container.clearAndRender(templates.resource_detail.formatting_data(this.resource));

    container.clearAndRender(templates.resource_detail.update(this.resource));
    if (this.data)
      this.go_to_line(container, this.data);

    this.text_search.update_search();
  };

  this.create_disabled_view = function(container)
  {
    container.clearAndRender(window.templates.disabled_view());
  };

  this.TEXT_RESOURCE_DETAIL_CONTAINER_SELECTOR = ["markup", "css", "script", "text"].map(function(v) {
    return ".resource-detail-" + v + "-container";
  });
  var HIGHLIGHTED_LINE_CLASSNAME = "highlighted-line";
  var TEXT = document.TEXT_NODE;
  var ELE = document.ELEMENT_NODE;
  var HIGHLIGHT_CONTEXT_SIZE = 8;
  var CR = "\r";
  var LF = "\n";
  this._span = document.createElement("span");
  this._span.textContent = " ";
  this._line_count = 0;
  this._line_found = false;
  this._target_line = 0;
  this._root_ele = null;
  this._tops = [];

  this._highlight_line = function(ele)
  {
    var child = ele.firstChild;
    while (child && !this._line_found)
    {
      if (child.nodeType == ELE)
      {
        this._highlight_line(child);
      }
      else if (child.nodeType == TEXT)
      {
        var value = child.nodeValue;
        for (var pos = 0, len = value.length; pos < len; pos++)
        {
          var c = value.charAt(pos);
          if ((c == CR) || (c == LF))
          {
            this._line_count++;
            if (this._line_count == this._target_line)
            {
              var target_pos = child.splitText(pos);
              child.parentNode.insertBefore(this._span, target_pos);
              this._tops.push(this._span.getBoundingClientRect().top);
              child.parentNode.removeChild(this._span);
              child.parentNode.normalize();
              if (this._tops.length < 2)
              {
                this._target_line += 1;
              }
              else
              {
                var scroll_container = ele;
                var container_top = scroll_container.getBoundingClientRect().top;
                var delta = this._tops[1] - this._tops[0];
                var scroll_top = scroll_container.scrollTop;
                ele.addClass(HIGHLIGHTED_LINE_CLASSNAME);
                ele.style.cssText =
                  "background-size: 100% " + delta + "px;" +
                  "background-position: 0 " +
                    (this._tops[0] - container_top + scroll_top) + "px;";

                var scroll_position = scroll_top + this._tops[0] - container_top;
                if (scroll_position <= this._root_ele.parentNode.clientHeight)
                  scroll_position -= HIGHLIGHT_CONTEXT_SIZE * window.defaults["js-source-line-height"];

                this._root_ele.scrollTop = scroll_position;
                this._line_found = true;
                return;
              }
            }

            if ((c == CR) && (value.charAt(pos + 1) == LF))
              pos++;
          }
        }
      }
      child = child && child.nextSibling;
    }
  };

  this.clear_line_highlight = function(container)
  {
    // reset all properties
    this._line_count = 0;
    this._line_found = false;
    this._target_line = 0;
    this._tops = [];
    var ele = container.querySelector("." + HIGHLIGHTED_LINE_CLASSNAME);
    if (ele)
      ele.removeClass(HIGHLIGHTED_LINE_CLASSNAME);
  };

  this.go_to_line = function(container, data)
  {
    if (!data || data.line == null)
      return;

    this._root_ele = container.querySelector(this.TEXT_RESOURCE_DETAIL_CONTAINER_SELECTOR);
    if (this._root_ele)
    {
      this.clear_line_highlight(this._root_ele);
      this._target_line = data.line;
      this._highlight_line(this._root_ele);
    }
  };

  this._show_resource = function(resource, data)
  {
    if (!resource || !resource.data)
      return false;

    this.data = data;
    this.resource = resource;
    this._resource_inspector.highlight_resource(resource.uid);
    this.update();

    return true;
  };

  this._show_resource_by_instance = function(resource, data)
  {
    if (!this._show_resource(resource, data))
      this._show_resource_by_key(resource.uid, data);
  };

  this._show_resource_by_key = function(key, data)
  {
    var inspector = this._resource_inspector;
    var resource = inspector.get_resource(key) || inspector.get_resource_by_url(key);

    var url = resource ? resource.url : key;

    if (!resource || !this._show_resource(resource, data))
      inspector.request_resource_data(url, this.show_resource.bind(this), data, resource);
  };

  /*
   *  The "key" can be the UID of or an instance of cls.ResourceInfo or a URL
   *
   *  This flexibility helps other services to display a resource regardless of its "form".
   */
  this.show_resource = function(key, data)
  {
    if (key instanceof cls.ResourceInfo)
      this._show_resource_by_instance(key, data);
    else
      this._show_resource_by_key(key, data);

    window.UI.instance.show_view(this.id);
  };

  this._on_debug_context_selected_bound = function()
  {
    this.resource = null;
    this.update();
  }.bind(this);

  this._init = function(id, name, container_class, html, default_handler, resource_inspector)
  {
    this.id = id;
    this._resource_inspector = resource_inspector;

    var messages = window.messages;
    messages.add_listener("debug-context-selected", this._on_debug_context_selected_bound);

    window.event_handlers.input["resource-detail-font"] = function(event, target)
    {
      window.settings.resource_detail_view.set("sample_string", target.value);
    };

    this.init(id, name, container_class, html, default_handler);
  };

  this._init(id, name, container_class, html, default_handler, resource_inspector)
};

cls.ResourceDetailView.create_ui_widgets = function()
{
  new Settings(
    "resource_detail_view",
    {
      "sample_string": "The quick brown fox jumps over the lazy dog. 0123456789"
    }
  );

  new ToolbarConfig(
  {
    view: "resource_detail_view",
    groups:
    [
      {
        type: UI.TYPE_INPUT,
        items:
        [
          {
            handler: "resource-text-search",
            shortcuts: "resource-text-search",
            title: ui_strings.S_SEARCH_INPUT_TOOLTIP,
            label: ui_strings.S_INPUT_DEFAULT_TEXT_SEARCH
          }
        ]
      }
    ]
  });

  var text_search = window.views.resource_detail_view.text_search = new TextSearch();

  window.event_handlers.input["resource-text-search"] = function(event, target)
  {
    text_search.searchDelayed(target.value);
  };

  ActionBroker.get_instance().get_global_handler().register_shortcut_listener(
    "resource-text-search",
    cls.Helpers.shortcut_search_cb.bind(text_search)
  );

  var on_view_created = function(msg)
  {
    if (msg.id === "resource_detail_view")
    {
      var view = window.views.resource_detail_view;
      var scroll_container = msg.container.querySelector(view.TEXT_RESOURCE_DETAIL_CONTAINER_SELECTOR);

      if (scroll_container)
      {
        text_search.setContainer(scroll_container);
        text_search.setFormInput(
          view.getToolbarControl(msg.container, "resource-text-search")
        );
      }
    }
  };

  var on_view_destroyed = function(msg)
  {
    if (msg.id == "resource_detail_view")
      text_search.cleanup();
  };

  window.messages.add_listener("view-created", on_view_created);
  window.messages.add_listener("view-destroyed", on_view_destroyed);
}

cls.ResourceDetailView.prototype = ViewBase;

<commit_msg>Explicit .join(",")<commit_after>"use strict";

window.cls || (window.cls = {});

/**
 * @constructor
 * @extends ViewBase
 */
cls.ResourceDetailView = function(id, name, container_class, html, default_handler, resource_inspector)
{
  this.createView = function(container)
  {
    if (this.resource && this.resource.data)
      container.clearAndRender(templates.resource_detail.formatting_data(this.resource));

    container.clearAndRender(templates.resource_detail.update(this.resource));
    if (this.data)
      this.go_to_line(container, this.data);

    this.text_search.update_search();
  };

  this.create_disabled_view = function(container)
  {
    container.clearAndRender(window.templates.disabled_view());
  };

  this.TEXT_RESOURCE_DETAIL_CONTAINER_SELECTOR = ["markup", "css", "script", "text"].map(function(v) {
    return ".resource-detail-" + v + "-container";
  }).join(",");
  var HIGHLIGHTED_LINE_CLASSNAME = "highlighted-line";
  var TEXT = document.TEXT_NODE;
  var ELE = document.ELEMENT_NODE;
  var HIGHLIGHT_CONTEXT_SIZE = 8;
  var CR = "\r";
  var LF = "\n";
  this._span = document.createElement("span");
  this._span.textContent = " ";
  this._line_count = 0;
  this._line_found = false;
  this._target_line = 0;
  this._root_ele = null;
  this._tops = [];

  this._highlight_line = function(ele)
  {
    var child = ele.firstChild;
    while (child && !this._line_found)
    {
      if (child.nodeType == ELE)
      {
        this._highlight_line(child);
      }
      else if (child.nodeType == TEXT)
      {
        var value = child.nodeValue;
        for (var pos = 0, len = value.length; pos < len; pos++)
        {
          var c = value.charAt(pos);
          if ((c == CR) || (c == LF))
          {
            this._line_count++;
            if (this._line_count == this._target_line)
            {
              var target_pos = child.splitText(pos);
              child.parentNode.insertBefore(this._span, target_pos);
              this._tops.push(this._span.getBoundingClientRect().top);
              child.parentNode.removeChild(this._span);
              child.parentNode.normalize();
              if (this._tops.length < 2)
              {
                this._target_line += 1;
              }
              else
              {
                var scroll_container = ele;
                var container_top = scroll_container.getBoundingClientRect().top;
                var delta = this._tops[1] - this._tops[0];
                var scroll_top = scroll_container.scrollTop;
                ele.addClass(HIGHLIGHTED_LINE_CLASSNAME);
                ele.style.cssText =
                  "background-size: 100% " + delta + "px;" +
                  "background-position: 0 " +
                    (this._tops[0] - container_top + scroll_top) + "px;";

                var scroll_position = scroll_top + this._tops[0] - container_top;
                if (scroll_position <= this._root_ele.parentNode.clientHeight)
                  scroll_position -= HIGHLIGHT_CONTEXT_SIZE * window.defaults["js-source-line-height"];

                this._root_ele.scrollTop = scroll_position;
                this._line_found = true;
                return;
              }
            }

            if ((c == CR) && (value.charAt(pos + 1) == LF))
              pos++;
          }
        }
      }
      child = child && child.nextSibling;
    }
  };

  this.clear_line_highlight = function(container)
  {
    // reset all properties
    this._line_count = 0;
    this._line_found = false;
    this._target_line = 0;
    this._tops = [];
    var ele = container.querySelector("." + HIGHLIGHTED_LINE_CLASSNAME);
    if (ele)
      ele.removeClass(HIGHLIGHTED_LINE_CLASSNAME);
  };

  this.go_to_line = function(container, data)
  {
    if (!data || data.line == null)
      return;

    this._root_ele = container.querySelector(this.TEXT_RESOURCE_DETAIL_CONTAINER_SELECTOR);
    if (this._root_ele)
    {
      this.clear_line_highlight(this._root_ele);
      this._target_line = data.line;
      this._highlight_line(this._root_ele);
    }
  };

  this._show_resource = function(resource, data)
  {
    if (!resource || !resource.data)
      return false;

    this.data = data;
    this.resource = resource;
    this._resource_inspector.highlight_resource(resource.uid);
    this.update();

    return true;
  };

  this._show_resource_by_instance = function(resource, data)
  {
    if (!this._show_resource(resource, data))
      this._show_resource_by_key(resource.uid, data);
  };

  this._show_resource_by_key = function(key, data)
  {
    var inspector = this._resource_inspector;
    var resource = inspector.get_resource(key) || inspector.get_resource_by_url(key);

    var url = resource ? resource.url : key;

    if (!resource || !this._show_resource(resource, data))
      inspector.request_resource_data(url, this.show_resource.bind(this), data, resource);
  };

  /*
   *  The "key" can be the UID of or an instance of cls.ResourceInfo or a URL
   *
   *  This flexibility helps other services to display a resource regardless of its "form".
   */
  this.show_resource = function(key, data)
  {
    if (key instanceof cls.ResourceInfo)
      this._show_resource_by_instance(key, data);
    else
      this._show_resource_by_key(key, data);

    window.UI.instance.show_view(this.id);
  };

  this._on_debug_context_selected_bound = function()
  {
    this.resource = null;
    this.update();
  }.bind(this);

  this._init = function(id, name, container_class, html, default_handler, resource_inspector)
  {
    this.id = id;
    this._resource_inspector = resource_inspector;

    var messages = window.messages;
    messages.add_listener("debug-context-selected", this._on_debug_context_selected_bound);

    window.event_handlers.input["resource-detail-font"] = function(event, target)
    {
      window.settings.resource_detail_view.set("sample_string", target.value);
    };

    this.init(id, name, container_class, html, default_handler);
  };

  this._init(id, name, container_class, html, default_handler, resource_inspector)
};

cls.ResourceDetailView.create_ui_widgets = function()
{
  new Settings(
    "resource_detail_view",
    {
      "sample_string": "The quick brown fox jumps over the lazy dog. 0123456789"
    }
  );

  new ToolbarConfig(
  {
    view: "resource_detail_view",
    groups:
    [
      {
        type: UI.TYPE_INPUT,
        items:
        [
          {
            handler: "resource-text-search",
            shortcuts: "resource-text-search",
            title: ui_strings.S_SEARCH_INPUT_TOOLTIP,
            label: ui_strings.S_INPUT_DEFAULT_TEXT_SEARCH
          }
        ]
      }
    ]
  });

  var text_search = window.views.resource_detail_view.text_search = new TextSearch();

  window.event_handlers.input["resource-text-search"] = function(event, target)
  {
    text_search.searchDelayed(target.value);
  };

  ActionBroker.get_instance().get_global_handler().register_shortcut_listener(
    "resource-text-search",
    cls.Helpers.shortcut_search_cb.bind(text_search)
  );

  var on_view_created = function(msg)
  {
    if (msg.id === "resource_detail_view")
    {
      var view = window.views.resource_detail_view;
      var scroll_container = msg.container.querySelector(view.TEXT_RESOURCE_DETAIL_CONTAINER_SELECTOR);

      if (scroll_container)
      {
        text_search.setContainer(scroll_container);
        text_search.setFormInput(
          view.getToolbarControl(msg.container, "resource-text-search")
        );
      }
    }
  };

  var on_view_destroyed = function(msg)
  {
    if (msg.id == "resource_detail_view")
      text_search.cleanup();
  };

  window.messages.add_listener("view-created", on_view_created);
  window.messages.add_listener("view-destroyed", on_view_destroyed);
}

cls.ResourceDetailView.prototype = ViewBase;

<|endoftext|>


<commit_before>var openseadragon = require('../lib/openseadragon');

module.exports = (ctx) => {
  var osd = document.getElementsByClassName('osd__toolbar');
  if (osd) {
    Array.prototype.slice.call(osd).forEach(function (el) {
      el.addEventListener('click', function (e) {
        if (!ctx.viewer) {
          openseadragon.init(ctx);
        }
        if (e.target.id === 'osd-fullpage') {
          ctx.viewer.setFullScreen(true);
        } else if (e.target.id === 'osd-home') {
          openseadragon.quit(ctx);
        }
      });
    });
  }
};
<commit_msg>allows clicking images to open zooms #763<commit_after>var openseadragon = require('../lib/openseadragon');

module.exports = (ctx) => {
  var osd = Array.prototype.slice.call(document.getElementsByClassName('osd__toolbar')) || [];
  var singleImages = Array.prototype.slice.call(document.getElementsByClassName('single_image')) || [];
  var selectedImages = Array.prototype.slice.call(document.getElementsByClassName('flickity-viewport')) || [];
  var images = osd.concat(singleImages).concat(selectedImages);

  images.forEach(function (el) {
    el.addEventListener('click', function (e) {
      if (!ctx.viewer) {
        openseadragon.init(ctx);
      }
      ctx.viewer.setFullScreen(true);
      if (e.target.id === 'osd-home') {
        openseadragon.quit(ctx);
      }
    });
  });
};
<|endoftext|>


<commit_before>'use strict';
var azure = require('azure-storage');
var pg = require('pg');
var request = require('request');
var parseString = require('xml2js').parseString;
var conString = 'postgres://' + process.env.POSTGRES + '/postgres';
var retryOperations = new azure.ExponentialRetryPolicyFilter();
var blobSvc = azure.createBlobService().withFilter(retryOperations);
blobSvc.createContainerIfNotExists('userpictures', {publicAccessLevel: 'blob'}, function(error, result, response) {
  if (!error) {
    console.log(result);
    console.log(response);
  } else {
    console.log('error creating azure blob container ', error);
  }
});

/**
 * Get tree data for a single tree (profile view)
 */
exports.getTreeData = function(req, res) {
  var treeid = req.params.treeId;
  pg.connect(conString, function(err, client, done) {
    var selectMessages = 'SELECT tree.name, tree.treeid, q.qspecies, tree.plotsize, tree.qcaretaker, tree.plantdate, l.latitude, l.longitude, image.imageurl, image.imagewidth, image.imageheight, image.imagetype from qspecies q JOIN tree ON (q.qspeciesid = tree.qspeciesid) JOIN "location" l ON (l.locationid = tree.locationid) JOIN image ON (q.qspeciesid = image.qspeciesid) WHERE treeid = $1;';
    client.query(selectMessages, [treeid], function(error, results) {
      if (error) {
        console.log(error, 'THERE WAS AN ERROR');
      } else {
        res.json(results.rows[0]);
      }
      done();
    });
  });
};

/**
 * Get tree data for 250 trees (list view)
 */
exports.getAll = function(req, res) {
  pg.connect(conString, function(err, client, done) {
    // console.log(err);
    var selectTrees = 'SELECT tree.name, tree.treeid, q.qspecies, l.latitude, l.longitude, thumbnail.url, thumbnail.width, thumbnail.height, thumbnail.contenttype FROM qspecies q JOIN tree ON (q.qspeciesid = tree.qspeciesid) JOIN "location" l ON (l.locationid = tree.locationid) JOIN thumbnail ON (q.qspeciesid = thumbnail.qspeciesid) LIMIT 250;';
    client.query(selectTrees, function(error, results) {
    }, function(error, results) {
      done();
      if (error) {
        console.log('Error is ', error);
      }
      res.json(results.rows);
    });
  });
};

/**
 * Gets tree's messages from database and sends it to client. Request is expecting a treeId.
 * @param req
 * @param res
 */
exports.getMessagesForTree = function(req, res) {
  var treeid = req.params.treeid;
  // console.log('in getMessageForTree and treeid is', treeid);
  pg.connect(conString, function(err, client, done) {
    console.log(err);
    var selectMessages = 'SELECT message.message, message.treeid, message.username, message.messageid, message.createdAt FROM message  WHERE treeid = $1 LIMIT 100;';
    client.query(selectMessages, [treeid], function(error, results) {
      res.json(results.rows);
    });
    done();
  });
};

/**
 * Gets messages from users and sends it to the database. Request is expecting an username.
 * @param req
 * @param res
 */
exports.getMessagesForUsers = function(req, res) {
  var username = req.params.userid;
  // console.log('in getMessageForUsers and username is', username);
  pg.connect(conString, function(err, client, done) {
    console.log(err);
    var selectMessages = 'SELECT message.message, message.treeid, tree.name, message.username, message.messageid, message.createdAt FROM message JOIN tree ON (tree.treeid = message.treeid) WHERE username = $1 LIMIT 100;';
    client.query(selectMessages, [username], function(error, results) {
      console.log('error is ', error);
      res.json(results.rows);
    });
    done();
  });
};

/**
 * Gets messages from both users or trees and uploads them to the database. Request expecting treeName or userName,
 * and a treeId.
 * @param req
 * @param res
 */
//
exports.postMessageFromUser = function(req, res) {
  // console.log('got into postMessageFromUser');
  var username = req.body.username;
  var message = req.body.message;
  var treeid = req.body.treeid;
  var returnMessages = [];
  var url = 'http://www.botlibre.com/rest/botlibre/form-chat?application=' + process.env.APPLICATIONID + '&message=' + message + '&instance=812292';
  request(url, function(error, response, body) {
    console.log('body', body);
    if (!error && response.statusCode === 200) {
      parseString(body, function(error, result) {
        pg.connect(conString, function(err, client, done) {
          if (err) {
            console.log('error is', err);
          }
          else {
            var insertMessages = 'INSERT INTO message (message, username, treeid, createdat) values ($1, $2, $3, now()) RETURNING *;';
            client.query(insertMessages, [message, username, treeid], function(error, results) {
              returnMessages.push(results.rows[0]);
              done();
            });
            var insertTreeMessages = 'INSERT INTO message (message, username, treeid, createdat) values ($1, $2, $3, now()) RETURNING *;';
            client.query(insertTreeMessages, [result.response.message[0], 'unknown', treeid], function(error, results) {
              // console.log(error);
              returnMessages.push(results.rows[0]);
              res.send(returnMessages);
              done();
            });
          }
        });
      });
    }
  });
};

/**
 * Search tree return 250 results matching the search params. It takes a search param, and an offset. If offset is not
 * provided it will default to 0.
 * @param req
 * @param res
 */
exports.searchTrees = function(req, res) {
  var search = req.params.search;
  search = search.replace('%20', ' ');
  var offset = req.offset || 0;
  var searchString = typeof search === 'string' ? '%' + search + '%' : 'do not use';
  var searchNum = typeof search === 'number' ? search : 0;
  pg.connect(conString, function(err, client, done) {
    // console.log('in search trees');
    // console.log(err);
    var selectTrees = 'SELECT tree.name, tree.treeid, q.qspecies, l.latitude, l.longitude, thumbnail.url, thumbnail.width, thumbnail.height, thumbnail.contenttype FROM qspecies q JOIN tree ON (q.qspeciesid = tree.qspeciesid) JOIN "location" l ON (l.locationid = tree.locationid) JOIN thumbnail ON (q.qspeciesid = thumbnail.qspeciesid) WHERE ' +
      'tree.treeid = $1 OR tree.name LIKE $2 OR q.qspecies LIKE $2 OR q.qspeciesid = $1 LIMIT 250 OFFSET $3;';

    // var selectTrees = 'SELECT tree.name, q.qspecies, l.latitude, l.longitude, thumbnail.url, thumbnail.width, ' +
    //   'thumbnail.height, thumbnail.contenttype FROM qspecies q JOIN tree ON (q.qspeciesid = tree.qspeciesid) JOIN ' +
    //   '"location" l ON (l.locationid = tree.locationid) JOIN thumbnail ON (q.qspeciesid = thumbnail.qspeciesid) WHERE ' +
    //   'tree.treeid = $1 OR tree.name LIKE $2 OR q.qspecies LIKE $2 OR q.qspeciesid = $1 LIMIT 250 OFFSET $3;';
    client.query(selectTrees, [searchNum, searchString, offset], function(error, results) {
      // console.log('error', error);
      //console.log('results', results);
      // console.log(results.rows);
      res.json(results.rows);
    });
    done();
  });
};

/**
 * This function expects a treeid to insert messages. The userid will be saved with the value of -1 which means that
 * the message was sent by the tree.
 * @param req
 * @param res
 */
exports.insertMessagesFromTrees = function(req, res) {
  console.log('got into insertMessagesFromTrees is ');
  var treeid = req.body.treeid;
  var userid = -1;
  var message = req.body.message;
  // console.log(treeid, userid, message);
  pg.connect(conString, function(err, client, done) {
    // console.log(err);
    var selectMessages = 'INSERT INTO message (message, treeid, userid, createdat) values($1, $2, $3, now())';
    client.query(selectMessages, [message, treeid, userid], function(error, results) {
      // console.log('results is ', results);
      res.send(results);
      done();
    });
  });
};

/**
 * Inserts likes into database. This takes username and treeid.
 * @param req
 * @param res
 */
exports.insertLikes = function(req, res) {
  var treeid = req.body.treeId;
  var username = req.body.username;
  pg.connect(conString, function(err, client, done) {
    // console.log(err);
    var insertLikes = 'INSERT INTO likes (username, treeid) values($1, $2)';
    client.query(insertLikes, [username, treeid], function(error, results) {
      // console.log('results is ', results);
      res.send(results);
      done();
    });
  });
};

/**
 * This gets a list of trees that the user likes. It takes an username.
 * @param req
 * @param res
 */
exports.getTreeLikes = function(req, res) {
  var username = req.body.username;
  pg.connect(conString, function(err, client, done) {
    var selectLikes = 'SELECT tree.name, tree.treeid, q.qspecies, thumbnail.url, thumbnail.width, thumbnail.height, ' +
      'thumbnail.contenttype FROM qspecies q JOIN tree ON (q.qspeciesid = tree.qspeciesid) JOIN thumbnail ON ' +
      '(q.qspeciesid = thumbnail.qspeciesid) JOIN likes ON (tree.treeid = likes.treeid)' + ' WHERE username = $1;';
    client.query(selectLikes, [username], function(error, results) {
      console.log('err', error);
      console.log('results is ', results);
      res.send(results.rows);
      done();
    });
  });
};

/**
 * This gets a list of users which like a particular tree. It takes a treeid.
 * @param req
 * @param res
 */
exports.getUserLikes = function(req, res) {
  var treeid = ''+req.body.treeId;
  pg.connect(conString, function(err, client, done) {
    // console.log(err);
    var selectLikes = 'SELECT username from likes WHERE treeid = $1;';
    client.query(selectLikes, [treeid], function(error, results) {
      // console.log('results is ', results);
      res.send(results.rows);
      done();
    });
  });
};

/**
 * Insert comments for a message into comments table. This takes a username, comment, treeid, and messageid.
 * @param req
 * @param res
 */
exports.insertComments = function(req, res) {
  console.log('insert comments');
  var username = req.body.username;
  var comment = req.body.comment;
  var treeid = req.body.treeid;
  var messageid = req.message.messageid;
  pg.connect(conString, function(err, client, done) {
    if (err) {
      console.log('error is', err);
    }
    else {
      var insertComments = 'INSERT INTO comment (comment, username, treeid, messageid, createdat) values ($1, $2, $3, $5, now) RETURNING *;';
      client.query(insertComments, [comment, username, treeid, messageid], function(error, results) {
        // console.log('postCommentFromUser result is ', results.rows);
        res.json(results.rows);
        done();
      });
    }
  });
};

/**
 * Gets comments from the database for a particular message. This takes a messageid.
 * @param req
 * @param res
 */
exports.getComments = function(req, res) {
  var messageid = req.params.messageid;
  console.log('in getComment');
  pg.connect(conString, function(err, client, done) {
    // console.log(err);
    var selectMessages = 'SELECT comment.comment, message.treeid, message.username, comment.createdAt FROM comments  WHERE messageid = $1 LIMIT 100;';
    client.query(selectMessages, [messageid], function(error, results) {
      res.json(results.rows);
    });
    done();
  });
};

/**
 * This queries the database for a location withing 0.01 from the users longitude and latitude. It expects a latitude
 * and longitude.
 * @param req
 * @param res
 */
exports.findTreesByLocation = function(req, res) {
  console.log('find by loc');
  var lat = parseFloat(req.query.latitude);
  var lng = parseFloat(req.query.longitude);
  var upperLongitude = lng + 0.001;
  var upperLatitude = lat + 0.001;
  var lowerLatitude = lat - 0.001;
  var lowerLongitude = lng - 0.001;
  pg.connect(conString, function(err, client, done) {
    // console.log('in location search');
    // console.log(err);
    var locationQuery = 'SELECT tree.name, q.qspecies, l.latitude, l.longitude, thumbnail.url, thumbnail.width, ' +
      'thumbnail.height, thumbnail.contenttype FROM qspecies q JOIN tree ON (q.qspeciesid = tree.qspeciesid) JOIN ' +
      '"location" l ON (l.locationid = tree.locationid) JOIN thumbnail ON (q.qspeciesid = thumbnail.qspeciesid) WHERE ' +
      '(l.latitude BETWEEN $1 AND $2) AND (l.longitude BETWEEN $3 AND $4)';
    client.query(locationQuery, [lowerLatitude, upperLatitude, lowerLongitude, upperLongitude], function(error, results) {
      // console.log('error', error);
      //console.log('results', results);
      // console.log(results.rows);
      res.json(results.rows);
      done();
    });
  });
};

/**
 * Return an image for a tree. This function takes a treeid.
 * @param req
 * @param res
 */
exports.getTreeImage = function(req, res){
  var treeid = req.params.treeId;
  // console.log('TREE ID', treeid)
  pg.connect(conString, function(err, client, done) {
    // console.log(err);
    var getImage = 'SELECT image.imageurl, image.imagewidth, image.imageheight, image.imagetype FROM image JOIN qspecies ON qspecies.qspeciesid = image.qspeciesid JOIN tree on tree.qspeciesid = qspecies.qspeciesid  WHERE tree.treeid = $1;';
    client.query(getImage, [treeid], function(error, results) {
      // console.log('err',error);
      // console.log('THESE results is ', results);
      res.send(results.rows[0]);
      done();
    });
  });
};

//This can be refactored to store image in DB instead of locally in folder
exports.uploadUserImage = function(req, res, imageName, cb) {
  //packages/articles/server/controllers/test/uploads/
  var localPath = 'packages/theme/public/assets/img/uploads/' + imageName;
  blobSvc.createBlockBlobFromLocalFile('userpictures', imageName, localPath, function(error, result, response) {
    if (!error) {
      console.log('file uploaded');
      cb();
    } else {
      console.log('error on image upload is ', error);
      return error;
    }
  });
};

<commit_msg>fixed for location search return<commit_after>'use strict';
var azure = require('azure-storage');
var pg = require('pg');
var request = require('request');
var parseString = require('xml2js').parseString;
var conString = 'postgres://' + process.env.POSTGRES + '/postgres';
var retryOperations = new azure.ExponentialRetryPolicyFilter();
var blobSvc = azure.createBlobService().withFilter(retryOperations);
blobSvc.createContainerIfNotExists('userpictures', {publicAccessLevel: 'blob'}, function(error, result, response) {
  if (!error) {
    console.log(result);
    console.log(response);
  } else {
    console.log('error creating azure blob container ', error);
  }
});

/**
 * Get tree data for a single tree (profile view)
 */
exports.getTreeData = function(req, res) {
  var treeid = req.params.treeId;
  pg.connect(conString, function(err, client, done) {
    var selectMessages = 'SELECT tree.name, tree.treeid, q.qspecies, tree.plotsize, tree.qcaretaker, tree.plantdate, l.latitude, l.longitude, image.imageurl, image.imagewidth, image.imageheight, image.imagetype from qspecies q JOIN tree ON (q.qspeciesid = tree.qspeciesid) JOIN "location" l ON (l.locationid = tree.locationid) JOIN image ON (q.qspeciesid = image.qspeciesid) WHERE treeid = $1;';
    client.query(selectMessages, [treeid], function(error, results) {
      if (error) {
        console.log(error, 'THERE WAS AN ERROR');
      } else {
        res.json(results.rows[0]);
      }
      done();
    });
  });
};

/**
 * Get tree data for 250 trees (list view)
 */
exports.getAll = function(req, res) {
  pg.connect(conString, function(err, client, done) {
    // console.log(err);
    var selectTrees = 'SELECT tree.name, tree.treeid, q.qspecies, l.latitude, l.longitude, thumbnail.url, thumbnail.width, thumbnail.height, thumbnail.contenttype FROM qspecies q JOIN tree ON (q.qspeciesid = tree.qspeciesid) JOIN "location" l ON (l.locationid = tree.locationid) JOIN thumbnail ON (q.qspeciesid = thumbnail.qspeciesid) LIMIT 250;';
    client.query(selectTrees, function(error, results) {
    }, function(error, results) {
      done();
      if (error) {
        console.log('Error is ', error);
      }
      res.json(results.rows);
    });
  });
};

/**
 * Gets tree's messages from database and sends it to client. Request is expecting a treeId.
 * @param req
 * @param res
 */
exports.getMessagesForTree = function(req, res) {
  var treeid = req.params.treeid;
  // console.log('in getMessageForTree and treeid is', treeid);
  pg.connect(conString, function(err, client, done) {
    console.log(err);
    var selectMessages = 'SELECT message.message, message.treeid, message.username, message.messageid, message.createdAt FROM message  WHERE treeid = $1 LIMIT 100;';
    client.query(selectMessages, [treeid], function(error, results) {
      res.json(results.rows);
    });
    done();
  });
};

/**
 * Gets messages from users and sends it to the database. Request is expecting an username.
 * @param req
 * @param res
 */
exports.getMessagesForUsers = function(req, res) {
  var username = req.params.userid;
  // console.log('in getMessageForUsers and username is', username);
  pg.connect(conString, function(err, client, done) {
    console.log(err);
    var selectMessages = 'SELECT message.message, message.treeid, tree.name, message.username, message.messageid, message.createdAt FROM message JOIN tree ON (tree.treeid = message.treeid) WHERE username = $1 LIMIT 100;';
    client.query(selectMessages, [username], function(error, results) {
      console.log('error is ', error);
      res.json(results.rows);
    });
    done();
  });
};

/**
 * Gets messages from both users or trees and uploads them to the database. Request expecting treeName or userName,
 * and a treeId.
 * @param req
 * @param res
 */
//
exports.postMessageFromUser = function(req, res) {
  // console.log('got into postMessageFromUser');
  var username = req.body.username;
  var message = req.body.message;
  var treeid = req.body.treeid;
  var returnMessages = [];
  var url = 'http://www.botlibre.com/rest/botlibre/form-chat?application=' + process.env.APPLICATIONID + '&message=' + message + '&instance=812292';
  request(url, function(error, response, body) {
    console.log('body', body);
    if (!error && response.statusCode === 200) {
      parseString(body, function(error, result) {
        pg.connect(conString, function(err, client, done) {
          if (err) {
            console.log('error is', err);
          }
          else {
            var insertMessages = 'INSERT INTO message (message, username, treeid, createdat) values ($1, $2, $3, now()) RETURNING *;';
            client.query(insertMessages, [message, username, treeid], function(error, results) {
              returnMessages.push(results.rows[0]);
              done();
            });
            var insertTreeMessages = 'INSERT INTO message (message, username, treeid, createdat) values ($1, $2, $3, now()) RETURNING *;';
            client.query(insertTreeMessages, [result.response.message[0], 'unknown', treeid], function(error, results) {
              // console.log(error);
              returnMessages.push(results.rows[0]);
              res.send(returnMessages);
              done();
            });
          }
        });
      });
    }
  });
};

/**
 * Search tree return 250 results matching the search params. It takes a search param, and an offset. If offset is not
 * provided it will default to 0.
 * @param req
 * @param res
 */
exports.searchTrees = function(req, res) {
  var search = req.params.search;
  search = search.replace('%20', ' ');
  var offset = req.offset || 0;
  var searchString = typeof search === 'string' ? '%' + search + '%' : 'do not use';
  var searchNum = typeof search === 'number' ? search : 0;
  pg.connect(conString, function(err, client, done) {
    // console.log('in search trees');
    // console.log(err);
    var selectTrees = 'SELECT tree.name, tree.treeid, q.qspecies, l.latitude, l.longitude, thumbnail.url, thumbnail.width, thumbnail.height, thumbnail.contenttype FROM qspecies q JOIN tree ON (q.qspeciesid = tree.qspeciesid) JOIN "location" l ON (l.locationid = tree.locationid) JOIN thumbnail ON (q.qspeciesid = thumbnail.qspeciesid) WHERE ' +
      'tree.treeid = $1 OR tree.name LIKE $2 OR q.qspecies LIKE $2 OR q.qspeciesid = $1 LIMIT 250 OFFSET $3;';

    // var selectTrees = 'SELECT tree.name, q.qspecies, l.latitude, l.longitude, thumbnail.url, thumbnail.width, ' +
    //   'thumbnail.height, thumbnail.contenttype FROM qspecies q JOIN tree ON (q.qspeciesid = tree.qspeciesid) JOIN ' +
    //   '"location" l ON (l.locationid = tree.locationid) JOIN thumbnail ON (q.qspeciesid = thumbnail.qspeciesid) WHERE ' +
    //   'tree.treeid = $1 OR tree.name LIKE $2 OR q.qspecies LIKE $2 OR q.qspeciesid = $1 LIMIT 250 OFFSET $3;';
    client.query(selectTrees, [searchNum, searchString, offset], function(error, results) {
      // console.log('error', error);
      //console.log('results', results);
      // console.log(results.rows);
      res.json(results.rows);
    });
    done();
  });
};

/**
 * This function expects a treeid to insert messages. The userid will be saved with the value of -1 which means that
 * the message was sent by the tree.
 * @param req
 * @param res
 */
exports.insertMessagesFromTrees = function(req, res) {
  console.log('got into insertMessagesFromTrees is ');
  var treeid = req.body.treeid;
  var userid = -1;
  var message = req.body.message;
  // console.log(treeid, userid, message);
  pg.connect(conString, function(err, client, done) {
    // console.log(err);
    var selectMessages = 'INSERT INTO message (message, treeid, userid, createdat) values($1, $2, $3, now())';
    client.query(selectMessages, [message, treeid, userid], function(error, results) {
      // console.log('results is ', results);
      res.send(results);
      done();
    });
  });
};

/**
 * Inserts likes into database. This takes username and treeid.
 * @param req
 * @param res
 */
exports.insertLikes = function(req, res) {
  var treeid = req.body.treeId;
  var username = req.body.username;
  pg.connect(conString, function(err, client, done) {
    // console.log(err);
    var insertLikes = 'INSERT INTO likes (username, treeid) values($1, $2)';
    client.query(insertLikes, [username, treeid], function(error, results) {
      // console.log('results is ', results);
      res.send(results);
      done();
    });
  });
};

/**
 * This gets a list of trees that the user likes. It takes an username.
 * @param req
 * @param res
 */
exports.getTreeLikes = function(req, res) {
  var username = req.body.username;
  pg.connect(conString, function(err, client, done) {
    var selectLikes = 'SELECT tree.name, tree.treeid, q.qspecies, thumbnail.url, thumbnail.width, thumbnail.height, ' +
      'thumbnail.contenttype FROM qspecies q JOIN tree ON (q.qspeciesid = tree.qspeciesid) JOIN thumbnail ON ' +
      '(q.qspeciesid = thumbnail.qspeciesid) JOIN likes ON (tree.treeid = likes.treeid)' + ' WHERE username = $1;';
    client.query(selectLikes, [username], function(error, results) {
      console.log('err', error);
      console.log('results is ', results);
      res.send(results.rows);
      done();
    });
  });
};

/**
 * This gets a list of users which like a particular tree. It takes a treeid.
 * @param req
 * @param res
 */
exports.getUserLikes = function(req, res) {
  var treeid = ''+req.body.treeId;
  pg.connect(conString, function(err, client, done) {
    // console.log(err);
    var selectLikes = 'SELECT username from likes WHERE treeid = $1;';
    client.query(selectLikes, [treeid], function(error, results) {
      // console.log('results is ', results);
      res.send(results.rows);
      done();
    });
  });
};

/**
 * Insert comments for a message into comments table. This takes a username, comment, treeid, and messageid.
 * @param req
 * @param res
 */
exports.insertComments = function(req, res) {
  console.log('insert comments');
  var username = req.body.username;
  var comment = req.body.comment;
  var treeid = req.body.treeid;
  var messageid = req.message.messageid;
  pg.connect(conString, function(err, client, done) {
    if (err) {
      console.log('error is', err);
    }
    else {
      var insertComments = 'INSERT INTO comment (comment, username, treeid, messageid, createdat) values ($1, $2, $3, $5, now) RETURNING *;';
      client.query(insertComments, [comment, username, treeid, messageid], function(error, results) {
        // console.log('postCommentFromUser result is ', results.rows);
        res.json(results.rows);
        done();
      });
    }
  });
};

/**
 * Gets comments from the database for a particular message. This takes a messageid.
 * @param req
 * @param res
 */
exports.getComments = function(req, res) {
  var messageid = req.params.messageid;
  console.log('in getComment');
  pg.connect(conString, function(err, client, done) {
    // console.log(err);
    var selectMessages = 'SELECT comment.comment, message.treeid, message.username, comment.createdAt FROM comments  WHERE messageid = $1 LIMIT 100;';
    client.query(selectMessages, [messageid], function(error, results) {
      res.json(results.rows);
    });
    done();
  });
};

/**
 * This queries the database for a location withing 0.01 from the users longitude and latitude. It expects a latitude
 * and longitude.
 * @param req
 * @param res
 */
exports.findTreesByLocation = function(req, res) {
  console.log('find by loc');
  var lat = parseFloat(req.query.latitude);
  var lng = parseFloat(req.query.longitude);
  var upperLongitude = lng + 0.001;
  var upperLatitude = lat + 0.001;
  var lowerLatitude = lat - 0.001;
  var lowerLongitude = lng - 0.001;
  pg.connect(conString, function(err, client, done) {
    // console.log('in location search');
    // console.log(err);
    var locationQuery = 'SELECT tree.name, tree.treeid, q.qspecies, l.latitude, l.longitude, thumbnail.url, thumbnail.width, thumbnail.height, thumbnail.contenttype FROM qspecies q JOIN tree ON (q.qspeciesid = tree.qspeciesid) JOIN "location" l ON (l.locationid = tree.locationid) JOIN thumbnail ON (q.qspeciesid = thumbnail.qspeciesid) WHERE ' +
      '(l.latitude BETWEEN $1 AND $2) AND (l.longitude BETWEEN $3 AND $4)';
    client.query(locationQuery, [lowerLatitude, upperLatitude, lowerLongitude, upperLongitude], function(error, results) {
      // console.log('error', error);
      //console.log('results', results);
      // console.log(results.rows);
      res.json(results.rows);
      done();
    });
  });
};

/**
 * Return an image for a tree. This function takes a treeid.
 * @param req
 * @param res
 */
exports.getTreeImage = function(req, res){
  var treeid = req.params.treeId;
  // console.log('TREE ID', treeid)
  pg.connect(conString, function(err, client, done) {
    // console.log(err);
    var getImage = 'SELECT image.imageurl, image.imagewidth, image.imageheight, image.imagetype FROM image JOIN qspecies ON qspecies.qspeciesid = image.qspeciesid JOIN tree on tree.qspeciesid = qspecies.qspeciesid  WHERE tree.treeid = $1;';
    client.query(getImage, [treeid], function(error, results) {
      // console.log('err',error);
      // console.log('THESE results is ', results);
      res.send(results.rows[0]);
      done();
    });
  });
};

//This can be refactored to store image in DB instead of locally in folder
exports.uploadUserImage = function(req, res, imageName, cb) {
  //packages/articles/server/controllers/test/uploads/
  var localPath = 'packages/theme/public/assets/img/uploads/' + imageName;
  blobSvc.createBlockBlobFromLocalFile('userpictures', imageName, localPath, function(error, result, response) {
    if (!error) {
      console.log('file uploaded');
      cb();
    } else {
      console.log('error on image upload is ', error);
      return error;
    }
  });
};

<|endoftext|>