<commit_before>/**
 * src/ui/Tree.js
 * Author: H.Alper Tuna <halpertuna@gmail.com>
 * Date: 12.07.2016
 */

'use strict';

define([
    '../core/Utils',
    './Element',
    './Label',
    './Button',
    './Check',
    './Group'
], function(Utils, Element, Label, Button, Check, Group){
    function setSubItemsFromCallBack(item, items){
        setSubItems(item.subItemContainer, items);
        item.refreshButton.setDisabled(false).setIcon('refresh');
        item.toggleCheck.setDisabled(false);

        /*if(!item.toggleCheck.getParent())
            item.group.prepend(item.toggleCheck);*/

        toggle(item);
    }
    function callSubItems(item){
        /*if(item.subItemContainer)*/
            item.subItemContainer.clear().hide();
        item.toggleCheck.setDisabled(true);//.setIcon('circle-o-notch fa-spin');
        item.refreshButton.setDisabled(true).setIcon('circle-o-notch fa-spin');
        item.subItemCallBack(setSubItemsFromCallBack.bind(this, item));
    }
    function toggle(item){
        if(item.callBackIsWaiting){
            item.callBackIsWaiting = false;
            item.toggleGroup.add(item.refreshButton);
            item.refresh();
            return;
        }

        if(item.toggleCheck.getValue()){
            item.subItemContainer.show();
        }else{
            item.subItemContainer.hide();
        }
    }
    function setSubItems(container, items){
        Utils.each(items, function(item){
            var group = Group.new().setSpace(true);
            item.group = group;
            var element = Element.new('li').add(
                group
            );

            if(item.subItems && item.subItems.length == 0) delete item.subItems;

            //If there are sub items
            if(item.subItems || item.subItemCallBack){
                //Sub Item Container
                var subItemContainer = Element.new('ul').hide();
                element.add(subItemContainer);
                item.subItemContainer = subItemContainer;

                //Toggle Button
                var toggleCheck = Check.new()
                .setIcon('minus')
                .on('change', toggle.bind(this, item));
                item.toggleCheck = toggleCheck;
            }else element.addClass('jb-tree-nochild');

            //If sub items from callback
            if(item.subItemCallBack){
                item.callBackIsWaiting = true;

                //Refresh Button
                var refreshButton = Button.new()
                .setIcon('refresh')
                .on('click', callSubItems.bind(this, item));
                item.refreshButton = refreshButton;
                item.refresh = callSubItems.bind(this, item); //To refresh from outside
                group.add(
                    item.toggleGroup = Group.new().add(
                        toggleCheck
                    )
                )
            }

            //If sub items from parameter
            if(item.subItems){
                item.callBackIsWaiting = false;

                setSubItems(subItemContainer, item.subItems);
                group.add(toggleCheck);
            }

            //Item
            group.add(item.element);

            container.add(element);
        });
    }

    return Element.extend({
        'init': function(items){
            this.super();
            this.addClass('jb-tree');

            var content = Element.new('ul');
            this.add(content);
            this.set('content', content);

            this.setItems(items);
        },
        'setItems': function(items){
            var content = this.get('content').clear();
            if(items.length == 0)
                content.hide();
            else
                setSubItems.call(this, content.show(), items);

            return this.ref;
        },
        'showRootLine': function(value){
            if(value)
                this.removeClass('jb-tree-noroot');
            else
                this.addClass('jb-tree-noroot');

            return this.ref;
        }
    })
})
<commit_msg>Tree, asynchronous sub item loading system is improved.<commit_after>/**
 * src/ui/Tree.js
 * Author: H.Alper Tuna <halpertuna@gmail.com>
 * Date: 12.07.2016
 */

'use strict';

define([
    '../core/Utils',
    './Element',
    './Label',
    './Button',
    './Check',
    './Group'
], function(Utils, Element, Label, Button, Check, Group){
    function setSubItemsFromCallBack(item, items){
        item.refreshButton.setDisabled(false).setIcon('refresh');
        if(items.length){
            item.toggleCheck.setDisabled(false).show();
            item.li.removeClass('jb-tree-nochild');
            setSubItems.call(this, item.subItemContainer, items);
            toggle(item);
        }else{
            item.toggleCheck.hide();
            item.li.addClass('jb-tree-nochild');
        }
    }
    function callSubItems(item){
        item.subItemContainer.clear().hide();
        item.toggleCheck.setDisabled(true);
        item.refreshButton.setDisabled(true).setIcon('circle-o-notch fa-spin');
        item.subItemCallBack(setSubItemsFromCallBack.bind(this, item));
    }
    function toggle(item){
        if(item.callBackIsWaiting){
            item.callBackIsWaiting = false;
            item.group.add(item.refreshButton);
            item.refresh();
            return;
        }

        if(item.toggleCheck.getValue())
            item.subItemContainer.show();
        else
            item.subItemContainer.hide();
    }
    function setSubItems(container, items){
        var itemList = this.get('itemList');

        Utils.each(items, function(item){
            var group = Group.new().setSpace(true);
            item.group = group;
            var li = Element.new('li').add(
                group
            );
            item.li = li;

            if(item.subItems && item.subItems.length == 0) delete item.subItems;

            //If there are sub items
            if(item.subItems || item.subItemCallBack){
                //Sub Item Container
                var subItemContainer = Element.new('ul').hide();
                li.add(subItemContainer);
                item.subItemContainer = subItemContainer;

                //Toggle Button
                var toggleCheck = Check.new()
                .setIcon('minus')
                .on('change', toggle.bind(this, item));
                item.toggleCheck = toggleCheck;
                group.add(toggleCheck);
            }else li.addClass('jb-tree-nochild');

            //If sub items from callback
            if(item.subItemCallBack){
                item.callBackIsWaiting = true;

                //Refresh Button
                var refreshButton = Button.new()
                .setIcon('refresh')
                .on('click', callSubItems.bind(this, item));
                item.refreshButton = refreshButton;
                item.refresh = callSubItems.bind(this, item); //To refresh from outside
            }

            //If sub items from parameter
            if(item.subItems){
                item.callBackIsWaiting = false;

                setSubItems.call(this, subItemContainer, item.subItems);
            }

            //If item has id, add it to list
            if(Utils.isSet(item.id))
                itemList[item.id] = item;

            //Item
            group.add(item.element);

            container.add(li);
        }, this);
    }

    return Element.extend({
        'init': function(items){
            this.super();
            this.addClass('jb-tree');

            var content = Element.new('ul');
            this.add(content);
            this.set('content', content);

            if(items)
                this.setItems(items);
        },
        'setItems': function(items){
            this.set('itemList', {});

            var content = this.get('content').clear();
            if(items.length == 0)
                content.hide();
            else
                setSubItems.call(this, content.show(), items);

            return this.ref;
        },
        'showRootLine': function(value){
            if(value)
                this.removeClass('jb-tree-noroot');
            else
                this.addClass('jb-tree-noroot');

            return this.ref;
        },
        'refresh': function(itemId){
            this.get('itemList')[itemId].refresh();
            return this.ref;
        }
    })
})
<|endoftext|>


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

function makeNewToken()
{
    var text = "";
    var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";

    for( var i=0; i < 8; i++ )
        text += possible.charAt(Math.floor(Math.random() * possible.length));

    return text;
}

function tokenConversation(bot, user, message) {
  bot.startPrivateConversation(message, function(err, conversation){
    db.getUser(user.name, function(err, lunchtimeUser){
      if(err || !lunchtimeUser){
        conversation.say("Sorry, I can't seem to find you in the lunchtime system.")
        conversation.stop();
        return;
      }

      conversation.say('Your current token is `' + lunchtimeUser.token + '`');
      conversation.ask('Would you like to change it?', [{
        pattern: bot.utterances.yes,
        callback: function(response, convo) {
          var newToken = makeNewToken();

          db.saveToken(lunchtimeUser, newToken, function(err){
            if(err){
              convo.say("Error: Couldn't save your new token!");
              convo.next();
              return;
            }
            convo.say('Great! Your new token is `' + newToken + '`');
            convo.next();
          })
        }
      },
      {
        default: true,
        callback: function(response, convo){
          convo.say('Never mind then...');
          convo.next();
        }
      }])
    })
  });
};

module.exports = {
  conversation: tokenConversation
}
<commit_msg>Reduce allowed token to just uppercase letters<commit_after>var db = require('../db');

function makeNewToken()
{
    var text = "";
    var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";

    for( var i=0; i < 8; i++ )
        text += possible.charAt(Math.floor(Math.random() * possible.length));

    return text;
}

function tokenConversation(bot, user, message) {
  bot.startPrivateConversation(message, function(err, conversation){
    db.getUser(user.name, function(err, lunchtimeUser){
      if(err || !lunchtimeUser){
        conversation.say("Sorry, I can't seem to find you in the lunchtime system.")
        conversation.stop();
        return;
      }

      conversation.say('Your current token is `' + lunchtimeUser.token + '`');
      conversation.ask('Would you like to change it?', [{
        pattern: bot.utterances.yes,
        callback: function(response, convo) {
          var newToken = makeNewToken();

          db.saveToken(lunchtimeUser, newToken, function(err){
            if(err){
              convo.say("Error: Couldn't save your new token!");
              convo.next();
              return;
            }
            convo.say('Great! Your new token is `' + newToken + '`');
            convo.next();
          })
        }
      },
      {
        default: true,
        callback: function(response, convo){
          convo.say('Never mind then...');
          convo.next();
        }
      }])
    })
  });
};

module.exports = {
  conversation: tokenConversation
}
<|endoftext|>


<commit_before>const request = require('request')
const fs = require("fs")
const path = require('path')
const child_process = require('child_process')
const extract = require('extract-zip')

const encoding = 'utf8'

/*
 Contacts the given hub and checks if an update is needed.
 Uses the given rootDir to check device-id and snapshot-id.
 If the hub says an update is needed, then I'll download the update,
 unzip it, and execute update.sh.
 I'll pass in the environment variable 'app_root' so the script knows where to put stuff.
 And finally, I'll contact the hub again to notify how it went (including output logs).

 See the update protocol for details:
 https://github.com/sveasmart/updater-protocol

 If an update was needed, and the updater managed to download the update script and execute it,
 then it will return something like through the callback:

 {
 deviceId: 'deviceA',
 snapshotId: '22',
 success: true,
 output: 'installing widget version 2.0.3... done!'
 }

 Same thing if the update script was executed but failed.

 @param {string} rootDir - the root dir that contains (or will contain) device-id, snapshot-id, apps, and downloads. For example "/home"
 @param {string} hubUrl - the base url of the updater hub. For example http://hub.updater.eu. No trailing slash.
 @param {bool} simulate - if true, I will only pretend to execute local update scripts.
 */
