<commit_before>import axios from 'axios'
import _ from 'lodash'
import crypto from 'crypto'
import Promise from 'bluebird'

import Provider from './base'
import Entities from './entities'

const LUIS_APP_VERSION = '1.0' // Static, we're not using this as everything is source-controlled in your bot
const LUIS_HASH_KVS_KEY = 'nlu/luis/updateMetadata'

export default class LuisProvider extends Provider {
  constructor(config) {
    super({ ...config, name: 'luis', entityKey: '@luis' })

    this.appId = this.config.luisAppId
    this.programmaticKey = this.config.luisProgrammaticKey
    this.appSecret = this.config.luisAppSecret
    this.appRegion = this.config.luisAppRegion
  }

  async init() {}

  async getRemoteVersion() {
    try {
      const res = await axios.get(
        `https://${this.appRegion}.api.cognitive.microsoft.com/luis/api/v2.0/apps/${this.appId}/versions`,
        {
          headers: {
            'Ocp-Apim-Subscription-Key': this.programmaticKey
          }
        }
      )

      return _.find(res.data, { version: LUIS_APP_VERSION })
    } catch (err) {
      this.logger.debug('[NLU::Luis] Could not fetch app versions')
      return []
    }
  }

  async deleteVersion() {
    try {
      const del = await axios.delete(
        `https://${this.appRegion}.api.cognitive.microsoft.com/luis/api/v2.0/apps/${this
          .appId}/versions/${LUIS_APP_VERSION}/`,
        {
          headers: {
            'Ocp-Apim-Subscription-Key': this.programmaticKey
          }
        }
      )

      if (del.statusCode === 200) {
        this.logger.debug('[NLU::Luis] Removed old version of the model')
      }
    } catch (err) {
      this.logger.debug('[NLU::Luis] Could not remove old version of the model')
    }
  }

  async getAppInfo() {
    try {
      const response = await axios.get(
        `https://${this.appRegion}.api.cognitive.microsoft.com/luis/api/v2.0/apps/${this.appId}`,
        {
          headers: {
            'Ocp-Apim-Subscription-Key': this.programmaticKey
          }
        }
      )
      return response.data
    } catch (err) {
      throw new Error('[NLU::Luis] Could not find app ' + this.appId)
    }
  }

  async isInSync(localIntents, remoteVersion) {
    const intentsHash = crypto
      .createHash('md5')
      .update(JSON.stringify(localIntents))
      .digest('hex')

    const metadata = await this.kvs.get(LUIS_HASH_KVS_KEY)

    return metadata && metadata.hash === intentsHash && metadata.time === remoteVersion.lastModifiedDateTime
  }

  async checkSyncNeeded() {
    const intents = await this.storage.getIntents()
    const currentVersion = await this.getRemoteVersion()
    return !await this.isInSync(intents, currentVersion)
  }

  async onSyncSuccess(localIntents, remoteVersion) {
    const intentsHash = crypto
      .createHash('md5')
      .update(JSON.stringify(localIntents))
      .digest('hex')

    await this.kvs.set(LUIS_HASH_KVS_KEY, {
      hash: intentsHash,
      time: remoteVersion.lastModifiedDateTime
    })
  }

  async getCustomEntities() {
    const { simples, composites, lists } = await this.getCustomLuisEntities()

    const mapToType = (list, type) =>
      list.map(x => ({
        name: '@custom.' + x.name,
        isFromProvider: false,
        nameProvider: x.name,
        providerType: type,
        definition: x
      }))

    return [
      ...mapToType(simples, 'entities'),
      ...mapToType(composites, 'composites'),
      ...mapToType(lists, 'closedLists')
    ]
  }

  async getCustomLuisEntities() {
    const entities = await this.storage.getCustomEntities()

    const simplesDef = _.find(entities, e => e.name.toLowerCase() === 'luis_entities')
    const compositesDef = _.find(entities, e => e.name.toLowerCase() === 'luis_composites')
    const listsDef = _.find(entities, e => e.name.toLowerCase() === 'luis_closedlists')

    const simples = (simplesDef && simplesDef.definition) || []
    const composites = (compositesDef && compositesDef.definition) || []
    const lists = (listsDef && listsDef.definition) || []

    return { simples, composites, lists }
  }

  validateCredentials() {
    const missingPattern = name =>
      '[NLU::LUIS] "' +
      name +
      '" is missing from the configuration, please have a look at botpress-nlu ' +
      'documentation to learn how to setup the credentials.'

    if (_.isEmpty(this.appId)) {
      throw new Error(missingPattern('Application Id'))
    }

    if (_.isEmpty(this.programmaticKey)) {
      throw new Error(missingPattern('Programmatic Key'))
    }

    if (_.isEmpty(this.appId)) {
      throw new Error(missingPattern('Application Secret'))
    }

    if (_.isEmpty(this.appId)) {
      throw new Error(missingPattern('Application Region'))
    }
  }

  async sync() {
    this.validateCredentials()

    const intents = await this.storage.getIntents()
    let currentVersion = await this.getRemoteVersion()

    if (await this.isInSync(intents, currentVersion)) {
      this.logger.debug('[NLU::Luis] Model is up to date')
      return
    } else {
      this.logger.debug('[NLU::Luis] The model needs to be updated')
    }

    if (currentVersion) {
      this.logger.debug('[NLU::Luis] Deleting old version of the model')
      await this.deleteVersion()
    }

    const utterances = []

    const builtinEntities = []
    const simpleEntities = []
    const compositeEntities = []
    const listEntities = []

    const availableEntities = await this.getAvailableEntities()

    intents.forEach(intent => {
      intent.utterances.forEach(utterance => {
        const extracted = this.parser.extractLabelsFromCanonical(utterance, intent.entities)
        const entities = []

        extracted.labels.forEach(label => {
          const entity = _.find(availableEntities, { name: label.type })

          if (!entity) {
            throw new Error('[NLU::Luis] Unknown entity: ' + label.type)
          }

          if (entity.isFromProvider && builtinEntities.indexOf(entity.nameProvider) === -1) {
            builtinEntities.push(entity.nameProvider)
          } else if (entity.providerType === 'entities' && !_.find(simpleEntities, { name: entity.nameProvider })) {
            simpleEntities.push(entity.definition)
          } else if (
            entity.providerType === 'composites' &&
            !_.find(compositeEntities, { name: entity.nameProvider })
          ) {
            compositeEntities.push(entity.definition)
          } else if (entity.providerType === 'closedLists' && !_.find(listEntities, { name: entity.nameProvider })) {
            listEntities.push(entity.definition)
          }

          entities.push({
            entity: entity.nameProvider,
            startPos: label.start,
            endPos: label.end
          })
        })

        utterances.push({
          text: extracted.text,
          intent: intent.name,
          entities: entities
        })
      })
    })

    const appInfo = await this.getAppInfo()

    const body = {
      luis_schema_version: '2.1.0',
      versionId: LUIS_APP_VERSION,
      name: appInfo.name,
      desc: appInfo.description,
      culture: appInfo.culture,
      intents: intents.map(i => ({ name: i.name })),
      entities: simpleEntities,
      composites: compositeEntities,
      closedLists: listEntities,
      bing_entities: builtinEntities,
      model_features: [],
      regex_features: [],
      utterances: utterances
    }

    try {
      const result = await axios.post(
        `https://${this.appRegion}.api.cognitive.microsoft.com/luis/api/v2.0/apps/${this
          .appId}/versions/import?versionId=${LUIS_APP_VERSION}`,
        body,
        {
          headers: {
            'Ocp-Apim-Subscription-Key': this.programmaticKey
          }
        }
      )

      await this.train()

      currentVersion = await this.getRemoteVersion()
      await this.onSyncSuccess(intents, currentVersion)

      this.logger.info('[NLU::Luis] Synced model [' + result.data + ']')
    } catch (err) {
      const detailedError = _.get(err, 'response.data.error.message') || (err && err.message) || err
      this.logger.error('[NLU::Luis] Could not sync the model. Error = ' + detailedError)
    }
  }

  async train() {
    let res = await axios.post(
      `https://${this.appRegion}.api.cognitive.microsoft.com/luis/api/v2.0/apps/${this
        .appId}/versions/${LUIS_APP_VERSION}/train`,
      {},
      {
        headers: {
          'Ocp-Apim-Subscription-Key': this.programmaticKey
        }
      }
    )

    if (res.data.status !== 'Queued') {
      throw new Error('Expected training to be Queued but was: ' + res.data.status)
    }

    while (true) {
      res = await axios.get(
        `https://${this.appRegion}.api.cognitive.microsoft.com/luis/api/v2.0/apps/${this
          .appId}/versions/${LUIS_APP_VERSION}/train`,
        {
          headers: {
            'Ocp-Apim-Subscription-Key': this.programmaticKey
          }
        }
      )

      const models = res.data

      const percent = (models.length - _.filter(models, m => m.details.status === 'InProgress').length) / models.length

      const error = _.find(models, m => m.details.status === 'Fail')

      if (error) {
        throw new Error(
          `[NLU::Luis] Error training model "${error.modelId}", reason is "${error.details.failureReason}"`
        )
      }

      if (percent >= 1) {
        this.logger.debug('[NLU::Luis] Model trained (100%)')
        break
      } else {
        this.logger.debug('[NLU::Luis] Training... ' + percent.toFixed(2) * 100 + '%')
      }

      await Promise.delay(1000)
    }

    await Promise.delay(1000)

    await axios.post(
      `https://${this.appRegion}.api.cognitive.microsoft.com/luis/api/v2.0/apps/${this.appId}/publish`,
      {
        versionId: LUIS_APP_VERSION,
        isStaging: !this.isProduction,
        region: this.appRegion
      },
      {
        headers: {
          'Ocp-Apim-Subscription-Key': this.programmaticKey
        }
      }
    )
  }

  async extract(incomingEvent) {
    try {
      this.validateCredentials()
    } catch (err) {
      this.logger.warn(
        '[NLU::Luis] Did not extract NLU metadata for incoming text because Luis is not configured properly.'
      )
      return {}
    }

    const res = await axios.get(`https://${this.appRegion}.api.cognitive.microsoft.com/luis/v2.0/apps/${this.appId}`, {
      params: {
        q: incomingEvent.text,
        verbose: false,
        spellCheck: false,
        staging: !this.isProduction
      },
      headers: {
        'Ocp-Apim-Subscription-Key': this.appSecret
      }
    })

    const intentName = _.get(res, 'data.topScoringIntent.intent') || 'None'
    const confidence = _.get(res, 'data.topScoringIntent.score') || 0
    const intents = _.get(res, 'data.intents') || []
    const entities = _.get(res, 'data.entities') || []

    return {
      intent: {
        name: intentName,
        confidence: parseFloat(confidence),
        provider: 'luis'
      },
      intents: intents.map(intent => ({
        name: intent.intent,
        confidence: parseFloat(intent.score),
        provider: 'luis'
      })),
      entities: entities.map(entity => ({
        name: null,
        type: entity.type,
        value: _.get(entity, 'resolution.values.0.value') || _.get(entity, 'resolution.value') || entity.entity,
        original: entity.entity,
        confidence: null,
        position: entity.startIndex,
        provider: 'luis'
      }))
    }
  }
}
<commit_msg>fix(nlu): don't sync luis if syncing in progress<commit_after>import axios from 'axios'
import _ from 'lodash'
import crypto from 'crypto'
import Promise from 'bluebird'

import Provider from './base'
import Entities from './entities'

