<commit_before>import JSON5 from 'json5'
import limitConcurrency from 'limit-concurrency-decorator'
import { BaseError } from 'make-error'
import {
  endsWith,
  findKey,
  forEach,
  get,
  identity,
  map,
  mapValues,
  mean,
  sum,
  uniq,
  zipWith,
} from 'lodash'

import { parseDateTime } from './xapi'

export class FaultyGranularity extends BaseError {}

// -------------------------------------------------------------------

// according to https://xapi-project.github.io/xen-api/metrics.html
// The values are stored at intervals of:
//  - 5 seconds for the past 10 minutes
//  - one minute for the past 2 hours
//  - one hour for the past week
//  - one day for the past year
const RRD_STEP_SECONDS = 5
const RRD_STEP_MINUTES = 60
const RRD_STEP_HOURS = 3600
const RRD_STEP_DAYS = 86400

const RRD_STEP_FROM_STRING = {
  seconds: RRD_STEP_SECONDS,
  minutes: RRD_STEP_MINUTES,
  hours: RRD_STEP_HOURS,
  days: RRD_STEP_DAYS,
}

// points = intervalInSeconds / step
const RRD_POINTS_PER_STEP = {
  [RRD_STEP_SECONDS]: 120,
  [RRD_STEP_MINUTES]: 120,
  [RRD_STEP_HOURS]: 168,
  [RRD_STEP_DAYS]: 366,
}

// -------------------------------------------------------------------
// Utils
// -------------------------------------------------------------------

// Return current local timestamp in seconds
function getCurrentTimestamp () {
  return Date.now() / 1000
}

function convertNanToNull (value) {
  return isNaN(value) ? null : value
}

async function getServerTimestamp (xapi, hostRef) {
  const serverLocalTime = await xapi.call('host.get_servertime', hostRef)
  return Math.floor(parseDateTime(serverLocalTime).getTime() / 1e3)
}

// -------------------------------------------------------------------
// Stats
// -------------------------------------------------------------------

const computeValues = (dataRow, legendIndex, transformValue = identity) =>
  map(dataRow, ({ values }) =>
    transformValue(convertNanToNull(values[legendIndex]))
  )

const combineStats = (stats, path, combineValues) =>
  zipWith(...map(stats, path), (...values) => combineValues(values))

// It browse the object in depth and initialise it's properties
// The targerPath can be a string or an array containing the depth
// targetPath: [a, b, c] => a.b.c
const getValuesFromDepth = (obj, targetPath) => {
  if (typeof targetPath === 'string') {
    return (obj[targetPath] = [])
  }

  forEach(targetPath, (path, key) => {
    if (obj[path] === undefined) {
      obj = obj[path] = targetPath.length - 1 === key ? [] : {}
      return
    }
    obj = obj[path]
  })
  return obj
}

const testMetric = (test, type) =>
  typeof test === 'string'
    ? test === type
    : typeof test === 'function'
      ? test(type)
      : test.exec(type)

const findMetric = (metrics, metricType) => {
  let testResult
  let metric

  forEach(metrics, (current, key) => {
    if (current.test === undefined) {
      const newValues = findMetric(current, metricType)

      metric = newValues.metric
      if (metric !== undefined) {
        testResult = newValues.testResult
        return false
      }
    } else if ((testResult = testMetric(current.test, metricType))) {
      metric = current
      return false
    }
  })

  return { metric, testResult }
}

// -------------------------------------------------------------------

// The metrics:
//  test: can be a function, regexp or string, default to: currentKey
//  getPath: default to: () => currentKey
//  transformValue: default to: identity
const STATS = {
  host: {
    load: {
      test: 'loadavg',
    },
    memoryFree: {
      test: 'memory_free_kib',
      transformValue: value => value * 1024,
    },
    memory: {
      test: 'memory_total_kib',
      transformValue: value => value * 1024,
    },
    cpus: {
      test: /^cpu(\d+)$/,
      getPath: matches => ['cpus', matches[1]],
      transformValue: value => value * 1e2,
    },
    pifs: {
      rx: {
        test: /^pif_eth(\d+)_rx$/,
        getPath: matches => ['pifs', 'rx', matches[1]],
      },
      tx: {
        test: /^pif_eth(\d+)_tx$/,
        getPath: matches => ['pifs', 'tx', matches[1]],
      },
    },
    iops: {
      r: {
        test: /^iops_read_(\w+)$/,
        getPath: matches => ['iops', 'r', matches[1]],
      },
      w: {
        test: /^iops_write_(\w+)$/,
        getPath: matches => ['iops', 'w', matches[1]],
      },
    },
    ioThroughput: {
      r: {
        test: /^io_throughput_read_(\w+)$/,
        getPath: matches => ['ioThroughput', 'r', matches[1]],
        transformValue: value => value * 2 ** 20,
      },
      w: {
        test: /^io_throughput_write_(\w+)$/,
        getPath: matches => ['ioThroughput', 'w', matches[1]],
        transformValue: value => value * 2 ** 20,
      },
    },
    latency: {
      r: {
        test: /^read_latency_(\w+)$/,
        getPath: matches => ['latency', 'r', matches[1]],
        transformValue: value => value / 1e3,
      },
      w: {
        test: /^write_latency_(\w+)$/,
        getPath: matches => ['latency', 'w', matches[1]],
        transformValue: value => value / 1e3,
      },
    },
    iowait: {
      test: /^iowait_(\w+)$/,
      getPath: matches => ['iowait', matches[1]],
    },
  },
  vm: {
    memoryFree: {
      test: 'memory_internal_free',
      transformValue: value => value * 1024,
    },
    memory: {
      test: metricType => endsWith(metricType, 'memory'),
    },
    cpus: {
      test: /^cpu(\d+)$/,
      getPath: matches => ['cpus', matches[1]],
      transformValue: value => value * 1e2,
    },
    vifs: {
      rx: {
        test: /^vif_(\d+)_rx$/,
        getPath: matches => ['vifs', 'rx', matches[1]],
      },
      tx: {
        test: /^vif_(\d+)_tx$/,
        getPath: matches => ['vifs', 'tx', matches[1]],
      },
    },
    xvds: {
      r: {
        test: /^vbd_xvd(.)_read$/,
        getPath: matches => ['xvds', 'r', matches[1]],
      },
      w: {
        test: /^vbd_xvd(.)_write$/,
        getPath: matches => ['xvds', 'w', matches[1]],
      },
    },
  },
}

// -------------------------------------------------------------------

export default class XapiStats {
  constructor () {
    this._statsByObject = {}
  }

  // Execute one http request on a XenServer for get stats
  // Return stats (Json format) or throws got exception
  @limitConcurrency(3)
  _getJson (xapi, host, timestamp, step) {
    return xapi
      .getResource('/rrd_updates', {
        host,
        query: {
          cf: 'AVERAGE',
          host: 'true',
          interval: step,
          json: 'true',
          start: timestamp,
        },
      })
      .then(response => response.readAll().then(JSON5.parse))
  }

  async _getNextTimestamp (xapi, host, step) {
    const currentTimeStamp = await getServerTimestamp(xapi, host.$ref)
    const maxDuration = step * RRD_POINTS_PER_STEP[step]
    const lastTimestamp = get(this._statsByObject, [
      host.uuid,
      step,
      'endTimestamp',
    ])

    if (
      lastTimestamp === undefined ||
      currentTimeStamp - lastTimestamp + step > maxDuration
    ) {
      return currentTimeStamp - maxDuration + step
    }
    return lastTimestamp
  }

  _getStats (hostUuid, step, vmUuid) {
    const hostStats = this._statsByObject[hostUuid][step]

    // Return host stats
    if (vmUuid === undefined) {
      return {
        interval: step,
        ...hostStats,
      }
    }

    // Return vm stats
    return {
      interval: step,
      endTimestamp: hostStats.endTimestamp,
      ...this._statsByObject[vmUuid][step],
    }
  }

  async _getAndUpdateStats (xapi, { host, vmUuid, granularity }) {
    const step =
      granularity === undefined
        ? RRD_STEP_SECONDS
        : RRD_STEP_FROM_STRING[granularity]

    if (step === undefined) {
      throw new FaultyGranularity(
        `Unknown granularity: '${granularity}'. Use 'seconds', 'minutes', 'hours', or 'days'.`
      )
    }

    // Limit the number of http requests
    const hostUuid = host.uuid

    if (
      get(this._statsByObject, [hostUuid, step, 'localTimestamp']) + step >
      getCurrentTimestamp()
    ) {
      return this._getStats(hostUuid, step, vmUuid)
    }

    const timestamp = await this._getNextTimestamp(xapi, host, step)
    const json = await this._getJson(xapi, host, timestamp, step)
    if (json.meta.step !== step) {
      throw new FaultyGranularity(
        `Unable to get the true granularity: ${json.meta.step}`
      )
    }

    // It exists data
    if (json.data.length !== 0) {
      // Warning: Sometimes, the json.xport.meta.start value does not match with the
      // timestamp of the oldest data value
      // So, we use the timestamp of the oldest data value !
      const startTimestamp = json.data[json.meta.rows - 1].t
      const endTimestamp = get(this._statsByObject, [
        hostUuid,
        step,
        'endTimestamp',
      ])

      const statsOffset = endTimestamp - startTimestamp + step
      if (endTimestamp !== undefined && statsOffset > 0) {
        const parseOffset = statsOffset / step
        // Remove useless data
        // Note: Older values are at end of json.data.row
        json.data.splice(json.data.length - parseOffset)
      }

      // It exists useful data
      if (json.data.length > 0) {
        // reorder data
        json.data.reverse()
        forEach(json.meta.legend, (legend, index) => {
          const [, type, uuid, metricType] = /^AVERAGE:([^:]+):(.+):(.+)$/.exec(
            legend
          )

          const metrics = STATS[type]
          if (metrics === undefined) {
            return
          }

          const { metric, testResult } = findMetric(metrics, metricType)

          if (metric === undefined) {
            return
          }

          const path =
            metric.getPath !== undefined
              ? metric.getPath(testResult)
              : [findKey(metrics, metric)]

          const metricValues = getValuesFromDepth(this._statsByObject, [
            uuid,
            step,
            'stats',
            ...path,
          ])

          metricValues.push(
            ...computeValues(json.data, index, metric.transformValue)
          )

          // remove older Values
          metricValues.splice(
            0,
            metricValues.length - RRD_POINTS_PER_STEP[step]
          )
        })
      }
    }

    // Update timestamp
    const hostStats = this._statsByObject[hostUuid][step]
    hostStats.endTimestamp = json.meta.end
    hostStats.localTimestamp = getCurrentTimestamp()
    return this._getStats(hostUuid, step, vmUuid)
  }

  getHostStats (xapi, hostId, granularity) {
    return this._getAndUpdateStats(xapi, {
      host: xapi.getObject(hostId),
      granularity,
    })
  }

  getVmStats (xapi, vmId, granularity) {
    const vm = xapi.getObject(vmId)
    const host = vm.$resident_on
    if (!host) {
      throw new Error(`VM ${vmId} is halted or host could not be found.`)
    }

    return this._getAndUpdateStats(xapi, {
      host,
      vmUuid: vm.uuid,
      granularity,
    })
  }