function checkForUpdate(rootDir, hubUrl, simulate, callback) {
  try {
    //Read the deviceId from file
    const deviceIdFile = path.join(rootDir, "device-id")
    const deviceId = fs.readFileSync(deviceIdFile, encoding).toString()

    //Read the snapshotId from file, or use 0 if not found.
    const snapshotIdFile = path.join(rootDir, "snapshot-id")
    var snapshotId = 0
    if (fs.existsSync(snapshotIdFile)) {
      snapshotId = fs.readFileSync(snapshotIdFile, encoding).toString()
    }

    //Go check if an update is needed
    console.log("Checking for update from " + hubUrl + " ... (deviceId = " + deviceId + ", snapshotId = " + snapshotId + ")")
    askHubToUpdateMe(rootDir, hubUrl, deviceId, snapshotId, simulate, callback)
  } catch (err) {
    console.log("Something went synchronously wrong when calling checkForUpdate. Caught the error, will return it in the callback.", err)
    callback(err)
  }

}

function askHubToUpdateMe(rootDir, hubUrl, deviceId, snapshotId, simulate, callback) {
  //Configure the HTTP request
  const options = {
    uri: hubUrl + "/updateme",
    json: true,
    method: 'GET',
    qs: {
      'deviceId': deviceId,
      'snapshotId': snapshotId
    }
  }

  //Do the http request
  request(options, function(err, response, body) {
    //Parse the response
    if (err) return callback(err)

    if (body.status === "noUpdateNeeded") {
      //Nothing to change. We are done!
      callback(null, body)

    } else if (body.status === "updateNeeded") {

      //The hub says we need to update! Get the URL to the file.
      const newSnapshotId = getMandatoryResponseProperty(body, "snapshotId").toString()
      const downloadUrl = getMandatoryResponseProperty(body, "downloadUrl")
      const downloadType = getOptionalResponseProperty(body, "downloadType", "zip").toLowerCase()
      const configParams = getOptionalResponseProperty(body, "config", {})

      console.log("Will update device " + deviceId + " from snapshot " + snapshotId + " to " + newSnapshotId)
      console.log("Config: " + configParams)

      //Execute the update
      try {
        executeUpdate(rootDir, deviceId, newSnapshotId, downloadUrl, downloadType, configParams, simulate, function(err, scriptOutput) {
          //OK the update script has been executed. Let's see how it worked out.
          if (err) {
            console.log("Update failed! ", err)
            //Oh, the update script failed! Let's tell the hub
            tellHubHowItWorkedOut(hubUrl, deviceId, newSnapshotId, false, err.message, callback)
          } else {
            console.log("Update succeeded!", scriptOutput)
            //The update script succeeded! Let's tell the hub.
            tellHubHowItWorkedOut(hubUrl, deviceId, newSnapshotId, true, scriptOutput, callback)
          }
        })
      } catch (err) {
        console.log("Update failed! Threw an error. ", err)
        //Oh, the update script failed! Let's tell the hub
        tellHubHowItWorkedOut(hubUrl, deviceId, newSnapshotId, false, err.message, callback)

      }

    } else {
      //Invalid response from the hub. Bail out!
      console.log("Unexpected or missing status in response body", body)
      callback(new Error("Unexpected or missing status in response body: " + body))
    }
  })
}

function tellHubHowItWorkedOut(hubUrl, deviceId, snapshotId, success, output, callback) {
  if (success) {
    console.log("Telling the hub that the update to " + snapshotId + " succeeded")
  } else {
    console.log("Telling the hub that the update to " + snapshotId + " failed")
  }
  //This is what we'll send to the hub (and to our callback)
  const result = {
    deviceId: deviceId,
    snapshotId: snapshotId,
    success: "" + success,
    output: output
  }

  const options = {
    uri: hubUrl + "/howitworkedout",
    json: true,
    method: 'POST',
    body: result
  }

  request(options, function(err, result) {
    if (err) return callback(err)
    callback(null, result.body)

  })
}

/*
Downloads the given file, unpacks, and calls update.sh.
If all goes well, the callback is called with the output from the script.
If anything goes wrong, the callback is called with an error.
If simulate == true then I'll just pretend everything worked out, and not run anything.
*/
function executeUpdate(rootDir, deviceId, snapshotId, downloadUrl, downloadType, configParams, simulate, callback) {
  //Create the download folder for this zip file
  const downloadsDir = makeDir(rootDir, 'downloads')
  const snapshotRoot = makeDir(downloadsDir, snapshotId)

  if (downloadType == 'zip') {
    const downloadedFile = path.join(snapshotRoot, 'download.zip')

    //Download the file
    const filePipe = request({uri: downloadUrl}).pipe(fs.createWriteStream(downloadedFile))

    filePipe.on('close', function() {
      //OK now I got my file. Unzip it.
      extract(downloadedFile, {dir: snapshotRoot}, function (err) {
        if (err) return callback(err)

        //OK, we've unzipped the file. Now let's call the update.sh script.
        const updateScript = findUpdateScript(snapshotRoot)

        if (updateScript) {
          executeUpdateScript(rootDir, updateScript, snapshotId, configParams, simulate, callback)
        } else {
          callback(new Error("The zip file didn't contain update.sh!"))
        }
      })
    })

  } else if (downloadType == 'sh') {
    const downloadedFile = path.join(snapshotRoot, 'update.sh')
    const filePipe = request({uri: downloadUrl}).pipe(fs.createWriteStream(downloadedFile))
    filePipe.on('close', function() {
      //OK now I got my file. Execute it.
      executeUpdateScript(rootDir, downloadedFile, snapshotId, configParams, simulate, callback)
    })
  } else if (downloadType == 'js') {
    const downloadedFile = path.join(snapshotRoot, 'update.js')
    const filePipe = request({uri: downloadUrl}).pipe(fs.createWriteStream(downloadedFile))
    filePipe.on('close', function() {
      //OK now I got my file. Execute it.
      executeUpdateScript(rootDir, downloadedFile, snapshotId, configParams, simulate, callback)
    })
  } else {
    callback(new Error("Invalid downloadType: '" + downloadType + "'. Should be 'zip' or 'sh'."))
  }

}

function executeUpdateScript(rootDir, updateScript, snapshotId, configParams, simulate, callback) {
  try {
    child_process.execSync("chmod a+x " + updateScript)

    const configString = JSON.stringify(configParams)

    const cwd = path.resolve(updateScript, "..")
    console.log("Setting cwd to " + cwd)

    const appsRootDir = path.resolve(rootDir, 'apps')
    const args = {}
    const options = {
      'env': {
        'apps_root': appsRootDir,
        'update_root': cwd,
        'config': configString
      }
    }
    //For some reason I have to update my process.env,
    //otherwise the apps_root env variable doesn't reach the update script.
    //Not when using the real (unmocked) child_process at least.
    process.env.apps_root = appsRootDir
    process.env.update_root = cwd
    process.env.config = configString

    let output
    if (simulate) {
      output = "simulate is true, so I'll just \n" +
        "pretend that I executed the " + updateScript + "\n" +
        "and successfully updated to snapshotId " + snapshotId + ".\n" +
        "Here is the environment I received: \n" + JSON.stringify(options.env, null, 2)
    } else if (updateScript.endsWith(".js")) {
      console.log("Executing: node " + updateScript)
      const outputBuffer = child_process.execSync("node " + updateScript, options)
      output = outputBuffer.toString()

    } else {
      console.log("Executing: " + updateScript)
      options.cwd = cwd
      const outputBuffer = child_process.execFileSync(updateScript, args, options)
      output = outputBuffer.toString()
    }

    //Yay, the script succeed!
    //Update the snapshot ID
    const snapshotIdFile = path.join(rootDir, "snapshot-id")
    fs.writeFileSync(snapshotIdFile, snapshotId)

    callback(null, output)

  } catch (err) {
    callback(err)
  }

}

/**
 * Looks for a file named update.sh in this path, or the first subfolder
 */
function findUpdateScript(parent) {
  let updateScript = path.join(parent, "update.sh")
  if (fs.existsSync(updateScript)) {
    return updateScript
  }
  const children = fs.readdirSync(parent)
  for (child of children) {
    const childFullPath = path.join(parent, child)
    if (fs.statSync(childFullPath).isDirectory()) {
      updateScript = path.join(childFullPath, "update.sh")
      if (fs.existsSync(updateScript)) {
        return updateScript
      }
    }
  }
  return null

}

/**
 * Creates the given dir under the given parent.
 * If the dir already exists, does nothing.
 * Returns the full path to the created dir.
 */
function makeDir(parent, dirName) {
  const dirPath = path.join(parent, dirName)
  if (!fs.existsSync(dirPath)) {
    fs.mkdirSync(dirPath)
  }
  return dirPath
}

//Gets the given property, or throws error if it doesn't exist.
function getMandatoryResponseProperty(body, propertyName) {
  if (propertyName in body) {
    return body[propertyName]
  } else {
    throw new Error("Can't find property " + propertyName + " in body: " + JSON.stringify(body))
  }
}

//Gets the given property if provided, otherwise returns the given default
function getOptionalResponseProperty(body, propertyName, defaultValue) {
  if (propertyName in body) {
    return body[propertyName]
  } else {
    return defaultValue
  }
}

exports.checkForUpdate = checkForUpdate

<commit_msg>Fixed a minor bug that only happens during development (for some reason). When calling child_process.execSync, we shouldn't send in an options object with env, because that overrides the real env variables. As a result, some commands (like 'chown') don't work locally because it doesn't have /usr/sbin/chown in PATH.<commit_after>const request = require('request')
const fs = require("fs")
const path = require('path')
const child_process = require('child_process')
const extract = require('extract-zip')

const encoding = 'utf8'

/*
 Contacts the given hub and checks if an update is needed.
 Uses the given rootDir to check device-id and snapshot-id.
 If the hub says an update is needed, then I'll download the update,
 unzip it, and execute update.sh.
 I'll pass in the environment variable 'app_root' so the script knows where to put stuff.
 And finally, I'll contact the hub again to notify how it went (including output logs).

 See the update protocol for details:
 https://github.com/sveasmart/updater-protocol

 If an update was needed, and the updater managed to download the update script and execute it,
 then it will return something like through the callback:

 {
 deviceId: 'deviceA',
 snapshotId: '22',
 success: true,
 output: 'installing widget version 2.0.3... done!'
 }

 Same thing if the update script was executed but failed.

 @param {string} rootDir - the root dir that contains (or will contain) device-id, snapshot-id, apps, and downloads. For example "/home"
 @param {string} hubUrl - the base url of the updater hub. For example http://hub.updater.eu. No trailing slash.
 @param {bool} simulate - if true, I will only pretend to execute local update scripts.
 */