const LUIS_APP_VERSION = '1.0' // Static, we're not using this as everything is source-controlled in your bot
const LUIS_HASH_KVS_KEY = 'nlu/luis/updateMetadata'

export default class LuisProvider extends Provider {
  constructor(config) {
    super({ ...config, name: 'luis', entityKey: '@luis' })

    this.appId = this.config.luisAppId
    this.programmaticKey = this.config.luisProgrammaticKey
    this.appSecret = this.config.luisAppSecret
    this.appRegion = this.config.luisAppRegion
  }

  async init() {}

  async getRemoteVersion() {
    try {
      const res = await axios.get(
        `https://${this.appRegion}.api.cognitive.microsoft.com/luis/api/v2.0/apps/${this.appId}/versions`,
        {
          headers: {
            'Ocp-Apim-Subscription-Key': this.programmaticKey
          }
        }
      )

      return _.find(res.data, { version: LUIS_APP_VERSION })
    } catch (err) {
      this.logger.debug('[NLU::Luis] Could not fetch app versions')
      return []
    }
  }

  async deleteVersion() {
    try {
      const del = await axios.delete(
        `https://${this.appRegion}.api.cognitive.microsoft.com/luis/api/v2.0/apps/${this
          .appId}/versions/${LUIS_APP_VERSION}/`,
        {
          headers: {
            'Ocp-Apim-Subscription-Key': this.programmaticKey
          }
        }
      )

      if (del.statusCode === 200) {
        this.logger.debug('[NLU::Luis] Removed old version of the model')
      }
    } catch (err) {
      this.logger.debug('[NLU::Luis] Could not remove old version of the model')
    }
  }

  async getAppInfo() {
    try {
      const response = await axios.get(
        `https://${this.appRegion}.api.cognitive.microsoft.com/luis/api/v2.0/apps/${this.appId}`,
        {
          headers: {
            'Ocp-Apim-Subscription-Key': this.programmaticKey
          }
        }
      )
      return response.data
    } catch (err) {
      throw new Error('[NLU::Luis] Could not find app ' + this.appId)
    }
  }

  async isInSync(localIntents, remoteVersion) {
    const intentsHash = crypto
      .createHash('md5')
      .update(JSON.stringify(localIntents))
      .digest('hex')

    const metadata = await this.kvs.get(LUIS_HASH_KVS_KEY)

    return metadata && metadata.hash === intentsHash && metadata.time === remoteVersion.lastModifiedDateTime
  }

  async checkSyncNeeded() {
    const intents = await this.storage.getIntents()
    const currentVersion = await this.getRemoteVersion()
    return !await this.isInSync(intents, currentVersion)
  }

  async onSyncSuccess(localIntents, remoteVersion) {
    const intentsHash = crypto
      .createHash('md5')
      .update(JSON.stringify(localIntents))
      .digest('hex')

    await this.kvs.set(LUIS_HASH_KVS_KEY, {
      hash: intentsHash,
      time: remoteVersion.lastModifiedDateTime
    })
  }

  async getCustomEntities() {
    const { simples, composites, lists } = await this.getCustomLuisEntities()

    const mapToType = (list, type) =>
      list.map(x => ({
        name: '@custom.' + x.name,
        isFromProvider: false,
        nameProvider: x.name,
        providerType: type,
        definition: x
      }))

    return [
      ...mapToType(simples, 'entities'),
      ...mapToType(composites, 'composites'),
      ...mapToType(lists, 'closedLists')
    ]
  }

  async getCustomLuisEntities() {
    const entities = await this.storage.getCustomEntities()

    const simplesDef = _.find(entities, e => e.name.toLowerCase() === 'luis_entities')
    const compositesDef = _.find(entities, e => e.name.toLowerCase() === 'luis_composites')
    const listsDef = _.find(entities, e => e.name.toLowerCase() === 'luis_closedlists')

    const simples = (simplesDef && simplesDef.definition) || []
    const composites = (compositesDef && compositesDef.definition) || []
    const lists = (listsDef && listsDef.definition) || []

    return { simples, composites, lists }
  }

  validateCredentials() {
    const missingPattern = name =>
      '[NLU::LUIS] "' +
      name +
      '" is missing from the configuration, please have a look at botpress-nlu ' +
      'documentation to learn how to setup the credentials.'

    if (_.isEmpty(this.appId)) {
      throw new Error(missingPattern('Application Id'))
    }

    if (_.isEmpty(this.programmaticKey)) {
      throw new Error(missingPattern('Programmatic Key'))
    }

    if (_.isEmpty(this.appId)) {
      throw new Error(missingPattern('Application Secret'))
    }

    if (_.isEmpty(this.appId)) {
      throw new Error(missingPattern('Application Region'))
    }
  }

  async sync() {
    if (this.syncingSince && new Date() - this.syncingSince <= 10 * 60 * 1000) {
      this.logger.warn('[NLU::Luis] Tried to sync while syncing in progress')
      return
    }

    this.syncingSince = new Date()

    this.validateCredentials()

    const intents = await this.storage.getIntents()
    let currentVersion = await this.getRemoteVersion()

    if (await this.isInSync(intents, currentVersion)) {
      this.logger.debug('[NLU::Luis] Model is up to date')
      return
    } else {
      this.logger.debug('[NLU::Luis] The model needs to be updated')
    }

    if (currentVersion) {
      this.logger.debug('[NLU::Luis] Deleting old version of the model')
      await this.deleteVersion()
    }

    const utterances = []

    const builtinEntities = []
    const simpleEntities = []
    const compositeEntities = []
    const listEntities = []

    const availableEntities = await this.getAvailableEntities()

    intents.forEach(intent => {
      intent.utterances.forEach(utterance => {
        const extracted = this.parser.extractLabelsFromCanonical(utterance, intent.entities)
        const entities = []

        extracted.labels.forEach(label => {
          const entity = _.find(availableEntities, { name: label.type })

          if (!entity) {
            throw new Error('[NLU::Luis] Unknown entity: ' + label.type)
          }

          if (entity.isFromProvider && builtinEntities.indexOf(entity.nameProvider) === -1) {
            builtinEntities.push(entity.nameProvider)
          } else if (entity.providerType === 'entities' && !_.find(simpleEntities, { name: entity.nameProvider })) {
            simpleEntities.push(entity.definition)
          } else if (
            entity.providerType === 'composites' &&
            !_.find(compositeEntities, { name: entity.nameProvider })
          ) {
            compositeEntities.push(entity.definition)
          } else if (entity.providerType === 'closedLists' && !_.find(listEntities, { name: entity.nameProvider })) {
            listEntities.push(entity.definition)
          }

          entities.push({
            entity: entity.nameProvider,
            startPos: label.start,
            endPos: label.end
          })
        })

        utterances.push({
          text: extracted.text,
          intent: intent.name,
          entities: entities
        })
      })
    })

    const appInfo = await this.getAppInfo()

    const body = {
      luis_schema_version: '2.1.0',
      versionId: LUIS_APP_VERSION,
      name: appInfo.name,
      desc: appInfo.description,
      culture: appInfo.culture,
      intents: intents.map(i => ({ name: i.name })),
      entities: simpleEntities,
      composites: compositeEntities,
      closedLists: listEntities,
      bing_entities: builtinEntities,
      model_features: [],
      regex_features: [],
      utterances: utterances
    }

    try {
      const result = await axios.post(
        `https://${this.appRegion}.api.cognitive.microsoft.com/luis/api/v2.0/apps/${this
          .appId}/versions/import?versionId=${LUIS_APP_VERSION}`,
        body,
        {
          headers: {
            'Ocp-Apim-Subscription-Key': this.programmaticKey
          }
        }
      )

      await this.train()

      currentVersion = await this.getRemoteVersion()
      await this.onSyncSuccess(intents, currentVersion)

      this.logger.info('[NLU::Luis] Synced model [' + result.data + ']')
    } catch (err) {
      const detailedError = _.get(err, 'response.data.error.message') || (err && err.message) || err
      this.logger.error('[NLU::Luis] Could not sync the model. Error = ' + detailedError)
    }
    this.syncingSince = null
  }

  async train() {
    let res = await axios.post(
      `https://${this.appRegion}.api.cognitive.microsoft.com/luis/api/v2.0/apps/${this
        .appId}/versions/${LUIS_APP_VERSION}/train`,
      {},
      {
        headers: {
          'Ocp-Apim-Subscription-Key': this.programmaticKey
        }
      }
    )

    if (res.data.status !== 'Queued') {
      throw new Error('Expected training to be Queued but was: ' + res.data.status)
    }

    while (true) {
      res = await axios.get(
        `https://${this.appRegion}.api.cognitive.microsoft.com/luis/api/v2.0/apps/${this
          .appId}/versions/${LUIS_APP_VERSION}/train`,
        {
          headers: {
            'Ocp-Apim-Subscription-Key': this.programmaticKey
          }
        }
      )

      const models = res.data

      const percent = (models.length - _.filter(models, m => m.details.status === 'InProgress').length) / models.length

      const error = _.find(models, m => m.details.status === 'Fail')

      if (error) {
        throw new Error(
          `[NLU::Luis] Error training model "${error.modelId}", reason is "${error.details.failureReason}"`
        )
      }

      if (percent >= 1) {
        this.logger.debug('[NLU::Luis] Model trained (100%)')
        break
      } else {
        this.logger.debug('[NLU::Luis] Training... ' + percent.toFixed(2) * 100 + '%')
      }

      await Promise.delay(1000)
    }

    await Promise.delay(1000)

    await axios.post(
      `https://${this.appRegion}.api.cognitive.microsoft.com/luis/api/v2.0/apps/${this.appId}/publish`,
      {
        versionId: LUIS_APP_VERSION,
        isStaging: !this.isProduction,
        region: this.appRegion
      },
      {
        headers: {
          'Ocp-Apim-Subscription-Key': this.programmaticKey
        }
      }
    )
  }

  async extract(incomingEvent) {
    try {
      this.validateCredentials()
    } catch (err) {
      this.logger.warn(
        '[NLU::Luis] Did not extract NLU metadata for incoming text because Luis is not configured properly.'
      )
      return {}
    }

    const res = await axios.get(`https://${this.appRegion}.api.cognitive.microsoft.com/luis/v2.0/apps/${this.appId}`, {
      params: {
        q: incomingEvent.text,
        verbose: false,
        spellCheck: false,
        staging: !this.isProduction
      },
      headers: {
        'Ocp-Apim-Subscription-Key': this.appSecret
      }
    })

    const intentName = _.get(res, 'data.topScoringIntent.intent') || 'None'
    const confidence = _.get(res, 'data.topScoringIntent.score') || 0
    const intents = _.get(res, 'data.intents') || []
    const entities = _.get(res, 'data.entities') || []

    return {
      intent: {
        name: intentName,
        confidence: parseFloat(confidence),
        provider: 'luis'
      },
      intents: intents.map(intent => ({
        name: intent.intent,
        confidence: parseFloat(intent.score),
        provider: 'luis'
      })),
      entities: entities.map(entity => ({
        name: null,
        type: entity.type,
        value: _.get(entity, 'resolution.values.0.value') || _.get(entity, 'resolution.value') || entity.entity,
        original: entity.entity,
        confidence: null,
        position: entity.startIndex,
        provider: 'luis'
      }))
    }
  }
}
<|endoftext|>


<commit_before>var util = require('../lib/test-utils.js');
var expect = require('chai').expect;

