<commit_before>/*
 * Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
 *
 * 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.
 *
 */

/*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50 */
/*global define, brackets, $ */

define(function (require, exports, module) {
    "use strict";

    // Load dependent modules
    var AppInit             = brackets.getModule("utils/AppInit"),
        CodeHintManager     = brackets.getModule("editor/CodeHintManager"),
        ExtensionUtils      = brackets.getModule("utils/ExtensionUtils"),
        HTMLUtils           = brackets.getModule("language/HTMLUtils"),
        HtmlSpecialChars    = require("text!SpecialChars.json"),
        specialChars;

    /**
     * Encodes the special Char value given.
     *
     * @param {String} value
     * The value to encode
     *
     * @return {String}
     * The encoded string
     */
    function _encodeValue(value) {
        return (value.indexOf("#") === -1) ? value.replace("&", "&") : value.replace("&", "&").replace("#", "#");
    }

    /**
     * Decodes the special Char value given.
     *
     * @param {String} value
     * The value to decode
     *
     * @return {String}
     * The decoded string
     */
    function _decodeValue(value) {
        return value.replace("#", "#").replace("&", "&").replace(";", ";");
    }

    /**
     * @constructor
     */
    function SpecialCharHints() {
        this.primaryTriggerKeys = "&ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#0123456789";
        this.currentQuery = "";
    }

    /**
     * Determines whether HtmlSpecialChar hints are available in the current editor
     * context.
     *
     * @param {Editor} editor
     * A non-null editor object for the active window.
     *
     * @param {String} implicitChar
     * Either null, if the hinting request was explicit, or a single character
     * that represents the last insertion and that indicates an implicit
     * hinting request.
     *
     * @return {Boolean}
     * Determines whether the current provider is able to provide hints for
     * the given editor context and, in case implicitChar is non- null,
     * whether it is appropriate to do so.
     */
    SpecialCharHints.prototype.hasHints = function (editor, implicitChar) {
        this.editor = editor;

        var query = this._getQuery();

        if (implicitChar === null) {
            return query !== null;
        }

        return implicitChar === "&" || query !== null;
    };

    /**
     * Returns a list of avaliable HtmlSpecialChar hints if possible for the current
     * editor context.
     *
     * @param {String} implicitChar
     * Either null, if the hinting request was explicit, or a single character
     * that represents the last insertion and that indicates an implicit
     * hinting request.
     *
     * @return {Object<hints: Array<(String + jQuery.Obj)>, match: String, selectInitial: Boolean>}
     * Null if the provider wishes to end the hinting session. Otherwise, a
     * response object that provides 1. a sorted array hints that consists
     * of strings; 2. a string match that is used by the manager to emphasize
     * matching substrings when rendering the hint list; and 3. a boolean that
     * indicates whether the first result, if one exists, should be selected
     * by default in the hint list window.
     */
    SpecialCharHints.prototype.getHints = function (implicitChar) {
        var query,
            result;

        if (this.primaryTriggerKeys.indexOf(implicitChar) !== -1 || implicitChar === null) {
            this.currentQuery = query = this._getQuery();
            result = $.map(specialChars, function (value, index) {
                if (value.indexOf(query) === 0) {
                    var shownValue = _encodeValue(value);
                    return shownValue  + "; " + value + ";";
                }
            }).sort(this._internalSort);

            query = _encodeValue(query);
            return {
                hints: result,
                match: query,
                selectInitial: true
            };
        }

        return null;
    };

    /**
     * Sort function used internally when sorting the Hints
     *
     * @param {String} value
     * The value to decode
     *
     * @return {String}
     * The decoded string
     */
    SpecialCharHints.prototype._internalSort = function (a, b) {
        a = _decodeValue(a.slice(0, a.indexOf(" "))).toLowerCase();
        b = _decodeValue(b.slice(0, b.indexOf(" "))).toLowerCase();

        if (a.indexOf("#") !== -1 && b.indexOf("#") !== -1) {
            var num1 = parseInt(a.slice(a.indexOf("#") + 1, a.length - 1), 10),
                num2 = parseInt(b.slice(b.indexOf("#") + 1, b.length - 1), 10);

            return (num1 === num2) ? 0 : (num1 > num2) ? 1 : -1;
        }

        return a.localeCompare(b);
    };

    /**
     * Returns a query for the Hints
     *
     * @return {String}
     * The Query for which to search
     */
    SpecialCharHints.prototype._getQuery = function () {
        var query,
            lineContent,
            startChar,
            endChar;

        query = "&";

        lineContent = this.editor.document.getRange({
            line: this.editor.getCursorPos().line,
            ch: 0
        }, this.editor.getCursorPos());

        startChar = lineContent.lastIndexOf("&");
        endChar = lineContent.lastIndexOf(";");

        if (endChar < startChar) {
            query = this.editor.document.getRange({
                line: this.editor.getCursorPos().line,
                ch: startChar
            }, this.editor.getCursorPos());
        }

        if (startChar !== -1 && HTMLUtils.getTagInfo(this.editor, this.editor.getCursorPos()).tagName === "" && endChar < startChar) {
            return query;
        }

        return null;
    };

    /**
     * Inserts a given HtmlSpecialChar hint into the current editor context.
     *
     * @param {String} completition
     * The hint to be inserted into the editor context.
     *
     * @return {Boolean}
     * Indicates whether the manager should follow hint insertion with an
     * additional explicit hint request.
     */
    SpecialCharHints.prototype.insertHint = function (completion) {
        var start = {line: -1, ch: -1},
            end = {line: -1, ch: -1},
            cursor = this.editor.getCursorPos();

        end.line = start.line = cursor.line;
        start.ch = cursor.ch - this.currentQuery.length;
        end.ch = start.ch + this.currentQuery.length;
        completion = completion.slice(0, completion.indexOf(" "));
        completion = _decodeValue(completion);
        if (start.ch !== end.ch) {
            this.editor.document.replaceRange(completion, start, end);
        } else {
            this.editor.document.replaceRange(completion, start);
        }

        return false;
    };

    AppInit.appReady(function () {
        ExtensionUtils.loadStyleSheet(module, "styles.css");
        // Parse JSON files
        specialChars = JSON.parse(HtmlSpecialChars);

        // Register code hint providers
        var specialCharHints = new SpecialCharHints();

        CodeHintManager.registerHintProvider(specialCharHints, ["html"], 1);
    });
});<commit_msg>A few other cleanup fixes<commit_after>/*
 * Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved.
 *
 * 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.
 *
 */

/*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50 */
/*global define, brackets, $ */

define(function (require, exports, module) {
    "use strict";

    // Load dependent modules
    var AppInit             = brackets.getModule("utils/AppInit"),
        CodeHintManager     = brackets.getModule("editor/CodeHintManager"),
        ExtensionUtils      = brackets.getModule("utils/ExtensionUtils"),
        HTMLUtils           = brackets.getModule("language/HTMLUtils"),
        HtmlSpecialChars    = require("text!SpecialChars.json"),
        specialChars;

    /**
     * Encodes the special Char value given.
     *
     * @param {String} value
     * The value to encode
     *
     * @return {String}
     * The encoded string
     */
    function _encodeValue(value) {
        return (value.indexOf("#") === -1) ? value.replace("&", "&") : value.replace("&", "&").replace("#", "#");
    }

    /**
     * Decodes the special Char value given.
     *
     * @param {String} value
     * The value to decode
     *
     * @return {String}
     * The decoded string
     */
    function _decodeValue(value) {
        return value.replace("#", "#").replace("&", "&").replace(";", ";");
    }

    /**
     * @constructor
     */
    function SpecialCharHints() {
        this.primaryTriggerKeys = "&ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#0123456789";
        this.currentQuery = "";
    }

    /**
     * Determines whether HtmlSpecialChar hints are available in the current editor
     * context.
     *
     * @param {Editor} editor
     * A non-null editor object for the active window.
     *
     * @param {String} implicitChar
     * Either null, if the hinting request was explicit, or a single character
     * that represents the last insertion and that indicates an implicit
     * hinting request.
     *
     * @return {Boolean}
     * Determines whether the current provider is able to provide hints for
     * the given editor context and, in case implicitChar is non- null,
     * whether it is appropriate to do so.
     */
    SpecialCharHints.prototype.hasHints = function (editor, implicitChar) {
        this.editor = editor;

        var query = this._getQuery();

        if (implicitChar === null) {
            return query !== null;
        }

        return query !== null;
    };

    /**
     * Returns a list of avaliable HtmlSpecialChar hints if possible for the current
     * editor context.
     *
     * @param {String} implicitChar
     * Either null, if the hinting request was explicit, or a single character
     * that represents the last insertion and that indicates an implicit
     * hinting request.
     *
     * @return {Object<hints: Array<(String + jQuery.Obj)>, match: String, selectInitial: Boolean>}
     * Null if the provider wishes to end the hinting session. Otherwise, a
     * response object that provides 1. a sorted array hints that consists
     * of strings; 2. a string match that is used by the manager to emphasize
     * matching substrings when rendering the hint list; and 3. a boolean that
     * indicates whether the first result, if one exists, should be selected
     * by default in the hint list window.
     */
    SpecialCharHints.prototype.getHints = function (implicitChar) {
        var query,
            result;

        if (implicitChar === null || this.primaryTriggerKeys.indexOf(implicitChar) !== -1) {
            this.currentQuery = query = this._getQuery();
            result = $.map(specialChars, function (value, index) {
                if (value.indexOf(query) === 0) {
                    var shownValue = _encodeValue(value);
                    return shownValue  + "; " + value + ";";
                }
            }).sort(this._internalSort);

            if (query !== null) {
                query = _encodeValue(query);
            }

            return {
                hints: result,
                match: query,
                selectInitial: true
            };
        }

        return null;
    };

    /**
     * Sort function used internally when sorting the Hints
     *
     * @param {String} value
     * The value to decode
     *
     * @return {String}
     * The decoded string
     */
    SpecialCharHints.prototype._internalSort = function (a, b) {
        a = _decodeValue(a.slice(0, a.indexOf(" "))).toLowerCase();
        b = _decodeValue(b.slice(0, b.indexOf(" "))).toLowerCase();

        if (a.indexOf("#") !== -1 && b.indexOf("#") !== -1) {
            var num1 = parseInt(a.slice(a.indexOf("#") + 1, a.length - 1), 10),
                num2 = parseInt(b.slice(b.indexOf("#") + 1, b.length - 1), 10);

            return (num1 === num2) ? 0 : (num1 > num2) ? 1 : -1;
        }

        return a.localeCompare(b);
    };

    /**
     * Returns a query for the Hints
     *
     * @return {String}
     * The Query for which to search
     */
    SpecialCharHints.prototype._getQuery = function () {
        if (HTMLUtils.getTagInfo(this.editor, this.editor.getCursorPos()).tagName !== "") {
            return null;
        }

        var query,
            lineContent,
            startChar,
            endChar;

        query = "&";

        lineContent = this.editor.document.getRange({
            line: this.editor.getCursorPos().line,
            ch: 0
        }, this.editor.getCursorPos());

        startChar = lineContent.lastIndexOf("&");
        endChar = lineContent.lastIndexOf(";");

        if (endChar < startChar) {
            query = this.editor.document.getRange({
                line: this.editor.getCursorPos().line,
                ch: startChar
            }, this.editor.getCursorPos());
        }

        if (startChar !== -1 && endChar < startChar) {
            return query;
        }

        return null;
    };

    /**
     * Inserts a given HtmlSpecialChar hint into the current editor context.
     *
     * @param {String} completition
     * The hint to be inserted into the editor context.
     *
     * @return {Boolean}
     * Indicates whether the manager should follow hint insertion with an
     * additional explicit hint request.
     */
    SpecialCharHints.prototype.insertHint = function (completion) {
        var start = {line: -1, ch: -1},
            end = {line: -1, ch: -1},
            cursor = this.editor.getCursorPos();

        end.line = start.line = cursor.line;
        start.ch = cursor.ch - this.currentQuery.length;
        end.ch = start.ch + this.currentQuery.length;
        completion = completion.slice(0, completion.indexOf(" "));
        completion = _decodeValue(completion);
        if (start.ch !== end.ch) {
            this.editor.document.replaceRange(completion, start, end);
        } else {
            this.editor.document.replaceRange(completion, start);
        }

        return false;
    };

    AppInit.appReady(function () {
        ExtensionUtils.loadStyleSheet(module, "styles.css");
        // Parse JSON files
        specialChars = JSON.parse(HtmlSpecialChars);

        // Register code hint providers
        var specialCharHints = new SpecialCharHints();

        CodeHintManager.registerHintProvider(specialCharHints, ["html"], 1);
    });
});<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2020 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