function checkForUpdate(rootDir, hubUrl, simulate, callback) {
  try {
    //Read the deviceId from file
    const deviceIdFile = path.join(rootDir, "device-id")
    const deviceId = fs.readFileSync(deviceIdFile, encoding).toString()

    //Read the snapshotId from file, or use 0 if not found.
    const snapshotIdFile = path.join(rootDir, "snapshot-id")
    var snapshotId = 0
    if (fs.existsSync(snapshotIdFile)) {
      snapshotId = fs.readFileSync(snapshotIdFile, encoding).toString()
    }

    //Go check if an update is needed
    console.log("Checking for update from " + hubUrl + " ... (deviceId = " + deviceId + ", snapshotId = " + snapshotId + ")")
    askHubToUpdateMe(rootDir, hubUrl, deviceId, snapshotId, simulate, callback)
  } catch (err) {
    console.log("Something went synchronously wrong when calling checkForUpdate. Caught the error, will return it in the callback.", err)
    callback(err)
  }

}

function askHubToUpdateMe(rootDir, hubUrl, deviceId, snapshotId, simulate, callback) {
  //Configure the HTTP request
  const options = {
    uri: hubUrl + "/updateme",
    json: true,
    method: 'GET',
    qs: {
      'deviceId': deviceId,
      'snapshotId': snapshotId
    }
  }

  //Do the http request
  request(options, function(err, response, body) {
    //Parse the response
    if (err) return callback(err)

    if (body.status === "noUpdateNeeded") {
      //Nothing to change. We are done!
      callback(null, body)

    } else if (body.status === "updateNeeded") {

      //The hub says we need to update! Get the URL to the file.
      const newSnapshotId = getMandatoryResponseProperty(body, "snapshotId").toString()
      const downloadUrl = getMandatoryResponseProperty(body, "downloadUrl")
      const downloadType = getOptionalResponseProperty(body, "downloadType", "zip").toLowerCase()
      const configParams = getOptionalResponseProperty(body, "config", {})

      console.log("Will update device " + deviceId + " from snapshot " + snapshotId + " to " + newSnapshotId)
      console.log("Config: " + configParams)

      //Execute the update
      try {
        executeUpdate(rootDir, deviceId, newSnapshotId, downloadUrl, downloadType, configParams, simulate, function(err, scriptOutput) {
          //OK the update script has been executed. Let's see how it worked out.
          if (err) {
            console.log("Update failed! ", err)
            //Oh, the update script failed! Let's tell the hub
            tellHubHowItWorkedOut(hubUrl, deviceId, newSnapshotId, false, err.message, callback)
          } else {
            console.log("Update succeeded!", scriptOutput)
            //The update script succeeded! Let's tell the hub.
            tellHubHowItWorkedOut(hubUrl, deviceId, newSnapshotId, true, scriptOutput, callback)
          }
        })
      } catch (err) {
        console.log("Update failed! Threw an error. ", err)
        //Oh, the update script failed! Let's tell the hub
        tellHubHowItWorkedOut(hubUrl, deviceId, newSnapshotId, false, err.message, callback)

      }

    } else {
      //Invalid response from the hub. Bail out!
      console.log("Unexpected or missing status in response body", body)
      callback(new Error("Unexpected or missing status in response body: " + body))
    }
  })
}

function tellHubHowItWorkedOut(hubUrl, deviceId, snapshotId, success, output, callback) {
  if (success) {
    console.log("Telling the hub that the update to " + snapshotId + " succeeded")
  } else {
    console.log("Telling the hub that the update to " + snapshotId + " failed")
  }
  //This is what we'll send to the hub (and to our callback)
  const result = {
    deviceId: deviceId,
    snapshotId: snapshotId,
    success: "" + success,
    output: output
  }

  const options = {
    uri: hubUrl + "/howitworkedout",
    json: true,
    method: 'POST',
    body: result
  }

  request(options, function(err, result) {
    if (err) return callback(err)
    callback(null, result.body)

  })
}

/*
Downloads the given file, unpacks, and calls update.sh.
If all goes well, the callback is called with the output from the script.
If anything goes wrong, the callback is called with an error.
If simulate == true then I'll just pretend everything worked out, and not run anything.
*/
function executeUpdate(rootDir, deviceId, snapshotId, downloadUrl, downloadType, configParams, simulate, callback) {
  //Create the download folder for this zip file
  const downloadsDir = makeDir(rootDir, 'downloads')
  const snapshotRoot = makeDir(downloadsDir, snapshotId)

  if (downloadType == 'zip') {
    const downloadedFile = path.join(snapshotRoot, 'download.zip')

    //Download the file
    const filePipe = request({uri: downloadUrl}).pipe(fs.createWriteStream(downloadedFile))

    filePipe.on('close', function() {
      //OK now I got my file. Unzip it.
      extract(downloadedFile, {dir: snapshotRoot}, function (err) {
        if (err) return callback(err)

        //OK, we've unzipped the file. Now let's call the update.sh script.
        const updateScript = findUpdateScript(snapshotRoot)

        if (updateScript) {
          executeUpdateScript(rootDir, updateScript, snapshotId, configParams, simulate, callback)
        } else {
          callback(new Error("The zip file didn't contain update.sh!"))
        }
      })
    })

  } else if (downloadType == 'sh') {
    const downloadedFile = path.join(snapshotRoot, 'update.sh')
    const filePipe = request({uri: downloadUrl}).pipe(fs.createWriteStream(downloadedFile))
    filePipe.on('close', function() {
      //OK now I got my file. Execute it.
      executeUpdateScript(rootDir, downloadedFile, snapshotId, configParams, simulate, callback)
    })
  } else if (downloadType == 'js') {
    const downloadedFile = path.join(snapshotRoot, 'update.js')
    const filePipe = request({uri: downloadUrl}).pipe(fs.createWriteStream(downloadedFile))
    filePipe.on('close', function() {
      //OK now I got my file. Execute it.
      executeUpdateScript(rootDir, downloadedFile, snapshotId, configParams, simulate, callback)
    })
  } else {
    callback(new Error("Invalid downloadType: '" + downloadType + "'. Should be 'zip' or 'sh'."))
  }

}

function executeUpdateScript(rootDir, updateScript, snapshotId, configParams, simulate, callback) {
  try {
    child_process.execSync("chmod a+x " + updateScript)

    const configString = JSON.stringify(configParams)

    const cwd = path.resolve(updateScript, "..")
    console.log("Setting cwd to " + cwd)

    const appsRootDir = path.resolve(rootDir, 'apps')

    process.env.apps_root = appsRootDir
    process.env.update_root = cwd
    process.env.config = configString

    let output
    if (simulate) {
      output = "simulate is true, so I'll just \n" +
        "pretend that I executed the " + updateScript + "\n" +
        "and successfully updated to snapshotId " + snapshotId + ".\n" +
        "Here is the environment I received: \n" + JSON.stringify(options.env, null, 2)
    } else if (updateScript.endsWith(".js")) {
      console.log("Executing: node " + updateScript)
      const outputBuffer = child_process.execSync("node " + updateScript)
      output = outputBuffer.toString()

    } else {
      console.log("Executing: " + updateScript)
      options.cwd = cwd
      const outputBuffer = child_process.execFileSync(updateScript)
      output = outputBuffer.toString()
    }

    //Yay, the script succeed!
    //Update the snapshot ID
    const snapshotIdFile = path.join(rootDir, "snapshot-id")
    fs.writeFileSync(snapshotIdFile, snapshotId)

    callback(null, output)

  } catch (err) {
    callback(err)
  }

}

/**
 * Looks for a file named update.sh in this path, or the first subfolder
 */
function findUpdateScript(parent) {
  let updateScript = path.join(parent, "update.sh")
  if (fs.existsSync(updateScript)) {
    return updateScript
  }
  const children = fs.readdirSync(parent)
  for (child of children) {
    const childFullPath = path.join(parent, child)
    if (fs.statSync(childFullPath).isDirectory()) {
      updateScript = path.join(childFullPath, "update.sh")
      if (fs.existsSync(updateScript)) {
        return updateScript
      }
    }
  }
  return null

}

/**
 * Creates the given dir under the given parent.
 * If the dir already exists, does nothing.
 * Returns the full path to the created dir.
 */
function makeDir(parent, dirName) {
  const dirPath = path.join(parent, dirName)
  if (!fs.existsSync(dirPath)) {
    fs.mkdirSync(dirPath)
  }
  return dirPath
}

//Gets the given property, or throws error if it doesn't exist.
function getMandatoryResponseProperty(body, propertyName) {
  if (propertyName in body) {
    return body[propertyName]
  } else {
    throw new Error("Can't find property " + propertyName + " in body: " + JSON.stringify(body))
  }
}

//Gets the given property if provided, otherwise returns the given default
function getOptionalResponseProperty(body, propertyName, defaultValue) {
  if (propertyName in body) {
    return body[propertyName]
  } else {
    return defaultValue
  }
}

exports.checkForUpdate = checkForUpdate

<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2012 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * Prototype for original proto-Models.
 * Used during bootstrapping to create the real Model
 * and PropertyModel.
 *
 * TODO: The handling of the various property types (properties,
 *   templates, listeners, etc.) shouldn't be handled here because
 *   it isn't extensible.  The handling should be defined in the
 *   properties property (so meta).
 *
 * TODO: Is still used by a few views in view.js.  Those views
 * should be fixed and then BootstrapModel should be deleted at
 * the end of metamodel.js once the real Model is created.
 **/

this.Constant = null;
this.Method = null;
this.Action = null;
this.Relationship = null;

/**
 * Override a method, making calling the overridden method possible by
 * calling this.SUPER();
 **/
/* Begin Rhino Compatible Version */
// The try/catch prevents JIT-ing of this method, but it is still faster than
// the alternate version
function override(cls, methodName, method) {
  var super_ = cls[methodName];

  var SUPER = function() { return super_.apply(this, arguments); };

  var slowF = function(OLD_SUPER, args) {
    try {
      return method.apply(this, args);
    } finally {
      this.SUPER = OLD_SUPER;
    }
  };
  var f = function() {
    var OLD_SUPER = this.SUPER;
    this.SUPER = SUPER;

    if ( OLD_SUPER ) return slowF.call(this, OLD_SUPER, arguments);

    // Fast-Path when it doesn't matter if we restore SUPER or not
    var ret = method.apply(this, arguments);
    this.SUPER = null;
    return ret;
  };

  f.super_ = super_;

  cls[methodName] = f;
}
/* End Rhino Compatible Version */