describe('fs.mknod', function() {
  beforeEach(util.setup);
  afterEach(util.cleanup);

  it('should be a function', function(done) {
    var fs = util.fs();
    expect(fs.mknod).to.be.a('function');
    done();
  });

  it('should return an error if part of the parent path does not exist', function(done) {
    var fs = util.fs();

    fs.mknod('/dir/mydir', 'DIRECTORY', function(error) {
      expect(error.code).to.equal('ENOENT');
      done();
    });
  });

  it('should return an error if path already exists', function(done) {
    var fs = util.fs();

    fs.mknod('/', 'DIRECTORY', function(error) {
      expect(error.code).to.equal('EEXIST');
      done();
    });
  });

  it('should return an error if the parent node is not a directory', function(done) {
    var fs = util.fs();

    fs.mknod('/file', 'FILE' , function(error, result) {
      if(error) throw error;
      fs.mknod('/file/myfile', 'FILE', function(error, result) {
        expect(error.code).to.equal('ENOTDIR');
        done();
      });
    });
  });

  it('should return an error if the mode provided is not DIRECTORY or FILE', function(done) {
    var fs = util.fs();

    fs.mknod('/symlink', 'SYMLINK', function(error) {
      expect(error).to.exist;
      expect(error.code).to.equal('EINVAL');
      done();
    });
  });

  it('should make a new directory', function(done) {
    var fs = util.fs();

    fs.mknod('/dir', 'DIRECTORY', function(error) {
      if(error) throw error;

      fs.stat('/dir', function(error, stats) {
        expect(error).not.to.exist;
        expect(stats.type).to.equal('DIRECTORY');
        done();
      });
    });
  });

  it('should make a new file', function(done) {
    var fs = util.fs();

    fs.mknod('/file', 'FILE' , function(error, result) {
      if(error) throw error;
      fs.stat('/file', function(error, result) {
        expect(error).not.to.exist;
        expect(result.type).to.equal('FILE');
        done();
      });
    });
  });
});
<commit_msg>Fix lint issues in fs.mknod.spec.js<commit_after>var util = require('../lib/test-utils.js');
var expect = require('chai').expect;

describe('fs.mknod', function() {
  beforeEach(util.setup);
  afterEach(util.cleanup);

  it('should be a function', function(done) {
    var fs = util.fs();
    expect(fs.mknod).to.be.a('function');
    done();
  });

  it('should return an error if part of the parent path does not exist', function(done) {
    var fs = util.fs();

    fs.mknod('/dir/mydir', 'DIRECTORY', function(error) {
      expect(error.code).to.equal('ENOENT');
      done();
    });
  });

  it('should return an error if path already exists', function(done) {
    var fs = util.fs();

    fs.mknod('/', 'DIRECTORY', function(error) {
      expect(error.code).to.equal('EEXIST');
      done();
    });
  });

  it('should return an error if the parent node is not a directory', function(done) {
    var fs = util.fs();

    fs.mknod('/file', 'FILE' , function(error) {
      if(error) throw error;

      fs.mknod('/file/myfile', 'FILE', function(error) {
        expect(error.code).to.equal('ENOTDIR');
        done();
      });
    });
  });

  it('should return an error if the mode provided is not DIRECTORY or FILE', function(done) {
    var fs = util.fs();

    fs.mknod('/symlink', 'SYMLINK', function(error) {
      expect(error).to.exist;
      expect(error.code).to.equal('EINVAL');
      done();
    });
  });

  it('should make a new directory', function(done) {
    var fs = util.fs();

    fs.mknod('/dir', 'DIRECTORY', function(error) {
      if(error) throw error;

      fs.stat('/dir', function(error, stats) {
        expect(error).not.to.exist;
        expect(stats.type).to.equal('DIRECTORY');
        done();
      });
    });
  });

  it('should make a new file', function(done) {
    var fs = util.fs();

    fs.mknod('/file', 'FILE' , function(error) {
      if(error) throw error;

      fs.stat('/file', function(error, result) {
        expect(error).not.to.exist;
        expect(result.type).to.equal('FILE');
        done();
      });
    });
  });
});
<|endoftext|>


<commit_before>var path = require('path')
var webpack = require('webpack')

const config = {
  entry: {
      example: './example/index.js',
      vendor: ['vue'],
  },
  output: {
    path: path.resolve(__dirname, "../example"),
    publicPath: '/',
    library: 'VuePortal',
    libraryTarget: 'umd',
    filename: '[name].build.js'
  },
  resolve: {
    alias: {
      'vue$': 'vue/dist/vue.common'
    }
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        loader: 'buble-loader',
        exclude: path.resolve(__dirname, "../node_modules")
      },
      {
        test: /\.css$/,
        loader: 'style-loader!css-loader',
        exclude: path.resolve(__dirname, "../node_modules")
      },
      {
        test: /\.vue$/,
        loader: 'vue-loader',
        include: path.resolve(__dirname,"../example")
      }
    ]
  },
  plugins: [
    new webpack.HotModuleReplacementPlugin(),
    new webpack.DefinePlugin({
      'process.env': {
        NODE_ENV: process.env.NODE_ENV ? JSON.stringify(process.env.NODE_ENV) : "'development'"
      }
    }),
    new webpack.optimize.CommonsChunkPlugin({
      name: 'vendor'
    })
  ],
  devtool: 'source-map',
  performance: {
    hints: false
  },
  devServer: {
    contentBase: 'example/'
  }
}

module.exports = config
<commit_msg>improve devServer output<commit_after>var path = require('path')
var webpack = require('webpack')

const config = {
  entry: {
      example: './example/index.js',
      vendor: ['vue'],
  },
  output: {
    path: path.resolve(__dirname, "../example"),
    publicPath: '/',
    library: 'VuePortal',
    libraryTarget: 'umd',
    filename: '[name].build.js'
  },
  resolve: {
    alias: {
      'vue$': 'vue/dist/vue.common'
    }
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        loader: 'buble-loader',
        exclude: path.resolve(__dirname, "../node_modules")
      },
      {
        test: /\.css$/,
        loader: 'style-loader!css-loader',
        exclude: path.resolve(__dirname, "../node_modules")
      },
      {
        test: /\.vue$/,
        loader: 'vue-loader',
        include: path.resolve(__dirname,"../example")
      }
    ]
  },
  plugins: [
    new webpack.HotModuleReplacementPlugin(),
    new webpack.DefinePlugin({
      'process.env': {
        NODE_ENV: process.env.NODE_ENV ? JSON.stringify(process.env.NODE_ENV) : "'development'"
      }
    }),
    new webpack.optimize.CommonsChunkPlugin({
      name: 'vendor'
    })
  ],
  devtool: 'source-map',
  performance: {
    hints: false
  },
  devServer: {
    contentBase: 'example/',
    inline: true,
    hot: true,
    stats: {
      colors: true,
      chunks: false
    }
  }
}

module.exports = config
<|endoftext|>


<commit_before>'use strict'

var test = require('tape').test
var memory = require('aedes-persistence')
var helper = require('./helper')
var aedes = require('../')
var setup = helper.setup
var connect = helper.connect

function willConnect (s, opts, connected) {
  opts = opts || {}
  opts.will = {
    topic: 'mywill',
    payload: Buffer.from('last will'),
    qos: 0,
    retain: false
  }

  return connect(s, opts, connected)
}

test('delivers a will', function (t) {
  t.plan(4)

  var opts = {}
  // willConnect populates opts with a will
  var s = willConnect(setup(),
    opts,
    function () {
      s.conn.destroy()
    })

  s.broker.mq.on('mywill', function (packet, cb) {
    t.equal(packet.topic, opts.will.topic, 'topic matches')
    t.deepEqual(packet.payload, opts.will.payload, 'payload matches')
    t.equal(packet.qos, opts.will.qos, 'qos matches')
    t.equal(packet.retain, opts.will.retain, 'retain matches')
    cb()
    t.end()
  })
})

test('calling close two times should not deliver two wills', function (t) {
  t.plan(4)

  var opts = {}
  var broker = aedes()

  broker.on('client', function (client) {
    client.close()
    client.close()
  })

  broker.mq.on('mywill', onWill)

  // willConnect populates opts with a will
  willConnect(setup(broker), opts)

  function onWill (packet, cb) {
    broker.mq.removeListener('mywill', onWill)
    broker.mq.on('mywill', t.fail.bind(t))
    t.equal(packet.topic, opts.will.topic, 'topic matches')
    t.deepEqual(packet.payload, opts.will.payload, 'payload matches')
    t.equal(packet.qos, opts.will.qos, 'qos matches')
    t.equal(packet.retain, opts.will.retain, 'retain matches')
    cb()
  }
})

test('delivers old will in case of a crash', function (t) {
  t.plan(7)

  var persistence = memory()
  var will = {
    topic: 'mywill',
    payload: Buffer.from('last will'),
    qos: 0,
    retain: false
  }

  persistence.broker = {
    id: 'anotherBroker'
  }

  persistence.putWill({
    id: 'myClientId42'
  }, will, function (err) {
    t.error(err, 'no error')

    var interval = 10 // ms, so that the will check happens fast!
    var broker = aedes({
      persistence: persistence,
      heartbeatInterval: interval
    })
    var start = Date.now()

    broker.mq.on('mywill', check)

    function check (packet, cb) {
      broker.mq.removeListener('mywill', check)
      t.ok(Date.now() - start >= 3 * interval, 'the will needs to be emitted after 3 heartbeats')
      t.equal(packet.topic, will.topic, 'topic matches')
      t.deepEqual(packet.payload, will.payload, 'payload matches')
      t.equal(packet.qos, will.qos, 'qos matches')
      t.equal(packet.retain, will.retain, 'retain matches')
      broker.mq.on('mywill', function (packet) {
        t.fail('the will must be delivered only once')
      })
      setImmediate(function () {
        broker.close(t.pass.bind(t, 'server closes'))
      })
      cb()
    }
  })
})

test('store the will in the persistence', function (t) {
  t.plan(5)

  var opts = {
    clientId: 'abcde'
  }

  // willConnect populates opts with a will
  var s = willConnect(setup(), opts)

  s.broker.on('client', function () {
    // this is connack
    s.broker.persistence.getWill({
      id: opts.clientId
    }, function (err, packet) {
      t.error(err, 'no error')
      t.deepEqual(packet.topic, opts.will.topic, 'will topic matches')
      t.deepEqual(packet.payload, opts.will.payload, 'will payload matches')
      t.deepEqual(packet.qos, opts.will.qos, 'will qos matches')
      t.deepEqual(packet.retain, opts.will.retain, 'will retain matches')
      t.end()
    })
  })
})

test('delete the will in the persistence after publish', function (t) {
  t.plan(2)

  var opts = {
    clientId: 'abcde'
  }

  var broker = aedes()

  broker.on('client', function (client) {
    setImmediate(function () {
      client.close()
    })
  })

  broker.mq.on('mywill', check)

  // willConnect populates opts with a will
  willConnect(setup(broker), opts)

  function check (packet, cb) {
    broker.mq.removeListener('mywill', check)
    setImmediate(function () {
      broker.persistence.getWill({
        id: opts.clientId
      }, function (err, p) {
        t.error(err, 'no error')
        t.notOk(p, 'packet is empty')
        t.end()
      })
    })
    cb()
  }
})