foam.CLASS({
  package: 'foam.nanos.crunch',
  name: 'CapabilityJunctionPayload',
  documentation: `
    Capability data that can be stored in a UserCapabilityJunction or on a
    Capable object.
  `,

  imports: [
    'capabilityDAO'
  ],

  javaImports: [
    'foam.core.ClassInfo',
    'foam.core.FObject',
    'foam.core.Validatable',
    'foam.dao.DAO',
    'foam.nanos.crunch.Capability'
  ],

  implements: [
    'foam.core.Validatable'
  ],

  // TODO: Can section off view

  properties: [
    {
      class: 'Reference',
      name: 'capability',
      of: 'foam.nanos.crunch.Capability'
    },
    {
      name: 'data',
      class: 'FObjectProperty'
    },
    {
      name: 'status',
      class: 'Enum',
      of: 'foam.nanos.crunch.CapabilityJunctionStatus',
      value: foam.nanos.crunch.CapabilityJunctionStatus.ACTION_REQUIRED
    },
    {
      class: 'Boolean',
      name: 'hasSafeStatus',
      // TODO: Check where this should be used in UCJ rules
      transient: true
    },
    {
      class: 'Boolean',
      name: 'needsApproval',
      // TODO: Check if this is useful for UCJ rules
      transient: true
    }
  ],

  methods: [
    {
      name: 'validate',
      javaCode: `
        DAO capabilityDAO = (DAO) x.get("capabilityDAO");
        Capability capability = (Capability) capabilityDAO.find(getCapability());
        ClassInfo dataClass = capability.getOf();
        if ( dataClass == null ) return;
        FObject dataObject = getData();
        if ( dataObject == null ) {
          throw new IllegalStateException(String.format(
            "Missing payload data for capability '%s'",
            capability.getId()
          ));
        }
        if ( ! dataClass.isInstance(dataObject) ) {
          throw new IllegalStateException(String.format(
            "Invalid payload data class for capability '%s'",
            capability.getId()
          ));
        }
        if ( dataObject instanceof Validatable ) {
          dataObject.validate(x);
        }
      `,
    },
    {
      name: 'toSummary',
      code: function(){
        return this.capabilityDAO.find(this.capability).then(capability => `${capability.name}`);
      }
    }
  ],
});
<commit_msg>Added any view to CapabilityJunctionPayload.data (#4812)<commit_after>/**
 * @license
 * Copyright 2020 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

foam.CLASS({
  package: 'foam.nanos.crunch',
  name: 'CapabilityJunctionPayload',
  documentation: `
    Capability data that can be stored in a UserCapabilityJunction or on a
    Capable object.
  `,

  imports: [
    'capabilityDAO'
  ],

  javaImports: [
    'foam.core.ClassInfo',
    'foam.core.FObject',
    'foam.core.Validatable',
    'foam.dao.DAO',
    'foam.nanos.crunch.Capability'
  ],

  implements: [
    'foam.core.Validatable'
  ],

  // TODO: Can section off view

  properties: [
    {
      class: 'Reference',
      name: 'capability',
      of: 'foam.nanos.crunch.Capability'
    },
    {
      name: 'data',
      class: 'FObjectProperty',
      view: 'foam.u2.view.AnyView'
    },
    {
      name: 'status',
      class: 'Enum',
      of: 'foam.nanos.crunch.CapabilityJunctionStatus',
      value: foam.nanos.crunch.CapabilityJunctionStatus.ACTION_REQUIRED
    },
    {
      class: 'Boolean',
      name: 'hasSafeStatus',
      // TODO: Check where this should be used in UCJ rules
      transient: true
    },
    {
      class: 'Boolean',
      name: 'needsApproval',
      // TODO: Check if this is useful for UCJ rules
      transient: true
    }
  ],

  methods: [
    {
      name: 'validate',
      javaCode: `
        DAO capabilityDAO = (DAO) x.get("capabilityDAO");
        Capability capability = (Capability) capabilityDAO.find(getCapability());
        ClassInfo dataClass = capability.getOf();
        if ( dataClass == null ) return;
        FObject dataObject = getData();
        if ( dataObject == null ) {
          throw new IllegalStateException(String.format(
            "Missing payload data for capability '%s'",
            capability.getId()
          ));
        }
        if ( ! dataClass.isInstance(dataObject) ) {
          throw new IllegalStateException(String.format(
            "Invalid payload data class for capability '%s'",
            capability.getId()
          ));
        }
        if ( dataObject instanceof Validatable ) {
          dataObject.validate(x);
        }
      `,
    },
    {
      name: 'toSummary',
      code: function(){
        return this.capabilityDAO.find(this.capability).then(capability => `${capability.name}`);
      }
    }
  ],
});
<|endoftext|>


<commit_before>export { GVizTQ_UrlComposer as UrlComposer };

import * as Pool from "../Pool.js";
import * as Recyclable from "../Recyclable.js";
import * as ValueMax from "../ValueMax.js";

/**
 * Compose a URL for downloading cells data (as JSON or .CSV format) from a Google
 * Sheets by using Google Visualzation Table Query API. The target spreadsheet should
 * be shared by either "Public on the web" or "Anyone with the link".
 *
 * (GVizTQ = Google Visualzation Table Query)
 *
 * The follwoings are some composed examples:
 *
 * @example
 * https://docs.google.com/spreadsheets/d/18YyEoy-OfSkODfw8wqBRApSrRnBTZpjRpRiwIKy8a0M/gviz/tq?range=A:A&tqx=version:0.6
 * https://docs.google.com/spreadsheets/d/18YyEoy-OfSkODfw8wqBRApSrRnBTZpjRpRiwIKy8a0M/gviz/tq?range=A:A&tqx=version:0.6;responseHandler:SetData
 * https://docs.google.com/spreadsheets/d/18YyEoy-OfSkODfw8wqBRApSrRnBTZpjRpRiwIKy8a0M/gviz/tq?range=A:A&tqx=out:json;responseHandler:SetData
 *
 * @example
 * https://docs.google.com/spreadsheets/d/18YyEoy-OfSkODfw8wqBRApSrRnBTZpjRpRiwIKy8a0M/gviz/tq?range=A:A&tqx=out:csv
 * https://docs.google.com/spreadsheets/d/18YyEoy-OfSkODfw8wqBRApSrRnBTZpjRpRiwIKy8a0M/gviz/tq?range=A:A&tqx=out:html
 *
 * @example
 * https://docs.google.com/spreadsheets/d/18YyEoy-OfSkODfw8wqBRApSrRnBTZpjRpRiwIKy8a0M/gviz/tq?gid=816353147&range=AH55:AK94&headers=0&tqx=out:csv
 *
 * @example
 * https://docs.google.com/spreadsheets/d/18YyEoy-OfSkODfw8wqBRApSrRnBTZpjRpRiwIKy8a0M/gviz/tq?gid=1504323928&range=B17&headers=0&tqx=out:csv
 *
 * @example
 * https://docs.google.com/spreadsheets/d/18YyEoy-OfSkODfw8wqBRApSrRnBTZpjRpRiwIKy8a0M/gviz/tq?sheet=Evolution.Param&range=B17&headers=0&tqx=out:csv
 *
 * @example
 * https://docs.google.com/spreadsheets/d/18YyEoy-OfSkODfw8wqBRApSrRnBTZpjRpRiwIKy8a0M/gviz/tq?range=Evolution.Param!B17&headers=0&tqx=out:csv
 *
 * @example
 * https://docs.google.com/spreadsheets/d/18YyEoy-OfSkODfw8wqBRApSrRnBTZpjRpRiwIKy8a0M/gviz/tq?range='Evolution.Param'!B17&headers=0&tqx=out:csv
 *
 */
class GVizTQ_UrlComposer extends Recyclable.Root {

  /**
   * Used as default GSheet.GVizTQ.UrlComposer provider for conforming to Recyclable interface.
   */
  static Pool = new Pool.Root( "GSheet.GVizTQ.UrlComposer.Pool",
    GVizTQ_UrlComposer, GVizTQ_UrlComposer.setAsConstructor );

  /**
   * If sheetId is null, sheetName is null, and no sheet name in the range's A1
   * notation, the first (most left) visible sheet inside the spreadsheet will be used.
   *
   * @param {string} spreadsheetId
   *   The identifier (the component after the "https://docs.google.com/spreadsheets/d/")
   * of the spreadsheet to be accessed.
   *
   * @param {string} range
   *   The cells' A1 notation. It describes the (name and) range of the sheet inside
   * the spreadsheet.
   *   - "A1" refers to one cell of the first (most left) visible sheet.
   *   - "B2:C5" refers to cells of a rectangle of the first (most left) visible sheet.
   *   - "Books!D8:D" refers to the column D of sheet named "Books" from rows 8 to the
   *       last rows.
   *   - "'Name has space'!7:10" refers to the rows 7 to 10 of sheet named "Name has
   *       space".
   *
   * @param {number} headers
   *   The component after the "headers=". It means how many header rows. It should be
   * zero or a positive integer.
   *
   * @param {string} responseHandler
   *   The function name of JSON content handler. Only meaningful when the content is
   * downloaded as JSONP format. This responseHandler name will be prepended in front
   * of the downloaded content.
   *
   * @param {number} sheetId
   *   The component after the "gid=". If the sheetName is used (or the sheet name in
   * the range's A1 notation is specified), keep this sheetId null (or undefined).
   *
   * @param {string} sheetName
   *   The component after the "sheet=". If the sheetId is used (or the sheet name in
   * the range's A1 notation is specified), keep this sheetName null (or undefined).
   *
   * @see {@link https://developers.google.com/sheets/api/guides/concepts}
   * @see {@link https://developers.google.com/chart/interactive/docs/dev/implementing_data_source}
   * @see {@link https://developers.google.com/chart/interactive/docs/spreadsheets}
   * @see {@link https://developers.google.com/chart/interactive/docs/querylanguage}
   */
  constructor(
    spreadsheetId,
    range, headers = 0, responseHandler = null, sheetId = null, sheetName = null ) {
    super();
    GVizTQ_UrlComposer.setAsConstructor_self.call( this,
      spreadsheetId,
      range, headers, responseHandler, sheetId, sheetName
    );
  }

  /** @override */
  static setAsConstructor() {
    super.setAsConstructor();
    GVizTQ_UrlComposer.setAsConstructor_self.call( this,
      spreadsheetId,
      range, headers, responseHandler, sheetId, sheetName
    );
    return this;
  }

  /** @override */
  static setAsConstructor_self(
    spreadsheetId,
    range, headers, responseHandler, sheetId, sheetName
  ) {
    this.set_by_spreadsheetId_range_headers_responseHandler_sheetId_sheetName(
      spreadsheetId,
      range, headers, responseHandler, sheetId, sheetName
    );
  }

  /** @override */
  disposeResources() {
    this.sheetName = undefined;
    this.sheetId = undefined;
    this.responseHandler = undefined;
    this.headers = undefined;
    this.range = undefined;
    this.spreadsheetId = undefined;
    super.disposeResources();
  }

  /**  */
  set_by_spreadsheetId_range_headers_responseHandler_sheetId_sheetName(
    spreadsheetId,
    range, headers, responseHandler, sheetId, sheetName
  ) {
    this.spreadsheetId = spreadsheetId;
    this.range = range;
    this.headers = headers;
    this.responseHandler = responseHandler;
    this.sheetId = sheetId;
    this.sheetName = sheetName;
    return this;
  }

  /**  */
  spreadsheetId_set( spreadsheetId ) {
    this.spreadsheetId = spreadsheetId;
    return this;
  }

  /**  */
  range_set( range ) {
    this.range = range;
    return this;
  }

  /**
   * Generator for composing the URL (according this object's data members), downloading
   * it as JSON format, extracting data as a two dimension (column-major) array.
   *
   * @param {ValueMax.Percentage.Aggregate} progressParent
   *   Some new progressToAdvance will be created and added to progressParent. The
   * created progressToAdvance will be increased when every time advanced. The
   * progressParent.root_get() will be returned when every time yield.
   *
   * @yield {ValueMax.Percentage.Aggregate}
   *   Yield ( value = progressParent.root_get() ) when ( done = false ).
   *
   * @yield {Array[]}
   *   - Yield ( value = a two dimension (column-major) array ) when ( done = true )
   *       successfully.
   *   - Yield ( value = null ) when ( done = true ) failed.
   */
  async* fetcher_JSON_ColumnMajorArrayArray( progressParent ) {
    let progressRoot = progressParent.root_get();
    let progressToAdvance = progressParent.child_add(
      ValueMax.Percentage.Concrete.Pool.get_or_create_by( 4 ) );

    try {
      // 1. Compose URL and download it as JSONP.
      let url = this.getUrl_forJSON();
      let response = await fetch( url );

      progressToAdvance.value_advance(); // 25%
      yield progressRoot;

      if ( !response.ok )
        return null;

      // 2. Google Visualization Table Query returns JSONP (not JSON).
      let text = await response.text();

      progressToAdvance.value_advance(); // 25%
      yield progressRoot;

      if ( !text )
        return null;

      // 3. Try to evaluate it as JSON.
      let json = GVizTQ_UrlComposer.evalJSONP( text );

      progressToAdvance.value_advance(); // 25%
      yield progressRoot;

      if ( !json )
        return null;

      // 4. Collect into column-major array.
      let ColumnMajorArrayArray = GVizTQ_UrlComposer.dataTable_to_ColumnMajorArrayArray( json.table );

      progressToAdvance.value_advance(); // 25%
      yield progressRoot;

      return ColumnMajorArrayArray;

    } catch ( e ) {
      return null;
    }
  }

  /**
   * @param {string} outputFormat
   *   Specify the data format when downloading the returned url. It should be null or
   * "json" or "csv" or "html". If null, there will be no format specified in the
   * generated url (means default format, usually same as "json").
   *
   * @return {string} The url for downloading the target as specified format.
   */
  getUrl_forFormat( outputFormat ) {
    // Because sheetId could be 0, it should be checked by comparing to null directly
    // (i.e. should not use ( !this.sheetId )).
    let url = `${GVizTQ_UrlComposer.spreadsheetUrlPrefix}/${
      encodeURIComponent(this.spreadsheetId)}/${

      GVizTQ_UrlComposer.GoogleVisualizationTableQueryUrlPostfix}?tqx=version:${
      GVizTQ_UrlComposer.GoogleVisualizationTableQueryAPIVersion}${
      ( outputFormat != null ) ? `;out:${encodeURIComponent(outputFormat)}` : "" }${
      ( this.responseHandler != null ) ? `;responseHandler=${
        encodeURIComponent(this.responseHandler)}` : "" }${

      ( this.sheetId != null ) ? `&gid=${encodeURIComponent(this.sheetId)}` : `${
      ( this.sheetName != null ) ? `&sheet=${encodeURIComponent(this.sheetName)}` : "" }` }${
      ( this.range != null ) ? `&range=${encodeURIComponent(this.range)}` : "" }&headers=${
        encodeURIComponent(this.headers)}`
      ;

    return url;
  }

  /**
   * @return {string} The url for downloading the target as JSONP format.
   */
  getUrl_forJSON() {
    return this.getUrl_forFormat( null ); // Because default format is "json".
  }

  /**
   * @return {string} The url for downloading the target as CSV format.
   */
  getUrl_forCSV() {
    return this.getUrl_forFormat( "csv" );
  }

  /**
   * @return {string} The url for downloading the target as HTML format.
   */
  getUrl_forHTML() {
    return this.getUrl_forFormat( "html" );
  }

  /**
   * Evaluate JSONP string by JSON.parse().
   *
   * A JSONP string looks like:
   * @example
   *   Handler( ... );
   *
   * This method will find the position of the first "(" and the last ")". Extract
   * the string between these two positions. Parse it by JSON.parse().
   *
   * @param {string} strJSONP
   *   The JSONP string to be parsed.
   *
   * @return {Object|value}
   *   Return the JSON parsed result. Return null, if left parenthesis or right
   * parenthesis is not found.
   */
  static evalJSONP( strJSONP ) {
    let beginIndex = strJSONP.indexOf( "(" );
    if ( beginIndex < 0 )
      return null;  // left parenthesis is not found.

    let endIndex = strJSONP.lastIndexOf( ")" );
    if ( endIndex < 0 )
      return null;  // right parenthesis is not found.

    // Note: "+ 1" for the next position of the left parenthesis.
    let strJSON = strJSONP.slice( beginIndex + 1, endIndex );
    let result = JSON.parse( strJSON );
    return result;
  }

  /**
   * Collects gvizDataTable.rows[ n ].c[ m ].v into a two dimension (column-major)
   * array. The outer array has m inner sub-arrays (corresponding to m columns).
   * Every inner sub-array has n elements (corresponding to n rows).
   *
   * @param {DataTable} gvizDataTable
   *   The DataTable object of Google Visualization API.
   *
   * @return {Array[]}
   *   Return a two dimension (column-major) array. Return null if failed.
   */
  static dataTable_to_ColumnMajorArrayArray( gvizDataTable ) {
    if ( !gvizDataTable )
      return null;

    let columnArray = new Array( gvizDataTable.cols.length );
    for ( let columnNo = 0; columnNo < columnArray.length; ++columnNo ) {
      let rowArray = columnArray[ columnNo ] = new Array( gvizDataTable.rows.length );
      for ( let rowNo = 0; rowNo < rowArray.length; ++rowNo ) {
        // Always value (.v), ignore formatted value string (.f).
        rowArray[ rowNo ] = gvizDataTable.rows[ rowNo ].c[ columnNo ].v;
      }
    }

    return columnArray;
  }

}

/** The url prefix of Google Sheets. */
GVizTQ_UrlComposer.spreadsheetUrlPrefix = "https://docs.google.com/spreadsheets/d";

/** The url postfix of Google Visualization Table Query. */
GVizTQ_UrlComposer.GoogleVisualizationTableQueryUrlPostfix = "gviz/tq";

/** The version of Google Visualization Table Query API. */
GVizTQ_UrlComposer.GoogleVisualizationTableQueryAPIVersion = "0.7";
<commit_msg>handle empty cell<commit_after>export { GVizTQ_UrlComposer as UrlComposer };

import * as Pool from "../Pool.js";
import * as Recyclable from "../Recyclable.js";
import * as ValueMax from "../ValueMax.js";

/**
 * Compose a URL for downloading cells data (as JSON or .CSV format) from a Google
 * Sheets by using Google Visualzation Table Query API. The target spreadsheet should
 * be shared by either "Public on the web" or "Anyone with the link".
 *
 * (GVizTQ = Google Visualzation Table Query)
 *
 * The follwoings are some composed examples:
 *
 * @example
 * https://docs.google.com/spreadsheets/d/18YyEoy-OfSkODfw8wqBRApSrRnBTZpjRpRiwIKy8a0M/gviz/tq?range=A:A&tqx=version:0.6
 * https://docs.google.com/spreadsheets/d/18YyEoy-OfSkODfw8wqBRApSrRnBTZpjRpRiwIKy8a0M/gviz/tq?range=A:A&tqx=version:0.6;responseHandler:SetData
 * https://docs.google.com/spreadsheets/d/18YyEoy-OfSkODfw8wqBRApSrRnBTZpjRpRiwIKy8a0M/gviz/tq?range=A:A&tqx=out:json;responseHandler:SetData
 *
 * @example
 * https://docs.google.com/spreadsheets/d/18YyEoy-OfSkODfw8wqBRApSrRnBTZpjRpRiwIKy8a0M/gviz/tq?range=A:A&tqx=out:csv
 * https://docs.google.com/spreadsheets/d/18YyEoy-OfSkODfw8wqBRApSrRnBTZpjRpRiwIKy8a0M/gviz/tq?range=A:A&tqx=out:html
 *
 * @example
 * https://docs.google.com/spreadsheets/d/18YyEoy-OfSkODfw8wqBRApSrRnBTZpjRpRiwIKy8a0M/gviz/tq?gid=816353147&range=AH55:AK94&headers=0&tqx=out:csv
 *
 * @example
 * https://docs.google.com/spreadsheets/d/18YyEoy-OfSkODfw8wqBRApSrRnBTZpjRpRiwIKy8a0M/gviz/tq?gid=1504323928&range=B17&headers=0&tqx=out:csv
 *
 * @example
 * https://docs.google.com/spreadsheets/d/18YyEoy-OfSkODfw8wqBRApSrRnBTZpjRpRiwIKy8a0M/gviz/tq?sheet=Evolution.Param&range=B17&headers=0&tqx=out:csv
 *
 * @example
 * https://docs.google.com/spreadsheets/d/18YyEoy-OfSkODfw8wqBRApSrRnBTZpjRpRiwIKy8a0M/gviz/tq?range=Evolution.Param!B17&headers=0&tqx=out:csv
 *
 * @example
 * https://docs.google.com/spreadsheets/d/18YyEoy-OfSkODfw8wqBRApSrRnBTZpjRpRiwIKy8a0M/gviz/tq?range='Evolution.Param'!B17&headers=0&tqx=out:csv
 *
 */
class GVizTQ_UrlComposer extends Recyclable.Root {

  /**
   * Used as default GSheet.GVizTQ.UrlComposer provider for conforming to Recyclable interface.
   */
  static Pool = new Pool.Root( "GSheet.GVizTQ.UrlComposer.Pool",
    GVizTQ_UrlComposer, GVizTQ_UrlComposer.setAsConstructor );

  /**
   * If sheetId is null, sheetName is null, and no sheet name in the range's A1
   * notation, the first (most left) visible sheet inside the spreadsheet will be used.
   *
   * @param {string} spreadsheetId
   *   The identifier (the component after the "https://docs.google.com/spreadsheets/d/")
   * of the spreadsheet to be accessed.
   *
   * @param {string} range
   *   The cells' A1 notation. It describes the (name and) range of the sheet inside
   * the spreadsheet.
   *   - "A1" refers to one cell of the first (most left) visible sheet.
   *   - "B2:C5" refers to cells of a rectangle of the first (most left) visible sheet.
   *   - "Books!D8:D" refers to the column D of sheet named "Books" from rows 8 to the
   *       last rows.
   *   - "'Name has space'!7:10" refers to the rows 7 to 10 of sheet named "Name has
   *       space".
   *
   * @param {number} headers
   *   The component after the "headers=". It means how many header rows. It should be
   * zero or a positive integer.
   *
   * @param {string} responseHandler
   *   The function name of JSON content handler. Only meaningful when the content is
   * downloaded as JSONP format. This responseHandler name will be prepended in front
   * of the downloaded content.
   *
   * @param {number} sheetId
   *   The component after the "gid=". If the sheetName is used (or the sheet name in
   * the range's A1 notation is specified), keep this sheetId null (or undefined).
   *
   * @param {string} sheetName
   *   The component after the "sheet=". If the sheetId is used (or the sheet name in
   * the range's A1 notation is specified), keep this sheetName null (or undefined).
   *
   * @see {@link https://developers.google.com/sheets/api/guides/concepts}
   * @see {@link https://developers.google.com/chart/interactive/docs/dev/implementing_data_source}
   * @see {@link https://developers.google.com/chart/interactive/docs/spreadsheets}
   * @see {@link https://developers.google.com/chart/interactive/docs/querylanguage}
   */
  constructor(
    spreadsheetId,
    range, headers = 0, responseHandler = null, sheetId = null, sheetName = null ) {
    super();
    GVizTQ_UrlComposer.setAsConstructor_self.call( this,
      spreadsheetId,
      range, headers, responseHandler, sheetId, sheetName
    );
  }

  /** @override */
  static setAsConstructor() {
    super.setAsConstructor();
    GVizTQ_UrlComposer.setAsConstructor_self.call( this,
      spreadsheetId,
      range, headers, responseHandler, sheetId, sheetName
    );
    return this;
  }

  /** @override */
  static setAsConstructor_self(
    spreadsheetId,
    range, headers, responseHandler, sheetId, sheetName
  ) {
    this.set_by_spreadsheetId_range_headers_responseHandler_sheetId_sheetName(
      spreadsheetId,
      range, headers, responseHandler, sheetId, sheetName
    );
  }

  /** @override */
  disposeResources() {
    this.sheetName = undefined;
    this.sheetId = undefined;
    this.responseHandler = undefined;
    this.headers = undefined;
    this.range = undefined;
    this.spreadsheetId = undefined;
    super.disposeResources();
  }

  /**  */
  set_by_spreadsheetId_range_headers_responseHandler_sheetId_sheetName(
    spreadsheetId,
    range, headers, responseHandler, sheetId, sheetName
  ) {
    this.spreadsheetId = spreadsheetId;
    this.range = range;
    this.headers = headers;
    this.responseHandler = responseHandler;
    this.sheetId = sheetId;
    this.sheetName = sheetName;
    return this;
  }

  /**  */
  spreadsheetId_set( spreadsheetId ) {
    this.spreadsheetId = spreadsheetId;
    return this;
  }

  /**  */
  range_set( range ) {
    this.range = range;
    return this;
  }

  /**
   * Generator for composing the URL (according this object's data members), downloading
   * it as JSON format, extracting data as a two dimension (column-major) array.
   *
   * @param {ValueMax.Percentage.Aggregate} progressParent
   *   Some new progressToAdvance will be created and added to progressParent. The
   * created progressToAdvance will be increased when every time advanced. The
   * progressParent.root_get() will be returned when every time yield.
   *
   * @yield {ValueMax.Percentage.Aggregate}
   *   Yield ( value = progressParent.root_get() ) when ( done = false ).
   *
   * @yield {Array[]}
   *   - Yield ( value = a two dimension (column-major) array ) when ( done = true )
   *       successfully.
   *   - Yield ( value = null ) when ( done = true ) failed.
   */
  async* fetcher_JSON_ColumnMajorArrayArray( progressParent ) {
    let progressRoot = progressParent.root_get();
    let progressToAdvance = progressParent.child_add(
      ValueMax.Percentage.Concrete.Pool.get_or_create_by( 4 ) );

    try {
      // 1. Compose URL and download it as JSONP.
      let url = this.getUrl_forJSON();
      let response = await fetch( url );

      progressToAdvance.value_advance(); // 25%
      yield progressRoot;

      if ( !response.ok )
        return null;

      // 2. Google Visualization Table Query returns JSONP (not JSON).
      let text = await response.text();

      progressToAdvance.value_advance(); // 25%
      yield progressRoot;

      if ( !text )
        return null;

      // 3. Try to evaluate it as JSON.
      let json = GVizTQ_UrlComposer.evalJSONP( text );

      progressToAdvance.value_advance(); // 25%
      yield progressRoot;

      if ( !json )
        return null;

      // 4. Collect into column-major array.
      let ColumnMajorArrayArray = GVizTQ_UrlComposer.dataTable_to_ColumnMajorArrayArray( json.table );

      progressToAdvance.value_advance(); // 25%
      yield progressRoot;

      return ColumnMajorArrayArray;

    } catch ( e ) {
      return null;
    }
  }

  /**
   * @param {string} outputFormat
   *   Specify the data format when downloading the returned url. It should be null or
   * "json" or "csv" or "html". If null, there will be no format specified in the
   * generated url (means default format, usually same as "json").
   *
   * @return {string} The url for downloading the target as specified format.
   */
  getUrl_forFormat( outputFormat ) {
    // Because sheetId could be 0, it should be checked by comparing to null directly
    // (i.e. should not use ( !this.sheetId )).
    let url = `${GVizTQ_UrlComposer.spreadsheetUrlPrefix}/${
      encodeURIComponent(this.spreadsheetId)}/${

      GVizTQ_UrlComposer.GoogleVisualizationTableQueryUrlPostfix}?tqx=version:${
      GVizTQ_UrlComposer.GoogleVisualizationTableQueryAPIVersion}${
      ( outputFormat != null ) ? `;out:${encodeURIComponent(outputFormat)}` : "" }${
      ( this.responseHandler != null ) ? `;responseHandler=${
        encodeURIComponent(this.responseHandler)}` : "" }${

      ( this.sheetId != null ) ? `&gid=${encodeURIComponent(this.sheetId)}` : `${
      ( this.sheetName != null ) ? `&sheet=${encodeURIComponent(this.sheetName)}` : "" }` }${
      ( this.range != null ) ? `&range=${encodeURIComponent(this.range)}` : "" }&headers=${
        encodeURIComponent(this.headers)}`
      ;

    return url;
  }

  /**
   * @return {string} The url for downloading the target as JSONP format.
   */
  getUrl_forJSON() {
    return this.getUrl_forFormat( null ); // Because default format is "json".
  }

  /**
   * @return {string} The url for downloading the target as CSV format.
   */
  getUrl_forCSV() {
    return this.getUrl_forFormat( "csv" );
  }

  /**
   * @return {string} The url for downloading the target as HTML format.
   */
  getUrl_forHTML() {
    return this.getUrl_forFormat( "html" );
  }

  /**
   * Evaluate JSONP string by JSON.parse().
   *
   * A JSONP string looks like:
   * @example
   *   Handler( ... );
   *
   * This method will find the position of the first "(" and the last ")". Extract
   * the string between these two positions. Parse it by JSON.parse().
   *
   * @param {string} strJSONP
   *   The JSONP string to be parsed.
   *
   * @return {Object|value}
   *   Return the JSON parsed result. Return null, if left parenthesis or right
   * parenthesis is not found.
   */
  static evalJSONP( strJSONP ) {
    let beginIndex = strJSONP.indexOf( "(" );
    if ( beginIndex < 0 )
      return null;  // left parenthesis is not found.

    let endIndex = strJSONP.lastIndexOf( ")" );
    if ( endIndex < 0 )
      return null;  // right parenthesis is not found.

    // Note: "+ 1" for the next position of the left parenthesis.
    let strJSON = strJSONP.slice( beginIndex + 1, endIndex );
    let result = JSON.parse( strJSON );
    return result;
  }

  /**
   * Collects gvizDataTable.rows[ n ].c[ m ].v into a two dimension (column-major)
   * array. The outer array has m inner sub-arrays (corresponding to m columns).
   * Every inner sub-array has n elements (corresponding to n rows).
   *
   * @param {DataTable} gvizDataTable
   *   The DataTable object of Google Visualization API.
   *
   * @return {Array[]}
   *   Return a two dimension (column-major) array. Return null if failed.
   */
  static dataTable_to_ColumnMajorArrayArray( gvizDataTable ) {
    if ( !gvizDataTable )
      return null;

    let columnArray = new Array( gvizDataTable.cols.length );
    for ( let columnNo = 0; columnNo < columnArray.length; ++columnNo ) {
      let rowArray = columnArray[ columnNo ] = new Array( gvizDataTable.rows.length );
      for ( let rowNo = 0; rowNo < rowArray.length; ++rowNo ) {
        // Always value (.v), ignore formatted value string (.f).
        rowArray[ rowNo ] = gvizDataTable.rows[ rowNo ]?.c[ columnNo ]?.v;
      }
    }

    return columnArray;
  }

}

/** The url prefix of Google Sheets. */
GVizTQ_UrlComposer.spreadsheetUrlPrefix = "https://docs.google.com/spreadsheets/d";

/** The url postfix of Google Visualization Table Query. */
GVizTQ_UrlComposer.GoogleVisualizationTableQueryUrlPostfix = "gviz/tq";

/** The version of Google Visualization Table Query API. */
GVizTQ_UrlComposer.GoogleVisualizationTableQueryAPIVersion = "0.7";
<|endoftext|>


<commit_before>"use strict";

/**
 * TODO:
 *    * Add length checking everywhere in teh program to stop invalid packets breaking the app
 *    * Add a funciton to send a broadcast packet to prompt discovery replies
 *    * Implement a way to broadcast out to >1 port because VLANs
 */

var protobuf = require("protocol-buffers"),
    dgram = require("dgram"),
    fs = require("fs"),
    bignum = require("bignum"),
    events = require("events"),
    BufferAppender = require("./BufferAppender.js"),
    multiSocket = require("./multisocket.js");

var seqNum = 1,
    clientId = Math.ceil(Math.random() * 100000000);

var messageTypes = protobuf(fs.readFileSync("steamdiscover.proto"));

var packetMagicBytes = new Buffer([0xff, 0xff, 0xff, 0xff, 0x21, 0x4c, 0x5f, 0xa0]);

function getOptions(opts) {
    opts = opts || {};
    if (opts.allInterfaces === undefined) {
      opts.allInterfaces = true;
    }
    return opts;
    //opts.addresses = ["10.0.0.40"]

}

class Listener extends events {
  constructor(opts) {
    super();
    this.opts = getOptions(opts);
    this.initSocket();

  }

  initSocket() {

    // CREATE MULTISOCKER HERE
    this.server = new multiSocket({defaultPort: this.opts.port || 27036});
    //this.server = dgram.createSocket("udp4");
    this.server.on("message", this.receiveMessage.bind(this));
    // this.server.bind(this.opts.port || 27036, this.opts.ip || "10.0.0.100", () => {
    //   this.server.setBroadcast(true);
    setTimeout(() => {
       this.emit("connected");
     }, 500);
    // });

  }

  // Given a whole packet, read the header, and do stuff!
  receiveMessage(m, rinfo) {
    this.emit("raw_message", {message: m, sender: rinfo });

    if (!m || m.length < 20) {
      this.emit("invalid_packet", {reason: "too short", message: m});
    }

    var offset = 0,
        mlength = m.length;
    var indicator = m.slice(0,8);

    if (indicator.compare(packetMagicBytes) !== 0) {
      this.emit("invalid_packet", {reason: "magic bytes mismatch", message: m});
      return;
    }

    offset += 8;
    var headerLength = m.readUInt32LE(offset);
    offset += 4; // 32 bit

    if (mlength < offset + headerLength) return;

    var header = m.slice(offset, offset + headerLength);
    var headerContent =  messageTypes.CMsgRemoteClientBroadcastHeader.decode(header);

    offset += headerLength;

    if (mlength < offset + 4) return;

    var bodyLength = m.readUInt32LE(offset);
    offset += 4; // 32 bit

    if (mlength < offset + bodyLength) return;

    if (headerContent.msg_type == messageTypes.ERemoteClientBroadcastMsg.k_ERemoteClientBroadcastMsgStatus) {
      var bodyData = m.slice(offset, offset + bodyLength);

      var bodyParsed = this.parseBody(bodyData, bodyLength);
      if (bodyParsed) {
        this.emit("client_seen", bodyParsed, rinfo);
      }

    }
    else
    {
       // Useful for debugging - type 0 is a discovery packet from another client
       //if (headerContent.msg_type === 0) {
       //  var body_data = m.slice(offset, offset + bodyLength);
       //  var discoveryPacket = messageTypes.CMsgRemoteClientBroadcastDiscovery.decode(body_data);
       //}
    }

  }

  // Given a buffer containing an k_ERemoteClientBroadcastMsgStatus, parse the Steam ID
  parseBody(bodyBuffer, length) {

      var bodyContent = messageTypes.CMsgRemoteClientBroadcastStatus.decode(bodyBuffer);
      var firstUser = bodyContent.users[0];
      var steamIdBuffer = (firstUser || {} ).steamid;

      if (steamIdBuffer) {
        firstUser.steamid = bignum.fromBuffer(steamIdBuffer, { endian: "little", size: 'auto'} ).toString();
        return bodyContent;
      }
  }

  sendBroadcast(buf) {
    this.server.send(buf, 27036, '255.255.255.255');
  }

  broadcastDiscovery() {
    console.info("sending discovery packet - client broadcast discovery");

    var body = messageTypes.CMsgRemoteClientBroadcastDiscovery.encode({
      seq_num: seqNum++
    });
    this.sendSteamMessage(0, body);
  }

  sendSteamMessage(messageType, body) {
    var headerBuf = messageTypes.CMsgRemoteClientBroadcastHeader.encode({
      client_id: Math.ceil(Math.random() * 100000000),
      msg_type: messageType
    });

    var int32Length = 4;
    var writeOffset = 0;
    var totalMessageLength = 8 + int32Length + headerBuf.length + int32Length + body.length;
    var messageBuf = new BufferAppender(totalMessageLength);

    messageBuf.appendBuffer(packetMagicBytes);
    messageBuf.appendUInt32LE(headerBuf.length);
    messageBuf.appendBuffer(headerBuf);
    messageBuf.appendUInt32LE(body.length);
    messageBuf.appendBuffer(body);

    this.sendBroadcast(messageBuf.buffer);
  }
}

function create(opts) {
  return new Listener(opts);
}

module.exports = {
  create: create
};
<commit_msg>Store IP address in databases too<commit_after>"use strict";

/**
 * TODO:
 *    * Add length checking everywhere in teh program to stop invalid packets breaking the app
 *    * Add a funciton to send a broadcast packet to prompt discovery replies
 *    * Implement a way to broadcast out to >1 port because VLANs
 */

var protobuf = require("protocol-buffers"),
    dgram = require("dgram"),
    fs = require("fs"),
    bignum = require("bignum"),
    events = require("events"),
    BufferAppender = require("./BufferAppender.js"),
    multiSocket = require("./multisocket.js");

var seqNum = 1,
    clientId = Math.ceil(Math.random() * 100000000);

var messageTypes = protobuf(fs.readFileSync("steamdiscover.proto"));

var packetMagicBytes = new Buffer([0xff, 0xff, 0xff, 0xff, 0x21, 0x4c, 0x5f, 0xa0]);

function getOptions(opts) {
    opts = opts || {};
    if (opts.allInterfaces === undefined) {
      opts.allInterfaces = true;
    }
    return opts;
    //opts.addresses = ["10.0.0.40"]

}

class Listener extends events {
  constructor(opts) {
    super();
    this.opts = getOptions(opts);
    this.initSocket();

  }

  initSocket() {

    // CREATE MULTISOCKER HERE
    this.server = new multiSocket({defaultPort: this.opts.port || 27036});
    //this.server = dgram.createSocket("udp4");
    this.server.on("message", this.receiveMessage.bind(this));
    // this.server.bind(this.opts.port || 27036, this.opts.ip || "10.0.0.100", () => {
    //   this.server.setBroadcast(true);
    setTimeout(() => {
       this.emit("connected");
     }, 500);
    // });

  }

  // Given a whole packet, read the header, and do stuff!
  receiveMessage(m, rinfo) {
    this.emit("raw_message", {message: m, sender: rinfo });

    if (!m || m.length < 20) {
      this.emit("invalid_packet", {reason: "too short", message: m});
    }

    var offset = 0,
        mlength = m.length;
    var indicator = m.slice(0,8);

    if (indicator.compare(packetMagicBytes) !== 0) {
      this.emit("invalid_packet", {reason: "magic bytes mismatch", message: m});
      return;
    }

    offset += 8;
    var headerLength = m.readUInt32LE(offset);
    offset += 4; // 32 bit

    if (mlength < offset + headerLength) return;

    var header = m.slice(offset, offset + headerLength);
    var headerContent =  messageTypes.CMsgRemoteClientBroadcastHeader.decode(header);

    offset += headerLength;

    if (mlength < offset + 4) return;

    var bodyLength = m.readUInt32LE(offset);
    offset += 4; // 32 bit

    if (mlength < offset + bodyLength) return;

    if (headerContent.msg_type == messageTypes.ERemoteClientBroadcastMsg.k_ERemoteClientBroadcastMsgStatus) {
      var bodyData = m.slice(offset, offset + bodyLength);

      var bodyParsed = this.parseBody(bodyData, bodyLength);
      if (bodyParsed) {
        bodyParsed["ip_address"] = rinfo.address;
        this.emit("client_seen", bodyParsed);
      }

    }
    else
    {
       // Useful for debugging - type 0 is a discovery packet from another client
       //if (headerContent.msg_type === 0) {
       //  var body_data = m.slice(offset, offset + bodyLength);
       //  var discoveryPacket = messageTypes.CMsgRemoteClientBroadcastDiscovery.decode(body_data);
       //}
    }

  }

  // Given a buffer containing an k_ERemoteClientBroadcastMsgStatus, parse the Steam ID
  parseBody(bodyBuffer, length) {

      var bodyContent = messageTypes.CMsgRemoteClientBroadcastStatus.decode(bodyBuffer);
      var firstUser = bodyContent.users[0];
      var steamIdBuffer = (firstUser || {} ).steamid;

      if (steamIdBuffer) {
        firstUser.steamid = bignum.fromBuffer(steamIdBuffer, { endian: "little", size: 'auto'} ).toString();
        return bodyContent;
      }
  }

  sendBroadcast(buf) {
    this.server.send(buf, 27036, '255.255.255.255');
  }

  broadcastDiscovery() {
    console.info("sending discovery packet - client broadcast discovery");

    var body = messageTypes.CMsgRemoteClientBroadcastDiscovery.encode({
      seq_num: seqNum++
    });
    this.sendSteamMessage(0, body);
  }

  sendSteamMessage(messageType, body) {
    var headerBuf = messageTypes.CMsgRemoteClientBroadcastHeader.encode({
      client_id: Math.ceil(Math.random() * 100000000),
      msg_type: messageType
    });

    var int32Length = 4;
    var writeOffset = 0;
    var totalMessageLength = 8 + int32Length + headerBuf.length + int32Length + body.length;
    var messageBuf = new BufferAppender(totalMessageLength);

    messageBuf.appendBuffer(packetMagicBytes);
    messageBuf.appendUInt32LE(headerBuf.length);
    messageBuf.appendBuffer(headerBuf);
    messageBuf.appendUInt32LE(body.length);
    messageBuf.appendBuffer(body);

    this.sendBroadcast(messageBuf.buffer);
  }
}

function create(opts) {
  return new Listener(opts);
}

module.exports = {
  create: create
};
<|endoftext|>


<commit_before>//Load session id
var scripts = document.getElementsByTagName('script');
var index = scripts.length - 1;
var myScript = scripts[index];
var src = myScript.src;
var session = src.substr(src.indexOf('=')+1);

//Load sections
var sections = document.getElementsByTagName("section");

//Create navigation menu
var menu = document.createElement("div");
menu.id = "menu";
var bg = document.createElement("div");
bg.id = "bg";
var content = document.createElement("div");
content.id = "content";

var header = document.getElementsByTagName("header")[0];

sections[0].parentNode.insertBefore(menu, header);
sections[0].parentNode.insertBefore(bg, header);
sections[0].parentNode.insertBefore(content, header);

content.appendChild(header);

var menuItems = []; //menu items array
for(var i=0; i<sections.length; i++){
	if(sections[i].id != "refs"){
		var itemName = sections[i].getElementsByTagName("h2")[0].childNodes[0].nodeValue; //collect section names
		//Create menu item
		var menuitem = document.createElement("div");
		menuitem.innerHTML = ""+itemName+" ";
		menuitem.className = "menuitem";
		menu.appendChild(menuitem);
		//Create led indicator
		var progress = document.createElement("div");
		//progress.innerHTML = "";
		progress.className = "progressIcon off";
		menu.appendChild(progress);
		menuItems.push(menuitem);
		sections[i].classList.add("hide");
	}
	content.appendChild(sections[i]); //append sections to the new content div
}

//Waypoints
/*
$('section').waypoint(function(event, direction){
	for(var i=0; i<sections.length; i++){
		if(sections[i]==this){
			var link = document.getElementsByClassName("menuitem")[i];
			link.classList.add("selected");
			$('.selected').removeClass('selected');
			break;
		}
	}
});
*/

var footer = document.getElementsByTagName("footer")[0];
content.appendChild(footer);

var progressdiv = document.createElement("div");
progressdiv.className = "progressdiv";
var progressbar = document.createElement("meter");
progressbar.max = 100;
progressbar.min = 0;
progressbar.low = 20;
progressbar.high = 80;
progressbar.optimum = 100;
progressbar.value = 0;
progressbar.innerHTML = "Progreso";
progressdiv.appendChild(progressbar);

menu.appendChild(progressdiv);

//Step in the progressbar
var step = Math.ceil(100 / menuItems.length);
var currentSection = 0;

//Init first section
indicateProgress();

//Help button
var helpButton = document.createElement("div");
helpButton.addEventListener("click", askForHelp);
helpButton.innerHTML = "AYUDA";
helpButton.className = "button red";
menu.appendChild(helpButton);
//Waiting for help?
var helpNeeded = false;

//Load info from localstorage; otherwise, from the server
var user = window.localStorage.getItem("user");

if(!user){
	user = [];
	requestUser();
}else{
	user = user.split(",");
	checkUsers(function(error){
		if(error){
			requestUser("Los NIAs almacenados no son correctos. Por favor, rev�salos y pincha OK.");
		}else{
			usersOK();
		}
	});
}

var usersInfo = [];
var info = document.createElement("div");
info.className = "info";
menu.appendChild(info);
function usersOK(){
	window.localStorage.setItem("user", user);
	//User info
	info.innerHTML = (usersInfo[0]?(usersInfo[0].surname+" "+usersInfo[0].name):"")+"
"+
	(usersInfo[1]?(usersInfo[1].surname+" "+usersInfo[1].name):"")+"
"+
	"<input type='button' onclick='logout()' value='DESCONECTAR' style='margin: auto;' />";

	$.unblockUI();
}

function logout(){
	window.localStorage.removeItem("user");
	window.location.reload();
}

function requestUser(error){
	window.localStorage.removeItem("user");
	$.blockUI({
		theme:     true,
        title:    'Participantes',
		message: "
Por favor, introduce el NIA de los participantes para acceder al enunciado. Gracias.

" +
		"NIA 1: <input type='text' id='p1' value='"+(user[0]?user[0]:"")+"' />
" +
		"NIA 2: <input type='text' id='p2' value='"+(user[1]?user[1]:"")+"' />
" +
		"
<input type='button' onclick='saveUsers()' value='OK' style='float: right;' />
" +
		"
"+(error?error:"")+"

"});
	user = [];
}

function saveUsers(){
	var user1 = document.getElementById("p1").value;
	if(user1) user.push(user1);
	var user2 = document.getElementById("p2").value;
	if(user2) user.push(user2);
	checkUsers(function(error){
		if(error){
			$.unblockUI();
			requestUser("Los NIAs indicados no son correctos. Por favor, rev�salos y pincha OK.");
		}else{
			usersOK();
		}
	});
}

function indicateProgress(back){
	if(back){

		if(currentSection==(menuItems.length-1)){
			progressdiv.removeChild(progressdiv.lastElementChild);
		}else{
			sections[currentSection+1].classList.add("hide");
			var nextIcon = menuItems[currentSection+1].nextElementSibling;
			nextIcon.removeEventListener("click", finishSection);
			nextIcon.className = "progressIcon off";
		}

		var currentIcon = menuItems[currentSection].nextElementSibling;
		currentIcon.removeEventListener("click", undoFinishSection);
		currentIcon.addEventListener("click", finishSection);
		currentIcon.className = "progressIcon progress active";

		if(currentSection>0){
			var formerIcon = menuItems[currentSection-1].nextElementSibling;
			formerIcon.addEventListener("click", undoFinishSection);
			formerIcon.className = "progressIcon finished active";
		}

	}else{
		sections[currentSection].classList.remove("hide");

		if(currentSection>1){ //Remove listener in former former icon
			var formerformerIcon = menuItems[currentSection-2].nextElementSibling;
			formerformerIcon.removeEventListener("click", undoFinishSection);
			formerformerIcon.className = "progressIcon finished";
		}

		if(currentSection>0){
			var formerIcon = menuItems[currentSection-1].nextElementSibling;
			formerIcon.addEventListener("click", undoFinishSection);
			formerIcon.removeEventListener("click", finishSection);
			formerIcon.className = "progressIcon finished active";
		}

		if(currentSection==menuItems.length){ //Last icon
			var completedDiv = document.createElement("div");
			completedDiv.className = "completed";
			completedDiv.innerHTML = "¡COMPLETADO!";
			progressdiv.appendChild(completedDiv);
		}else{
			if(currentSection>0){
				$('html, body').animate({
			         scrollTop: $("#"+sections[currentSection].id).offset().top
			     }, 2000);
			}
			var currentIcon = menuItems[currentSection].nextElementSibling;
			currentIcon.addEventListener("click", finishSection);
			currentIcon.className = "progressIcon progress active";
		}
	}
}

function askForHelp(){
	$.blockUI({
		theme:     true,
        title:    (helpNeeded)?'Solución':'Duda',
		message: "
Por favor, describe brevemente la "+(helpNeeded?'respuesta a tu duda':'duda que vas a preguntar')+".

" +
		"<textarea id='duda' style='width:100%;height:100px'></textarea>

" +
		"
<input type='button' onclick='$.unblockUI();' value='Cancelar' style='float:left;' />" +
		"<input type='button' onclick='askForHelp2()' value='OK' style='float: right;' />

"});
}

function askForHelp2(){
	var eventType = "help";
	if(helpNeeded){
		helpButton.innerHTML = "AYUDA";
		helpButton.className = "button red";
		eventType = "solved";
		/*
		if(helpNeeded === 1){
			helpNeeded = true;
			eventType = "connection";
		}
		*/
	}else{
		helpButton.innerHTML = "SOLUCIONADO?";
		helpButton.className = "button green";
	}

	//Send event to the server

	var event = {
			eventType: eventType,
	};
	var duda = document.getElementById("duda");
	if(duda){
		event.description = duda.value;
	}
	sendEventToServer('new event', event);

	helpNeeded = !helpNeeded;
	$.unblockUI();
}

function finishSection(){
	$.blockUI({
		theme:     true,
        title:    "Sección terminada",
		message: "
¿Seguro que has terminado el apartado correpondiente?

" +
		"
<input type='button' onclick='$.unblockUI();' value='Cancelar' style='margin-left: 0px;' />" +
		"<input type='button' onclick='finishSection2();' value='OK' style='float:right;' />

"});
}

function finishSection2(){
	//Send event to the server
	sendEventToServer('new event', {eventType: 'finishSection'});
	finishSection3();
	$.unblockUI();
}
function finishSection3(){
	progressbar.value += step;
	currentSection++;
	indicateProgress();
}

function undoFinishSection(){
	$.blockUI({
		theme:     true,
        title:    "Deshacer progreso",
		message: "
¿Seguro que deseas volver al apartado anterior?

" +
		"
<input type='button' onclick='$.unblockUI();' value='Cancelar' style='margin-left: 0px;' />" +
		"<input type='button' onclick='undoFinishSection2();' value='OK' style='float:right;' />

"});
}

function undoFinishSection2(){
	//Send event to the server
	sendEventToServer('new event', {eventType: 'undoFinishSection'});

	progressbar.value -= step;
	currentSection--;
	indicateProgress(-1);
	$.unblockUI();
}

function setExercise(savedSection){
	for (var i=0; i<savedSection; i++){
		finishSection3();
	}
}

var socket;
function checkUsers(callback){
	if(!socket){
		var server = document.location.href.hostname;
		//var server = document.location.href.substr(0,document.location.href.lastIndexOf(':'));
		server = "163.117.141.206";
		//server = "127.0.0.1";
		socket = io.connect(server+':80');
		socket.on('connect', function() {
			sendEventToServer('new student', {session: session});
			//console.log('new student when connecting');
			//socket.emit('new student', {user: user, session: session});

			socket.on('student registered', function(regInfo){
				//console.log("student registered:"+JSON.stringify(regInfo));
				if(!regInfo.error){
					usersInfo = regInfo.userInfoArray;
					if(regInfo.exercise){
						setExercise(regInfo.exercise);
						//console.log("exercise:"+state.exercise);
					}
					if(regInfo.help){
						helpNeeded = true;
						helpButton.innerHTML = "SOLUCIONADO?";
						helpButton.className = "button green";
					}
					//Connection to the practice event
					sendEventToServer('new event', {eventType: "connection"});
					//console.log('send connection event to server');
				}
				callback(regInfo.error);
			});

			socket.on('update queue', function(position){
				console.log("new position in queue:"+position);
				helpButton.innerHTML = "SOLUCIONADO?
("+position+")";
				if(position==0){
					askForHelp();
				}
			});
		});
	}else{
		//socket.emit('new student', {user: user, session: session});
		sendEventToServer('new student', {session: session});

	}
}

function sendEventToServer(eventName, event){
	event.user = user;
	if(eventName!="new student"){
		event.eventSection = currentSection;
		event.session = usersInfo[0].group + session;
	}

	//Testing IPs
	//event.IP = "163.117.101."+document.location.href.substr(document.location.href.lastIndexOf('?')+1).split("=")[1];
	socket.emit(eventName, event);
	console.log('new event sent:'+eventName);
}

<commit_msg>Minor update student UI<commit_after>//Load session id
var scripts = document.getElementsByTagName('script');
var index = scripts.length - 1;
var myScript = scripts[index];
var src = myScript.src;
var session = src.substr(src.indexOf('=')+1);

//Load sections
var sections = document.getElementsByTagName("section");

//Create navigation menu
var menu = document.createElement("div");
menu.id = "menu";
var bg = document.createElement("div");
bg.id = "bg";
var content = document.createElement("div");
content.id = "content";

var header = document.getElementsByTagName("header")[0];

sections[0].parentNode.insertBefore(menu, header);
sections[0].parentNode.insertBefore(bg, header);
sections[0].parentNode.insertBefore(content, header);

content.appendChild(header);

var menuItems = []; //menu items array
for(var i=0; i<sections.length; i++){
	if(sections[i].id != "refs"){
		var itemName = sections[i].getElementsByTagName("h2")[0].childNodes[0].nodeValue; //collect section names
		//Create menu item
		var menuitem = document.createElement("div");
		menuitem.innerHTML = ""+itemName+" ";
		menuitem.className = "menuitem";
		menu.appendChild(menuitem);
		//Create led indicator
		var progress = document.createElement("div");
		//progress.innerHTML = "";
		progress.className = "progressIcon off";
		menu.appendChild(progress);
		menuItems.push(menuitem);
		sections[i].classList.add("hide");
	}
	content.appendChild(sections[i]); //append sections to the new content div
}

//Waypoints
/*
$('section').waypoint(function(event, direction){
	for(var i=0; i<sections.length; i++){
		if(sections[i]==this){
			var link = document.getElementsByClassName("menuitem")[i];
			link.classList.add("selected");
			$('.selected').removeClass('selected');
			break;
		}
	}
});
*/

var footer = document.getElementsByTagName("footer")[0];
content.appendChild(footer);

var progressdiv = document.createElement("div");
progressdiv.className = "progressdiv";
var progressbar = document.createElement("meter");
progressbar.max = 100;
progressbar.min = 0;
progressbar.low = 20;
progressbar.high = 80;
progressbar.optimum = 100;
progressbar.value = 0;
progressbar.innerHTML = "Progreso";
progressdiv.appendChild(progressbar);

menu.appendChild(progressdiv);

//Step in the progressbar
var step = Math.ceil(100 / menuItems.length);
var currentSection = 0;

//Init first section
indicateProgress();

//Help button
var helpButton = document.createElement("div");
helpButton.addEventListener("click", askForHelp);
helpButton.innerHTML = "AYUDA";
helpButton.className = "button red";
menu.appendChild(helpButton);
//Waiting for help?
var helpNeeded = false;

//Load info from localstorage; otherwise, from the server
var user = window.localStorage.getItem("user");

if(!user){
	user = [];
	requestUser();
}else{
	user = user.split(",");
	checkUsers(function(error){
		if(error){
			requestUser("Los NIAs almacenados no son correctos. Por favor, revísalos y pincha OK.");
		}else{
			usersOK();
		}
	});
}

var usersInfo = [];
var info = document.createElement("div");
info.className = "info";
menu.appendChild(info);
function usersOK(){
	window.localStorage.setItem("user", user);
	//User info
	info.innerHTML = (usersInfo[0]?(usersInfo[0].surname+" "+usersInfo[0].name):"")+"
"+
	(usersInfo[1]?(usersInfo[1].surname+" "+usersInfo[1].name):"")+"
"+
	"<input type='button' onclick='logout()' value='DESCONECTAR' style='margin: auto;' />";

	$.unblockUI();
}

function logout(){
	window.localStorage.removeItem("user");
	window.location.reload();
}

function requestUser(error){
	window.localStorage.removeItem("user");
	$.blockUI({
		theme:     true,
        title:    'Participantes',
		message: "
Por favor, introduce el NIA de los participantes para acceder al enunciado. Gracias.

" +
		"NIA 1: <input type='text' id='p1' value='"+(user[0]?user[0]:"")+"' />
" +
		"NIA 2: <input type='text' id='p2' value='"+(user[1]?user[1]:"")+"' />
" +
		"
<input type='button' onclick='saveUsers()' value='OK' style='float: right;' />
" +
		"
"+(error?error:"")+"

"});
	user = [];
}

function saveUsers(){
	var user1 = document.getElementById("p1").value;
	if(user1) user.push(user1);
	var user2 = document.getElementById("p2").value;
	if(user2) user.push(user2);
	checkUsers(function(error){
		if(error){
			$.unblockUI();
			requestUser("Los NIAs indicados no son correctos. Por favor, revísalos y pincha OK.");
		}else{
			usersOK();
		}
	});
}

function indicateProgress(back){
	if(back){

		if(currentSection==(menuItems.length-1)){
			progressdiv.removeChild(progressdiv.lastElementChild);
		}else{
			sections[currentSection+1].classList.add("hide");
			var nextIcon = menuItems[currentSection+1].nextElementSibling;
			nextIcon.removeEventListener("click", finishSection);
			nextIcon.className = "progressIcon off";
		}

		var currentIcon = menuItems[currentSection].nextElementSibling;
		currentIcon.removeEventListener("click", undoFinishSection);
		currentIcon.addEventListener("click", finishSection);
		currentIcon.className = "progressIcon progress active";

		if(currentSection>0){
			var formerIcon = menuItems[currentSection-1].nextElementSibling;
			formerIcon.addEventListener("click", undoFinishSection);
			formerIcon.className = "progressIcon finished active";
		}

	}else{
		sections[currentSection].classList.remove("hide");

		if(currentSection>1){ //Remove listener in former former icon
			var formerformerIcon = menuItems[currentSection-2].nextElementSibling;
			formerformerIcon.removeEventListener("click", undoFinishSection);
			formerformerIcon.className = "progressIcon finished";
		}

		if(currentSection>0){
			var formerIcon = menuItems[currentSection-1].nextElementSibling;
			formerIcon.addEventListener("click", undoFinishSection);
			formerIcon.removeEventListener("click", finishSection);
			formerIcon.className = "progressIcon finished active";
		}

		if(currentSection==menuItems.length){ //Last icon
			var completedDiv = document.createElement("div");
			completedDiv.className = "completed";
			completedDiv.innerHTML = "¡COMPLETADO!";
			progressdiv.appendChild(completedDiv);
		}else{
			if(currentSection>0){
				$('html, body').animate({
			         scrollTop: $("#"+sections[currentSection].id).offset().top
			     }, 2000);
			}
			var currentIcon = menuItems[currentSection].nextElementSibling;
			currentIcon.addEventListener("click", finishSection);
			currentIcon.className = "progressIcon progress active";
		}
	}
}

function askForHelp(){
	$.blockUI({
		theme:     true,
        title:    (helpNeeded)?'Solución':'Duda',
		message: "
Por favor, describe brevemente la "+(helpNeeded?'respuesta a tu duda':'duda que vas a preguntar')+".

" +
		"<textarea id='duda' style='width:100%;height:100px'></textarea>

" +
		"
<input type='button' onclick='$.unblockUI();' value='Cancelar' style='float:left;' />" +
		"<input type='button' onclick='askForHelp2()' value='OK' style='float: right;' />

"});
}

function askForHelp2(){
	var eventType = "help";
	if(helpNeeded){
		helpButton.innerHTML = "AYUDA";
		helpButton.className = "button red";
		eventType = "solved";
		/*
		if(helpNeeded === 1){
			helpNeeded = true;
			eventType = "connection";
		}
		*/
	}else{
		helpButton.innerHTML = "SOLUCIONADO?";
		helpButton.className = "button green";
	}

	//Send event to the server

	var event = {
			eventType: eventType,
	};
	var duda = document.getElementById("duda");
	if(duda){
		event.description = duda.value;
	}
	sendEventToServer('new event', event);

	helpNeeded = !helpNeeded;
	$.unblockUI();
}

function finishSection(){
	$.blockUI({
		theme:     true,
        title:    "Sección terminada",
		message: "
¿Seguro que has terminado el apartado correpondiente?

" +
		"
<input type='button' onclick='$.unblockUI();' value='Cancelar' style='margin-left: 0px;' />" +
		"<input type='button' onclick='finishSection2();' value='OK' style='float:right;' />

"});
}

function finishSection2(){
	//Send event to the server
	sendEventToServer('new event', {eventType: 'finishSection'});
	finishSection3();
	$.unblockUI();
}
function finishSection3(){
	progressbar.value += step;
	currentSection++;
	indicateProgress();
}

function undoFinishSection(){
	$.blockUI({
		theme:     true,
        title:    "Deshacer progreso",
		message: "
¿Seguro que deseas volver al apartado anterior?

" +
		"
<input type='button' onclick='$.unblockUI();' value='Cancelar' style='margin-left: 0px;' />" +
		"<input type='button' onclick='undoFinishSection2();' value='OK' style='float:right;' />

"});
}

function undoFinishSection2(){
	//Send event to the server
	sendEventToServer('new event', {eventType: 'undoFinishSection'});

	progressbar.value -= step;
	currentSection--;
	indicateProgress(-1);
	$.unblockUI();
}

function setExercise(savedSection){
	for (var i=0; i<savedSection; i++){
		finishSection3();
	}
}

var socket;
function checkUsers(callback){
	if(!socket){
		var server = document.location.href.hostname;
		//var server = document.location.href.substr(0,document.location.href.lastIndexOf(':'));
		server = "163.117.141.206";
		//server = "127.0.0.1";
		socket = io.connect(server+':80');
		socket.on('connect', function() {
			sendEventToServer('new student', {session: session});
			//console.log('new student when connecting');
			//socket.emit('new student', {user: user, session: session});

			socket.on('student registered', function(regInfo){
				//console.log("student registered:"+JSON.stringify(regInfo));
				if(!regInfo.error){
					usersInfo = regInfo.userInfoArray;
					if(regInfo.exercise){
						setExercise(regInfo.exercise);
						//console.log("exercise:"+state.exercise);
					}
					if(regInfo.help){
						helpNeeded = true;
						helpButton.innerHTML = "SOLUCIONADO?";
						helpButton.className = "button green";
					}
					//Connection to the practice event
					sendEventToServer('new event', {eventType: "connection"});
					//console.log('send connection event to server');
				}
				callback(regInfo.error);
			});

			socket.on('update queue', function(position){
				console.log("new position in queue:"+position);
				helpButton.innerHTML = "SOLUCIONADO?
(Posición en cola: "+position+")";
				if(position==0){
					askForHelp();
				}
			});
		});
	}else{
		//socket.emit('new student', {user: user, session: session});
		sendEventToServer('new student', {session: session});

	}
}

function sendEventToServer(eventName, event){
	event.user = user;
	if(eventName!="new student"){
		event.eventSection = currentSection;
		event.session = usersInfo[0].group + session;
	}

	//Testing IPs
	//event.IP = "163.117.101."+document.location.href.substr(document.location.href.lastIndexOf('?')+1).split("=")[1];
	socket.emit(eventName, event);
	console.log('new event sent:'+eventName);
}

<|endoftext|>


<commit_before><commit_msg>Update script.js<commit_after><|endoftext|>


<commit_before>var context = require_core("server/context");

var config = require_core("server/config");
var ua_parser = require("ua-parser");
var app_name = require_core("server/app_name");

var geoip;
try {
  geoip = require('geoip-lite');
} catch(e) {
  console.log("NO GEOLITE DATA INSTALLED, IGNORING GEOIP DATA");

}

// {{{ SAMPLE API
// override Sample.__send to use this instrumentation to its fullest!
var Sample = function(dataset) {
  var dict = { integer: {}, set: {}, string: {} };

  var obj = {
    dataset: dataset,
    meta: {
      dataset: dataset,
    },
    integer: function(k, v) {
      dict.integer[k] = v;
      return obj;
    },
    string: function(k, v) {
      dict.string[k] = v;
      return obj;
    },
    send: function() {
      if (dict.__sent) {
        console.log("Trying to double send sample");
        return;
      }

      var copy = {};
      dict.__sent = true;
      var fields = [ "integer", "string" ];
      for (var f = 0; f < fields.length; f++) {
        var inner = dict[fields[f]];
        var keys = Object.keys(inner);
        if (dict[fields[f]] && keys.length > 0) {
          var inner_copy = {};
          for (var i = 0; i < keys.length; i++) {
            // only copy non falsey values
            if (inner[keys[i]]) {
              inner_copy[keys[i]] = inner[keys[i]];
            }
          }
          copy[fields[f]] = inner_copy;
        }
      }

      if (!copy.__ts) {
        copy.__ts = obj.__ts || obj.meta.ts;
        if (!copy.__ts) { delete copy.__ts; }
      }

      if (!copy.dataset) {
        copy.dataset = obj.dataset;
      }

      Sample.__send(copy, obj.meta);
    },
    data: dict,
  };

  return obj;
};

Sample.__send = function(s, meta) {
  console.log("Sending Sample", JSON.stringify(s), "with metadata", meta);
};

// }}} SAMPLE API

var crypto = require("crypto");
var gen_md5 = function(h) {
  var hash = crypto.Hash("md5");
  hash.update("" + h);
  return hash.digest("hex");
};

module.exports = gen_md5;
var DECO = {
  browser_info: function(s, req) {
    if (typeof req === "undefined") {
      req = context("request");
    }

    if (!req) {
      return;
    }

    // Add in UA flavors
    var userAgent = req.headers["user-agent"];
    var ua = ua_parser.parseUA(userAgent);
    var os = ua_parser.parseOS(userAgent);

    s.string("browser_family", ua.family);
    s.string("browser_major", ua.major);
    s.string("browser_minor", ua.minor);

    s.string("os_family", os.family);

    var ip = req.ip;

    s.string("ip", gen_md5(app_name + ":" + ip));
    if (ip && geoip) {
      var geo = geoip.lookup(ip);

      if (geo) {
        s.string("country", geo.country);
        s.string("region", geo.region);
        s.string("city", geo.city);
      }

    }

    var sid = req.headers && req.headers.sid;
    if (sid) {
      var hashsid = gen_md5(app_name + ":" + sid);
      s.string("sid", hashsid);
    }

    return s;

  },
  server_info: "SERVER_INFO",
};

// Post it to the snorkel instance...
module.exports = {
  Sample: Sample,
  decorate_sample: function(s, decorations, request) {
    if (!_.isArray(decorations)) {
      if (_.isFunction(decorations)) {
        decorations = [ decorations ];
      }
    }

    _.each(decorations, function(deco) {
      deco(s, request);
    });
  },
  DECO: DECO,
  handle_json_sample: function(json_obj, request) {
    var s = new Sample();

    s.data.integer = json_obj.integer || {};
    s.data.string = json_obj.string || {};
    s.data.set = json_obj.set || {};

    // record server and client side timestamps, separately
    var ts = Date.now();
    if (s.__ts) {
      ts = +new Date(s.__ts);
      s.integer("client_time", parseInt(ts / 1000, 10));
    }

    s.integer("time", parseInt(ts / 1000, 10));

    s.dataset = json_obj.dataset;

    module.exports.decorate_sample(s, DECO.browser_info, request);

    _.each(s.data.integer, function(v, k) {
      s.data.integer[k] = parseInt(v, 10);
    });

    _.defer(function() {
      s.send();
    }, 1000);
    return s;
  },
}

function send_samples(dataset, samples) {
  var data = {
    dataset: app_name, // TODO: pull the dataset from the app name
    subset: dataset,
    samples: JSON.stringify(samples)
  };

  if (!config.analytics) {
    return;
  }

  var options = _.defaults(config.analytics, {
    hostname: 'localhost',
    port: 3000,
    path: '/data/import',
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
    }
  });

  if (!options.enabled) {
    return;
  }

  var http = require('http');
  if (options.https) {
    http = require('https');
  }

  var req = http.request(options, function(res) { });
  req.on('error', function(err) { });

  req.write(JSON.stringify(data));
  req.end();
}

var queue_sample_send = _.throttle(function() {
  _.each(SAMPLES, function(samples, dataset) {
    send_samples(dataset, samples);
    SAMPLES[dataset] = [];
  });

}, 10000);

var SAMPLES = {};
Sample.__send = function(sample) {
  var sample_list = SAMPLES[sample.dataset];
  if (!sample_list) {
    SAMPLES[sample.dataset] = [];
    sample_list = SAMPLES[sample.dataset];
    delete sample.dataset;
  }

  sample_list.push(sample);
  queue_sample_send();
};
<commit_msg>[npm audit] removing ua-parser and using useragent instead<commit_after>var context = require_core("server/context");

var config = require_core("server/config");
var useragent = require("useragent");
var app_name = require_core("server/app_name");

var geoip;
try {
  geoip = require('geoip-lite');
} catch(e) {
  console.log("NO GEOLITE DATA INSTALLED, IGNORING GEOIP DATA");

}

// {{{ SAMPLE API
// override Sample.__send to use this instrumentation to its fullest!
var Sample = function(dataset) {
  var dict = { integer: {}, set: {}, string: {} };

  var obj = {
    dataset: dataset,
    meta: {
      dataset: dataset,
    },
    integer: function(k, v) {
      dict.integer[k] = v;
      return obj;
    },
    string: function(k, v) {
      dict.string[k] = v;
      return obj;
    },
    send: function() {
      if (dict.__sent) {
        console.log("Trying to double send sample");
        return;
      }

      var copy = {};
      dict.__sent = true;
      var fields = [ "integer", "string" ];
      for (var f = 0; f < fields.length; f++) {
        var inner = dict[fields[f]];
        var keys = Object.keys(inner);
        if (dict[fields[f]] && keys.length > 0) {
          var inner_copy = {};
          for (var i = 0; i < keys.length; i++) {
            // only copy non falsey values
            if (inner[keys[i]]) {
              inner_copy[keys[i]] = inner[keys[i]];
            }
          }
          copy[fields[f]] = inner_copy;
        }
      }

      if (!copy.__ts) {
        copy.__ts = obj.__ts || obj.meta.ts;
        if (!copy.__ts) { delete copy.__ts; }
      }

      if (!copy.dataset) {
        copy.dataset = obj.dataset;
      }

      Sample.__send(copy, obj.meta);
    },
    data: dict,
  };

  return obj;
};

Sample.__send = function(s, meta) {
  console.log("Sending Sample", JSON.stringify(s), "with metadata", meta);
};

// }}} SAMPLE API

var crypto = require("crypto");
var gen_md5 = function(h) {
  var hash = crypto.Hash("md5");
  hash.update("" + h);
  return hash.digest("hex");
};

module.exports = gen_md5;
var DECO = {
  browser_info: function(s, req) {
    if (typeof req === "undefined") {
      req = context("request");
    }

    if (!req) {
      return;
    }

    // Add in UA flavors
    var userAgent = req.headers["user-agent"];
    var ua = useragent.parse(userAgent);
    var os = ua.os;

    s.string("browser_family", ua.family);
    s.string("browser_major", ua.major);
    s.string("browser_minor", ua.minor);

    s.string("os_family", os.family);

    var ip = req.ip;

    s.string("ip", gen_md5(app_name + ":" + ip));
    if (ip && geoip) {
      var geo = geoip.lookup(ip);

      if (geo) {
        s.string("country", geo.country);
        s.string("region", geo.region);
        s.string("city", geo.city);
      }

    }

    var sid = req.headers && req.headers.sid;
    if (sid) {
      var hashsid = gen_md5(app_name + ":" + sid);
      s.string("sid", hashsid);
    }

    return s;

  },
  server_info: "SERVER_INFO",
};

// Post it to the snorkel instance...
module.exports = {
  Sample: Sample,
  decorate_sample: function(s, decorations, request) {
    if (!_.isArray(decorations)) {
      if (_.isFunction(decorations)) {
        decorations = [ decorations ];
      }
    }

    _.each(decorations, function(deco) {
      deco(s, request);
    });
  },
  DECO: DECO,
  handle_json_sample: function(json_obj, request) {
    var s = new Sample();

    s.data.integer = json_obj.integer || {};
    s.data.string = json_obj.string || {};
    s.data.set = json_obj.set || {};

    // record server and client side timestamps, separately
    var ts = Date.now();
    if (s.__ts) {
      ts = +new Date(s.__ts);
      s.integer("client_time", parseInt(ts / 1000, 10));
    }

    s.integer("time", parseInt(ts / 1000, 10));

    s.dataset = json_obj.dataset;

    module.exports.decorate_sample(s, DECO.browser_info, request);

    _.each(s.data.integer, function(v, k) {
      s.data.integer[k] = parseInt(v, 10);
    });

    _.defer(function() {
      s.send();
    }, 1000);
    return s;
  },
}

function send_samples(dataset, samples) {
  var data = {
    dataset: app_name, // TODO: pull the dataset from the app name
    subset: dataset,
    samples: JSON.stringify(samples)
  };

  if (!config.analytics) {
    return;
  }

  var options = _.defaults(config.analytics, {
    hostname: 'localhost',
    port: 3000,
    path: '/data/import',
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
    }
  });

  if (!options.enabled) {
    return;
  }

  var http = require('http');
  if (options.https) {
    http = require('https');
  }

  var req = http.request(options, function(res) { });
  req.on('error', function(err) { });

  req.write(JSON.stringify(data));
  req.end();
}

var queue_sample_send = _.throttle(function() {
  _.each(SAMPLES, function(samples, dataset) {
    send_samples(dataset, samples);
    SAMPLES[dataset] = [];
  });

}, 10000);

var SAMPLES = {};
Sample.__send = function(sample) {
  var sample_list = SAMPLES[sample.dataset];
  if (!sample_list) {
    SAMPLES[sample.dataset] = [];
    sample_list = SAMPLES[sample.dataset];
    delete sample.dataset;
  }

  sample_list.push(sample);
  queue_sample_send();
};
<|endoftext|>