/* Begin Non-Rhino Version */
/*
function override(cls, methodName, method) {
  method.super_ = cls[methodName];
  cls[methodName] = method;
}

Object.defineProperty(FObject, 'SUPER', {
  get: function() {
    return arguments.callee.caller.super_.bind(this);
  }
});
*/
/* End Non-Rhino Version */

var BootstrapModel = {

  __proto__: PropertyChangeSupport,

  TYPE: 'BootstrapModel <startup only, error if you see this>',

  buildPrototype: function() { /* Internal use only. */
    function addTraitToModel(traitModel, parentModel) {
      var name = parentModel.name + '_ExtendedWith_' + traitModel.name;
      if ( ! GLOBAL[name] ) {
        var model = traitModel.deepClone();
        model.name = name;
        model.extendsModel = parentModel.name;
        GLOBAL.registerModel(model);
      }

      return GLOBAL[name];
    }

    if ( this.extendsModel && ! GLOBAL[this.extendsModel] ) throw 'Unknown Model in extendsModel: ' + this.extendsModel;

    var extendsModel = this.extendsModel && GLOBAL[this.extendsModel];

    if ( this.traits ) for ( var i = 0 ; i < this.traits.length ; i++ ) {
      var trait = this.traits[i];
      var traitModel = GLOBAL[trait];

      if ( traitModel ) {
        extendsModel = addTraitToModel(traitModel, extendsModel);
      } else {
        console.warn('Missing trait: ', trait, ', in Model: ', this.name);
      }
    }

    var proto  = extendsModel ? extendsModel.getPrototype() : FObject;
    var cls    = Object.create(proto);
    cls.model_ = this;
    cls.name_  = this.name;
    cls.TYPE   = this.name;

    // Install a custom constructor so that Objects are named properly
    // in the JS memory profiler.
    // Doesn't work for Model because of some Bootstrap ordering issues.
    if ( this.name !== 'Model' ) {
      var s = '(function() { var XXX = function() { }; XXX.prototype = this; return function() { return new XXX(); }; })'.replace(/XXX/g, this.name);
      try { cls.create_ = eval(s).call(cls); } catch (e) { }
    }

    /** Add a method to 'cls' and set it's name. **/
    function addMethod(name, method) {
      if ( cls.__proto__[name] ) {
        override(cls, name, method);
      } else {
        cls[name] = method;
      }
    }

    // add sub-models
    //        this.models && this.models.forEach(function(m) {
    //          cls[m.name] = JSONUtil.mapToObj(m);
    //        });
    // Workaround for crbug.com/258552
    this.models && Object_forEach(this.models, function(m) {
      cls.model_[m.name] = cls[m.name] = JSONUtil.mapToObj(X, m, Model);
    });

    if ( extendsModel ) this.requires = this.requires.concat(extendsModel.requires);
    // build requires
    Object_forEach(this.requires, function(i) {
      var imp  = i.split(' as ');
      var m    = imp[0];
      var path = m.split('.');
      var key  = imp[1] || path[path.length-1];

      Object.defineProperty(cls, key, {
        get: function() {
          var x = this.X;
          for ( var j = 0 ; j < path.length ; j++ ) {
            x = x[path[j]];
            if ( ! x ) return;
          }
          return x;
        }
      });
    });

    if ( ! this.properties ) this.properties = [];
    var props = this.properties;

    if ( extendsModel ) this.imports = this.imports.concat(extendsModel.imports);
    // build imports as psedo-properties
    Object_forEach(this.imports, function(i) {
      function findProp(name) {
        for ( var i = 0 ; i < props.length ; i++ ) {
          if ( props[i].name == name ) return i;
        }

        return -1;
      }
      var imp   = i.split(' as ');
      var key   = imp[0];
      var alias = imp[1] || imp[0];

      if ( alias.length && alias.charAt(alias.length-1) == '$' )
        alias = alias.slice(0, alias.length-1);

      var i = findProp(alias);

      if ( i == -1 ) {
        props.push(Property.create({
          name: alias,
          transient: true,
          hidden: true
        }));
      } else {
        var p = props[i];
      }
    });

    if ( extendsModel ) this.exports = this.exports.concat(extendsModel.exports);

    // build properties
    for ( var i = 0 ; i < props.length ; i++ ) {
      var p = props[i];
      if ( extendsModel ) {
        var superProp = extendsModel.getProperty(p.name);
        if ( superProp ) {
          p = superProp.clone().copyFrom(p);
          props[i] = p;
          this[p.name.constantize()] = p;
        }
      }
      cls.defineProperty(p);
    }
    this.propertyMap_ = null;

    // Copy parent Model's Property Contants to this Model.
    if ( extendsModel ) {
      for ( var i = 0 ; i < extendsModel.properties.length ; i++ ) {
        var p = extendsModel.properties[i];
        var name = p.name.constantize();

        if ( ! this[name] ) this[name] = p;
      }
      for ( i = 0 ; i < extendsModel.relationships.length ; i++ ) {
        var r = extendsModel.relationships[i];
        var name = r.name.constantize();

        if ( ! this[name] ) this[name] = r;
      }
    }

    // templates
    this.templates && Object_forEach(this.templates, function(t) {
      addMethod(t.name, TemplateUtil.lazyCompile(t));
    });

    // mix-in mixins
    // Workaround for crbug.com/258522
    // this.mixins && Object_forEach(this.mixins, function(m) { /* TODO: something */ });

    // add action
    if ( this.actions ) {
      for ( var i = 0 ; i < this.actions.length ; i++ ) {
        (function(a) {
          if ( extendsModel ) {
            var superAction = extendsModel.getAction(a.name);
            if ( superAction ) {
              a = superAction.clone().copyFrom(a);
              this.actions[i] = a;
            }
          }
          addMethod(a.name, function(opt_x) { a.callIfEnabled(opt_x || this.X, this); });
        }.bind(this))(this.actions[i]);
      }
    }

    // add constants
    for ( var key in this.constants ) {
      var c = this.constants[key];
      if ( Constant && Constant.isInstance(c) ) {
        // TODO(kgr): only add to Proto when Model cleanup done.
        Object.defineProperty(cls, c.name, {value: c.value});
        Object.defineProperty(this, c.name, {value: c.value});
        // cls[c.name] = this[c.name] = c.value;
      } else {
        debugger;
      }
    }

    // add methods
    for ( var key in this.methods ) {
      var m = this.methods[key];
      if ( Method && Method.isInstance(m) ) {
        addMethod(m.name, m.generateFunction());
      } else {
        addMethod(key, m);
      }
    }

    var self = this;
    // add relationships
    this.relationships && this.relationships.forEach(function(r) {
      // console.log('************** rel: ', r, r.name, r.label, r.relatedModel, r.relatedProperty);

      //           this[r.name.constantize()] = r;
      var name = r.name.constantize();
      if ( ! self[name] ) self[name] = r;
      defineLazyProperty(cls, r.name, function() {
        var m = this.X[r.relatedModel];
        var dao = this.X[m.name + 'DAO'] || this.X[m.plural];
        if ( ! dao ) {
          console.error('Relationship ' + r.name + ' needs ' + (m.name + 'DAO') + ' or ' +
              m.plural + ' in the context, and neither was found.');
        }

        return {
          get: function() { return dao.where(EQ(m.getProperty(r.relatedProperty), this.id)); },
          configurable: true
        };
      });
    });

    // todo: move this somewhere better
    var createListenerTrampoline = function(cls, name, fn, isMerged, isFramed) {
      // bind a trampoline to the function which
      // re-binds a bound version of the function
      // when first called
      console.assert( fn, 'createListenerTrampoline: fn not defined');
      fn.name = name;

      Object.defineProperty(cls, name, {
        get: function () {
          var l = fn.bind(this);
          /*
          if ( ( isFramed || isMerged ) && this.X.isBackground ) {
            console.log('*********************** ', this.model_.name);
          }
          */
          if ( isFramed )
            l = EventService.framed(l, this.X);
          else if ( isMerged ) {
            l = EventService.merged(
              l,
              (isMerged === true) ? undefined : isMerged, this.X);
          }

          Object.defineProperty(this, name, { value: l});

          return l;
        },
        configurable: true
      });
    };

    // add listeners
    if ( Array.isArray(this.listeners) ) {
      for ( var i = 0 ; i < this.listeners.length ; i++ ) {
        var l = this.listeners[i];
        createListenerTrampoline(cls, l.name, l.code, l.isMerged, l.isFramed);
      }
    } else if ( this.listeners )
      //          this.listeners.forEach(function(l, key) {
      // Workaround for crbug.com/258522
      Object_forEach(this.listeners, function(l, key) {
        createListenerTrampoline(cls, key, l);
      });

    // add topics
    //        this.topics && this.topics.forEach(function(t) {
    // Workaround for crbug.com/258522
    this.topics && Object_forEach(this.topics, function(t) {
      // TODO: something
    });

    // copy parent model's properties and actions into this model
    if ( extendsModel ) {
      for ( var i = extendsModel.properties.length-1 ; i >= 0 ; i-- ) {
        var p = extendsModel.properties[i];
        if ( ! ( this.getProperty && this.getPropertyWithoutCache_(p.name) ) )
          this.properties.unshift(p);
      }
      this.propertyMap_ = null;
      this.actions = extendsModel.actions.concat(this.actions);
    }

    // build primary key getter and setter
    if ( this.properties.length > 0 && ! cls.__lookupGetter__('id') ) {
      var primaryKey = this.ids;

      if ( primaryKey.length == 1 ) {
        cls.__defineGetter__('id', function() { return this[primaryKey[0]]; });
        cls.__defineSetter__('id', function(val) { this[primaryKey[0]] = val; });
      } else if (primaryKey.length > 1) {
        cls.__defineGetter__('id', function() {
          return primaryKey.map(function(key) { return this[key]; }); });
        cls.__defineSetter__('id', function(val) {
          primaryKey.map(function(key, i) { this[key] = val[i]; }); });
      }
    }

    return cls;
  },

  getPrototype: function() { /* Returns the definition $$DOC{ref:'Model'} of this instance. */
    return this.prototype_ && this.prototype_.model_ == this ?
      this.prototype_ :
      ( this.prototype_ = this.buildPrototype() );
  },

  create: function(args, opt_X) { return this.getPrototype().create(args, opt_X); },

  isSubModel: function(model) {
    /* Returns true if the given instance extends this $$DOC{ref:'Model'} or a descendant of this. */
    try {
      return model && ( model === this || this.isSubModel(model.getPrototype().__proto__.model_) );
    } catch (x) {
      return false;
    }
  },

  getPropertyWithoutCache_: function(name) { /* Internal use only. */
    for ( var i = 0 ; i < this.properties.length ; i++ ) {
      var p = this.properties[i];

      if ( p.name === name ) return p;
    }

    return null;
  },

  getProperty: function(name) { /* Returns the requested $$DOC{ref:'Property'} of this instance. */
    // NOTE: propertyMap_ is invalidated in a few places
    // when properties[] is updated.
    if ( ! this.propertyMap_ ) {
      if ( ! this.properties.length ) return undefined;

      var m = {};

      for ( var i = 0 ; i < this.properties.length ; i++ ) {
        var prop = this.properties[i];
        m[prop.name] = prop;
      }

      this.propertyMap_ = m;
    }

    return this.propertyMap_[name];
  },

  getAction: function(name) { /* Returns the requested $$DOC{ref:'Action'} of this instance. */
    for ( var i = 0 ; i < this.actions.length ; i++ )
      if ( this.actions[i].name === name ) return this.actions[i];
  },

  hashCode: function() {
    var string = "";
    for ( var key in this.properties ) {
      string += this.properties[key].toString();
    }
    return string.hashCode();
  },

  isInstance: function(obj) { /* Returns true if the given instance extends this $$DOC{ref:'Model'}. */
    return obj && obj.model_ && this.isSubModel(obj.model_);
  },

  toString: function() { return "BootstrapModel(" + this.name + ")"; }
};