test('delivers a will with authorization', function (t) {
  t.plan(7)

  let authorized = false
  var opts = {}
  // willConnect populates opts with a will
  var s = willConnect(
    setup(aedes({ authorizePublish: (_1, _2, callback) => { authorized = true; callback(null) } })),
    opts,
    function () {
      s.conn.destroy()
    })

  s.broker.on('clientDisconnect', function (client) {
    t.equal(client.connected, false)
    t.equal(client.disconnected, true)
  })

  s.broker.mq.on('mywill', function (packet, cb) {
    t.equal(packet.topic, opts.will.topic, 'topic matches')
    t.deepEqual(packet.payload, opts.will.payload, 'payload matches')
    t.equal(packet.qos, opts.will.qos, 'qos matches')
    t.equal(packet.retain, opts.will.retain, 'retain matches')
    t.equal(authorized, true, 'authorization called')
    cb()
  })

  s.broker.on('closed', t.end.bind(t))
})

test('delivers a will waits for authorization', function (t) {
  t.plan(6)

  let authorized = false
  var opts = {}
  // willConnect populates opts with a will
  var s = willConnect(
    setup(aedes({ authorizePublish: (_1, _2, callback) => { authorized = true; setImmediate(() => { callback(null) }) } })),
    opts,
    function () {
      s.conn.destroy()
    })

  s.broker.on('clientDisconnect', function () {
    t.pass('client is disconnected')
  })

  s.broker.mq.on('mywill', function (packet, cb) {
    t.equal(packet.topic, opts.will.topic, 'topic matches')
    t.deepEqual(packet.payload, opts.will.payload, 'payload matches')
    t.equal(packet.qos, opts.will.qos, 'qos matches')
    t.equal(packet.retain, opts.will.retain, 'retain matches')
    t.equal(authorized, true, 'authorization called')
    cb()
  })

  s.broker.on('closed', t.end.bind(t))
})

test('does not deliver a will without authorization', function (t) {
  t.plan(1)

  let authorized = false
  var opts = {}
  // willConnect populates opts with a will
  var s = willConnect(
    setup(aedes({ authorizePublish: (_1, _2, callback) => { authorized = true; callback(new Error()) } })),
    opts,
    function () {
      s.conn.destroy()
    })

  s.broker.on('clientDisconnect', function () {
    t.equal(authorized, true, 'authorization called')
    t.end()
  })

  s.broker.mq.on('mywill', function (packet, cb) {
    t.fail('received will without authorization')
    cb()
  })
})

test('does not deliver a will without authentication', function (t) {
  t.plan(1)

  let authenticated = false
  var opts = {}
  // willConnect populates opts with a will
  var s = willConnect(
    setup(aedes({ authenticate: (_1, _2, _3, callback) => { authenticated = true; callback(new Error(), false) } })),
    opts)

  s.broker.once('clientError', function () {
    t.equal(authenticated, true, 'authentication called')
    t.end()
  })

  s.broker.mq.on('mywill', function (packet, cb) {
    t.fail('received will without authentication')
    cb()
  })
})

test('does not deliver will if keepalive is triggered during authentication', function (t) {
  t.plan(0)

  var opts = {}
  opts.keepalive = 1
  var broker = aedes({
    authenticate: function (c, u, p, cb) {
      setTimeout(function () {
        cb(null, true)
      }, 3000)
    }
  })

  broker.on('keepaliveTimeout', function () {
    t.end()
  })

  broker.mq.on('mywill', function (packet, cb) {
    cb()
    t.fail('Received will when it was not expected')
  })

  willConnect(setup(broker), opts)
})

// [MQTT-3.14.4-1]
test('does not deliver will when client sends a DISCONNECT', function (t) {
  t.plan(0)

  var broker = aedes()
  var s = willConnect(setup(broker), {}, function () {
    s.inStream.end({
      cmd: 'disconnect'
    })
  })

  s.broker.mq.on('mywill', function (packet, cb) {
    t.fail(packet)
  })

  broker.on('closed', t.end.bind(t))
})
<commit_msg>chore: test for multiple will with same clientid<commit_after>'use strict'

var test = require('tape').test
var memory = require('aedes-persistence')
var helper = require('./helper')
var aedes = require('../')
var setup = helper.setup
var connect = helper.connect

function willConnect (s, opts, connected) {
  opts = opts || {}
  opts.will = {
    topic: 'mywill',
    payload: Buffer.from('last will'),
    qos: 0,
    retain: false
  }

  return connect(s, opts, connected)
}

test('delivers a will', function (t) {
  t.plan(4)

  var opts = {}
  // willConnect populates opts with a will
  var s = willConnect(setup(),
    opts,
    function () {
      s.conn.destroy()
    })

  s.broker.mq.on('mywill', function (packet, cb) {
    t.equal(packet.topic, opts.will.topic, 'topic matches')
    t.deepEqual(packet.payload, opts.will.payload, 'payload matches')
    t.equal(packet.qos, opts.will.qos, 'qos matches')
    t.equal(packet.retain, opts.will.retain, 'retain matches')
    cb()
    t.end()
  })
})

test('calling close two times should not deliver two wills', function (t) {
  t.plan(4)

  var opts = {}
  var broker = aedes()

  broker.on('client', function (client) {
    client.close()
    client.close()
  })

  broker.mq.on('mywill', onWill)

  // willConnect populates opts with a will
  willConnect(setup(broker), opts)

  function onWill (packet, cb) {
    broker.mq.removeListener('mywill', onWill)
    broker.mq.on('mywill', t.fail.bind(t))
    t.equal(packet.topic, opts.will.topic, 'topic matches')
    t.deepEqual(packet.payload, opts.will.payload, 'payload matches')
    t.equal(packet.qos, opts.will.qos, 'qos matches')
    t.equal(packet.retain, opts.will.retain, 'retain matches')
    cb()
  }
})

test('delivers old will in case of a crash', function (t) {
  t.plan(7)

  var persistence = memory()
  var will = {
    topic: 'mywill',
    payload: Buffer.from('last will'),
    qos: 0,
    retain: false
  }

  persistence.broker = {
    id: 'anotherBroker'
  }

  persistence.putWill({
    id: 'myClientId42'
  }, will, function (err) {
    t.error(err, 'no error')

    var interval = 10 // ms, so that the will check happens fast!
    var broker = aedes({
      persistence: persistence,
      heartbeatInterval: interval
    })
    var start = Date.now()

    broker.mq.on('mywill', check)

    function check (packet, cb) {
      broker.mq.removeListener('mywill', check)
      t.ok(Date.now() - start >= 3 * interval, 'the will needs to be emitted after 3 heartbeats')
      t.equal(packet.topic, will.topic, 'topic matches')
      t.deepEqual(packet.payload, will.payload, 'payload matches')
      t.equal(packet.qos, will.qos, 'qos matches')
      t.equal(packet.retain, will.retain, 'retain matches')
      broker.mq.on('mywill', function (packet) {
        t.fail('the will must be delivered only once')
      })
      setImmediate(function () {
        broker.close(t.pass.bind(t, 'server closes'))
      })
      cb()
    }
  })
})

test('store the will in the persistence', function (t) {
  t.plan(5)

  var opts = {
    clientId: 'abcde'
  }

  // willConnect populates opts with a will
  var s = willConnect(setup(), opts)

  s.broker.on('client', function () {
    // this is connack
    s.broker.persistence.getWill({
      id: opts.clientId
    }, function (err, packet) {
      t.error(err, 'no error')
      t.deepEqual(packet.topic, opts.will.topic, 'will topic matches')
      t.deepEqual(packet.payload, opts.will.payload, 'will payload matches')
      t.deepEqual(packet.qos, opts.will.qos, 'will qos matches')
      t.deepEqual(packet.retain, opts.will.retain, 'will retain matches')
      t.end()
    })
  })
})

test('delete the will in the persistence after publish', function (t) {
  t.plan(2)

  var opts = {
    clientId: 'abcde'
  }

  var broker = aedes()

  broker.on('client', function (client) {
    setImmediate(function () {
      client.close()
    })
  })

  broker.mq.on('mywill', check)

  // willConnect populates opts with a will
  willConnect(setup(broker), opts)

  function check (packet, cb) {
    broker.mq.removeListener('mywill', check)
    setImmediate(function () {
      broker.persistence.getWill({
        id: opts.clientId
      }, function (err, p) {
        t.error(err, 'no error')
        t.notOk(p, 'packet is empty')
        t.end()
      })
    })
    cb()
  }
})

test('delivers a will with authorization', function (t) {
  t.plan(7)

  let authorized = false
  var opts = {}
  // willConnect populates opts with a will
  var s = willConnect(
    setup(aedes({ authorizePublish: (_1, _2, callback) => { authorized = true; callback(null) } })),
    opts,
    function () {
      s.conn.destroy()
    })

  s.broker.on('clientDisconnect', function (client) {
    t.equal(client.connected, false)
    t.equal(client.disconnected, true)
  })

  s.broker.mq.on('mywill', function (packet, cb) {
    t.equal(packet.topic, opts.will.topic, 'topic matches')
    t.deepEqual(packet.payload, opts.will.payload, 'payload matches')
    t.equal(packet.qos, opts.will.qos, 'qos matches')
    t.equal(packet.retain, opts.will.retain, 'retain matches')
    t.equal(authorized, true, 'authorization called')
    cb()
  })

  s.broker.on('closed', t.end.bind(t))
})

test('delivers a will waits for authorization', function (t) {
  t.plan(6)

  let authorized = false
  var opts = {}
  // willConnect populates opts with a will
  var s = willConnect(
    setup(aedes({ authorizePublish: (_1, _2, callback) => { authorized = true; setImmediate(() => { callback(null) }) } })),
    opts,
    function () {
      s.conn.destroy()
    })

  s.broker.on('clientDisconnect', function () {
    t.pass('client is disconnected')
  })

  s.broker.mq.on('mywill', function (packet, cb) {
    t.equal(packet.topic, opts.will.topic, 'topic matches')
    t.deepEqual(packet.payload, opts.will.payload, 'payload matches')
    t.equal(packet.qos, opts.will.qos, 'qos matches')
    t.equal(packet.retain, opts.will.retain, 'retain matches')
    t.equal(authorized, true, 'authorization called')
    cb()
  })

  s.broker.on('closed', t.end.bind(t))
})

test('does not deliver a will without authorization', function (t) {
  t.plan(1)

  let authorized = false
  var opts = {}
  // willConnect populates opts with a will
  var s = willConnect(
    setup(aedes({ authorizePublish: (_1, _2, callback) => { authorized = true; callback(new Error()) } })),
    opts,
    function () {
      s.conn.destroy()
    })

  s.broker.on('clientDisconnect', function () {
    t.equal(authorized, true, 'authorization called')
    t.end()
  })

  s.broker.mq.on('mywill', function (packet, cb) {
    t.fail('received will without authorization')
    cb()
  })
})

test('does not deliver a will without authentication', function (t) {
  t.plan(1)

  let authenticated = false
  var opts = {}
  // willConnect populates opts with a will
  var s = willConnect(
    setup(aedes({ authenticate: (_1, _2, _3, callback) => { authenticated = true; callback(new Error(), false) } })),
    opts)

  s.broker.once('clientError', function () {
    t.equal(authenticated, true, 'authentication called')
    t.end()
  })

  s.broker.mq.on('mywill', function (packet, cb) {
    t.fail('received will without authentication')
    cb()
  })
})

test('does not deliver will if keepalive is triggered during authentication', function (t) {
  t.plan(0)

  var opts = {}
  opts.keepalive = 1
  var broker = aedes({
    authenticate: function (c, u, p, cb) {
      setTimeout(function () {
        cb(null, true)
      }, 3000)
    }
  })

  broker.on('keepaliveTimeout', function () {
    t.end()
  })

  broker.mq.on('mywill', function (packet, cb) {
    cb()
    t.fail('Received will when it was not expected')
  })

  willConnect(setup(broker), opts)
})