  async getSrStats (xapi, srId, granularity) {
    const sr = xapi.getObject(srId)

    const hostsStats = {}
    await Promise.all(
      map(uniq(map(sr.$PBDs, 'host')), hostId =>
        this.getHostStats(xapi, hostId, granularity).then(stats => {
          hostsStats[xapi.getObject(hostId).name_label] = stats
        })
      )
    )

    const srShortUUID = sr.uuid.slice(0, 8)
    return {
      interval: hostsStats[Object.keys(hostsStats)[0]].interval,
      endTimestamp: Math.max(...map(hostsStats, 'endTimestamp')),
      localTimestamp: Math.min(...map(hostsStats, 'localTimestamp')),
      stats: {
        iops: {
          r: combineStats(hostsStats, `stats.iops.r[${srShortUUID}]`, sum),
          w: combineStats(hostsStats, `stats.iops.w[${srShortUUID}]`, sum),
        },
        ioThroughput: {
          r: combineStats(
            hostsStats,
            `stats.ioThroughput.r[${srShortUUID}]`,
            sum
          ),
          w: combineStats(
            hostsStats,
            `stats.ioThroughput.w[${srShortUUID}]`,
            sum
          ),
        },
        latency: {
          r: combineStats(hostsStats, `stats.latency.r[${srShortUUID}]`, mean),
          w: combineStats(hostsStats, `stats.latency.w[${srShortUUID}]`, mean),
        },
        iowait: mapValues(hostsStats, `stats.iowait[${srShortUUID}]`),
      },
    }
  }
}
<commit_msg>feat(xo-server/xapi-stats): expose VM IOPS metric (#3454)<commit_after>import JSON5 from 'json5'
import limitConcurrency from 'limit-concurrency-decorator'
import { BaseError } from 'make-error'
import {
  endsWith,
  findKey,
  forEach,
  get,
  identity,
  map,
  mapValues,
  mean,
  sum,
  uniq,
  zipWith,
} from 'lodash'

import { parseDateTime } from './xapi'

export class FaultyGranularity extends BaseError {}

// -------------------------------------------------------------------

// according to https://xapi-project.github.io/xen-api/metrics.html
// The values are stored at intervals of:
//  - 5 seconds for the past 10 minutes
//  - one minute for the past 2 hours
//  - one hour for the past week
//  - one day for the past year
const RRD_STEP_SECONDS = 5
const RRD_STEP_MINUTES = 60
const RRD_STEP_HOURS = 3600
const RRD_STEP_DAYS = 86400

const RRD_STEP_FROM_STRING = {
  seconds: RRD_STEP_SECONDS,
  minutes: RRD_STEP_MINUTES,
  hours: RRD_STEP_HOURS,
  days: RRD_STEP_DAYS,
}

// points = intervalInSeconds / step
const RRD_POINTS_PER_STEP = {
  [RRD_STEP_SECONDS]: 120,
  [RRD_STEP_MINUTES]: 120,
  [RRD_STEP_HOURS]: 168,
  [RRD_STEP_DAYS]: 366,
}

// -------------------------------------------------------------------
// Utils
// -------------------------------------------------------------------

// Return current local timestamp in seconds
function getCurrentTimestamp () {
  return Date.now() / 1000
}

function convertNanToNull (value) {
  return isNaN(value) ? null : value
}

async function getServerTimestamp (xapi, hostRef) {
  const serverLocalTime = await xapi.call('host.get_servertime', hostRef)
  return Math.floor(parseDateTime(serverLocalTime).getTime() / 1e3)
}

// -------------------------------------------------------------------
// Stats
// -------------------------------------------------------------------

const computeValues = (dataRow, legendIndex, transformValue = identity) =>
  map(dataRow, ({ values }) =>
    transformValue(convertNanToNull(values[legendIndex]))
  )

const combineStats = (stats, path, combineValues) =>
  zipWith(...map(stats, path), (...values) => combineValues(values))

// It browse the object in depth and initialise it's properties
// The targerPath can be a string or an array containing the depth
// targetPath: [a, b, c] => a.b.c
const getValuesFromDepth = (obj, targetPath) => {
  if (typeof targetPath === 'string') {
    return (obj[targetPath] = [])
  }

  forEach(targetPath, (path, key) => {
    if (obj[path] === undefined) {
      obj = obj[path] = targetPath.length - 1 === key ? [] : {}
      return
    }
    obj = obj[path]
  })
  return obj
}

const testMetric = (test, type) =>
  typeof test === 'string'
    ? test === type
    : typeof test === 'function'
      ? test(type)
      : test.exec(type)

const findMetric = (metrics, metricType) => {
  let testResult
  let metric

  forEach(metrics, (current, key) => {
    if (current.test === undefined) {
      const newValues = findMetric(current, metricType)

      metric = newValues.metric
      if (metric !== undefined) {
        testResult = newValues.testResult
        return false
      }
    } else if ((testResult = testMetric(current.test, metricType))) {
      metric = current
      return false
    }
  })

  return { metric, testResult }
}

// -------------------------------------------------------------------

// The metrics:
//  test: can be a function, regexp or string, default to: currentKey
//  getPath: default to: () => currentKey
//  transformValue: default to: identity
const STATS = {
  host: {
    load: {
      test: 'loadavg',
    },
    memoryFree: {
      test: 'memory_free_kib',
      transformValue: value => value * 1024,
    },
    memory: {
      test: 'memory_total_kib',
      transformValue: value => value * 1024,
    },
    cpus: {
      test: /^cpu(\d+)$/,
      getPath: matches => ['cpus', matches[1]],
      transformValue: value => value * 1e2,
    },
    pifs: {
      rx: {
        test: /^pif_eth(\d+)_rx$/,
        getPath: matches => ['pifs', 'rx', matches[1]],
      },
      tx: {
        test: /^pif_eth(\d+)_tx$/,
        getPath: matches => ['pifs', 'tx', matches[1]],
      },
    },
    iops: {
      r: {
        test: /^iops_read_(\w+)$/,
        getPath: matches => ['iops', 'r', matches[1]],
      },
      w: {
        test: /^iops_write_(\w+)$/,
        getPath: matches => ['iops', 'w', matches[1]],
      },
    },
    ioThroughput: {
      r: {
        test: /^io_throughput_read_(\w+)$/,
        getPath: matches => ['ioThroughput', 'r', matches[1]],
        transformValue: value => value * 2 ** 20,
      },
      w: {
        test: /^io_throughput_write_(\w+)$/,
        getPath: matches => ['ioThroughput', 'w', matches[1]],
        transformValue: value => value * 2 ** 20,
      },
    },
    latency: {
      r: {
        test: /^read_latency_(\w+)$/,
        getPath: matches => ['latency', 'r', matches[1]],
        transformValue: value => value / 1e3,
      },
      w: {
        test: /^write_latency_(\w+)$/,
        getPath: matches => ['latency', 'w', matches[1]],
        transformValue: value => value / 1e3,
      },
    },
    iowait: {
      test: /^iowait_(\w+)$/,
      getPath: matches => ['iowait', matches[1]],
    },
  },
  vm: {
    memoryFree: {
      test: 'memory_internal_free',
      transformValue: value => value * 1024,
    },
    memory: {
      test: metricType => endsWith(metricType, 'memory'),
    },
    cpus: {
      test: /^cpu(\d+)$/,
      getPath: matches => ['cpus', matches[1]],
      transformValue: value => value * 1e2,
    },
    vifs: {
      rx: {
        test: /^vif_(\d+)_rx$/,
        getPath: matches => ['vifs', 'rx', matches[1]],
      },
      tx: {
        test: /^vif_(\d+)_tx$/,
        getPath: matches => ['vifs', 'tx', matches[1]],
      },
    },
    xvds: {
      r: {
        test: /^vbd_xvd(.)_read$/,
        getPath: matches => ['xvds', 'r', matches[1]],
      },
      w: {
        test: /^vbd_xvd(.)_write$/,
        getPath: matches => ['xvds', 'w', matches[1]],
      },
    },
    iops: {
      r: {
        test: /^vbd_xvd(.)_iops_read$/,
        getPath: matches => ['iops', 'r', matches[1]],
      },
      w: {
        test: /^vbd_xvd(.)_iops_write$/,
        getPath: matches => ['iops', 'w', matches[1]],
      },
    },
  },
}

// -------------------------------------------------------------------

export default class XapiStats {
  constructor () {
    this._statsByObject = {}
  }

  // Execute one http request on a XenServer for get stats
  // Return stats (Json format) or throws got exception
  @limitConcurrency(3)
  _getJson (xapi, host, timestamp, step) {
    return xapi
      .getResource('/rrd_updates', {
        host,
        query: {
          cf: 'AVERAGE',
          host: 'true',
          interval: step,
          json: 'true',
          start: timestamp,
        },
      })
      .then(response => response.readAll().then(JSON5.parse))
  }

  async _getNextTimestamp (xapi, host, step) {
    const currentTimeStamp = await getServerTimestamp(xapi, host.$ref)
    const maxDuration = step * RRD_POINTS_PER_STEP[step]
    const lastTimestamp = get(this._statsByObject, [
      host.uuid,
      step,
      'endTimestamp',
    ])

    if (
      lastTimestamp === undefined ||
      currentTimeStamp - lastTimestamp + step > maxDuration
    ) {
      return currentTimeStamp - maxDuration + step
    }
    return lastTimestamp
  }

  _getStats (hostUuid, step, vmUuid) {
    const hostStats = this._statsByObject[hostUuid][step]

    // Return host stats
    if (vmUuid === undefined) {
      return {
        interval: step,
        ...hostStats,
      }
    }

    // Return vm stats
    return {
      interval: step,
      endTimestamp: hostStats.endTimestamp,
      ...this._statsByObject[vmUuid][step],
    }
  }

  async _getAndUpdateStats (xapi, { host, vmUuid, granularity }) {
    const step =
      granularity === undefined
        ? RRD_STEP_SECONDS
        : RRD_STEP_FROM_STRING[granularity]

    if (step === undefined) {
      throw new FaultyGranularity(
        `Unknown granularity: '${granularity}'. Use 'seconds', 'minutes', 'hours', or 'days'.`
      )
    }

    // Limit the number of http requests
    const hostUuid = host.uuid

    if (
      get(this._statsByObject, [hostUuid, step, 'localTimestamp']) + step >
      getCurrentTimestamp()
    ) {
      return this._getStats(hostUuid, step, vmUuid)
    }

    const timestamp = await this._getNextTimestamp(xapi, host, step)
    const json = await this._getJson(xapi, host, timestamp, step)
    if (json.meta.step !== step) {
      throw new FaultyGranularity(
        `Unable to get the true granularity: ${json.meta.step}`
      )
    }

    // It exists data
    if (json.data.length !== 0) {
      // Warning: Sometimes, the json.xport.meta.start value does not match with the
      // timestamp of the oldest data value
      // So, we use the timestamp of the oldest data value !
      const startTimestamp = json.data[json.meta.rows - 1].t
      const endTimestamp = get(this._statsByObject, [
        hostUuid,
        step,
        'endTimestamp',
      ])

      const statsOffset = endTimestamp - startTimestamp + step
      if (endTimestamp !== undefined && statsOffset > 0) {
        const parseOffset = statsOffset / step
        // Remove useless data
        // Note: Older values are at end of json.data.row
        json.data.splice(json.data.length - parseOffset)
      }

      // It exists useful data
      if (json.data.length > 0) {
        // reorder data
        json.data.reverse()
        forEach(json.meta.legend, (legend, index) => {
          const [, type, uuid, metricType] = /^AVERAGE:([^:]+):(.+):(.+)$/.exec(
            legend
          )

          const metrics = STATS[type]
          if (metrics === undefined) {
            return
          }

          const { metric, testResult } = findMetric(metrics, metricType)

          if (metric === undefined) {
            return
          }

          const path =
            metric.getPath !== undefined
              ? metric.getPath(testResult)
              : [findKey(metrics, metric)]

          const metricValues = getValuesFromDepth(this._statsByObject, [
            uuid,
            step,
            'stats',
            ...path,
          ])

          metricValues.push(
            ...computeValues(json.data, index, metric.transformValue)
          )

          // remove older Values
          metricValues.splice(
            0,
            metricValues.length - RRD_POINTS_PER_STEP[step]
          )
        })
      }
    }

    // Update timestamp
    const hostStats = this._statsByObject[hostUuid][step]
    hostStats.endTimestamp = json.meta.end
    hostStats.localTimestamp = getCurrentTimestamp()
    return this._getStats(hostUuid, step, vmUuid)
  }

  getHostStats (xapi, hostId, granularity) {
    return this._getAndUpdateStats(xapi, {
      host: xapi.getObject(hostId),
      granularity,
    })
  }

  getVmStats (xapi, vmId, granularity) {
    const vm = xapi.getObject(vmId)
    const host = vm.$resident_on
    if (!host) {
      throw new Error(`VM ${vmId} is halted or host could not be found.`)
    }

    return this._getAndUpdateStats(xapi, {
      host,
      vmUuid: vm.uuid,
      granularity,
    })
  }

  async getSrStats (xapi, srId, granularity) {
    const sr = xapi.getObject(srId)

    const hostsStats = {}
    await Promise.all(
      map(uniq(map(sr.$PBDs, 'host')), hostId =>
        this.getHostStats(xapi, hostId, granularity).then(stats => {
          hostsStats[xapi.getObject(hostId).name_label] = stats
        })
      )
    )

    const srShortUUID = sr.uuid.slice(0, 8)
    return {
      interval: hostsStats[Object.keys(hostsStats)[0]].interval,
      endTimestamp: Math.max(...map(hostsStats, 'endTimestamp')),
      localTimestamp: Math.min(...map(hostsStats, 'localTimestamp')),
      stats: {
        iops: {
          r: combineStats(hostsStats, `stats.iops.r[${srShortUUID}]`, sum),
          w: combineStats(hostsStats, `stats.iops.w[${srShortUUID}]`, sum),
        },
        ioThroughput: {
          r: combineStats(
            hostsStats,
            `stats.ioThroughput.r[${srShortUUID}]`,
            sum
          ),
          w: combineStats(
            hostsStats,
            `stats.ioThroughput.w[${srShortUUID}]`,
            sum
          ),
        },
        latency: {
          r: combineStats(hostsStats, `stats.latency.r[${srShortUUID}]`, mean),
          w: combineStats(hostsStats, `stats.latency.w[${srShortUUID}]`, mean),
        },
        iowait: mapValues(hostsStats, `stats.iowait[${srShortUUID}]`),
      },
    }
  }
}
<|endoftext|>


<commit_before>/*!
 * OS.js - JavaScript Operating System
 *
 * Copyright (c) 2011-2015, Anders Evenrud <andersevenrud@gmail.com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this
 *    list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * @author  Anders Evenrud <andersevenrud@gmail.com>
 * @licence Simplified BSD License
 */
(function(API, Utils, VFS) {
  'use strict';

  window.OSjs = window.OSjs || {};
  OSjs.GUI = OSjs.GUI || {};

  /////////////////////////////////////////////////////////////////////////////
  // INTERNAL HELPERS
  /////////////////////////////////////////////////////////////////////////////

  /**
   * Internal for parsing GUI elements
   */
  function parseDynamic(scheme, node, win, args) {
    args = args || {};

    var translator = args._ || API._;

    node.querySelectorAll('*[data-label]').forEach(function(el) {
      var label = translator(el.getAttribute('data-label'));
      el.setAttribute('data-label', label);
    });

    node.querySelectorAll('gui-label, gui-button, gui-list-view-column, gui-select-option, gui-select-list-option').forEach(function(el) {
      if ( !el.children.length && !el.getAttribute('data-no-translate') ) {
        var lbl = OSjs.GUI.Helpers.getValueLabel(el);
        el.appendChild(document.createTextNode(translator(lbl)));
      }
    });

    node.querySelectorAll('gui-button').forEach(function(el) {
      var label = OSjs.GUI.Helpers.getValueLabel(el);
      if ( label ) {
        el.appendChild(document.createTextNode(API._(label)));
      }
    });

    node.querySelectorAll('*[data-icon]').forEach(function(el) {
      var image = OSjs.GUI.Helpers.getIcon(el, win);
      el.setAttribute('data-icon', image);
    });
  }

  /**
   * Method for adding children (moving)
   */
  function addChildren(frag, root) {
    if ( frag ) {
      var children = frag.children;
      var i = 0;
      while ( children.length && i < 10000 ) {
        root.appendChild(children[0]);
        i++;
      }
    }
  }

  /**
   * Makes sure "include" fragments are rendered correctly
   */
  function resolveFragments(scheme, node, el) {
    function _resolve() {
      var nodes = node.querySelectorAll('gui-fragment');
      if ( nodes.length ) {
        nodes.forEach(function(el) {
          var id = el.getAttribute('data-fragment-id');
          var frag = scheme.getFragment(id, 'application-fragment');

          addChildren(frag, el.parentNode);
          Utils.$remove(el);
        });
        return true;
      }

      return false;
    }

    var resolving = true;
    while ( resolving ) {
      resolving = _resolve();
    }
  }

  /////////////////////////////////////////////////////////////////////////////
  // API
  /////////////////////////////////////////////////////////////////////////////

  /**
   * The class for loading and parsing UI Schemes
   *
   * @api   OSjs.GUI.Scheme
   *
   * @class Scheme
   */
  function UIScheme(url) {
    console.group('UIScheme::construct()');
    console.log(url);
    console.groupEnd();

    this.url = url;
    this.scheme = null;
    this.triggers = {render: []};
  }

  UIScheme.prototype.destroy = function() {
    Utils.$empty(this.scheme);

    this.scheme = null;
    this.triggers = {};
  };

  UIScheme.prototype.on = function(f, fn) {
    this.triggers[f].push(fn);
  };

  UIScheme.prototype._trigger = function(f, args) {
    args = args || [];

    var self = this;
    this.triggers[f].forEach(function(fn) {
      fn.apply(self, args);
    });
  };

  UIScheme.prototype.load = function(cb) {
    var self = this;

    function removeSelfClosingTags(html) {
      var split = (html || '').split('/>');
      var newhtml = '';
      for (var i = 0; i < split.length - 1;i++) {
        var edsplit = split[i].split('<');
        newhtml += split[i] + '></' + edsplit[edsplit.length - 1].split(' ')[0] + '>';
      }
      return newhtml + split[split.length-1];
    }

    function finished(html) {
      var doc = document.createDocumentFragment();
      var wrapper = document.createElement('div');
      wrapper.innerHTML = Utils.cleanHTML(removeSelfClosingTags(html));
      doc.appendChild(wrapper);

      self.scheme = doc.cloneNode(true);

      wrapper = null;
      doc = null;

      cb(false, self.scheme);
    }

    if ( window.location.protocol.match(/^file/) ) {
      var url = this.url;
      if ( !url.match(/^\//) ) {
        url = '/' + url;
      }
      finished(OSjs.API.getDefaultSchemes(url));
      return;
    }

    console.debug('UIScheme::load()', this.url);

    Utils.ajax({
      url: this.url,
      onsuccess: function(html) {
        finished(html);
      },
      onerror: function() {
        cb('Failed to fetch scheme');
      }
    });
  };

  UIScheme.prototype.getFragment = function(id, type) {
    var content = null;
    if ( id ) {
      if ( type ) {
        content = this.scheme.querySelector(type + '[data-id="' + id + '"]');
      } else {
        content = this.scheme.querySelector('application-window[data-id="' + id + '"]') ||
                  this.scheme.querySelector('application-fragment[data-id="' + id + '"]');
      }
    }
    return content;
  };

  UIScheme.prototype.parse = function(id, type, win, onparse, args) {
    var self = this;
    var content = this.getFragment(id, type);

    console.debug('UIScheme::parse()', id);

    if ( !content ) {
      console.error('UIScheme::parse()', 'No fragment found', id, type);
      return null;
    }

    type = type || content.tagName.toLowerCase();
    onparse = onparse || function() {};
    args = args || {};

    if ( content ) {
      var node = content.cloneNode(true);

      // Resolve fragment includes before dynamic rendering
      resolveFragments(this, node);

      // Apply a default className to non-containers
      node.querySelectorAll('*').forEach(function(el) {
        var lcase = el.tagName.toLowerCase();
        if ( lcase.match(/^gui\-/) && !lcase.match(/(\-container|\-(h|v)box|\-columns?|\-rows?|(status|tool)bar|(button|menu)\-bar|bar\-entry)$/) ) {
          Utils.$addClass(el, 'gui-element');
        }
      });

      // Go ahead and parse dynamic elements (like labels)
      parseDynamic(this, node, win, args);

      // Lastly render elements
      onparse(node);

      Object.keys(OSjs.GUI.Elements).forEach(function(key) {
        node.querySelectorAll(key).forEach(function(pel) {
          if ( pel._wasParsed ) {
            return;
          }

          try {
            OSjs.GUI.Elements[key].build(pel);
          } catch ( e ) {
            console.warn('UIScheme::parse()', id, type, win, 'exception');
            console.warn(e, e.stack);
          }
          pel._wasParsed = true;
        });
      });

      return node;
    }

    return null;
  };

  UIScheme.prototype.render = function(win, id, root, type, onparse, args) {
    root = root || win._getRoot();
    if ( root instanceof OSjs.GUI.Element ) {
      root = root.$element;
    }

    console.debug('UIScheme::render()', id);

    var content = this.parse(id, type, win, onparse, args);
    addChildren(content, root);

    this._trigger('render', [root]);
  };

  UIScheme.prototype.create = function(win, tagName, params, parentNode, applyArgs) {
    tagName = tagName || '';
    params = params || {};
    parentNode = parentNode || win.getRoot();
    if ( parentNode instanceof OSjs.GUI.Element ) {
      parentNode = parentNode.$element;
    }

    var el;
    if ( OSjs.GUI.Elements[tagName] && OSjs.GUI.Elements[tagName].create ) {
      el = OSjs.GUI.Elements[tagName].create(params);
    } else {
      el = OSjs.GUI.Helpers.createElement(tagName, params);
    }

    parentNode.appendChild(el);
    OSjs.GUI.Elements[tagName].build(el, applyArgs, win);

    return new OSjs.GUI.Element(el);
  };

  UIScheme.prototype.find = function(win, id, root) {
    root = root || win._getRoot();
    var q = '[data-id="' + id + '"]';
    return this.get(root.querySelector(q), q);
  };

  UIScheme.prototype.get = function(el, q) {
    if ( el ) {
      var tagName = el.tagName.toLowerCase();
      if ( tagName.match(/^gui\-(list|tree|icon|file)\-view$/) || tagName.match(/^gui\-select/) ) {
        return new OSjs.GUI.ElementDataView(el, q);
      }
    }
    return new OSjs.GUI.Element(el, q);
  };

  /////////////////////////////////////////////////////////////////////////////
  // EXPORTS
  /////////////////////////////////////////////////////////////////////////////

  OSjs.GUI.Scheme = UIScheme;

  /**
   * Shortcut for creating a new UIScheme class
   *
   * @param String    url     URL to scheme file
   * @return UIScheme
   * @api OSjs.GUI.createScheme()
   */
  OSjs.GUI.createScheme = function(url) {
    return new UIScheme(url);
  };

})(OSjs.API, OSjs.Utils, OSjs.VFS);
<commit_msg>Fixed standalone scheme loading<commit_after>/*!
 * OS.js - JavaScript Operating System
 *
 * Copyright (c) 2011-2015, Anders Evenrud <andersevenrud@gmail.com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this
 *    list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * @author  Anders Evenrud <andersevenrud@gmail.com>
 * @licence Simplified BSD License
 */
(function(API, Utils, VFS) {
  'use strict';

  window.OSjs = window.OSjs || {};
  OSjs.GUI = OSjs.GUI || {};

  /////////////////////////////////////////////////////////////////////////////
  // INTERNAL HELPERS
  /////////////////////////////////////////////////////////////////////////////

  /**
   * Internal for parsing GUI elements
   */
  function parseDynamic(scheme, node, win, args) {
    args = args || {};

    var translator = args._ || API._;

    node.querySelectorAll('*[data-label]').forEach(function(el) {
      var label = translator(el.getAttribute('data-label'));
      el.setAttribute('data-label', label);
    });

    node.querySelectorAll('gui-label, gui-button, gui-list-view-column, gui-select-option, gui-select-list-option').forEach(function(el) {
      if ( !el.children.length && !el.getAttribute('data-no-translate') ) {
        var lbl = OSjs.GUI.Helpers.getValueLabel(el);
        el.appendChild(document.createTextNode(translator(lbl)));
      }
    });

    node.querySelectorAll('gui-button').forEach(function(el) {
      var label = OSjs.GUI.Helpers.getValueLabel(el);
      if ( label ) {
        el.appendChild(document.createTextNode(API._(label)));
      }
    });

    node.querySelectorAll('*[data-icon]').forEach(function(el) {
      var image = OSjs.GUI.Helpers.getIcon(el, win);
      el.setAttribute('data-icon', image);
    });
  }

  /**
   * Method for adding children (moving)
   */
  function addChildren(frag, root) {
    if ( frag ) {
      var children = frag.children;
      var i = 0;
      while ( children.length && i < 10000 ) {
        root.appendChild(children[0]);
        i++;
      }
    }
  }

  /**
   * Makes sure "include" fragments are rendered correctly
   */
  function resolveFragments(scheme, node, el) {
    function _resolve() {
      var nodes = node.querySelectorAll('gui-fragment');
      if ( nodes.length ) {
        nodes.forEach(function(el) {
          var id = el.getAttribute('data-fragment-id');
          var frag = scheme.getFragment(id, 'application-fragment');

          addChildren(frag, el.parentNode);
          Utils.$remove(el);
        });
        return true;
      }

      return false;
    }

    var resolving = true;
    while ( resolving ) {
      resolving = _resolve();
    }
  }

  /////////////////////////////////////////////////////////////////////////////
  // API
  /////////////////////////////////////////////////////////////////////////////

  /**
   * The class for loading and parsing UI Schemes
   *
   * @api   OSjs.GUI.Scheme
   *
   * @class Scheme
   */
  function UIScheme(url) {
    console.group('UIScheme::construct()');
    console.log(url);
    console.groupEnd();

    this.url = url;
    this.scheme = null;
    this.triggers = {render: []};
  }

  UIScheme.prototype.destroy = function() {
    Utils.$empty(this.scheme);

    this.scheme = null;
    this.triggers = {};
  };

  UIScheme.prototype.on = function(f, fn) {
    this.triggers[f].push(fn);
  };

  UIScheme.prototype._trigger = function(f, args) {
    args = args || [];

    var self = this;
    this.triggers[f].forEach(function(fn) {
      fn.apply(self, args);
    });
  };

  UIScheme.prototype.load = function(cb) {
    var self = this;

    function removeSelfClosingTags(html) {
      var split = (html || '').split('/>');
      var newhtml = '';
      for (var i = 0; i < split.length - 1;i++) {
        var edsplit = split[i].split('<');
        newhtml += split[i] + '></' + edsplit[edsplit.length - 1].split(' ')[0] + '>';
      }
      return newhtml + split[split.length-1];
    }

    function finished(html) {
      var doc = document.createDocumentFragment();
      var wrapper = document.createElement('div');
      wrapper.innerHTML = Utils.cleanHTML(removeSelfClosingTags(html));
      doc.appendChild(wrapper);

      self.scheme = doc.cloneNode(true);

      wrapper = null;
      doc = null;

      cb(false, self.scheme);
    }

    if ( window.location.protocol.match(/^file/) ) {
      var url = this.url;
      if ( !url.match(/^\//) ) {
        url = '/' + url;
      }
      finished(OSjs.API.getDefaultSchemes(url.replace(/^\/packages/, '')));
      return;
    }

    console.debug('UIScheme::load()', this.url);

    Utils.ajax({
      url: this.url,
      onsuccess: function(html) {
        finished(html);
      },
      onerror: function() {
        cb('Failed to fetch scheme');
      }
    });
  };

  UIScheme.prototype.getFragment = function(id, type) {
    var content = null;
    if ( id ) {
      if ( type ) {
        content = this.scheme.querySelector(type + '[data-id="' + id + '"]');
      } else {
        content = this.scheme.querySelector('application-window[data-id="' + id + '"]') ||
                  this.scheme.querySelector('application-fragment[data-id="' + id + '"]');
      }
    }
    return content;
  };

  UIScheme.prototype.parse = function(id, type, win, onparse, args) {
    var self = this;
    var content = this.getFragment(id, type);

    console.debug('UIScheme::parse()', id);

    if ( !content ) {
      console.error('UIScheme::parse()', 'No fragment found', id, type);
      return null;
    }

    type = type || content.tagName.toLowerCase();
    onparse = onparse || function() {};
    args = args || {};

    if ( content ) {
      var node = content.cloneNode(true);

      // Resolve fragment includes before dynamic rendering
      resolveFragments(this, node);

      // Apply a default className to non-containers
      node.querySelectorAll('*').forEach(function(el) {
        var lcase = el.tagName.toLowerCase();
        if ( lcase.match(/^gui\-/) && !lcase.match(/(\-container|\-(h|v)box|\-columns?|\-rows?|(status|tool)bar|(button|menu)\-bar|bar\-entry)$/) ) {
          Utils.$addClass(el, 'gui-element');
        }
      });

      // Go ahead and parse dynamic elements (like labels)
      parseDynamic(this, node, win, args);

      // Lastly render elements
      onparse(node);

      Object.keys(OSjs.GUI.Elements).forEach(function(key) {
        node.querySelectorAll(key).forEach(function(pel) {
          if ( pel._wasParsed ) {
            return;
          }

          try {
            OSjs.GUI.Elements[key].build(pel);
          } catch ( e ) {
            console.warn('UIScheme::parse()', id, type, win, 'exception');
            console.warn(e, e.stack);
          }
          pel._wasParsed = true;
        });
      });

      return node;
    }

    return null;
  };

  UIScheme.prototype.render = function(win, id, root, type, onparse, args) {
    root = root || win._getRoot();
    if ( root instanceof OSjs.GUI.Element ) {
      root = root.$element;
    }

    console.debug('UIScheme::render()', id);

    var content = this.parse(id, type, win, onparse, args);
    addChildren(content, root);

    this._trigger('render', [root]);
  };

  UIScheme.prototype.create = function(win, tagName, params, parentNode, applyArgs) {
    tagName = tagName || '';
    params = params || {};
    parentNode = parentNode || win.getRoot();
    if ( parentNode instanceof OSjs.GUI.Element ) {
      parentNode = parentNode.$element;
    }

    var el;
    if ( OSjs.GUI.Elements[tagName] && OSjs.GUI.Elements[tagName].create ) {
      el = OSjs.GUI.Elements[tagName].create(params);
    } else {
      el = OSjs.GUI.Helpers.createElement(tagName, params);
    }

    parentNode.appendChild(el);
    OSjs.GUI.Elements[tagName].build(el, applyArgs, win);

    return new OSjs.GUI.Element(el);
  };

  UIScheme.prototype.find = function(win, id, root) {
    root = root || win._getRoot();
    var q = '[data-id="' + id + '"]';
    return this.get(root.querySelector(q), q);
  };

  UIScheme.prototype.get = function(el, q) {
    if ( el ) {
      var tagName = el.tagName.toLowerCase();
      if ( tagName.match(/^gui\-(list|tree|icon|file)\-view$/) || tagName.match(/^gui\-select/) ) {
        return new OSjs.GUI.ElementDataView(el, q);
      }
    }
    return new OSjs.GUI.Element(el, q);
  };

  /////////////////////////////////////////////////////////////////////////////
  // EXPORTS
  /////////////////////////////////////////////////////////////////////////////

  OSjs.GUI.Scheme = UIScheme;

  /**
   * Shortcut for creating a new UIScheme class
   *
   * @param String    url     URL to scheme file
   * @return UIScheme
   * @api OSjs.GUI.createScheme()
   */
  OSjs.GUI.createScheme = function(url) {
    return new UIScheme(url);
  };

})(OSjs.API, OSjs.Utils, OSjs.VFS);
<|endoftext|>


<commit_before>import { expect } from 'chai';
import sinon from 'sinon';

import createStore from '../../resolve-es/src';
import memoryEsDriver from '../../resolve-es-memory/src';

import createBus from '../../resolve-bus/src';
import memoryBusDriver from '../../resolve-bus-memory/src';

import commandHandler from '../src';

describe('command', () => {
    const AGGREGATE_NAME = 'test_aggregate';
    let store;
    let bus;
    let execute;
    let aggregates;
    let testCommand;
    let testEvent;

    beforeEach(() => {
        testCommand = {
            aggregateId: 'test-id',
            aggregate: AGGREGATE_NAME,
            commandName: 'CREATE',
            payload: { name: 'Vasiliy' }
        };

        testEvent = {
            aggregateId: 'test-id',
            type: 'USER_CREATED',
            payload: { name: 'Vasiliy' }
        };

        aggregates = {
            [AGGREGATE_NAME]: {
                commands: {
                    CREATE: () => testEvent
                }
            }
        };

        store = createStore({ driver: memoryEsDriver() });
        bus = createBus({ driver: memoryBusDriver() });
        execute = commandHandler({ store, bus, aggregates });
    });

    it('should save and publish event', () => {
        const eventHandlerSpy = sinon.spy();
        bus.onEvent(['USER_CREATED'], eventHandlerSpy);

        return execute(testCommand).then(() => {
            expect(eventHandlerSpy.callCount).to.be.equal(1);
            expect(eventHandlerSpy.lastCall.args[0]).to.be.deep.equal(testEvent);

            const storedEvents = [];
            return store
                .loadEventsByAggregateId('test-id', event => storedEvents.push(event))
                .then(() => expect(storedEvents).to.be.deep.equal([testEvent]));
        });
    });

    it('should reject event in case of commandName absense', () => {
        delete testCommand.commandName;

        return execute(testCommand)
            .then(() => expect(false).to.be.true)
            .catch(err => expect(err).to.be.equal('commandName argument is required'));
    });

    it('should reject event in case of aggregateId absense', () => {
        delete testCommand.aggregateId;

        return execute(testCommand)
            .then(() => expect(false).to.be.true)
            .catch(err => expect(err).to.be.equal('aggregateId argument is required'));
    });

    it('should pass initialState and args to command handler', () => {
        const createHandlerSpy = sinon.spy(() => testEvent);

        aggregates[AGGREGATE_NAME].commands.CREATE = createHandlerSpy;

        return execute(testCommand).then(() => {
            expect(createHandlerSpy.lastCall.args).to.be.deep.equal([{}, testCommand]);
        });
    });

    it('should get custom initialState and args to command handler', () => {
        const createHandlerSpy = sinon.spy(() => testEvent);

        aggregates[AGGREGATE_NAME].commands.CREATE = createHandlerSpy;
        aggregates[AGGREGATE_NAME].initialState = () => ({
            name: 'Initial name'
        });

        return execute(testCommand).then(() => {
            expect(createHandlerSpy.lastCall.args).to.be.deep.equal([
                { name: 'Initial name' },
                testCommand
            ]);
        });
    });

    it('should pass initialState and args to command handler', () => {
        const events = [
            {
                aggregateId: 'test-id',
                type: 'USER_CREATED',
                payload: { name: 'User1' }
            },
            {
                aggregateId: 'test-id-2',
                type: 'USER_CREATED'
            },
            {
                aggregateId: 'test-id',
                type: 'USER_UPDATED',
                payload: {
                    name: 'User1',
                    newName: 'User2'
                }
            }
        ];

        store = createStore({ driver: memoryEsDriver(events) });

        const createHandlerSpy = sinon.spy(() => testEvent);

        const userCreatedHandlerSpy = sinon.spy((state, event) => ({
            name: event.payload.name
        }));

        const userUpdatedHandlerSpy = sinon.spy((state, event) => ({
            name: event.payload.newName
        }));

        aggregates[AGGREGATE_NAME] = {
            eventHandlers: {
                USER_CREATED: userCreatedHandlerSpy,
                USER_UPDATED: userUpdatedHandlerSpy
            },
            commands: {
                CREATE: createHandlerSpy
            }
        };

        execute = commandHandler({ store, bus, aggregates });

        return execute(testCommand).then(() => {
            expect(createHandlerSpy.lastCall.args).to.be.deep.equal([
                { name: 'User2' },
                testCommand
            ]);

            expect(userCreatedHandlerSpy.callCount).to.be.equal(1);
            expect(userCreatedHandlerSpy.lastCall.args).to.be.deep.equal([{}, events[0]]);

            expect(userUpdatedHandlerSpy.callCount).to.be.equal(1);
            expect(userUpdatedHandlerSpy.lastCall.args).to.be.deep.equal([
                { name: 'User1' },
                events[2]
            ]);
        });
    });

    it('should return event without additional fields', () => {
        const createHandlerSpy = sinon.spy((state, args) => ({
            type: 'TEST_HANDLED',
            payload: { name: args.name }
        }));

        Object.assign(aggregates[AGGREGATE_NAME], {
            eventHandlers: {
                TEST_HANDLED: (state, event) => ({
                    name: event.name
                })
            },
            commands: {
                CREATE: createHandlerSpy
            }
        });

        return execute(testCommand).then(() => execute(testCommand)).then(() => {
            expect(createHandlerSpy.lastCall.args).to.be.deep.equal([
                { name: testCommand.name },
                testCommand
            ]);
        });
    });

    it('should handles correctly unnecessary event', () => {
        const events = [
            {
                aggregateId: 'test-id',
                type: 'USER_CREATED',
                payload: { name: 'User1' }
            },
            {
                aggregateId: 'test-id-2',
                type: 'USER_CREATED'
            },
            {
                aggregateId: 'test-id',
                type: 'USER_UPDATED',
                payload: { newName: 'User2' }
            },
            {
                aggregateId: 'test-id',
                type: 'USER_UPDATED',
                payload: { newName: 'User3' }
            }
        ];

        store = createStore({ driver: memoryEsDriver(events) });

        const createHandlerSpy = sinon.spy(() => testEvent);

        aggregates[AGGREGATE_NAME] = {
            eventHandlers: {
                USER_CREATED: (_, event) => ({ name: event.payload.name })
            },
            commands: {
                CREATE: createHandlerSpy
            }
        };

        execute = commandHandler({ store, bus, aggregates });

        return execute(testCommand).then(() => {
            expect(createHandlerSpy.lastCall.args).to.be.deep.equal([
                { name: 'User1' },
                testCommand
            ]);
        });
    });
});
<commit_msg>Use neutral name in tests<commit_after>import { expect } from 'chai';
import sinon from 'sinon';

import createStore from '../../resolve-es/src';
import memoryEsDriver from '../../resolve-es-memory/src';

import createBus from '../../resolve-bus/src';
import memoryBusDriver from '../../resolve-bus-memory/src';

import commandHandler from '../src';

describe('command', () => {
    const AGGREGATE_NAME = 'test_aggregate';
    let store;
    let bus;
    let execute;
    let aggregates;
    let testCommand;
    let testEvent;

    beforeEach(() => {
        testCommand = {
            aggregateId: 'test-id',
            aggregate: AGGREGATE_NAME,
            commandName: 'CREATE',
            payload: { name: 'Jack' }
        };

        testEvent = {
            aggregateId: 'test-id',
            type: 'USER_CREATED',
            payload: { name: 'Jack' }
        };

        aggregates = {
            [AGGREGATE_NAME]: {
                commands: {
                    CREATE: () => testEvent
                }
            }
        };

        store = createStore({ driver: memoryEsDriver() });
        bus = createBus({ driver: memoryBusDriver() });
        execute = commandHandler({ store, bus, aggregates });
    });

    it('should save and publish event', () => {
        const eventHandlerSpy = sinon.spy();
        bus.onEvent(['USER_CREATED'], eventHandlerSpy);

        return execute(testCommand).then(() => {
            expect(eventHandlerSpy.callCount).to.be.equal(1);
            expect(eventHandlerSpy.lastCall.args[0]).to.be.deep.equal(testEvent);

            const storedEvents = [];
            return store
                .loadEventsByAggregateId('test-id', event => storedEvents.push(event))
                .then(() => expect(storedEvents).to.be.deep.equal([testEvent]));
        });
    });

    it('should reject event in case of commandName absense', () => {
        delete testCommand.commandName;

        return execute(testCommand)
            .then(() => expect(false).to.be.true)
            .catch(err => expect(err).to.be.equal('commandName argument is required'));
    });

    it('should reject event in case of aggregateId absense', () => {
        delete testCommand.aggregateId;

        return execute(testCommand)
            .then(() => expect(false).to.be.true)
            .catch(err => expect(err).to.be.equal('aggregateId argument is required'));
    });

    it('should pass initialState and args to command handler', () => {
        const createHandlerSpy = sinon.spy(() => testEvent);

        aggregates[AGGREGATE_NAME].commands.CREATE = createHandlerSpy;

        return execute(testCommand).then(() => {
            expect(createHandlerSpy.lastCall.args).to.be.deep.equal([{}, testCommand]);
        });
    });

    it('should get custom initialState and args to command handler', () => {
        const createHandlerSpy = sinon.spy(() => testEvent);

        aggregates[AGGREGATE_NAME].commands.CREATE = createHandlerSpy;
        aggregates[AGGREGATE_NAME].initialState = () => ({
            name: 'Initial name'
        });

        return execute(testCommand).then(() => {
            expect(createHandlerSpy.lastCall.args).to.be.deep.equal([
                { name: 'Initial name' },
                testCommand
            ]);
        });
    });

    it('should pass initialState and args to command handler', () => {
        const events = [
            {
                aggregateId: 'test-id',
                type: 'USER_CREATED',
                payload: { name: 'User1' }
            },
            {
                aggregateId: 'test-id-2',
                type: 'USER_CREATED'
            },
            {
                aggregateId: 'test-id',
                type: 'USER_UPDATED',
                payload: {
                    name: 'User1',
                    newName: 'User2'
                }
            }
        ];

        store = createStore({ driver: memoryEsDriver(events) });

        const createHandlerSpy = sinon.spy(() => testEvent);

        const userCreatedHandlerSpy = sinon.spy((state, event) => ({
            name: event.payload.name
        }));

        const userUpdatedHandlerSpy = sinon.spy((state, event) => ({
            name: event.payload.newName
        }));

        aggregates[AGGREGATE_NAME] = {
            eventHandlers: {
                USER_CREATED: userCreatedHandlerSpy,
                USER_UPDATED: userUpdatedHandlerSpy
            },
            commands: {
                CREATE: createHandlerSpy
            }
        };

        execute = commandHandler({ store, bus, aggregates });

        return execute(testCommand).then(() => {
            expect(createHandlerSpy.lastCall.args).to.be.deep.equal([
                { name: 'User2' },
                testCommand
            ]);

            expect(userCreatedHandlerSpy.callCount).to.be.equal(1);
            expect(userCreatedHandlerSpy.lastCall.args).to.be.deep.equal([{}, events[0]]);

            expect(userUpdatedHandlerSpy.callCount).to.be.equal(1);
            expect(userUpdatedHandlerSpy.lastCall.args).to.be.deep.equal([
                { name: 'User1' },
                events[2]
            ]);
        });
    });

    it('should return event without additional fields', () => {
        const createHandlerSpy = sinon.spy((state, args) => ({
            type: 'TEST_HANDLED',
            payload: { name: args.name }
        }));

        Object.assign(aggregates[AGGREGATE_NAME], {
            eventHandlers: {
                TEST_HANDLED: (state, event) => ({
                    name: event.name
                })
            },
            commands: {
                CREATE: createHandlerSpy
            }
        });

        return execute(testCommand).then(() => execute(testCommand)).then(() => {
            expect(createHandlerSpy.lastCall.args).to.be.deep.equal([
                { name: testCommand.name },
                testCommand
            ]);
        });
    });

    it('should handles correctly unnecessary event', () => {
        const events = [
            {
                aggregateId: 'test-id',
                type: 'USER_CREATED',
                payload: { name: 'User1' }
            },
            {
                aggregateId: 'test-id-2',
                type: 'USER_CREATED'
            },
            {
                aggregateId: 'test-id',
                type: 'USER_UPDATED',
                payload: { newName: 'User2' }
            },
            {
                aggregateId: 'test-id',
                type: 'USER_UPDATED',
                payload: { newName: 'User3' }
            }
        ];

        store = createStore({ driver: memoryEsDriver(events) });

        const createHandlerSpy = sinon.spy(() => testEvent);

        aggregates[AGGREGATE_NAME] = {
            eventHandlers: {
                USER_CREATED: (_, event) => ({ name: event.payload.name })
            },
            commands: {
                CREATE: createHandlerSpy
            }
        };

        execute = commandHandler({ store, bus, aggregates });

        return execute(testCommand).then(() => {
            expect(createHandlerSpy.lastCall.args).to.be.deep.equal([
                { name: 'User1' },
                testCommand
            ]);
        });
    });
});
<|endoftext|>


<commit_before>
var stop_reading = false; //when the system is creating the blocks in Scratch, the reading of the codes stops
var detectedBarcodeMarkers = {};  //detected codes and its attributes
var codes_detected = []; // an array of the detected codes numbers
var last_codes_detected = []; // an array of the last detected codes numbers
var old_ids = [] // ids of the blocks that was created in Scratch last time

// numbers of the blocks in Scratch
var greenFlag_block = 75;
var meow_block = 39;
var drum_block = 43;
var echo_block = 49;
var no_effect_block = 51;

// codes and blocks in Scratch
var code_to_block = {0:greenFlag_block, 1:meow_block, 2:drum_block, 3:echo_block, 4: no_effect_block};

// check if the block regognized has already been added to the codes_detected list
checkBlock = function(list, item) {
  var obj;
  for (i = 0; i<list.length; i++) {
    if (list[i][0] == item[0] && list[i][1] == item[1]) {
      return 1;
    }
  }
  return -1; // the block is not in the list
}

invert_list_order = function(list) {
  var list_inverted = [];
  list_inverted.push(list[0]);
  for (var i = list.length - 1; i > 0; i--) {
    list_inverted.push(list[i]);
  }
  return list_inverted;
}

getPosition = function(pos) {
  var pos_2 = [];
  pos_2[0] = parseFloat(pos[0].toFixed(2));
  pos_2[1] = parseFloat(pos[1].toFixed(2));
  return pos_2;
}

// method to create the blocks in Scratch
createBlocksInScratch = function() {

	// loop to delete the blocks that are in Scratch Script
  if (old_ids.length != 0){
    var delete_blocks = new window.Blockly.Events.fromJson({type: Blockly.Events.DELETE,
      ids: old_ids}, workspace);
    delete_blocks.run(true);
    old_ids = [];
  }
  console.log("teste last_codes_detected ", last_codes_detected);
  last_codes_detected = invert_list_order(last_codes_detected);
	var last_id = ""; // save the last block id created in Scratch so it can be used to connect to the next one
	var codes = last_codes_detected;

	console.log("teste codes ", codes);
	// loop to create the blocks and connect them
	for (var i = 0; i < codes.length; i++) {
		var toolbox = document.getElementById('toolbox');
		var blocks = toolbox.getElementsByTagName('block');
		var blockXML = blocks[code_to_block[codes[i]]];
		var block = window.Blockly.Xml.domToBlock(blockXML, workspace);
		block.initSvg();
    old_ids.push(block.id);

		if (last_id.localeCompare("") != 0) {
			child_id = block.id;
			parent_id = last_id;
			var moveEvent = new window.Blockly.Events.fromJson({type: Blockly.Events.MOVE, blockId: child_id,
				newParentId: parent_id}, workspace);

      moveEvent.run(true); // Event to connect the blocks

		}
		last_id = block.id;
	}
	stop_reading = false;
  old_codes = codes_detected;
}
transformPosition = function(position) {

	return  new_position;
}

window.ARThreeOnLoad = function() {

	ARController.getUserMediaThreeScene({maxARVideoSize: 320, cameraParam: 'Data/camera_para-iPhone 5 rear 640x480 1.0m.dat',
	onSuccess: function(arScene, arController, arCamera) {

		arController.setPatternDetectionMode(artoolkit.AR_MATRIX_CODE_DETECTION);

		document.body.className = arController.orientation;

		var renderer = new THREE.WebGLRenderer({antialias: true});
		if (arController.orientation === 'portrait') {
			//var w = (window.innerWidth / arController.videoHeight) * arController.videoWidth;
			//var h = window.innerWidth;
			//renderer.setSize(w, h);
			//renderer.domElement.style.paddingBottom = (w-h) + 'px';
		} else {
			if (/Android|mobile|iPad|iPhone/i.test(navigator.userAgent)) {
				//renderer.setSize(window.innerWidth, (window.innerWidth / arController.videoWidth) * arController.videoHeight);
			} else {
				renderer.setSize(arController.videoWidth, arController.videoHeight);
				document.body.className += ' desktop';
			}
		}
		renderer.domElement.style.visibility = "hidden";
		renderer.setSize(0, 0);

		document.body.insertBefore(renderer.domElement, document.body.firstChild);

		arController.addEventListener('getMarker', function(ev) { // event that a marker was recognized
			var barcodeId = ev.data.marker.idMatrix;
			if (barcodeId !== -1 && stop_reading == false) {
				var transform = ev.data.matrix;
				if (!detectedBarcodeMarkers[barcodeId]) {
					detectedBarcodeMarkers[barcodeId] = {
						visible: true,
						pos: [],
						matrix: new Float32Array(16)
					}
					detectedBarcodeMarkers[barcodeId].visible = true;
          detectedBarcodeMarkers[barcodeId].pos.push(getPosition(ev.data.marker.pos));
					//detectedBarcodeMarkers[barcodeId].pos.push(ev.data.marker.pos);
					//console.log("saw a barcode marker with id", barcodeId);
					//console.log("position : ", detectedBarcodeMarkers[barcodeId].pos[0]);
					detectedBarcodeMarkers[barcodeId].matrix.set(transform);
					codes_detected.push(barcodeId);
				}
        else {
          if (checkBlock(detectedBarcodeMarkers[barcodeId].pos, getPosition(ev.data.marker.pos)) == -1) {
          //if the barcode detected was not added to the list, add it
              detectedBarcodeMarkers[barcodeId].pos.push(getPosition(ev.data.marker.pos));
              //console.log("again saw a barcode marker with id", barcodeId);
    					//console.log("position : ", detectedBarcodeMarkers[barcodeId].pos[1]);
              detectedBarcodeMarkers[barcodeId].visible = true;
              detectedBarcodeMarkers[barcodeId].matrix.set(transform);
    					codes_detected.push(barcodeId);
          }
        }
        // restart the list with the first block, the green flag
				if (detectedBarcodeMarkers[0] && barcodeId == 0) {
					detectedBarcodeMarkers = {};
          last_codes_detected = codes_detected;
					codes_detected = [];
					detectedBarcodeMarkers[barcodeId] = {
						visible: true,
						pos: [],
						matrix: new Float32Array(16)
					}
					detectedBarcodeMarkers[barcodeId].visible = true;
          detectedBarcodeMarkers[barcodeId].pos.push(getPosition(ev.data.marker.pos));
					//detectedBarcodeMarkers[barcodeId].pos.push(ev.data.marker.pos);
					detectedBarcodeMarkers[barcodeId].matrix.set(transform);
					codes_detected.push(barcodeId);
				}

			}
		});
		var tick = function() {
			arScene.process();
			arScene.renderOn(renderer);
			requestAnimationFrame(tick);
		};
		tick();
	}});
	delete window.ARThreeOnLoad;
};

if (window.ARController && ARController.getUserMediaThreeScene) {
	ARThreeOnLoad();
}
<commit_msg>using a code as a green flag button - first try<commit_after>
var stop_reading = false; //when the system is creating the blocks in Scratch, the reading of the codes stops
var detectedBarcodeMarkers = {};  //detected codes and its attributes
var codes_detected = []; // an array of the detected codes numbers
var last_codes_detected = []; // an array of the last detected codes numbers
var old_ids = [] // ids of the blocks that was created in Scratch last time

// numbers of the blocks in Scratch
var greenFlag_block = 75;
var meow_block = 39;
var drum_block = 43;
var echo_block = 49;
var no_effect_block = 51;

// codes and blocks in Scratch
var code_to_block = {0:greenFlag_block, 1:meow_block, 2:drum_block, 3:echo_block, 4: no_effect_block};

// code 4 -> green flag button TO TEST!
var green_flag_count = 0;

// check if the block regognized has already been added to the codes_detected list
checkBlock = function(list, item) {
  var obj;
  for (i = 0; i<list.length; i++) {
    if (list[i][0] == item[0] && list[i][1] == item[1]) {
      return 1;
    }
  }
  return -1; // the block is not in the list
}

invert_list_order = function(list) {
  var list_inverted = [];
  list_inverted.push(list[0]);
  for (var i = list.length - 1; i > 0; i--) {
    list_inverted.push(list[i]);
  }
  return list_inverted;
}

getPosition = function(pos) {
  var pos_2 = [];
  pos_2[0] = parseFloat(pos[0].toFixed(2));
  pos_2[1] = parseFloat(pos[1].toFixed(2));
  return pos_2;
}

// method to create the blocks in Scratch
createBlocksInScratch = function() {

	// loop to delete the blocks that are in Scratch Script
  if (old_ids.length != 0){
    var delete_blocks = new window.Blockly.Events.fromJson({type: Blockly.Events.DELETE,
      ids: old_ids}, workspace);
    delete_blocks.run(true);
    old_ids = [];
  }
  console.log("teste last_codes_detected ", last_codes_detected);
  last_codes_detected = invert_list_order(last_codes_detected);
	var last_id = ""; // save the last block id created in Scratch so it can be used to connect to the next one
	var codes = last_codes_detected;

	console.log("teste codes ", codes);
	// loop to create the blocks and connect them
	for (var i = 0; i < codes.length; i++) {
		var toolbox = document.getElementById('toolbox');
		var blocks = toolbox.getElementsByTagName('block');
		var blockXML = blocks[code_to_block[codes[i]]];
		var block = window.Blockly.Xml.domToBlock(blockXML, workspace);
		block.initSvg();
    old_ids.push(block.id);

		if (last_id.localeCompare("") != 0) {
			child_id = block.id;
			parent_id = last_id;
			var moveEvent = new window.Blockly.Events.fromJson({type: Blockly.Events.MOVE, blockId: child_id,
				newParentId: parent_id}, workspace);

      moveEvent.run(true); // Event to connect the blocks

		}
		last_id = block.id;
	}
	stop_reading = false;
  old_codes = codes_detected;
}
transformPosition = function(position) {

	return  new_position;
}

window.ARThreeOnLoad = function() {

	ARController.getUserMediaThreeScene({maxARVideoSize: 320, cameraParam: 'Data/camera_para-iPhone 5 rear 640x480 1.0m.dat',
	onSuccess: function(arScene, arController, arCamera) {

		arController.setPatternDetectionMode(artoolkit.AR_MATRIX_CODE_DETECTION);

		document.body.className = arController.orientation;

		var renderer = new THREE.WebGLRenderer({antialias: true});
		if (arController.orientation === 'portrait') {
			//var w = (window.innerWidth / arController.videoHeight) * arController.videoWidth;
			//var h = window.innerWidth;
			//renderer.setSize(w, h);
			//renderer.domElement.style.paddingBottom = (w-h) + 'px';
		} else {
			if (/Android|mobile|iPad|iPhone/i.test(navigator.userAgent)) {
				//renderer.setSize(window.innerWidth, (window.innerWidth / arController.videoWidth) * arController.videoHeight);
			} else {
				renderer.setSize(arController.videoWidth, arController.videoHeight);
				document.body.className += ' desktop';
			}
		}
		renderer.domElement.style.visibility = "hidden";
		renderer.setSize(0, 0);

		document.body.insertBefore(renderer.domElement, document.body.firstChild);

		arController.addEventListener('getMarker', function(ev) { // event that a marker was recognized

      if (last_codes_detected.length > 0) {
        if (last_codes_detected.indexOf(4) == -1 && green_flag_count < 1000) {
          green_flag_count ++;
        }
        else {
          green_flag_count = 0;
        }
        if (green_flag_count == 1000) {
          vm.greenFlag();
        }
      }

			var barcodeId = ev.data.marker.idMatrix;
			if (barcodeId !== -1 && stop_reading == false) {
				var transform = ev.data.matrix;
				if (!detectedBarcodeMarkers[barcodeId]) {
					detectedBarcodeMarkers[barcodeId] = {
						visible: true,
						pos: [],
						matrix: new Float32Array(16)
					}
					detectedBarcodeMarkers[barcodeId].visible = true;
          detectedBarcodeMarkers[barcodeId].pos.push(getPosition(ev.data.marker.pos));
					//detectedBarcodeMarkers[barcodeId].pos.push(ev.data.marker.pos);
					//console.log("saw a barcode marker with id", barcodeId);
					//console.log("position : ", detectedBarcodeMarkers[barcodeId].pos[0]);
					detectedBarcodeMarkers[barcodeId].matrix.set(transform);
					codes_detected.push(barcodeId);
				}
        else {
          if (checkBlock(detectedBarcodeMarkers[barcodeId].pos, getPosition(ev.data.marker.pos)) == -1) {
          //if the barcode detected was not added to the list, add it
              detectedBarcodeMarkers[barcodeId].pos.push(getPosition(ev.data.marker.pos));
              //console.log("again saw a barcode marker with id", barcodeId);
    					//console.log("position : ", detectedBarcodeMarkers[barcodeId].pos[1]);
              detectedBarcodeMarkers[barcodeId].visible = true;
              detectedBarcodeMarkers[barcodeId].matrix.set(transform);
    					codes_detected.push(barcodeId);
          }
        }
        // restart the list with the first block, the green flag
				if (detectedBarcodeMarkers[0] && barcodeId == 0) {
					detectedBarcodeMarkers = {};
          last_codes_detected = codes_detected;
					codes_detected = [];
					detectedBarcodeMarkers[barcodeId] = {
						visible: true,
						pos: [],
						matrix: new Float32Array(16)
					}
					detectedBarcodeMarkers[barcodeId].visible = true;
          detectedBarcodeMarkers[barcodeId].pos.push(getPosition(ev.data.marker.pos));
					//detectedBarcodeMarkers[barcodeId].pos.push(ev.data.marker.pos);
					detectedBarcodeMarkers[barcodeId].matrix.set(transform);
					codes_detected.push(barcodeId);
				}

			}
		});
		var tick = function() {
			arScene.process();
			arScene.renderOn(renderer);
			requestAnimationFrame(tick);
		};
		tick();
	}});
	delete window.ARThreeOnLoad;
};

if (window.ARController && ARController.getUserMediaThreeScene) {
	ARThreeOnLoad();
}
<|endoftext|>


<commit_before>function showFooter() {
	var optionsView = null;
	var view = Ti.UI.createView({
		backgroundColor:'#F2F2F2',
		height:40,
		bottom:0,
		zIndex:1000
	});
	var tools = Ti.UI.createView({
		height:40,
		width:50,
		left:0
	});
	tools.add(Ti.UI.createImageView({image:'images/tools_dark.png'}));

	var ok = Ti.UI.createView({height:40,width:50,left:0,opacity:0});
	ok.add(Ti.UI.createLabel({text:L('ok')}));

	tools.addEventListener('singletap', function() {
		//alert('Deshabilitado temporalmente...');
		optionsView = options(optionsView, tools, ok);
	});
	ok.addEventListener('singletap', function() {
		optionsView = options(optionsView, tools, ok);
	});

	var takePhoto = Ti.UI.createView({
		height:40,
		//width:50,
		right:0,
		borderWidth:1,
		borderColor:'#333',
		layout:'horizontal'
	});
	takePhoto.add(Ti.UI.createImageView({left:10, image:'images/camera.png'}));
	takePhoto.add(Ti.UI.createLabel({left:10, color:'#333', text:L('take_photo')}));
	takePhoto.addEventListener('singletap', function() {
		var dialog = Ti.UI.createOptionDialog({
			title: L('txt_get_image'),
			options: [L('txt_camara'), L('txt_galeria'), L('txt_cancelar')],
			cancel:2
		});
		dialog.show();

		dialog.addEventListener('click', function(e) {
			if (e.index == 0) {
				camera();
			} else if (e.index == 1) {
				gallery();
			}
		});
	});

	var menu = Ti.UI.createView({
		height:40,
		width:150,
		right:0
	});
	menu.add(Ti.UI.createLabel({text:L('menu')});
	menu.addEventListener('click', function(e) {
		var submenu = Ti.UI.createView({
			width:150,
			height:250,
			bottom:-300,
			backgrounColor:'#F2F2F2',
			zIndex:100,
			layout:'vertical'
		});
		submenu.add(takePhoto);
		win.add(submenu);
		submenu.animate({bottom:40{);
	});

	var separator = Ti.UI.createView({
		width:1,
		backgroundColor:'#BFB8B2',
		left:50
	});
	var separator2 = Ti.UI.createView({
		width:1,
		backgroundColor:'#BFB8B2',
		right:150
	});

	view.add(separator);
	view.add(tools);
	view.add(ok);
	view.add(separator2);
	//view.add(takePhoto);
	view.add(menu);

	win.add(view);

	view._tools = tools;
	view._ok = ok;

	function gallery() {
		Ti.Media.openPhotoGallery({
			mediaType:[Ti.Media.MEDIA_TYPE_PHOTO],
			success: function(e) {
				success(e);
			},
			cancel: function() {

			},
			error: function(e) {
				var error = Ti.UI.createAlertDialog({
					ok:L('ok'),
					title:L('txt_error'),
					message:L('gallery_error')
				});
				error.show();
			},
			allowEditing:false
		})
	}

	function camera() {
		Ti.Media.showCamera({
			mediaTypes:[Ti.Media.MEDIA_TYPE_PHOTO],
			success: function(e) {
				success(e);
			},
			cancel: function() {

			},
			error: function(e) {
				var error = Ti.UI.createAlertDialog({
					ok:L('ok'),
					title:L('txt_error'),
					message:L('camera_error')
				});
				error.show();
			},
			allowEditing:false,
			saveToPhotoGallery:true
		});
	}

	function success(e) {
		if (Ti.App.Properties.getBool('registered')) {
			var uploading = require('uploading');
			uploading(win, e, false);
		} else {
			var register = Ti.UI.createWindow({url:'register.js'});
			register.e = e;
			register.left = 400;
			register.open({left:0});
		}
	}

	return view;
}
module.exports = showFooter;
<commit_msg>cambios<commit_after>function showFooter() {
	var optionsView = null;
	var view = Ti.UI.createView({
		backgroundColor:'#F2F2F2',
		height:40,
		bottom:0,
		zIndex:1000
	});
	var tools = Ti.UI.createView({
		height:40,
		width:50,
		left:0
	});
	tools.add(Ti.UI.createImageView({image:'images/tools_dark.png'}));

	var ok = Ti.UI.createView({height:40,width:50,left:0,opacity:0});
	ok.add(Ti.UI.createLabel({text:L('ok')}));

	tools.addEventListener('singletap', function() {
		//alert('Deshabilitado temporalmente...');
		optionsView = options(optionsView, tools, ok);
	});
	ok.addEventListener('singletap', function() {
		optionsView = options(optionsView, tools, ok);
	});

	var takePhoto = Ti.UI.createView({
		height:40,
		//width:50,
		right:0,
		borderWidth:1,
		borderColor:'#333',
		layout:'horizontal'
	});
	takePhoto.add(Ti.UI.createImageView({left:10, image:'images/camera.png'}));
	takePhoto.add(Ti.UI.createLabel({left:10, color:'#333', text:L('take_photo')}));
	takePhoto.addEventListener('singletap', function() {
		var dialog = Ti.UI.createOptionDialog({
			title: L('txt_get_image'),
			options: [L('txt_camara'), L('txt_galeria'), L('txt_cancelar')],
			cancel:2
		});
		dialog.show();

		dialog.addEventListener('click', function(e) {
			if (e.index == 0) {
				camera();
			} else if (e.index == 1) {
				gallery();
			}
		});
	});

	var menu = Ti.UI.createView({
		height:40,
		width:100,
		right:0
	});
	menu.add(Ti.UI.createLabel({text:L('menu')}));
	menu.addEventListener('click', function(e) {
		var submenu = Ti.UI.createView({
			right:0,
			width:150,
			height:250,
			bottom:-300,
			backgroundColor:'#F2F2F2',
			zIndex:100,
			layout:'vertical'
		});
		submenu.add(takePhoto);
		win.add(submenu);
		submenu.animate({bottom:40});
	});

	var separator = Ti.UI.createView({
		width:1,
		backgroundColor:'#BFB8B2',
		left:50
	});
	var separator2 = Ti.UI.createView({
		width:1,
		backgroundColor:'#BFB8B2',
		right:100
	});

	view.add(separator);
	view.add(tools);
	view.add(ok);
	view.add(separator2);
	//view.add(takePhoto);
	view.add(menu);

	win.add(view);

	view._tools = tools;
	view._ok = ok;

	function gallery() {
		Ti.Media.openPhotoGallery({
			mediaType:[Ti.Media.MEDIA_TYPE_PHOTO],
			success: function(e) {
				success(e);
			},
			cancel: function() {

			},
			error: function(e) {
				var error = Ti.UI.createAlertDialog({
					ok:L('ok'),
					title:L('txt_error'),
					message:L('gallery_error')
				});
				error.show();
			},
			allowEditing:false
		})
	}

	function camera() {
		Ti.Media.showCamera({
			mediaTypes:[Ti.Media.MEDIA_TYPE_PHOTO],
			success: function(e) {
				success(e);
			},
			cancel: function() {

			},
			error: function(e) {
				var error = Ti.UI.createAlertDialog({
					ok:L('ok'),
					title:L('txt_error'),
					message:L('camera_error')
				});
				error.show();
			},
			allowEditing:false,
			saveToPhotoGallery:true
		});
	}

	function success(e) {
		if (Ti.App.Properties.getBool('registered')) {
			var uploading = require('uploading');
			uploading(win, e, false);
		} else {
			var register = Ti.UI.createWindow({url:'register.js'});
			register.e = e;
			register.left = 400;
			register.open({left:0});
		}
	}

	return view;
}
module.exports = showFooter;
<|endoftext|>


<commit_before>
var fs = require('fs');
var path = require('path');
var sweet = require('sweet.js');
var uglify = require('uglify-js');
var ki = require('./ki.js');

// TODO: add option to build a version of ki macros that includes modules

var argv = require("optimist")
  .usage("Usage: ki [options] path/to/file.js")
  .alias('o', 'output')
  .describe('o', 'output file path')
  .alias('i', 'include')
  .describe('i', 'include code from file (relative path or npm package)')
  .alias('w', 'watch')
  .describe('w', 'watch a file')
  .boolean('watch')
  .alias('s', 'sourceMap')
  .describe('s', 'generate a sourceMap')
  .boolean('sourceMap')
  .alias('u', 'minify')
  .describe('u', 'minify output code using UglifyJS2')
  .boolean('minify')
  .alias('c', 'compress')
  .describe('c', 'compress and optimize during minify (default true if minify)')
  .boolean('compress')
  .alias('a', 'autonomous')
  .describe('a', 'embed mori in the output')
  .boolean('autonomous')
  .argv;

exports.run = function() {
  var infile = argv._[0];
  var outfile = argv.output;
  var watch = argv.watch;
  var sourceMap = argv.sourceMap;
  var minify = argv.minify;
  var compress = argv.compress;
  var autonomous = argv.autonomous;
  compress = compress ? {} : false;

  var file;
  if (infile) {
    file = fs.readFileSync(infile, 'utf8');
  }
  else if (argv._.length === 0) {
    console.log(require("optimist").help());
    return 0;
  }

  var ki_core = fs.readFileSync(path.join(path.dirname(fs.realpathSync(__filename)),'../macros/index.js'), 'utf8');

  var moriSrc;
  if (autonomous) {
    moriSrc = fs.readFileSync(path.join(path.dirname(fs.realpathSync(__filename)),'../node_modules/mori/mori.js'), 'utf8');
  }

  var parseIncludedRules = function(includes) {
    var includeFiles = includes.map(function(path) {
      return fs.readFileSync(path, 'utf8');
    });

    var rules = includeFiles.map(function(includeFile) {
      return ki.parseMacros(includeFile);
    });

    return rules;
  }

  var includes = (typeof argv.include === 'string' ? [argv.include] : argv.include) || [];

  var rules = parseIncludedRules(includes);

  var module = ki.joinModule(file,ki_core,rules);

  var mapfile = path.basename(outfile) + '.map';

  var options = {
    filename: infile,
    modules: sweet.loadModule(module),
    sourceMap: sourceMap,
    mapfile: mapfile,
    minify: minify,
    compress: compress,
    outfile: outfile,
    rules: rules
  };

  var compile = function(src, options, uglify, fs) {
    try {
      var result = ki.compile(src,options,uglify,fs);
      if (autonomous) {
        result.code += 'var mori = ' + moriSrc;
      }
      if (options.outfile) {
        fs.writeFileSync(options.outfile,result.code,'utf8');
        if (options.sourceMap) {
          fs.writeFileSync(options.mapfile,result.sourceMap,'utf8');
        }
      }
      else {
        console.log(result.code);
      }
      return 0;
    }
    catch (e) {
      console.error(e);
      return 1;
    }
  }

  var ret = compile(file,options,uglify,fs);

  var onFileChanged = function() {
    var src = fs.readFileSync(infile, 'utf8');
    var newModule = ki.joinModule(src,ki_core,rules);
    if (module != newModule) {
      module = newModule;
      options.modules = sweet.loadModule(module);
    }
    try {
      if(compile(src,options,uglify,fs) === 0) {
        console.log('Compiled', infile);
      }
      else {
        console.error('Failed to compile', infile);
      }
    }
    catch (e) {
      console.error(e);
    }
  }

  var onIncludedFileChanged = function() {
    try {
      rules = parseIncludedRules(includes);
    }
    catch (e) {
      console.error("Failed to parse", includes);
      return; // don't compile if parseIncludedRules throws
    }
    var module = ki.joinModule(file,ki_core,rules);
    options.modules = sweet.loadModule(module);
    onFileChanged();
  }

  if (watch) {
    var watchOptions = {
      persistent: true,
      interval: 1000
    };
    fs.watchFile(infile, watchOptions, onFileChanged);
    var includeFiles = includes.forEach(function(path) {
      fs.watchFile(path, watchOptions, onIncludedFileChanged);
    });
    return -1;
  }

  return ret;
}
<commit_msg>parseIncludedRules throws if it fails to parse the rules of an included file, so we can console.error a specific path rather than an array of all the includes<commit_after>
var fs = require('fs');
var path = require('path');
var sweet = require('sweet.js');
var uglify = require('uglify-js');
var ki = require('./ki.js');

// TODO: add option to build a version of ki macros that includes modules

var argv = require("optimist")
  .usage("Usage: ki [options] path/to/file.js")
  .alias('o', 'output')
  .describe('o', 'output file path')
  .alias('i', 'include')
  .describe('i', 'include code from file (relative path or npm package)')
  .alias('w', 'watch')
  .describe('w', 'watch a file')
  .boolean('watch')
  .alias('s', 'sourceMap')
  .describe('s', 'generate a sourceMap')
  .boolean('sourceMap')
  .alias('u', 'minify')
  .describe('u', 'minify output code using UglifyJS2')
  .boolean('minify')
  .alias('c', 'compress')
  .describe('c', 'compress and optimize during minify (default true if minify)')
  .boolean('compress')
  .alias('a', 'autonomous')
  .describe('a', 'embed mori in the output')
  .boolean('autonomous')
  .argv;

exports.run = function() {
  var infile = argv._[0];
  var outfile = argv.output;
  var watch = argv.watch;
  var sourceMap = argv.sourceMap;
  var minify = argv.minify;
  var compress = argv.compress;
  var autonomous = argv.autonomous;
  compress = compress ? {} : false;

  var file;
  if (infile) {
    file = fs.readFileSync(infile, 'utf8');
  }
  else if (argv._.length === 0) {
    console.log(require("optimist").help());
    return 0;
  }

  var ki_core = fs.readFileSync(path.join(path.dirname(fs.realpathSync(__filename)),'../macros/index.js'), 'utf8');

  var moriSrc;
  if (autonomous) {
    moriSrc = fs.readFileSync(path.join(path.dirname(fs.realpathSync(__filename)),'../node_modules/mori/mori.js'), 'utf8');
  }

  var parseIncludedRules = function(includes) {
    var includeFiles = includes.map(function(path) {
      return {"path": path, "file": fs.readFileSync(path, 'utf8')};
    });

    var rules = includeFiles.map(function(include) {
      try {
        return ki.parseMacros(include.file);
      }
      catch (e) {
        // console.error(e); // not useful error, so far
        throw new Error("Failed to parse " + include.path);
      }
    });

    return rules;
  }

  var includes = (typeof argv.include === 'string' ? [argv.include] : argv.include) || [];

  var rules = parseIncludedRules(includes);

  var module = ki.joinModule(file,ki_core,rules);

  var mapfile = path.basename(outfile) + '.map';

  var options = {
    filename: infile,
    modules: sweet.loadModule(module),
    sourceMap: sourceMap,
    mapfile: mapfile,
    minify: minify,
    compress: compress,
    outfile: outfile,
    rules: rules
  };

  var compile = function(src, options, uglify, fs) {
    try {
      var result = ki.compile(src,options,uglify,fs);
      if (autonomous) {
        result.code += 'var mori = ' + moriSrc;
      }
      if (options.outfile) {
        fs.writeFileSync(options.outfile,result.code,'utf8');
        if (options.sourceMap) {
          fs.writeFileSync(options.mapfile,result.sourceMap,'utf8');
        }
      }
      else {
        console.log(result.code);
      }
      return 0;
    }
    catch (e) {
      console.error(e);
      return 1;
    }
  }

  var ret = compile(file,options,uglify,fs);

  var onFileChanged = function() {
    var src = fs.readFileSync(infile, 'utf8');
    var newModule = ki.joinModule(src,ki_core,rules);
    if (module != newModule) {
      module = newModule;
      options.modules = sweet.loadModule(module);
    }
    try {
      if(compile(src,options,uglify,fs) === 0) {
        console.log('Compiled', infile);
      }
      else {
        console.error('Failed to compile', infile);
      }
    }
    catch (e) {
      console.error(e);
    }
  }

  var onIncludedFileChanged = function() {
    try {
      rules = parseIncludedRules(includes);
    }
    catch (e) {
      console.error(e);
      return; // don't compile if parseIncludedRules throws
    }
    var module = ki.joinModule(file,ki_core,rules);
    options.modules = sweet.loadModule(module);
    onFileChanged();
  }

  if (watch) {
    var watchOptions = {
      persistent: true,
      interval: 1000
    };
    fs.watchFile(infile, watchOptions, onFileChanged);
    var includeFiles = includes.forEach(function(path) {
      fs.watchFile(path, watchOptions, onIncludedFileChanged);
    });
    return -1;
  }

  return ret;
}
<|endoftext|>


<commit_before>/*
 * layout
 * https://github.com/twolfson/layout
 *
 * Copyright (c) 2012 Todd Wolfson
 * Licensed under the MIT license.
 */

exports.awesome = function() {
  return 'awesome';
};

/**
 * Method to add new algorithms via
 * @param {String} name Name of algorithm
 * @param {Function} algorithm Algorithm to bind under name
 */
function addAlgorithm(name, algorithm) {
  // Save the algorithm to algorithms
  algorithms[name] = algorithm;
}
// Make algorithms easier to add
Spritesmith.addAlgorithm = addAlgorithm;
Spritesmith.algorithms = algorithms;

// Add default algorithms
addAlgorithm('top-down', require('./algorithms/top-down.algorithm.js'));
addAlgorithm('bottom-up', require('./algorithms/bottom-up.algorithm.js'));
addAlgorithm('left-right', require('./algorithms/left-right.algorithm.js'));
addAlgorithm('right-left', require('./algorithms/right-left.algorithm.js'));
addAlgorithm('diagonal', require('./algorithms/diagonal.algorithm.js'));
addAlgorithm('negative-diagonal', require('./algorithms/negative-diagonal.algorithm.js'));
addAlgorithm('reverse-diagonal', require('./algorithms/reverse-diagonal.algorithm.js'));<commit_msg>Converted layout to decent API<commit_after>// Load in packing.smith (from spritesmith) and create algorithm store
var PackingSmith = require('./smiths/packing.smith.js'),
    algorithms = {};

/**
 * Layout adds items in an algorithmic fashion
 * @constructor
 * @param {String|Function} [algorithm="top-down"] Name of algorithm or custom algorithm to use
 * Algorithms available: top-down, bottom-up, left-right, right-left, diagonal, negative-diagonal, reverse-diagonal
 */
function Layout(algorithmName) {
  // Save the algorithmName as our algorithm (assume function)
  var algorithm = algorithmName || 'top-down';

  // If the algorithm is a string, look it up
  if (typeof algorithm === 'string') {
    algorithm = algorithms[algorithmName];

    // Assert that the algorithm was found
    assert(algorithm, 'Sorry, the \'' + algorithmName +'\' algorithm could not be loaded.');
  }

  // Create a new PackingSmith with our algorithm and return
  var retSmith = new PackingSmith(algorithm);
  return retSmith;
}

/**
 * Method to add new algorithms via
 * @param {String} name Name of algorithm
 * @param {Function} algorithm Algorithm to bind under name
 */
function addAlgorithm(name, algorithm) {
  // Save the algorithm to algorithms
  algorithms[name] = algorithm;
}
// Make algorithms easier to add and expose them
Layout.addAlgorithm = addAlgorithm;
Layout.algorithms = algorithms;

// Add default algorithms
addAlgorithm('top-down', require('./algorithms/top-down.algorithm.js'));
addAlgorithm('bottom-up', require('./algorithms/bottom-up.algorithm.js'));
addAlgorithm('left-right', require('./algorithms/left-right.algorithm.js'));
addAlgorithm('right-left', require('./algorithms/right-left.algorithm.js'));
addAlgorithm('diagonal', require('./algorithms/diagonal.algorithm.js'));
addAlgorithm('negative-diagonal', require('./algorithms/negative-diagonal.algorithm.js'));
addAlgorithm('reverse-diagonal', require('./algorithms/reverse-diagonal.algorithm.js'));

// Expose Layout to the outside
module.exports = Layout;<|endoftext|>


<commit_before>'use strict';

var Container = require('./container');

var container = require('./layout/container');
var resizer = require('./layout/resizer');

var defaults = require('./layout/options');

/**
 * The Layout view
 * @class
 */
class Layout extends Container {

	/**
	 * [constructor description]
	 * @param  {Object} options The class options
	 * @return {Object} The class instance
	 */
	constructor(options){
		super(options);

		this.init(options);
		this.build();

		return this;
	}

	/**
	 * initiate class
	 * @param  {Object} options The class options
	 * @return {Object} The class instance
	 */
	init(options) {
		super.init(options);
		this.name = 'layout',
		// merge options
		this.options = [ defaults, options ].reduce(Object.assign, {});

		Object.assign(this, container);
		Object.assign(this, resizer);

		return this;
	}

	/**
	 * Build
	 * @return {Object} [description]
	 */
	build(options) {
		super.build(options);

		var opts = this.options;

		this.addClass('ui-layout');
		this.addClass('layout-standard');
		this.addClass('ui-container');

		if (opts.node && opts.node._node)
			this.addClass('layout-' + opts.node._name);

		if (this.options.theme)
			this.addClass('theme-' + this.options.theme);

		var node = opts.node;
		this.settings = opts.settings || {};
		// this.component = {};
		// this.components = [];
		this.resizer = {};

		node.container = this.c.body;

		this.render(node);

		return this;
	}

	/**
	 * [_process description]
	 * @param  {Object} node Layout structure
	 * @return {string} type type of node e. tab
	 */
	render(node, type, level) {

		var list = node._list || [];
			level = level++ || 1;

		if (type !== 'tab') {
			this._initFlexDirection(node.container, node._axis);
		}

		for (var i = 0, len = list.length; i < list.length; i++) {
			var name = list[i];
			var comp = node[name] || {};

			comp.clss = comp.clss || this.options.clss;
			comp.opts = comp.opts || {};
			comp.opts.name = name;
			comp.opts.position = i + 1;
			comp.opts.nComp = list.length;

			if (name === "navi")
				comp.opts.useUnderlay = true;

			if (i === list.length - 1) {
				comp.opts.last = true;
			}

			if (type !== 'tab') {
				comp.opts.container = node.container;
			}

			var component = this._initContainer(comp);

			if (type === 'tab') {
				component.options.noResizer = true;
				node.container.addTab(component);
			}

			component.element.addClass('container-'+name);

			if (comp.node) {
				comp.node.container = component;

				if (component.options.clss === 'tab') {
					this.render(comp.node, 'tab', level);
				} else {
					this.render(comp.node, null, level);
				}
			}
		}
	}

	/**
	 * [_initFlexDirection description]
	 * @param  {Element} container Init direction for the given container
	 * @param  {string} axis (x,y)
	 */
	_initFlexDirection(container, axis) {
		if (!container) return;

		axis = axis || 'x';

		if (axis === 'x') {
			container.addClass('flex-raw');
		} else if (axis === 'y') {
			container.addClass('flex-column');
		}
	}
}

module.exports = Layout;
<commit_msg>ude parent constructor<commit_after>'use strict';

var Container = require('./container');

var container = require('./layout/container');
var resizer = require('./layout/resizer');

var defaults = require('./layout/options');

/**
 * The Layout view
 * @class
 */
class Layout extends Container {

	/**
	 * initiate class
	 * @param  {Object} options The class options
	 * @return {Object} The class instance
	 */
	init(options) {
		super.init(options);
		this.name = 'layout',
		// merge options
		this.options = [ defaults, options ].reduce(Object.assign, {});

		Object.assign(this, container);
		Object.assign(this, resizer);

		return this;
	}

	/**
	 * Build
	 * @return {Object} [description]
	 */
	build(options) {
		super.build(options);

		var opts = this.options;

		this.addClass('ui-layout');
		this.addClass('layout-standard');
		this.addClass('ui-container');

		if (opts.node && opts.node._node)
			this.addClass('layout-' + opts.node._name);

		if (this.options.theme)
			this.addClass('theme-' + this.options.theme);

		var node = opts.node;
		this.settings = opts.settings || {};
		// this.component = {};
		// this.components = [];
		this.resizer = {};

		node.container = this.c.body;

		this.render(node);

		return this;
	}

	/**
	 * [_process description]
	 * @param  {Object} node Layout structure
	 * @return {string} type type of node e. tab
	 */
	render(node, type, level) {
		var list = node._list || [];
			level = level++ || 1;

		if (type !== 'tab') {
			this._initFlexDirection(node.container, node._axis);
		}

		for (var i = 0, len = list.length; i < len; i++) {
			var name = list[i];
			var comp = node[name] || {};

			comp.clss = comp.clss || this.options.clss;
			comp.opts = comp.opts || {};
			comp.opts.name = name;
			comp.opts.position = i + 1;
			comp.opts.nComp = list.length;

			if (i === list.length - 1) {
				comp.opts.last = true;
			}

			if (type !== 'tab') {
				comp.opts.container = node.container;
			}

			var component = this._initContainer(comp);

			if (type === 'tab') {
				component.options.noResizer = true;
				node.container.addTab(component);
			}

			component.element.addClass('container-'+name);

			if (comp.node) {
				comp.node.container = component;

				if (component.options.clss === 'tab') {
					this.render(comp.node, 'tab', level);
				} else {
					this.render(comp.node, null, level);
				}
			}
		}
	}

	/**
	 * [_initFlexDirection description]
	 * @param  {Element} container Init direction for the given container
	 * @param  {string} axis (x,y)
	 */
	_initFlexDirection(container, axis) {
		if (!container) return;

		axis = axis || 'x';

		if (axis === 'x') {
			container.addClass('flex-raw');
		} else if (axis === 'y') {
			container.addClass('flex-column');
		}
	}
}

module.exports = Layout;
<|endoftext|>