/*
 * Ex.
 * OR(EQ(Issue.ASSIGNED_TO, 'kgr'), EQ(Issue.SEVERITY, 'Minor')).toSQL();
 *   -> "(assignedTo = 'kgr' OR severity = 'Minor')"
 * OR(EQ(Issue.ASSIGNED_TO, 'kgr'), EQ(Issue.SEVERITY, 'Minor')).f(Issue.create({assignedTo: 'kgr'}));
 *   -> true
 */
<commit_msg>Added support for extending models in packages.<commit_after>/**
 * @license
 * Copyright 2012 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * Prototype for original proto-Models.
 * Used during bootstrapping to create the real Model
 * and PropertyModel.
 *
 * TODO: The handling of the various property types (properties,
 *   templates, listeners, etc.) shouldn't be handled here because
 *   it isn't extensible.  The handling should be defined in the
 *   properties property (so meta).
 *
 * TODO: Is still used by a few views in view.js.  Those views
 * should be fixed and then BootstrapModel should be deleted at
 * the end of metamodel.js once the real Model is created.
 **/

this.Constant = null;
this.Method = null;
this.Action = null;
this.Relationship = null;

/**
 * Override a method, making calling the overridden method possible by
 * calling this.SUPER();
 **/
/* Begin Rhino Compatible Version */
// The try/catch prevents JIT-ing of this method, but it is still faster than
// the alternate version
function override(cls, methodName, method) {
  var super_ = cls[methodName];

  var SUPER = function() { return super_.apply(this, arguments); };

  var slowF = function(OLD_SUPER, args) {
    try {
      return method.apply(this, args);
    } finally {
      this.SUPER = OLD_SUPER;
    }
  };
  var f = function() {
    var OLD_SUPER = this.SUPER;
    this.SUPER = SUPER;

    if ( OLD_SUPER ) return slowF.call(this, OLD_SUPER, arguments);

    // Fast-Path when it doesn't matter if we restore SUPER or not
    var ret = method.apply(this, arguments);
    this.SUPER = null;
    return ret;
  };

  f.super_ = super_;

  cls[methodName] = f;
}
/* End Rhino Compatible Version */

/* Begin Non-Rhino Version */
/*
function override(cls, methodName, method) {
  method.super_ = cls[methodName];
  cls[methodName] = method;
}

Object.defineProperty(FObject, 'SUPER', {
  get: function() {
    return arguments.callee.caller.super_.bind(this);
  }
});
*/
/* End Non-Rhino Version */

var BootstrapModel = {

  __proto__: PropertyChangeSupport,

  TYPE: 'BootstrapModel <startup only, error if you see this>',

  buildPrototype: function() { /* Internal use only. */
    function addTraitToModel(traitModel, parentModel) {
      var name = parentModel.name + '_ExtendedWith_' + traitModel.name;

      if ( ! FOAM.lookup(name) ) {
        var model = traitModel.deepClone();
        model.name = name;
        model.extendsModel = parentModel.name;
        GLOBAL.registerModel(model);
      }

      return FOAM.lookup(name);
    }

    if ( this.extendsModel && ! FOAM.lookup(this.extendsModel) ) throw 'Unknown Model in extendsModel: ' + this.extendsModel;

    var extendsModel = this.extendsModel && FOAM.lookup(this.extendsModel);

    if ( this.traits ) for ( var i = 0 ; i < this.traits.length ; i++ ) {
      var trait      = this.traits[i];
      var traitModel = FOAM.lookup(trait);

      if ( traitModel ) {
        extendsModel = addTraitToModel(traitModel, extendsModel);
      } else {
        console.warn('Missing trait: ', trait, ', in Model: ', this.name);
      }
    }

    var proto  = extendsModel ? extendsModel.getPrototype() : FObject;
    var cls    = Object.create(proto);
    cls.model_ = this;
    cls.name_  = this.name;
    cls.TYPE   = this.name;

    // Install a custom constructor so that Objects are named properly
    // in the JS memory profiler.
    // Doesn't work for Model because of some Bootstrap ordering issues.
    if ( this.name !== 'Model' ) {
      var s = '(function() { var XXX = function() { }; XXX.prototype = this; return function() { return new XXX(); }; })'.replace(/XXX/g, this.name);
      try { cls.create_ = eval(s).call(cls); } catch (e) { }
    }

    /** Add a method to 'cls' and set it's name. **/
    function addMethod(name, method) {
      if ( cls.__proto__[name] ) {
        override(cls, name, method);
      } else {
        cls[name] = method;
      }
    }

    // add sub-models
    //        this.models && this.models.forEach(function(m) {
    //          cls[m.name] = JSONUtil.mapToObj(m);
    //        });
    // Workaround for crbug.com/258552
    this.models && Object_forEach(this.models, function(m) {
      cls.model_[m.name] = cls[m.name] = JSONUtil.mapToObj(X, m, Model);
    });

    if ( extendsModel ) this.requires = this.requires.concat(extendsModel.requires);
    // build requires
    Object_forEach(this.requires, function(i) {
      var imp  = i.split(' as ');
      var m    = imp[0];
      var path = m.split('.');
      var key  = imp[1] || path[path.length-1];

      Object.defineProperty(cls, key, {
        get: function() {
          var x = this.X;
          for ( var j = 0 ; j < path.length ; j++ ) {
            x = x[path[j]];
            if ( ! x ) return;
          }
          return x;
        }
      });
    });

    if ( ! this.properties ) this.properties = [];
    var props = this.properties;

    if ( extendsModel ) this.imports = this.imports.concat(extendsModel.imports);
    // build imports as psedo-properties
    Object_forEach(this.imports, function(i) {
      function findProp(name) {
        for ( var i = 0 ; i < props.length ; i++ ) {
          if ( props[i].name == name ) return i;
        }

        return -1;
      }
      var imp   = i.split(' as ');
      var key   = imp[0];
      var alias = imp[1] || imp[0];

      if ( alias.length && alias.charAt(alias.length-1) == '$' )
        alias = alias.slice(0, alias.length-1);

      var i = findProp(alias);

      if ( i == -1 ) {
        props.push(Property.create({
          name: alias,
          transient: true,
          hidden: true
        }));
      } else {
        var p = props[i];
      }
    });

    if ( extendsModel ) this.exports = this.exports.concat(extendsModel.exports);

    // build properties
    for ( var i = 0 ; i < props.length ; i++ ) {
      var p = props[i];
      if ( extendsModel ) {
        var superProp = extendsModel.getProperty(p.name);
        if ( superProp ) {
          p = superProp.clone().copyFrom(p);
          props[i] = p;
          this[p.name.constantize()] = p;
        }
      }
      cls.defineProperty(p);
    }
    this.propertyMap_ = null;

    // Copy parent Model's Property Contants to this Model.
    if ( extendsModel ) {
      for ( var i = 0 ; i < extendsModel.properties.length ; i++ ) {
        var p = extendsModel.properties[i];
        var name = p.name.constantize();

        if ( ! this[name] ) this[name] = p;
      }
      for ( i = 0 ; i < extendsModel.relationships.length ; i++ ) {
        var r = extendsModel.relationships[i];
        var name = r.name.constantize();

        if ( ! this[name] ) this[name] = r;
      }
    }

    // templates
    this.templates && Object_forEach(this.templates, function(t) {
      addMethod(t.name, TemplateUtil.lazyCompile(t));
    });

    // mix-in mixins
    // Workaround for crbug.com/258522
    // this.mixins && Object_forEach(this.mixins, function(m) { /* TODO: something */ });

    // add action
    if ( this.actions ) {
      for ( var i = 0 ; i < this.actions.length ; i++ ) {
        (function(a) {
          if ( extendsModel ) {
            var superAction = extendsModel.getAction(a.name);
            if ( superAction ) {
              a = superAction.clone().copyFrom(a);
              this.actions[i] = a;
            }
          }
          addMethod(a.name, function(opt_x) { a.callIfEnabled(opt_x || this.X, this); });
        }.bind(this))(this.actions[i]);
      }
    }

    // add constants
    for ( var key in this.constants ) {
      var c = this.constants[key];
      if ( Constant && Constant.isInstance(c) ) {
        // TODO(kgr): only add to Proto when Model cleanup done.
        Object.defineProperty(cls, c.name, {value: c.value});
        Object.defineProperty(this, c.name, {value: c.value});
        // cls[c.name] = this[c.name] = c.value;
      } else {
        debugger;
      }
    }

    // add methods
    for ( var key in this.methods ) {
      var m = this.methods[key];
      if ( Method && Method.isInstance(m) ) {
        addMethod(m.name, m.generateFunction());
      } else {
        addMethod(key, m);
      }
    }

    var self = this;
    // add relationships
    this.relationships && this.relationships.forEach(function(r) {
      // console.log('************** rel: ', r, r.name, r.label, r.relatedModel, r.relatedProperty);

      //           this[r.name.constantize()] = r;
      var name = r.name.constantize();
      if ( ! self[name] ) self[name] = r;
      defineLazyProperty(cls, r.name, function() {
        var m = this.X[r.relatedModel];
        var dao = this.X[m.name + 'DAO'] || this.X[m.plural];
        if ( ! dao ) {
          console.error('Relationship ' + r.name + ' needs ' + (m.name + 'DAO') + ' or ' +
              m.plural + ' in the context, and neither was found.');
        }

        return {
          get: function() { return dao.where(EQ(m.getProperty(r.relatedProperty), this.id)); },
          configurable: true
        };
      });
    });

    // todo: move this somewhere better
    var createListenerTrampoline = function(cls, name, fn, isMerged, isFramed) {
      // bind a trampoline to the function which
      // re-binds a bound version of the function
      // when first called
      console.assert( fn, 'createListenerTrampoline: fn not defined');
      fn.name = name;

      Object.defineProperty(cls, name, {
        get: function () {
          var l = fn.bind(this);
          /*
          if ( ( isFramed || isMerged ) && this.X.isBackground ) {
            console.log('*********************** ', this.model_.name);
          }
          */
          if ( isFramed )
            l = EventService.framed(l, this.X);
          else if ( isMerged ) {
            l = EventService.merged(
              l,
              (isMerged === true) ? undefined : isMerged, this.X);
          }

          Object.defineProperty(this, name, { value: l});

          return l;
        },
        configurable: true
      });
    };

    // add listeners
    if ( Array.isArray(this.listeners) ) {
      for ( var i = 0 ; i < this.listeners.length ; i++ ) {
        var l = this.listeners[i];
        createListenerTrampoline(cls, l.name, l.code, l.isMerged, l.isFramed);
      }
    } else if ( this.listeners )
      //          this.listeners.forEach(function(l, key) {
      // Workaround for crbug.com/258522
      Object_forEach(this.listeners, function(l, key) {
        createListenerTrampoline(cls, key, l);
      });

    // add topics
    //        this.topics && this.topics.forEach(function(t) {
    // Workaround for crbug.com/258522
    this.topics && Object_forEach(this.topics, function(t) {
      // TODO: something
    });

    // copy parent model's properties and actions into this model
    if ( extendsModel ) {
      for ( var i = extendsModel.properties.length-1 ; i >= 0 ; i-- ) {
        var p = extendsModel.properties[i];
        if ( ! ( this.getProperty && this.getPropertyWithoutCache_(p.name) ) )
          this.properties.unshift(p);
      }
      this.propertyMap_ = null;
      this.actions = extendsModel.actions.concat(this.actions);
    }

    // build primary key getter and setter
    if ( this.properties.length > 0 && ! cls.__lookupGetter__('id') ) {
      var primaryKey = this.ids;

      if ( primaryKey.length == 1 ) {
        cls.__defineGetter__('id', function() { return this[primaryKey[0]]; });
        cls.__defineSetter__('id', function(val) { this[primaryKey[0]] = val; });
      } else if (primaryKey.length > 1) {
        cls.__defineGetter__('id', function() {
          return primaryKey.map(function(key) { return this[key]; }); });
        cls.__defineSetter__('id', function(val) {
          primaryKey.map(function(key, i) { this[key] = val[i]; }); });
      }
    }

    return cls;
  },

  getPrototype: function() { /* Returns the definition $$DOC{ref:'Model'} of this instance. */
    return this.prototype_ && this.prototype_.model_ == this ?
      this.prototype_ :
      ( this.prototype_ = this.buildPrototype() );
  },

  create: function(args, opt_X) { return this.getPrototype().create(args, opt_X); },

  isSubModel: function(model) {
    /* Returns true if the given instance extends this $$DOC{ref:'Model'} or a descendant of this. */
    try {
      return model && ( model === this || this.isSubModel(model.getPrototype().__proto__.model_) );
    } catch (x) {
      return false;
    }
  },

  getPropertyWithoutCache_: function(name) { /* Internal use only. */
    for ( var i = 0 ; i < this.properties.length ; i++ ) {
      var p = this.properties[i];

      if ( p.name === name ) return p;
    }

    return null;
  },

  getProperty: function(name) { /* Returns the requested $$DOC{ref:'Property'} of this instance. */
    // NOTE: propertyMap_ is invalidated in a few places
    // when properties[] is updated.
    if ( ! this.propertyMap_ ) {
      if ( ! this.properties.length ) return undefined;

      var m = {};

      for ( var i = 0 ; i < this.properties.length ; i++ ) {
        var prop = this.properties[i];
        m[prop.name] = prop;
      }

      this.propertyMap_ = m;
    }

    return this.propertyMap_[name];
  },

  getAction: function(name) { /* Returns the requested $$DOC{ref:'Action'} of this instance. */
    for ( var i = 0 ; i < this.actions.length ; i++ )
      if ( this.actions[i].name === name ) return this.actions[i];
  },

  hashCode: function() {
    var string = "";
    for ( var key in this.properties ) {
      string += this.properties[key].toString();
    }
    return string.hashCode();
  },

  isInstance: function(obj) { /* Returns true if the given instance extends this $$DOC{ref:'Model'}. */
    return obj && obj.model_ && this.isSubModel(obj.model_);
  },

  toString: function() { return "BootstrapModel(" + this.name + ")"; }
};