// [MQTT-3.14.4-1]
test('does not deliver will when client sends a DISCONNECT', function (t) {
  t.plan(0)

  var broker = aedes()
  var s = willConnect(setup(broker), {}, function () {
    s.inStream.end({
      cmd: 'disconnect'
    })
  })

  s.broker.mq.on('mywill', function (packet, cb) {
    t.fail(packet)
  })

  broker.on('closed', t.end.bind(t))
})

test('does not store multiple will with same clientid', function (t) {
  var opts = { clientId: 'abcde' }

  var broker = aedes()
  var s = willConnect(setup(broker), opts, function () {
    // gracefully close client so no will is sent
    s.inStream.end({
      cmd: 'disconnect'
    })

    // reconnect same client with will
    s = willConnect(setup(broker), opts, function () {
      // check that there are not 2 will messages for the same clientid
      s.broker.persistence.delWill({ id: opts.clientId }, function (err, packet) {
        t.error(err, 'no error')
        t.equal(packet.clientId, opts.clientId, 'will packet found')
        s.broker.persistence.delWill({ id: opts.clientId }, function (err, packet) {
          t.error(err, 'no error')
          t.equal(!!packet, false, 'no duplicated packets')
        })
      })
    })
  })

  broker.on('closed', t.end.bind(t))
})
<|endoftext|>


<commit_before>
var SpeeddialContext = function() {

  this.properties = {};

  global.opr.speeddial.get(function(speeddialProperties) {
    this.properties.url = speeddialProperties.url;
    this.properties.title = speeddialProperties.title;

    // Set WinTabs feature to LOADED
    deferredComponentsLoadStatus['SPEEDDIAL_LOADED'] = true;
  });

};

SpeeddialContext.prototype.constructor = SpeeddialContext;

SpeeddialContext.prototype.__defineGetter__('url', function() {
  return this.properties.url || "";
}); // read

SpeeddialContext.prototype.__defineSetter__('url', function(val) {

  global.opr.speeddial.update({ 'url': val }, function(speeddialProperties) {
    this.properties.url = speeddialProperties.url;
  }.bind(this));

}); // write

SpeeddialContext.prototype.__defineGetter__('title', function() {
  return this.properties.title || "";
}); // read

SpeeddialContext.prototype.__defineSetter__('title', function(val) {

  global.opr.speeddial.update({ 'title': val }, function(speeddialProperties) {
    this.properties.title = speeddialProperties.title;
  }.bind(this));

}); // write
<commit_msg>Minor bug fix to Speed Dial API code<commit_after>
var SpeeddialContext = function() {

  this.properties = {};

  global.opr.speeddial.get(function(speeddialProperties) {
    this.properties.url = speeddialProperties.url;
    this.properties.title = speeddialProperties.title;

    // Set WinTabs feature to LOADED
    deferredComponentsLoadStatus['SPEEDDIAL_LOADED'] = true;
  }.bind(this));

};

SpeeddialContext.prototype.constructor = SpeeddialContext;

SpeeddialContext.prototype.__defineGetter__('url', function() {
  return this.properties.url || "";
}); // read

SpeeddialContext.prototype.__defineSetter__('url', function(val) {

  global.opr.speeddial.update({ 'url': val }, function(speeddialProperties) {
    this.properties.url = speeddialProperties.url;
  }.bind(this));

}); // write

SpeeddialContext.prototype.__defineGetter__('title', function() {
  return this.properties.title || "";
}); // read

SpeeddialContext.prototype.__defineSetter__('title', function(val) {

  global.opr.speeddial.update({ 'title': val }, function(speeddialProperties) {
    this.properties.title = speeddialProperties.title;
  }.bind(this));

}); // write
<|endoftext|>


<commit_before>/* global module */
var glob = require('glob');
var _ = require('underscore');
var fs = require('fs');
var path = require('path');
//var buildPack = require('./tools/buildPack');

module.exports = function (grunt) {
	'use strict';
	var engineVersion = grunt.option('goo-version') || 'UNOFFICIAL';

	var gooModule = {
		name: 'goo'
	};
	var engineFilename = './out/goo.js';

	// Returns the code to add at the start and end of the minified file
	function getWrapper() {
		var wrapperHead = '';
		var wrapperTail = '';

		wrapperHead +=
			'/* Goo Engine ' + engineVersion + '\n' +
			' * Copyright 2014 Goo Technologies AB\n' +
			' */\n';

		var customRequire = fs.readFileSync('tools/customRequire.js');
		wrapperHead += customRequire;

		wrapperHead += '(function(window) {';

		// Put all calls to define and require in the f function
		wrapperHead +=
			'function f(){';
		wrapperTail +=
			'}' +
			'try{' +
			'if(window.localStorage&&window.localStorage.gooPath){' +
				// We're configured to not use the engine from goo.js.
				// Don't call the f function so the modules won't be defined
				// and require will load them separately instead.
				'window.require.config({' +
					'paths:{goo:localStorage.gooPath}' +
				'})' +
			'}else f()' +
			'}catch(e){f()}';

		wrapperTail += '})(window,undefined)';
		return [wrapperHead, wrapperTail];
	}

	// Project configuration.
	grunt.initConfig({
		pkg: grunt.file.readJSON('package.json'),
		requirejs: {
			build: {
				// Options: https://github.com/jrburke/r.js/blob/master/build/example.build.js
				options: {
					baseUrl: 'src/',
					optimize: 'uglify2',  // uglify, uglify2, closure, closure.keepLines
					preserveLicenseComments: false,
					useStrict: true,
					wrap: false,
					keepBuildDir: true,
					//generateSourceMaps: true,
					dir: 'out/minified/',
					modules: [gooModule],
					paths: {
						'requireLib': '../lib/require'
					}

					// I tried using a wrap block like this, but it has no effect
					// wrap: { ... }
					/*
					uglify2: {
						output: {
							beautify: true
						},
						compress: {
							sequences: false,
							global_defs: {
								DEBUG: false
							}
						},
						warnings: true,
						mangle: false
					}*/
				}
			}
		},
		wrap: {
			build: {
				src: ['out/minified/goo.js'],
				dest: engineFilename,
				options: {
					wrapper: getWrapper()
				}
			}
		},
		clean: {
			build: {
				src: [
					'out/',
					'src/goo.js'
				]
			},
			toc: {
				src: [
					'visual-test/index.html',
					'examples/index.html'
				]
			},
			docs: [
				'goojs-jsdoc/',
				'goojs-jsdoc-json/',
				'goojs-jsdoc_*.tar.gz'
			]
		},
		'build-pack': {
			fsmpack: {
				packPath: 'fsmpack',
				outBaseDir: 'out'
			},
			geometrypack: {
				packPath: 'geometrypack',
				outBaseDir: 'out'
			},
			quadpack: {
				packPath: 'quadpack',
				outBaseDir: 'out'
			},
			timelinepack: {
				packPath: 'timelinepack',
				outBaseDir: 'out'
			},
			debugpack: {
				packPath: 'debugpack',
				outBaseDir: 'out'
			},
			scriptpack: {
				packPath: 'scriptpack',
				outBaseDir: 'out'
			},
			p2pack: {
				packPath: 'addons/p2pack',
				outBaseDir: 'out'
			},
			box2dpack: {
				packPath: 'addons/box2dpack',
				outBaseDir: 'out'
			},
			terrainpack: {
				packPath: 'addons/terrainpack',
				outBaseDir: 'out'
			},
			ammopack: {
				packPath: 'addons/ammopack',
				outBaseDir: 'out'
			},
			cannonpack: {
				packPath: 'addons/cannonpack',
				outBaseDir: 'out'
			},
			howlerpack: {
				packPath: 'addons/howlerpack',
				outBaseDir: 'out'
			},
			waterpack: {
				packPath: 'addons/waterpack',
				outBaseDir: 'out'
			},
			animationpack: {
				packPath: 'animationpack',
				outBaseDir: 'out'
			},
			soundmanager2pack: {
				packPath: 'addons/soundmanager2pack',
				outBaseDir: 'out'
			},
			gamepadpack: {
				packPath: 'addons/gamepadpack',
				outBaseDir: 'out'
			},
			passpack: {
				packPath: 'passpack',
				outBaseDir: 'out'
			},
			gizmopack: {
				packPath: 'util/gizmopack',
				outBaseDir: 'out'
			}
		},
		'generate-toc': {
			'visual-test': {
				path: 'visual-test',
				title: 'Visual tests'
			},
			'examples': {
				path: 'examples',
				title: 'Examples'
			}
		},
		'build-custom': {
			myBundle: {
				outFile: 'bundle.js'
			}
		},
		karma: {
			unit: {
				configFile: 'test/unit/karma.conf.js',
				singleRun: true,
				browsers: ['Chrome'] // Phantom just doesn't have support for the goodies we've come to know and love
			}
		},
		shell: {
			jsdoc: {
				command: path.resolve('tools', 'generate_jsdoc.sh')
			},
			jsdoc_json: {
				command: path.resolve('tools', 'generate_jsdoc_json.sh')
			},
			update_webdriver: {
				options: {
					stdout: true
				},
				command: path.resolve('node_modules/webdriver-manager/bin/webdriver-manager') + ' update --standalone --chrome'
			},
			e2e: {
				command: 'node test/e2etesting/manualSpec.js'
			}
		},
		/*
	    jsdoc : { // Could replace tools/generate_jsdoc.sh, but still need something that makes the tar.gz docs bundle
	        dist : {
	            src: ['src','tools/jsdoc-template/static/README.md'],
	            options: {
	                destination: 'goojs-jsdoc',
	                template: 'tools/jsdoc-template',
	                'private': false,
	                recurse: true
	            }
	        }
	    },
	    */
		jshint: {
			all: ['src'],
			options: {
				jshintrc: '.jshintrc',
				force: true // Do not fail the task
			}
		}
	});

	grunt.loadNpmTasks('grunt-contrib-uglify');
	grunt.loadNpmTasks('grunt-contrib-requirejs');
	grunt.loadNpmTasks('grunt-wrap');
	grunt.loadNpmTasks('grunt-contrib-clean');
	grunt.loadNpmTasks('grunt-karma');
	//grunt.loadNpmTasks('grunt-jsdoc');
	grunt.loadNpmTasks('grunt-shell');
	grunt.loadNpmTasks('grunt-contrib-jshint');

	grunt.loadTasks('tools/grunt_tasks');

	grunt.registerTask('default',	['minify']);
	grunt.registerTask('docs',		['shell:jsdoc']);
	grunt.registerTask('jsdoc',		['shell:jsdoc']);
	grunt.registerTask('minify',	['main-file', 'requirejs:build', 'wrap', 'build-pack']);
	grunt.registerTask('unittest',	['karma:unit']);
	grunt.registerTask('e2e',		['shell:e2e']);
	grunt.registerTask('test',		['unittest', 'e2e']); // this gruntfile is a mess

	//! AT: no better place to put this
	function extractFilename(path) {
		var index = path.lastIndexOf('/');
		return index === -1 ? path : path.substr(index + 1);
	}

	// Creates src/goo.js that depends on all engine modules
	grunt.registerTask('main-file', function () {
		// prefiltering out files in packs on the 'ground' level
		var sourceFiles = glob.sync('!(*pack)/**/*.js', { cwd: 'src/goo/', nonegate: true });

		// filtering files in packs that are not on the 'ground' level
		var regexp = /.+pack\/.+/;
		sourceFiles = sourceFiles.filter(function (sourceFile) {
			return !regexp.test(sourceFile);
		});

		var allModules = _.map(sourceFiles, function (f) {
			return 'goo/' + f.replace(/\.js/, '');
		});

		var lines = [];
		lines.push('require([');
		lines.push(_.map(allModules, function (m) { return "\t'" + m + "'"; }).join(',\n'));
		lines.push('], function (');

		var fileNames = allModules.map(extractFilename);

		lines.push('\t' + fileNames.join(',\n\t'));
		lines.push(') {');
		lines.push('\tvar goo = window.goo;\n\tif (!goo) { return; }');
		fileNames.forEach(function (fileName) {
			lines.push('\tgoo.' + fileName + ' = ' + fileName + ';');
		});

		lines.push('});');

		fs.writeFileSync('src/goo.js', lines.join('\n'));
	});

	grunt.registerTask('init-git', function () {
		fs.writeFileSync('.git/hooks/pre-commit', '#!/bin/sh\nexec node tools/pre-commit.js\n');
	});

	// Generates reference screenshots
	grunt.registerTask('refs', function () {
		var done = this.async();
		require('child_process').exec('node test/e2etesting/generate-reference-screenshots', function (error, stdout, stderr) {
		    console.log('stdout: ' + stdout);
		    console.log('stderr: ' + stderr);
		    if (error !== null) {
				console.log('exec error: ' + error);
		    }
		    done();
		});
	});
};
<commit_msg>Howler...<commit_after>/* global module */
var glob = require('glob');
var _ = require('underscore');
var fs = require('fs');
var path = require('path');
//var buildPack = require('./tools/buildPack');