/*
 * Ex.
 * OR(EQ(Issue.ASSIGNED_TO, 'kgr'), EQ(Issue.SEVERITY, 'Minor')).toSQL();
 *   -> "(assignedTo = 'kgr' OR severity = 'Minor')"
 * OR(EQ(Issue.ASSIGNED_TO, 'kgr'), EQ(Issue.SEVERITY, 'Minor')).f(Issue.create({assignedTo: 'kgr'}));
 *   -> true
 */
<|endoftext|>


<commit_before>import Npm from 'silent-npm-registry-client';
import boxen from 'boxen';
import chalk from 'chalk';
import pkg from '../package.json';

export default function() {
  return new Promise(function(resolve) {
    const params = {
      timeout: 1000,
      package: pkg.name,
      auth: {}
    };

    const npm = new Npm();
    const uri = 'https://registry.npmjs.org/npm';
    npm.distTags.fetch(uri, params, function(err, res) {
      if (err) {
        resolve();
        return;
      }

      const npmVersion = res.latest;
      const local = pkg.version.split('.').map(n => Number(n));
      const remote = npmVersion.split('.').map(n => Number(n));

      const available = remote[0] > local[0] ||
        remote[0] === local[0] && remote[1] > local[1] ||
        remote[1] === local[1] && remote[2] > local[2];

      if (available) {
        let text = `update available ${pkg.version} => ${npmVersion}`;
        text += `\nTo update, run ${chalk.green('npm i -g mup')}`;
        console.log(
          boxen(text, {
            padding: 1,
            margin: 1,
            align: 'center',
            borderColor: 'yellow'
          })
        );
      }

      resolve();
    });
  });
}
<commit_msg>Add support for beta versions to update notifier<commit_after>import Npm from 'silent-npm-registry-client';
import boxen from 'boxen';
import chalk from 'chalk';
import pkg from '../package.json';