module.exports = function (grunt) {
	'use strict';
	var engineVersion = grunt.option('goo-version') || 'UNOFFICIAL';

	var gooModule = {
		name: 'goo'
	};
	var engineFilename = './out/goo.js';

	// Returns the code to add at the start and end of the minified file
	function getWrapper() {
		var wrapperHead = '';
		var wrapperTail = '';

		wrapperHead +=
			'/* Goo Engine ' + engineVersion + '\n' +
			' * Copyright 2014 Goo Technologies AB\n' +
			' */\n';

		var customRequire = fs.readFileSync('tools/customRequire.js');
		wrapperHead += customRequire;

		wrapperHead += '(function(window) {';

		// Put all calls to define and require in the f function
		wrapperHead +=
			'function f(){';
		wrapperTail +=
			'}' +
			'try{' +
			'if(window.localStorage&&window.localStorage.gooPath){' +
				// We're configured to not use the engine from goo.js.
				// Don't call the f function so the modules won't be defined
				// and require will load them separately instead.
				'window.require.config({' +
					'paths:{goo:localStorage.gooPath}' +
				'})' +
			'}else f()' +
			'}catch(e){f()}';

		wrapperTail += '})(window,undefined)';
		return [wrapperHead, wrapperTail];
	}

	// Project configuration.
	grunt.initConfig({
		pkg: grunt.file.readJSON('package.json'),
		requirejs: {
			build: {
				// Options: https://github.com/jrburke/r.js/blob/master/build/example.build.js
				options: {
					baseUrl: 'src/',
					optimize: 'uglify2',  // uglify, uglify2, closure, closure.keepLines
					preserveLicenseComments: false,
					useStrict: true,
					wrap: false,
					keepBuildDir: true,
					//generateSourceMaps: true,
					dir: 'out/minified/',
					modules: [gooModule],
					paths: {
						'requireLib': '../lib/require'
					}

					// I tried using a wrap block like this, but it has no effect
					// wrap: { ... }
					/*
					uglify2: {
						output: {
							beautify: true
						},
						compress: {
							sequences: false,
							global_defs: {
								DEBUG: false
							}
						},
						warnings: true,
						mangle: false
					}*/
				}
			}
		},
		wrap: {
			build: {
				src: ['out/minified/goo.js'],
				dest: engineFilename,
				options: {
					wrapper: getWrapper()
				}
			}
		},
		clean: {
			build: {
				src: [
					'out/',
					'src/goo.js'
				]
			},
			toc: {
				src: [
					'visual-test/index.html',
					'examples/index.html'
				]
			},
			docs: [
				'goojs-jsdoc/',
				'goojs-jsdoc-json/',
				'goojs-jsdoc_*.tar.gz'
			]
		},
		'build-pack': {
			fsmpack: {
				packPath: 'fsmpack',
				outBaseDir: 'out'
			},
			geometrypack: {
				packPath: 'geometrypack',
				outBaseDir: 'out'
			},
			quadpack: {
				packPath: 'quadpack',
				outBaseDir: 'out'
			},
			timelinepack: {
				packPath: 'timelinepack',
				outBaseDir: 'out'
			},
			debugpack: {
				packPath: 'debugpack',
				outBaseDir: 'out'
			},
			scriptpack: {
				packPath: 'scriptpack',
				outBaseDir: 'out'
			},
			p2pack: {
				packPath: 'addons/p2pack',
				outBaseDir: 'out'
			},
			box2dpack: {
				packPath: 'addons/box2dpack',
				outBaseDir: 'out'
			},
			terrainpack: {
				packPath: 'addons/terrainpack',
				outBaseDir: 'out'
			},
			ammopack: {
				packPath: 'addons/ammopack',
				outBaseDir: 'out'
			},
			cannonpack: {
				packPath: 'addons/cannonpack',
				outBaseDir: 'out'
			},
			waterpack: {
				packPath: 'addons/waterpack',
				outBaseDir: 'out'
			},
			animationpack: {
				packPath: 'animationpack',
				outBaseDir: 'out'
			},
			soundmanager2pack: {
				packPath: 'addons/soundmanager2pack',
				outBaseDir: 'out'
			},
			gamepadpack: {
				packPath: 'addons/gamepadpack',
				outBaseDir: 'out'
			},
			passpack: {
				packPath: 'passpack',
				outBaseDir: 'out'
			},
			gizmopack: {
				packPath: 'util/gizmopack',
				outBaseDir: 'out'
			}
		},
		'generate-toc': {
			'visual-test': {
				path: 'visual-test',
				title: 'Visual tests'
			},
			'examples': {
				path: 'examples',
				title: 'Examples'
			}
		},
		'build-custom': {
			myBundle: {
				outFile: 'bundle.js'
			}
		},
		karma: {
			unit: {
				configFile: 'test/unit/karma.conf.js',
				singleRun: true,
				browsers: ['Chrome'] // Phantom just doesn't have support for the goodies we've come to know and love
			}
		},
		shell: {
			jsdoc: {
				command: path.resolve('tools', 'generate_jsdoc.sh')
			},
			jsdoc_json: {
				command: path.resolve('tools', 'generate_jsdoc_json.sh')
			},
			update_webdriver: {
				options: {
					stdout: true
				},
				command: path.resolve('node_modules/webdriver-manager/bin/webdriver-manager') + ' update --standalone --chrome'
			},
			e2e: {
				command: 'node test/e2etesting/manualSpec.js'
			}
		},
		/*
	    jsdoc : { // Could replace tools/generate_jsdoc.sh, but still need something that makes the tar.gz docs bundle
	        dist : {
	            src: ['src','tools/jsdoc-template/static/README.md'],
	            options: {
	                destination: 'goojs-jsdoc',
	                template: 'tools/jsdoc-template',
	                'private': false,
	                recurse: true
	            }
	        }
	    },
	    */
		jshint: {
			all: ['src'],
			options: {
				jshintrc: '.jshintrc',
				force: true // Do not fail the task
			}
		}
	});

	grunt.loadNpmTasks('grunt-contrib-uglify');
	grunt.loadNpmTasks('grunt-contrib-requirejs');
	grunt.loadNpmTasks('grunt-wrap');
	grunt.loadNpmTasks('grunt-contrib-clean');
	grunt.loadNpmTasks('grunt-karma');
	//grunt.loadNpmTasks('grunt-jsdoc');
	grunt.loadNpmTasks('grunt-shell');
	grunt.loadNpmTasks('grunt-contrib-jshint');

	grunt.loadTasks('tools/grunt_tasks');

	grunt.registerTask('default',	['minify']);
	grunt.registerTask('docs',		['shell:jsdoc']);
	grunt.registerTask('jsdoc',		['shell:jsdoc']);
	grunt.registerTask('minify',	['main-file', 'requirejs:build', 'wrap', 'build-pack']);
	grunt.registerTask('unittest',	['karma:unit']);
	grunt.registerTask('e2e',		['shell:e2e']);
	grunt.registerTask('test',		['unittest', 'e2e']); // this gruntfile is a mess

	//! AT: no better place to put this
	function extractFilename(path) {
		var index = path.lastIndexOf('/');
		return index === -1 ? path : path.substr(index + 1);
	}

	// Creates src/goo.js that depends on all engine modules
	grunt.registerTask('main-file', function () {
		// prefiltering out files in packs on the 'ground' level
		var sourceFiles = glob.sync('!(*pack)/**/*.js', { cwd: 'src/goo/', nonegate: true });

		// filtering files in packs that are not on the 'ground' level
		var regexp = /.+pack\/.+/;
		sourceFiles = sourceFiles.filter(function (sourceFile) {
			return !regexp.test(sourceFile);
		});

		var allModules = _.map(sourceFiles, function (f) {
			return 'goo/' + f.replace(/\.js/, '');
		});

		var lines = [];
		lines.push('require([');
		lines.push(_.map(allModules, function (m) { return "\t'" + m + "'"; }).join(',\n'));
		lines.push('], function (');

		var fileNames = allModules.map(extractFilename);

		lines.push('\t' + fileNames.join(',\n\t'));
		lines.push(') {');
		lines.push('\tvar goo = window.goo;\n\tif (!goo) { return; }');
		fileNames.forEach(function (fileName) {
			lines.push('\tgoo.' + fileName + ' = ' + fileName + ';');
		});

		lines.push('});');

		fs.writeFileSync('src/goo.js', lines.join('\n'));
	});

	grunt.registerTask('init-git', function () {
		fs.writeFileSync('.git/hooks/pre-commit', '#!/bin/sh\nexec node tools/pre-commit.js\n');
	});

	// Generates reference screenshots
	grunt.registerTask('refs', function () {
		var done = this.async();
		require('child_process').exec('node test/e2etesting/generate-reference-screenshots', function (error, stdout, stderr) {
		    console.log('stdout: ' + stdout);
		    console.log('stderr: ' + stderr);
		    if (error !== null) {
				console.log('exec error: ' + error);
		    }
		    done();
		});
	});
};
<|endoftext|>


<commit_before>// -------------------------------------------------------------------------- \\
// File: ToManyAttribute.js                                                   \\
// Module: DataStore                                                          \\
// Requires: RecordAttribute.js                                               \\
// Author: Neil Jenkins                                                       \\
// License: © 2010-2015 FastMail Pty Ltd. MIT Licensed.                       \\
// -------------------------------------------------------------------------- \\

"use strict";