export default function() {
  return new Promise(function(resolve) {
    const params = {
      timeout: 1000,
      package: pkg.name,
      auth: {}
    };

    const npm = new Npm();
    const uri = 'https://registry.npmjs.org/npm';
    npm.distTags.fetch(uri, params, function(err, res) {
      if (err) {
        resolve();
        return;
      }

      const npmVersion = '1.2.7' || res.latest;
      const local = pkg.version.split('.').slice(0, 3).map(n => Number(n.split('-')[0]));
      const remote = npmVersion.split('.').map(n => Number(n.split('-')[0]));

      const beta = pkg.version.split('.')[2].split('-').length > 1;

      let available = remote[0] > local[0] ||
        remote[0] === local[0] && remote[1] > local[1] ||
        remote[1] === local[1] && remote[2] > local[2];

      if (beta && !available) {
        available = remote[0] === local[0] && remote[1] === local[1] && remote[2] === local[2];
      }

      if (available) {
        let text = `update available ${pkg.version} => ${npmVersion}`;
        text += `\nTo update, run ${chalk.green('npm i -g mup')}`;
        console.log(
          boxen(text, {
            padding: 1,
            margin: 1,
            align: 'center',
            borderColor: 'yellow'
          })
        );
      }

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


<commit_before>/**
 * @license
 * Visual Blocks Editor
 *
 * Copyright 2012 Google Inc.
 * https://developers.google.com/blockly/
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * @fileoverview Variable input field.
 * @author fraser@google.com (Neil Fraser)
 */
'use strict';

goog.provide('Blockly.FieldVariable');

goog.require('Blockly.FieldDropdown');
goog.require('Blockly.Msg');
goog.require('Blockly.VariableModel');
goog.require('Blockly.Variables');
goog.require('goog.asserts');
goog.require('goog.string');

/**
 * Class for a variable's dropdown field.
 * @param {?string} varname The default name for the variable.  If null,
 *     a unique variable name will be generated.
 * @param {Function=} opt_validator A function that is executed when a new
 *     option is selected.  Its sole argument is the new option value.
 * @param {Array.<string>} opt_variableTypes A list of the types of variables to
 *     include in the dropdown.
 * @extends {Blockly.FieldDropdown}
 * @constructor
 */
Blockly.FieldVariable = function(varname, opt_validator, opt_variableTypes) {
  // The FieldDropdown constructor would call setValue, which might create a
  // spurious variable.  Just do the relevant parts of the constructor.
  this.menuGenerator_ = Blockly.FieldVariable.dropdownCreate;
  this.size_ = new goog.math.Size(Blockly.BlockSvg.FIELD_WIDTH,
      Blockly.BlockSvg.FIELD_HEIGHT);
  this.setValidator(opt_validator);
  // TODO (blockly #1499): Add opt_default_type to match default value.
  // If not set, ''.
  this.defaultVariableName = (varname || '');
  var hasSingleVarType = opt_variableTypes && (opt_variableTypes.length == 1);
  this.defaultType_ = hasSingleVarType ? opt_variableTypes[0] : '';
  this.variableTypes = opt_variableTypes;

  this.value_ = null;
};
goog.inherits(Blockly.FieldVariable, Blockly.FieldDropdown);

/**
 * Initialize everything needed to render this field.  This includes making sure
 * that the field's value is valid.
 * @public
 */
Blockly.FieldVariable.prototype.init = function() {
  if (this.fieldGroup_) {
    // Dropdown has already been initialized once.
    return;
  }
  Blockly.FieldVariable.superClass_.init.call(this);

  // TODO (blockly #1010): Change from init/initModel to initView/initModel
  this.initModel();
};

/**
 * Initialize the model for this field if it has not already been initialized.
 * If the value has not been set to a variable by the first render, we make up a
 * variable rather than let the value be invalid.
 * @package
 */
Blockly.FieldVariable.prototype.initModel = function() {
  if (this.variable_) {
    return; // Initialization already happened.
  }
  this.workspace_ = this.sourceBlock_.workspace;
  // Initialize this field if it's in a broadcast block in the flyout
  var variable = this.initFlyoutBroadcast_(this.workspace_);
  if (!variable) {
    var variable = Blockly.Variables.getOrCreateVariablePackage(
        this.workspace_, null, this.defaultVariableName, this.defaultType_);
  }
  // Don't fire a change event for this setValue.  It would have null as the
  // old value, which is not valid.
  Blockly.Events.disable();
  try {
    this.setValue(variable.getId());
  } finally {
    Blockly.Events.enable();
  }
};

/**
 * Initialize broadcast blocks in the flyout.
 * Implicit deletion of broadcast messages from the scratch vm may cause
 * broadcast blocks in the flyout to change which variable they display as the
 * selected option when the workspace is refreshed.
 * Re-sort the broadcast messages by name, and set the field value to the id
 * of the variable that comes first in sorted order.
 * @param {!Blockly.Workspace} workspace The flyout workspace containing the
 * broadcast block.
 * @return {string} The variable of type 'broadcast_msg' that comes
 * first in sorted order.
 */
Blockly.FieldVariable.prototype.initFlyoutBroadcast_ = function(workspace) {
  // Using shorter name for this constant
  var broadcastMsgType = Blockly.BROADCAST_MESSAGE_VARIABLE_TYPE;
  var broadcastVars = workspace.getVariablesOfType(broadcastMsgType);
  if(workspace.isFlyout && this.defaultType_ == broadcastMsgType &&
      broadcastVars.length != 0) {
    broadcastVars.sort(Blockly.VariableModel.compareByName);
    return broadcastVars[0];
  }
};

/**
 * Dispose of this field.
 * @public
 */
Blockly.FieldVariable.dispose = function() {
  Blockly.FieldVariable.superClass_.dispose.call(this);
  this.workspace_ = null;
  this.variableMap_ = null;
};

/**
 * Attach this field to a block.
 * @param {!Blockly.Block} block The block containing this field.
 */
Blockly.FieldVariable.prototype.setSourceBlock = function(block) {
  goog.asserts.assert(!block.isShadow(),
      'Variable fields are not allowed to exist on shadow blocks.');
  Blockly.FieldVariable.superClass_.setSourceBlock.call(this, block);
};

/**
 * Get the variable's ID.
 * @return {string} Current variable's ID.
 */
Blockly.FieldVariable.prototype.getValue = function() {
  return this.variable_ ? this.variable_.getId() : null;
};

/**
 * Get the text from this field, which is the selected variable's name.
 * @return {string} The selected variable's name, or the empty string if no
 *     variable is selected.
 */
Blockly.FieldVariable.prototype.getText = function() {
  return this.variable_ ? this.variable_.name : '';
};

/**
 * Get the variable model for the selected variable.
 * Not guaranteed to be in the variable map on the workspace (e.g. if accessed
 * after the variable has been deleted).
 * @return {?Blockly.VariableModel} the selected variable, or null if none was
 *     selected.
 * @package
 */
Blockly.FieldVariable.prototype.getVariable = function() {
  return this.variable_;
};

/**
 * Set the variable ID.
 * @param {string} id New variable ID, which must reference an existing
 *     variable.
 */
Blockly.FieldVariable.prototype.setValue = function(id) {
  var workspace = this.sourceBlock_.workspace;
  var variable = Blockly.Variables.getVariable(workspace, id);

  if (!variable) {
    throw new Error('Variable id doesn\'t point to a real variable!  ID was ' +
        id);
  }
  // Type checks!
  var type = variable.type;
  if (!this.typeIsAllowed_(type)) {
    throw new Error('Variable type doesn\'t match this field!  Type was ' +
        type);
  }
  if (this.sourceBlock_ && Blockly.Events.isEnabled()) {
    var oldValue = this.variable_ ? this.variable_.getId() : null;
    Blockly.Events.fire(new Blockly.Events.BlockChange(
        this.sourceBlock_, 'field', this.name, oldValue, id));
  }
  this.variable_ = variable;
  this.value_ = id;
  this.setText(variable.name);
};

/**
 * Check whether the given variable type is allowed on this field.
 * @param {string} type The type to check.
 * @return {boolean} True if the type is in the list of allowed types.
 * @private
 */
Blockly.FieldVariable.prototype.typeIsAllowed_ = function(type) {
  var typeList = this.getVariableTypes_();
  if (!typeList) {
    return true; // If it's null, all types are valid.
  }
  for (var i = 0; i < typeList.length; i++) {
    if (type == typeList[i]) {
      return true;
    }
  }
  return false;
};

/**
 * Return a list of variable types to include in the dropdown.
 * @return {!Array.<string>} Array of variable types.
 * @throws {Error} if variableTypes is an empty array.
 * @private
 */
Blockly.FieldVariable.prototype.getVariableTypes_ = function() {
  // TODO (#1513): Try to avoid calling this every time the field is edited.
  var variableTypes = this.variableTypes;
  if (variableTypes === null) {
    // If variableTypes is null, return all variable types.
    if (this.sourceBlock_) {
      var workspace = this.sourceBlock_.workspace;
      return workspace.getVariableTypes();
    }
  }
  variableTypes = variableTypes || [''];
  if (variableTypes.length == 0) {
    // Throw an error if variableTypes is an empty list.
    var name = this.getText();
    throw new Error('\'variableTypes\' of field variable ' +
      name + ' was an empty list');
  }
  return variableTypes;
};

/**
 * Return a sorted list of variable names for variable dropdown menus.
 * Include a special option at the end for creating a new variable name.
 * @return {!Array.<string>} Array of variable names.
 * @this {Blockly.FieldVariable}
 */
Blockly.FieldVariable.dropdownCreate = function() {
  if (!this.variable_) {
    throw new Error('Tried to call dropdownCreate on a variable field with no' +
        ' variable selected.');
  }
  var variableModelList = [];
  var name = this.getText();
  var workspace = null;
  if (this.sourceBlock_) {
    workspace = this.sourceBlock_.workspace;
  }
  if (workspace) {
    var variableTypes = this.getVariableTypes_();
    var variableModelList = [];
    // Get a copy of the list, so that adding rename and new variable options
    // doesn't modify the workspace's list.
    for (var i = 0; i < variableTypes.length; i++) {
      var variableType = variableTypes[i];
      var variables = workspace.getVariablesOfType(variableType);
      variableModelList = variableModelList.concat(variables);

      var potentialVarMap = workspace.getPotentialVariableMap();
      if (potentialVarMap) {
        var potentialVars = potentialVarMap.getVariablesOfType(variableType);
        variableModelList = variableModelList.concat(potentialVars);
      }
    }
  }
  variableModelList.sort(Blockly.VariableModel.compareByName);

  var options = [];
  for (var i = 0; i < variableModelList.length; i++) {
    // Set the uuid as the internal representation of the variable.
    options[i] = [variableModelList[i].name, variableModelList[i].getId()];
  }
  if (this.defaultType_ == Blockly.BROADCAST_MESSAGE_VARIABLE_TYPE) {
    options.push([Blockly.Msg.NEW_BROADCAST_MESSAGE, Blockly.NEW_BROADCAST_MESSAGE_ID]);
  } else {
    options.push([Blockly.Msg.RENAME_VARIABLE, Blockly.RENAME_VARIABLE_ID]);
    if (Blockly.Msg.DELETE_VARIABLE) {
      options.push([Blockly.Msg.DELETE_VARIABLE.replace('%1', name),
          Blockly.DELETE_VARIABLE_ID]);
    }
  }

  return options;
};

/**
 * Handle the selection of an item in the variable dropdown menu.
 * Special case the 'Rename variable...', 'Delete variable...',
 * and 'New message...' options.
 * In the rename case, prompt the user for a new name.
 * @param {!goog.ui.Menu} menu The Menu component clicked.
 * @param {!goog.ui.MenuItem} menuItem The MenuItem selected within menu.
 */
Blockly.FieldVariable.prototype.onItemSelected = function(menu, menuItem) {
  var id = menuItem.getValue();
  if (this.sourceBlock_ && this.sourceBlock_.workspace) {
    var workspace = this.sourceBlock_.workspace;
    if (id == Blockly.RENAME_VARIABLE_ID) {
      // Rename variable.
      Blockly.Variables.renameVariable(workspace, this.variable_);
      return;
    } else if (id == Blockly.DELETE_VARIABLE_ID) {
      // Delete variable.
      workspace.deleteVariableById(this.variable_.getId());
      return;
    } else if (id == Blockly.NEW_BROADCAST_MESSAGE_ID) {
      var thisField = this;
      var updateField = function(varId) {
        if (varId) {
          thisField.setValue(varId);
        }
      };
      Blockly.Variables.createVariable(workspace, updateField,
          Blockly.BROADCAST_MESSAGE_VARIABLE_TYPE);
      return;
    }

    // TODO (blockly #1529): Call any validation function, and allow it to override.
  }
  this.setValue(id);
};
<commit_msg>Move 'new message' to top of broadcast dropdowns<commit_after>/**
 * @license
 * Visual Blocks Editor
 *
 * Copyright 2012 Google Inc.
 * https://developers.google.com/blockly/
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * @fileoverview Variable input field.
 * @author fraser@google.com (Neil Fraser)
 */
'use strict';

goog.provide('Blockly.FieldVariable');

goog.require('Blockly.FieldDropdown');
goog.require('Blockly.Msg');
goog.require('Blockly.VariableModel');
goog.require('Blockly.Variables');
goog.require('goog.asserts');
goog.require('goog.string');

/**
 * Class for a variable's dropdown field.
 * @param {?string} varname The default name for the variable.  If null,
 *     a unique variable name will be generated.
 * @param {Function=} opt_validator A function that is executed when a new
 *     option is selected.  Its sole argument is the new option value.
 * @param {Array.<string>} opt_variableTypes A list of the types of variables to
 *     include in the dropdown.
 * @extends {Blockly.FieldDropdown}
 * @constructor
 */
Blockly.FieldVariable = function(varname, opt_validator, opt_variableTypes) {
  // The FieldDropdown constructor would call setValue, which might create a
  // spurious variable.  Just do the relevant parts of the constructor.
  this.menuGenerator_ = Blockly.FieldVariable.dropdownCreate;
  this.size_ = new goog.math.Size(Blockly.BlockSvg.FIELD_WIDTH,
      Blockly.BlockSvg.FIELD_HEIGHT);
  this.setValidator(opt_validator);
  // TODO (blockly #1499): Add opt_default_type to match default value.
  // If not set, ''.
  this.defaultVariableName = (varname || '');
  var hasSingleVarType = opt_variableTypes && (opt_variableTypes.length == 1);
  this.defaultType_ = hasSingleVarType ? opt_variableTypes[0] : '';
  this.variableTypes = opt_variableTypes;

  this.value_ = null;
};
goog.inherits(Blockly.FieldVariable, Blockly.FieldDropdown);

/**
 * Initialize everything needed to render this field.  This includes making sure
 * that the field's value is valid.
 * @public
 */
Blockly.FieldVariable.prototype.init = function() {
  if (this.fieldGroup_) {
    // Dropdown has already been initialized once.
    return;
  }
  Blockly.FieldVariable.superClass_.init.call(this);

  // TODO (blockly #1010): Change from init/initModel to initView/initModel
  this.initModel();
};

/**
 * Initialize the model for this field if it has not already been initialized.
 * If the value has not been set to a variable by the first render, we make up a
 * variable rather than let the value be invalid.
 * @package
 */
Blockly.FieldVariable.prototype.initModel = function() {
  if (this.variable_) {
    return; // Initialization already happened.
  }
  this.workspace_ = this.sourceBlock_.workspace;
  // Initialize this field if it's in a broadcast block in the flyout
  var variable = this.initFlyoutBroadcast_(this.workspace_);
  if (!variable) {
    var variable = Blockly.Variables.getOrCreateVariablePackage(
        this.workspace_, null, this.defaultVariableName, this.defaultType_);
  }
  // Don't fire a change event for this setValue.  It would have null as the
  // old value, which is not valid.
  Blockly.Events.disable();
  try {
    this.setValue(variable.getId());
  } finally {
    Blockly.Events.enable();
  }
};

/**
 * Initialize broadcast blocks in the flyout.
 * Implicit deletion of broadcast messages from the scratch vm may cause
 * broadcast blocks in the flyout to change which variable they display as the
 * selected option when the workspace is refreshed.
 * Re-sort the broadcast messages by name, and set the field value to the id
 * of the variable that comes first in sorted order.
 * @param {!Blockly.Workspace} workspace The flyout workspace containing the
 * broadcast block.
 * @return {string} The variable of type 'broadcast_msg' that comes
 * first in sorted order.
 */
Blockly.FieldVariable.prototype.initFlyoutBroadcast_ = function(workspace) {
  // Using shorter name for this constant
  var broadcastMsgType = Blockly.BROADCAST_MESSAGE_VARIABLE_TYPE;
  var broadcastVars = workspace.getVariablesOfType(broadcastMsgType);
  if(workspace.isFlyout && this.defaultType_ == broadcastMsgType &&
      broadcastVars.length != 0) {
    broadcastVars.sort(Blockly.VariableModel.compareByName);
    return broadcastVars[0];
  }
};

/**
 * Dispose of this field.
 * @public
 */
Blockly.FieldVariable.dispose = function() {
  Blockly.FieldVariable.superClass_.dispose.call(this);
  this.workspace_ = null;
  this.variableMap_ = null;
};

/**
 * Attach this field to a block.
 * @param {!Blockly.Block} block The block containing this field.
 */
Blockly.FieldVariable.prototype.setSourceBlock = function(block) {
  goog.asserts.assert(!block.isShadow(),
      'Variable fields are not allowed to exist on shadow blocks.');
  Blockly.FieldVariable.superClass_.setSourceBlock.call(this, block);
};

/**
 * Get the variable's ID.
 * @return {string} Current variable's ID.
 */
Blockly.FieldVariable.prototype.getValue = function() {
  return this.variable_ ? this.variable_.getId() : null;
};

/**
 * Get the text from this field, which is the selected variable's name.
 * @return {string} The selected variable's name, or the empty string if no
 *     variable is selected.
 */
Blockly.FieldVariable.prototype.getText = function() {
  return this.variable_ ? this.variable_.name : '';
};

/**
 * Get the variable model for the selected variable.
 * Not guaranteed to be in the variable map on the workspace (e.g. if accessed
 * after the variable has been deleted).
 * @return {?Blockly.VariableModel} the selected variable, or null if none was
 *     selected.
 * @package
 */
Blockly.FieldVariable.prototype.getVariable = function() {
  return this.variable_;
};

/**
 * Set the variable ID.
 * @param {string} id New variable ID, which must reference an existing
 *     variable.
 */
Blockly.FieldVariable.prototype.setValue = function(id) {
  var workspace = this.sourceBlock_.workspace;
  var variable = Blockly.Variables.getVariable(workspace, id);

  if (!variable) {
    throw new Error('Variable id doesn\'t point to a real variable!  ID was ' +
        id);
  }
  // Type checks!
  var type = variable.type;
  if (!this.typeIsAllowed_(type)) {
    throw new Error('Variable type doesn\'t match this field!  Type was ' +
        type);
  }
  if (this.sourceBlock_ && Blockly.Events.isEnabled()) {
    var oldValue = this.variable_ ? this.variable_.getId() : null;
    Blockly.Events.fire(new Blockly.Events.BlockChange(
        this.sourceBlock_, 'field', this.name, oldValue, id));
  }
  this.variable_ = variable;
  this.value_ = id;
  this.setText(variable.name);
};

/**
 * Check whether the given variable type is allowed on this field.
 * @param {string} type The type to check.
 * @return {boolean} True if the type is in the list of allowed types.
 * @private
 */
Blockly.FieldVariable.prototype.typeIsAllowed_ = function(type) {
  var typeList = this.getVariableTypes_();
  if (!typeList) {
    return true; // If it's null, all types are valid.
  }
  for (var i = 0; i < typeList.length; i++) {
    if (type == typeList[i]) {
      return true;
    }
  }
  return false;
};

/**
 * Return a list of variable types to include in the dropdown.
 * @return {!Array.<string>} Array of variable types.
 * @throws {Error} if variableTypes is an empty array.
 * @private
 */
Blockly.FieldVariable.prototype.getVariableTypes_ = function() {
  // TODO (#1513): Try to avoid calling this every time the field is edited.
  var variableTypes = this.variableTypes;
  if (variableTypes === null) {
    // If variableTypes is null, return all variable types.
    if (this.sourceBlock_) {
      var workspace = this.sourceBlock_.workspace;
      return workspace.getVariableTypes();
    }
  }
  variableTypes = variableTypes || [''];
  if (variableTypes.length == 0) {
    // Throw an error if variableTypes is an empty list.
    var name = this.getText();
    throw new Error('\'variableTypes\' of field variable ' +
      name + ' was an empty list');
  }
  return variableTypes;
};

/**
 * Return a sorted list of variable names for variable dropdown menus.
 * Include a special option at the end for creating a new variable name.
 * @return {!Array.<string>} Array of variable names.
 * @this {Blockly.FieldVariable}
 */
Blockly.FieldVariable.dropdownCreate = function() {
  if (!this.variable_) {
    throw new Error('Tried to call dropdownCreate on a variable field with no' +
        ' variable selected.');
  }
  var variableModelList = [];
  var name = this.getText();
  var workspace = null;
  if (this.sourceBlock_) {
    workspace = this.sourceBlock_.workspace;
  }
  if (workspace) {
    var variableTypes = this.getVariableTypes_();
    var variableModelList = [];
    // Get a copy of the list, so that adding rename and new variable options
    // doesn't modify the workspace's list.
    for (var i = 0; i < variableTypes.length; i++) {
      var variableType = variableTypes[i];
      var variables = workspace.getVariablesOfType(variableType);
      variableModelList = variableModelList.concat(variables);

      var potentialVarMap = workspace.getPotentialVariableMap();
      if (potentialVarMap) {
        var potentialVars = potentialVarMap.getVariablesOfType(variableType);
        variableModelList = variableModelList.concat(potentialVars);
      }
    }
  }
  variableModelList.sort(Blockly.VariableModel.compareByName);

  var options = [];
  for (var i = 0; i < variableModelList.length; i++) {
    // Set the uuid as the internal representation of the variable.
    options[i] = [variableModelList[i].name, variableModelList[i].getId()];
  }
  if (this.defaultType_ == Blockly.BROADCAST_MESSAGE_VARIABLE_TYPE) {
    options.unshift([Blockly.Msg.NEW_BROADCAST_MESSAGE, Blockly.NEW_BROADCAST_MESSAGE_ID]);
  } else {
    options.push([Blockly.Msg.RENAME_VARIABLE, Blockly.RENAME_VARIABLE_ID]);
    if (Blockly.Msg.DELETE_VARIABLE) {
      options.push([Blockly.Msg.DELETE_VARIABLE.replace('%1', name),
          Blockly.DELETE_VARIABLE_ID]);
    }
  }

  return options;
};

/**
 * Handle the selection of an item in the variable dropdown menu.
 * Special case the 'Rename variable...', 'Delete variable...',
 * and 'New message...' options.
 * In the rename case, prompt the user for a new name.
 * @param {!goog.ui.Menu} menu The Menu component clicked.
 * @param {!goog.ui.MenuItem} menuItem The MenuItem selected within menu.
 */
Blockly.FieldVariable.prototype.onItemSelected = function(menu, menuItem) {
  var id = menuItem.getValue();
  if (this.sourceBlock_ && this.sourceBlock_.workspace) {
    var workspace = this.sourceBlock_.workspace;
    if (id == Blockly.RENAME_VARIABLE_ID) {
      // Rename variable.
      Blockly.Variables.renameVariable(workspace, this.variable_);
      return;
    } else if (id == Blockly.DELETE_VARIABLE_ID) {
      // Delete variable.
      workspace.deleteVariableById(this.variable_.getId());
      return;
    } else if (id == Blockly.NEW_BROADCAST_MESSAGE_ID) {
      var thisField = this;
      var updateField = function(varId) {
        if (varId) {
          thisField.setValue(varId);
        }
      };
      Blockly.Variables.createVariable(workspace, updateField,
          Blockly.BROADCAST_MESSAGE_VARIABLE_TYPE);
      return;
    }

    // TODO (blockly #1529): Call any validation function, and allow it to override.
  }
  this.setValue(id);
};
<|endoftext|>


<commit_before>import ApolloClient, { createNetworkInterface } from "apollo-client";
import { readQueryFromStore } from "apollo-client/lib/src/data/readFromStore";

const heighliner = createNetworkInterface("http://localhost:8888/");
const apolloClient = new ApolloClient({
  networkInterface: heighliner,
});

export {
  apolloClient,
  readQueryFromStore,
};
<commit_msg>integrate apollo store with existing store<commit_after>import ApolloClient, { createNetworkInterface } from "apollo-client";
import { readQueryFromStore } from "apollo-client/lib/src/data/readFromStore";
import { addReducer, addMiddleware } from "../store";

const heighliner = createNetworkInterface("http://localhost:8888/");
const apolloClient = new ApolloClient({
  networkInterface: heighliner,
});

addReducer({
  apollo: apolloClient.reducer(),
});
addMiddleware(apolloClient.middleware());

export {
  apolloClient,
  readQueryFromStore,
};
<|endoftext|>