( function ( NS ) {

var slice = Array.prototype.slice;

var RecordArray = NS.Class({

    Extends: NS.ObservableArray,

    init: function ( record, propKey, value, Type ) {
        this.record = record;
        this.propKey = propKey;
        this.Type = Type;
        this.store = record.get( 'store' );

        this._updatingStore = false;

        RecordArray.parent.init.call( this, value && value.slice() );

        record.addObserverForKey( propKey, this, 'updateListFromRecord' );
    },

    destroy: function () {
        this.get( 'record' ).removeObserverForKey(
            this.get( 'propKey' ), this, 'updateListFromRecord' );
        RecordArray.parent.destroy.call( this );
    },

    toJSON: function () {
        return this._array.slice();
    },

    updateListFromRecord: function () {
        if ( !this._updatingStore ) {
            var record = this.get( 'record' ),
                propKey = this.get( 'propKey' ),
                list = record[ propKey ].getRaw( record, propKey );

            this.set( '[]', list ? list.slice() : [] );
        }
    },

    getObjectAt: function ( index ) {
        var storeKey = RecordArray.parent.getObjectAt.call( this, index );
        return storeKey ?
            this.get( 'store' )
                .getRecord( this.get( 'Type' ), '#' + storeKey ) :
            null;
    },

    setObjectAt: function ( index, value ) {
        this.replaceObjectsAt( index, 1, [ value ] );
        return this;
    },

    replaceObjectsAt: function ( index, numberRemoved, newItems ) {
        newItems = newItems ? slice.call( newItems ) : [];

        var record = this.get( 'record' ),
            propKey = this.get( 'propKey' ),
            Type = this.get( 'Type' ),
            store = this.get( 'store' ),
            oldItems = RecordArray.parent.replaceObjectsAt.call(
                this, index, numberRemoved, newItems.map( function ( record ) {
                    return record.get( 'storeKey' );
                })
            ).map( function ( storeKey ) {
                return store.getRecord( Type, '#' + storeKey );
            });

        this._updatingStore = true;
        record[ propKey ].setRaw( record, propKey, this._array.slice() );
        this._updatingStore = false;

        return oldItems;
    },

    add: function ( record ) {
        var index = this._array.indexOf( record.get( 'storeKey' ) );
        if ( index === -1 ) {
            this.replaceObjectsAt(
                this.get( 'length' ), 0, [ record ] );
        }
        return this;
    },

    remove: function ( record ) {
        var index = this._array.indexOf( record.get( 'storeKey' ) );
        if ( index > -1 ) {
            this.replaceObjectsAt( index, 1 );
        }
        return this;
    }
});

var ToManyAttribute = NS.Class({

    Extends: NS.RecordAttribute,

    Type: Array,
    recordType: null,

    call: function ( record, _, propKey ) {
        var arrayKey = '_' + propKey + 'RecordArray';
        return record[ arrayKey ] || ( record[ arrayKey ] =
            new RecordArray( record, propKey, ToManyAttribute.parent.call.call(
                this, record, undefined, propKey ), this.recordType )
        );
    },

    getRaw: function ( record, propKey ) {
        return ToManyAttribute.parent.call.call(
            this, record, undefined, propKey );
    },

    setRaw: function ( record, propKey, data ) {
        return ToManyAttribute.parent.call.call(
            this, record, data, propKey );
    }
});

NS.ToManyAttribute = ToManyAttribute;

NS.Record.toMany = function ( mixin ) {
    return new ToManyAttribute( mixin );
};

}( O ) );
<commit_msg>Make ToManyAttribute observer more efficient<commit_after>// -------------------------------------------------------------------------- \\
// File: ToManyAttribute.js                                                   \\
// Module: DataStore                                                          \\
// Requires: Record.js, RecordAttribute.js                                    \\
// Author: Neil Jenkins                                                       \\
// License: © 2010-2015 FastMail Pty Ltd. MIT Licensed.                       \\
// -------------------------------------------------------------------------- \\

"use strict";

( function ( NS ) {

var slice = Array.prototype.slice;
var meta = NS.meta;
var Record = NS.Record;

// ---

Record.implement({
    /**
        Method: O.Record#notifyRecordArray

        Parameters:
            _       - Unused
            propKey - The propKey that changed on the

        If true, any changes to the record will not be committed to the source.
    */
    notifyRecordArray: function ( _, propKey ) {
        var recordArray = this[ '_' + propKey + 'RecordArray' ];
        var isInCache = propKey in meta( this ).cache;
        // If it's already been updated due to a fetch to the property,
        // the array will be in the cache. Don't waste time calling again.
        if ( recordArray && !isInCache ) {
            recordArray.updateListFromRecord();
        }
    }
});

// ---

var RecordArray = NS.Class({

    Extends: NS.ObservableArray,

    init: function ( record, propKey, value, Type ) {
        this.record = record;
        this.propKey = propKey;
        this.Type = Type;
        this.store = record.get( 'store' );

        this._updatingStore = false;

        RecordArray.parent.init.call( this, value && value.slice() );
    },

    toJSON: function () {
        return this._array.slice();
    },

    updateListFromRecord: function () {
        if ( !this._updatingStore ) {
            var record = this.get( 'record' ),
                propKey = this.get( 'propKey' ),
                list = record[ propKey ].getRaw( record, propKey );

            this.set( '[]', list ? list.slice() : [] );
        }
    },

    getObjectAt: function ( index ) {
        var storeKey = RecordArray.parent.getObjectAt.call( this, index );
        return storeKey ?
            this.get( 'store' )
                .getRecord( this.get( 'Type' ), '#' + storeKey ) :
            null;
    },

    setObjectAt: function ( index, value ) {
        this.replaceObjectsAt( index, 1, [ value ] );
        return this;
    },

    replaceObjectsAt: function ( index, numberRemoved, newItems ) {
        newItems = newItems ? slice.call( newItems ) : [];

        var record = this.get( 'record' ),
            propKey = this.get( 'propKey' ),
            Type = this.get( 'Type' ),
            store = this.get( 'store' ),
            oldItems = RecordArray.parent.replaceObjectsAt.call(
                this, index, numberRemoved, newItems.map( function ( record ) {
                    return record.get( 'storeKey' );
                })
            ).map( function ( storeKey ) {
                return store.getRecord( Type, '#' + storeKey );
            });

        this._updatingStore = true;
        record[ propKey ].setRaw( record, propKey, this._array.slice() );
        this._updatingStore = false;

        return oldItems;
    },

    add: function ( record ) {
        var index = this._array.indexOf( record.get( 'storeKey' ) );
        if ( index === -1 ) {
            this.replaceObjectsAt(
                this.get( 'length' ), 0, [ record ] );
        }
        return this;
    },

    remove: function ( record ) {
        var index = this._array.indexOf( record.get( 'storeKey' ) );
        if ( index > -1 ) {
            this.replaceObjectsAt( index, 1 );
        }
        return this;
    }
});

// ---

var notifyRecordArrayObserver = {
    object: null,
    method: 'notifyRecordArray'
};

var ToManyAttribute = NS.Class({

    Extends: NS.RecordAttribute,

    __setupProperty__: function ( metadata, propKey, object ) {
        ToManyAttribute.parent
            .__setupProperty__.call( this, metadata, propKey, object );
        var observers = metadata.observers;
        var keyObservers = observers[ propKey ];
        if ( !observers.hasOwnProperty( propKey ) ) {
            keyObservers = observers[ propKey ] = keyObservers ?
                keyObservers.slice() : [];
        }
        keyObservers.push( notifyRecordArrayObserver );
    },

    __teardownProperty__: function ( metadata, propKey, object ) {
        ToManyAttribute.parent
            .__teardownProperty__.call( this, metadata, propKey, object );
        var observers = metadata.observers;
        var keyObservers = observers[ propKey ];
        if ( !observers.hasOwnProperty( propKey ) ) {
            keyObservers = observers[ propKey ] = keyObservers.slice();
        }
        keyObservers.erase( notifyRecordArrayObserver );
    },

    Type: Array,
    recordType: null,

    call: function ( record, _, propKey ) {
        var arrayKey = '_' + propKey + 'RecordArray';
        var recordArray = record[ arrayKey ];
        // Race condition: another observer may fetch this before
        // our notifyRecordArray method has been called.
        if ( recordArray ) {
            recordArray.updateListFromRecord();
        } else {
            recordArray = record[ arrayKey ] =
            new RecordArray(
                record,
                propKey,
                ToManyAttribute.parent.call.call(
                    this, record, undefined, propKey ),
                this.recordType
            );
        }
        return recordArray;
    },

    getRaw: function ( record, propKey ) {
        return ToManyAttribute.parent.call.call(
            this, record, undefined, propKey );
    },

    setRaw: function ( record, propKey, data ) {
        return ToManyAttribute.parent.call.call(
            this, record, data, propKey );
    }
});

NS.ToManyAttribute = ToManyAttribute;

NS.Record.toMany = function ( mixin ) {
    return new ToManyAttribute( mixin );
};

}( O ) );
<|endoftext|>


<commit_before>// This is the publication used to display the board list. We publish all the
// non-archived boards:
// 1. that the user is a member of
// 2. the user has starred
Meteor.publish('boards', function() {
  const userId = this.userId;
  // Ensure that the user is connected. If it is not, we need to return an empty
  // array to tell the client to remove the previously published docs.
  if (!Match.test(userId, String) || !userId) return [];

  // Defensive programming to verify that starredBoards has the expected
  // format -- since the field is in the `profile` a user can modify it.
  const { starredBoards = [] } = (Users.findOne(userId) || {}).profile || {};
  check(starredBoards, [String]);

  return Boards.find(
    {
      archived: false,
      $or: [
        {
          _id: { $in: starredBoards },
          permission: 'public',
        },
        { members: { $elemMatch: { userId, isActive: true } } },
      ],
    },
    {
      fields: {
        _id: 1,
        archived: 1,
        slug: 1,
        title: 1,
        description: 1,
        color: 1,
        members: 1,
        permission: 1,
        type: 1,
      },
    },
  );
});

Meteor.publish('archivedBoards', function() {
  const userId = this.userId;
  if (!Match.test(userId, String)) return [];

  return Boards.find(
    {
      archived: true,
      members: {
        $elemMatch: {
          userId,
          isAdmin: true,
        },
      },
    },
    {
      fields: {
        _id: 1,
        archived: 1,
        slug: 1,
        title: 1,
      },
    },
  );
});

// If isArchived = false, this will only return board elements which are not archived.
// If isArchived = true, this will only return board elements which are archived.
Meteor.publishRelations('board', function(boardId, isArchived) {
  this.unblock();
  check(boardId, String);
  check(isArchived, Boolean);
  const thisUserId = this.userId;
  const $or = [{ permission: 'public' }];

  if (thisUserId) {
    $or.push({
      members: { $elemMatch: { userId: thisUserId, isActive: true } },
    });
  }

  this.cursor(
    Boards.find(
      {
        _id: boardId,
        archived: false,
        // If the board is not public the user has to be a member of it to see
        // it.
        $or,
        // Sort required to ensure oplog usage
      },
      { limit: 1, sort: { _id: 1 } },
    ),
    function(boardId, board) {
      this.cursor(Lists.find({ boardId, archived: isArchived }));
      this.cursor(Swimlanes.find({ boardId, archived: isArchived }));
      this.cursor(Integrations.find({ boardId }));
      this.cursor(
        CustomFields.find(
          { boardIds: { $in: [boardId] } },
          { sort: { name: 1 } },
        ),
      );

      // Cards and cards comments
      // XXX Originally we were publishing the card documents as a child of the
      // list publication defined above using the following selector `{ listId:
      // list._id }`. But it was causing a race condition in publish-composite,
      // that I documented here:
      //
      //   https://github.com/englue/meteor-publish-composite/issues/29
      //
      // cottz:publish had a similar problem:
      //
      //   https://github.com/Goluis/cottz-publish/issues/4
      //
      // The current state of relational publishing in meteor is a bit sad,
      // there are a lot of various packages, with various APIs, some of them
      // are unmaintained. Fortunately this is something that will be fixed by
      // meteor-core at some point:
      //
      //   https://trello.com/c/BGvIwkEa/48-easy-joins-in-subscriptions
      //
      // And in the meantime our code below works pretty well -- it's not even a
      // hack!

      // Gather queries and send in bulk
      const cardComments = this.join(CardComments);
      cardComments.selector = _ids => ({ cardId: _ids });
      const attachments = this.join(Attachments);
      attachments.selector = _ids => ({ cardId: _ids });
      const checklists = this.join(Checklists);
      checklists.selector = _ids => ({ cardId: _ids });
      const checklistItems = this.join(ChecklistItems);
      checklistItems.selector = _ids => ({ cardId: _ids });
      const parentCards = this.join(Cards);
      parentCards.selector = _ids => ({ parentId: _ids });
      const boards = this.join(Boards);
      const subCards = this.join(Cards);
      subCards.selector = () => ({ archived: isArchived });

      this.cursor(
        Cards.find({
          boardId: { $in: [boardId, board.subtasksDefaultBoardId] },
          archived: isArchived,
        }),
        function(cardId, card) {
          if (card.type === 'cardType-linkedCard') {
            const impCardId = card.linkedId;
            subCards.push(impCardId); // GitHub issue #2688 and #2693
            cardComments.push(impCardId);
            attachments.push(impCardId);
            checklists.push(impCardId);
            checklistItems.push(impCardId);
          } else if (card.type === 'cardType-linkedBoard') {
            boards.push(card.linkedId);
          }
          cardComments.push(cardId);
          attachments.push(cardId);
          checklists.push(cardId);
          checklistItems.push(cardId);
          parentCards.push(cardId);
        },
      );

      // Send bulk queries for all found ids
      subCards.send();
      cardComments.send();
      attachments.send();
      checklists.send();
      checklistItems.send();
      boards.send();
      parentCards.send();

      if (board.members) {
        // Board members. This publication also includes former board members that
        // aren't members anymore but may have some activities attached to them in
        // the history.
        const memberIds = _.pluck(board.members, 'userId');

        // We omit the current user because the client should already have that data,
        // and sending it triggers a subtle bug:
        // https://github.com/wefork/wekan/issues/15
        this.cursor(
          Users.find(
            {
              _id: { $in: _.without(memberIds, thisUserId) },
            },
            {
              fields: {
                username: 1,
                'profile.fullname': 1,
                'profile.avatarUrl': 1,
              },
            },
          ),
        );

        this.cursor(presences.find({ userId: { $in: memberIds } }));
      }
    },
  );

  return this.ready();
});
<commit_msg>Fix #2688 subcard selector<commit_after>// This is the publication used to display the board list. We publish all the
// non-archived boards:
// 1. that the user is a member of
// 2. the user has starred
Meteor.publish('boards', function() {
  const userId = this.userId;
  // Ensure that the user is connected. If it is not, we need to return an empty
  // array to tell the client to remove the previously published docs.
  if (!Match.test(userId, String) || !userId) return [];

  // Defensive programming to verify that starredBoards has the expected
  // format -- since the field is in the `profile` a user can modify it.
  const { starredBoards = [] } = (Users.findOne(userId) || {}).profile || {};
  check(starredBoards, [String]);

  return Boards.find(
    {
      archived: false,
      $or: [
        {
          _id: { $in: starredBoards },
          permission: 'public',
        },
        { members: { $elemMatch: { userId, isActive: true } } },
      ],
    },
    {
      fields: {
        _id: 1,
        archived: 1,
        slug: 1,
        title: 1,
        description: 1,
        color: 1,
        members: 1,
        permission: 1,
        type: 1,
      },
    },
  );
});

Meteor.publish('archivedBoards', function() {
  const userId = this.userId;
  if (!Match.test(userId, String)) return [];

  return Boards.find(
    {
      archived: true,
      members: {
        $elemMatch: {
          userId,
          isAdmin: true,
        },
      },
    },
    {
      fields: {
        _id: 1,
        archived: 1,
        slug: 1,
        title: 1,
      },
    },
  );
});

// If isArchived = false, this will only return board elements which are not archived.
// If isArchived = true, this will only return board elements which are archived.
Meteor.publishRelations('board', function(boardId, isArchived) {
  this.unblock();
  check(boardId, String);
  check(isArchived, Boolean);
  const thisUserId = this.userId;
  const $or = [{ permission: 'public' }];

  if (thisUserId) {
    $or.push({
      members: { $elemMatch: { userId: thisUserId, isActive: true } },
    });
  }

  this.cursor(
    Boards.find(
      {
        _id: boardId,
        archived: false,
        // If the board is not public the user has to be a member of it to see
        // it.
        $or,
        // Sort required to ensure oplog usage
      },
      { limit: 1, sort: { _id: 1 } },
    ),
    function(boardId, board) {
      this.cursor(Lists.find({ boardId, archived: isArchived }));
      this.cursor(Swimlanes.find({ boardId, archived: isArchived }));
      this.cursor(Integrations.find({ boardId }));
      this.cursor(
        CustomFields.find(
          { boardIds: { $in: [boardId] } },
          { sort: { name: 1 } },
        ),
      );

      // Cards and cards comments
      // XXX Originally we were publishing the card documents as a child of the
      // list publication defined above using the following selector `{ listId:
      // list._id }`. But it was causing a race condition in publish-composite,
      // that I documented here:
      //
      //   https://github.com/englue/meteor-publish-composite/issues/29
      //
      // cottz:publish had a similar problem:
      //
      //   https://github.com/Goluis/cottz-publish/issues/4
      //
      // The current state of relational publishing in meteor is a bit sad,
      // there are a lot of various packages, with various APIs, some of them
      // are unmaintained. Fortunately this is something that will be fixed by
      // meteor-core at some point:
      //
      //   https://trello.com/c/BGvIwkEa/48-easy-joins-in-subscriptions
      //
      // And in the meantime our code below works pretty well -- it's not even a
      // hack!

      // Gather queries and send in bulk
      const cardComments = this.join(CardComments);
      cardComments.selector = _ids => ({ cardId: _ids });
      const attachments = this.join(Attachments);
      attachments.selector = _ids => ({ cardId: _ids });
      const checklists = this.join(Checklists);
      checklists.selector = _ids => ({ cardId: _ids });
      const checklistItems = this.join(ChecklistItems);
      checklistItems.selector = _ids => ({ cardId: _ids });
      const parentCards = this.join(Cards);
      parentCards.selector = _ids => ({ parentId: _ids });
      const boards = this.join(Boards);
      const subCards = this.join(Cards);
      subCards.selector = _ids => ({ _id: _ids, archived: isArchived });

      this.cursor(
        Cards.find({
          boardId: { $in: [boardId, board.subtasksDefaultBoardId] },
          archived: isArchived,
        }),
        function(cardId, card) {
          if (card.type === 'cardType-linkedCard') {
            const impCardId = card.linkedId;
            subCards.push(impCardId); // GitHub issue #2688 and #2693
            cardComments.push(impCardId);
            attachments.push(impCardId);
            checklists.push(impCardId);
            checklistItems.push(impCardId);
          } else if (card.type === 'cardType-linkedBoard') {
            boards.push(card.linkedId);
          }
          cardComments.push(cardId);
          attachments.push(cardId);
          checklists.push(cardId);
          checklistItems.push(cardId);
          parentCards.push(cardId);
        },
      );

      // Send bulk queries for all found ids
      subCards.send();
      cardComments.send();
      attachments.send();
      checklists.send();
      checklistItems.send();
      boards.send();
      parentCards.send();

      if (board.members) {
        // Board members. This publication also includes former board members that
        // aren't members anymore but may have some activities attached to them in
        // the history.
        const memberIds = _.pluck(board.members, 'userId');

        // We omit the current user because the client should already have that data,
        // and sending it triggers a subtle bug:
        // https://github.com/wefork/wekan/issues/15
        this.cursor(
          Users.find(
            {
              _id: { $in: _.without(memberIds, thisUserId) },
            },
            {
              fields: {
                username: 1,
                'profile.fullname': 1,
                'profile.avatarUrl': 1,
              },
            },
          ),
        );

        this.cursor(presences.find({ userId: { $in: memberIds } }));
      }
    },
  );

  return this.ready();
});
<|endoftext|>


<commit_before>// DecentCMS (c) 2014 Bertrand Le Roy, under MIT. See LICENSE.txt for licensing details.
'use strict';

module.exports = function gruntModule(grunt) {
  grunt.initConfig();

  grunt.loadNpmTasks('grunt-mocha-test');

  grunt.config.merge({
    mochaTest: {
      test: {
        src: ['test/*.js'],
        options: {
          reporter: 'min'
        }
      }
    }
  });

  grunt.registerTask('test', 'mochaTest');
  grunt.registerTask('default', ['mochaTest']);
};<commit_msg>Remove Gruntfile.js<commit_after><|endoftext|>


<commit_before>module.exports = function(grunt) {

    grunt.initConfig({
        libsass: {
            development: {
                src: 'public/src/main.scss',
                dest: 'public/build/main.css'
            }
        },
        watch: {
            'browserify': {
                files: 'public/src/**/*.js*',
                tasks: ['browserify']
            },
            'sass': {
                files: 'public/src/**/*.scss',
                tasks: ['libsass','autoprefixer']
            }
        },
        browserify: {
            options: {
                transform: [require('grunt-react').browserify],
            },
            client: {
                src: ['public/src/**/*.jsx'],
                dest: 'public/build/main.js'
            }
        },
        autoprefixer: {
            prefix: {
                src: 'public/build/main.css',
                dest: 'public/build/main.css'
            }
        }
    });

    grunt.loadNpmTasks('grunt-contrib-watch');
    grunt.loadNpmTasks('grunt-libsass');
    grunt.loadNpmTasks('grunt-browserify');
    grunt.loadNpmTasks('grunt-autoprefixer');

    grunt.registerTask('build', ['browserify', 'libsass', 'autoprefixer']);
    grunt.registerTask('default', ['build']);
};
<commit_msg>Added browserify debug flag<commit_after>module.exports = function(grunt) {

    grunt.initConfig({
        libsass: {
            development: {
                src: 'public/src/main.scss',
                dest: 'public/build/main.css'
            }
        },
        watch: {
            'browserify': {
                files: 'public/src/**/*.js*',
                tasks: ['browserify']
            },
            'sass': {
                files: 'public/src/**/*.scss',
                tasks: ['libsass','autoprefixer']
            }
        },
        browserify: {
            options: {
                transform: [require('grunt-react').browserify],
                browserifyOptions: {
                    debug: true
                }
            },
            client: {
                src: ['public/src/**/*.jsx'],
                dest: 'public/build/main.js'
            }
        },
        autoprefixer: {
            prefix: {
                src: 'public/build/main.css',
                dest: 'public/build/main.css'
            }
        }
    });

    grunt.loadNpmTasks('grunt-contrib-watch');
    grunt.loadNpmTasks('grunt-libsass');
    grunt.loadNpmTasks('grunt-browserify');
    grunt.loadNpmTasks('grunt-autoprefixer');

    grunt.registerTask('build', ['browserify', 'libsass', 'autoprefixer']);
    grunt.registerTask('default', ['build']);
};
<|endoftext|>