<commit_before>/*!
 * Copyright (c) 2015-2019 Cisco Systems, Inc. See LICENSE file.
 */

import {Batcher} from '@webex/webex-core';

/**
 * @class
 * @ignore
 */
const PersonUUIDRequestBatcher = Batcher.extend({
  namespace: 'People',

  /**
   * @instance
   * @memberof PersonUUIDRequestBatcher
   * @param {HttpResponseObject} res
   * @returns {Promise}
   */
  handleHttpSuccess(res) {
    return Promise.all(res.body.items.map((personResponse) =>
      this.handleItemSuccess(personResponse.id, personResponse)));
  },

  handleItemFailure(email, response) {
    return this.getDeferredForResponse(email)
      .then((defer) => {
        defer.reject(response);
      });
  },

  /**
   * @instance
   * @memberof PersonUUIDRequestBatcher
   * @param {string} email
   * @param {Object} response
   * @returns {Promise}
   */
  handleItemSuccess(email, response) {
    return this.getDeferredForResponse(email)
      .then((defer) => {
        defer.resolve(response);
      });
  },

  /**
   * @instance
   * @memberof PersonUUIDRequestBatcher
   * @param {string} uuidOrHydraId
   * @returns {Promise<string>}
   */
  fingerprintRequest(uuidOrHydraId) {
    const hydraId = this.webex.people.inferPersonIdFromUuid(uuidOrHydraId);

    return Promise.resolve(hydraId);
  },

  /**
   * @instance
   * @memberof PersonUUIDRequestBatcher
   * @param {string} hydraId
   * @returns {Promise<string>}
   */
  fingerprintResponse(hydraId) {
    return Promise.resolve(hydraId);
  },

  prepareRequest(ids) {
    return Promise.resolve(ids.join());
  },

  /**
   * @instance
   * @memberof PersonUUIDRequestBatcher
   * @param {Object} ids
   * @returns {Promise<HttpResponseObject>}
   */
  submitHttpRequest(ids) {
    return this.webex.request({
      service: 'hydra',
      resource: `people/?id=${ids}&showAllTypes=${this.config.showAllTypes}`
    });
  }
});

export default PersonUUIDRequestBatcher;
<commit_msg>fix(plugin-people): handle failed lookups from hydra<commit_after>/*!
 * Copyright (c) 2015-2019 Cisco Systems, Inc. See LICENSE file.
 */

import {Batcher} from '@webex/webex-core';

/**
 * @class
 * @ignore
 */
const PersonUUIDRequestBatcher = Batcher.extend({
  namespace: 'People',

  /**
   * @instance
   * @memberof PersonUUIDRequestBatcher
   * @param {HttpResponseObject} res
   * @returns {Promise}
   */
  handleHttpSuccess(res) {
    const promises = res.body.items.map((personResponse) =>
      this.handleItemSuccess(personResponse.id, personResponse));

    if (res.body.notFoundIds) {
      promises.concact(res.body.notFoundIds.map((id) =>
        this.handleItemFailure(id)));
    }

    return Promise.all(promises);
  },

  handleItemFailure(id) {
    return this.getDeferredForResponse(id)
      .then((defer) => {
        defer.reject(id);
      });
  },

  /**
   * @instance
   * @memberof PersonUUIDRequestBatcher
   * @param {string} email
   * @param {Object} response
   * @returns {Promise}
   */
  handleItemSuccess(email, response) {
    return this.getDeferredForResponse(email)
      .then((defer) => {
        defer.resolve(response);
      });
  },

  /**
   * @instance
   * @memberof PersonUUIDRequestBatcher
   * @param {string} uuidOrHydraId
   * @returns {Promise<string>}
   */
  fingerprintRequest(uuidOrHydraId) {
    const hydraId = this.webex.people.inferPersonIdFromUuid(uuidOrHydraId);

    return Promise.resolve(hydraId);
  },

  /**
   * @instance
   * @memberof PersonUUIDRequestBatcher
   * @param {string} hydraId
   * @returns {Promise<string>}
   */
  fingerprintResponse(hydraId) {
    return Promise.resolve(hydraId);
  },

  prepareRequest(ids) {
    return Promise.resolve(ids.join());
  },

  /**
   * @instance
   * @memberof PersonUUIDRequestBatcher
   * @param {Object} ids
   * @returns {Promise<HttpResponseObject>}
   */
  submitHttpRequest(ids) {
    return this.webex.request({
      service: 'hydra',
      resource: `people/?id=${ids}&showAllTypes=${this.config.showAllTypes}`
    });
  }
});

export default PersonUUIDRequestBatcher;
<|endoftext|>


<commit_before>const t = require('@babel/types')

const {
  getModelEventFunctionExpr
} = require('./util')

module.exports = function processRef (paths, path, state) {
  const modelPath = paths.model
  if (modelPath) {
    const callbackProperty = modelPath.node.value.properties.find(property => {
      return property.key.name === 'callback'
    })

    const exprProperty = modelPath.node.value.properties.find(
      property => property.key.name === 'expression'
    )

    const prop = exprProperty.value.value.trim()

    const onPath = paths.on

    // on:{'input':__m('msg',$event)}
    if (!onPath) {
      path.node.properties.unshift(
        t.objectProperty(t.identifier('on'), t.objectExpression([
          t.objectProperty(
            t.stringLiteral('input'),
            getModelEventFunctionExpr(
              callbackProperty.value,
              prop
            )
          )
        ]))
      )
      paths.on = path.get('properties').find(
        propertyPath => propertyPath.node.key.name === 'on'
      )
    } else {
      const existingInput = onPath.node.value.properties.find(
        property => property.key.value === 'input'
      )
      if (existingInput) {
        if (!t.isArrayExpression(existingInput.value)) {
          existingInput.value = t.arrayExpression([existingInput.value])
        }
        existingInput.value.elements.unshift(getModelEventFunctionExpr(
          callbackProperty.value,
          prop
        ))
      } else {
        onPath.node.value.properties.push(
          t.objectProperty(
            t.stringLiteral('input'),
            getModelEventFunctionExpr(
              callbackProperty.value,
              prop
            )
          )
        )
      }
    }

    return [ // attrs:{value:value}
      t.objectProperty(
        t.stringLiteral('value'),
        t.identifier(prop)
      )
    ]
  }
  return []
}
<commit_msg>fix(mp):  v-model<commit_after>const t = require('@babel/types')

const {
  getModelEventFunctionExpr
} = require('./util')

module.exports = function processRef (paths, path, state) {
  const modelPath = paths.model
  if (modelPath) {
    const callbackProperty = modelPath.node.value.properties.find(property => {
      return property.key.name === 'callback'
    })

    const exprProperty = modelPath.node.value.properties.find(
      property => property.key.name === 'expression'
    )

    const prop = exprProperty.value.value.trim()

    const onPath = paths.on

    // on:{'input':__m('msg',$event)}
    if (!onPath) {
      path.node.properties.unshift(
        t.objectProperty(t.identifier('on'), t.objectExpression([
          t.objectProperty(
            t.stringLiteral('input'),
            getModelEventFunctionExpr(
              callbackProperty.value,
              prop
            )
          )
        ]))
      )
      paths.on = path.get('properties').find(
        propertyPath => propertyPath.node.key.name === 'on'
      )
    } else {
      const existingInput = onPath.node.value.properties.find(
        property => property.key.value === 'input'
      )
      if (existingInput) {
        if (!t.isArrayExpression(existingInput.value)) {
          existingInput.value = t.arrayExpression([existingInput.value])
        }
        existingInput.value.elements.unshift(getModelEventFunctionExpr(
          callbackProperty.value,
          prop
        ))
      } else {
        onPath.node.value.properties.push(
          t.objectProperty(
            t.stringLiteral('input'),
            getModelEventFunctionExpr(
              callbackProperty.value,
              prop
            )
          )
        )
      }
    }

    return [ // attrs:{value:value}
      t.objectProperty(
        t.stringLiteral(process.env.UNI_USING_VUE3 ? 'modelValue' : 'value'),
        t.identifier(prop)
      )
    ]
  }
  return []
}
<|endoftext|>


<commit_before>/*global define */

define({
    ADD_ENDLINE_TO_THE_END_OF_FILE:     "Adicionar endline no fim do arquivo",
    ADD_TO_GITIGNORE:                   "Adicionar ao .gitignore",
    AMEND_COMMIT:                       "Emendar no último commit",
    AMEND_COMMIT_FORBIDDEN:             "Não é possível emendar o commit quando não tem unpushed commits",
    BASH_NOT_AVAILABLE:                 "O console de comando não está disponível ou propriamente configurado",
    BASIC_CONFIGURATION:                "Configuração básica",
    BRACKETS_GIT_ERROR:                 "Brackets Git encontrou um erro\u2026",
    BRANCH_NAME:                        "Nome do Branch",
    BUTTON_CANCEL:                      "Cancelar",
    BUTTON_CHANGELOG:                   "Histórico de alterações",
    BUTTON_CLOSE:                       "Fechar",
    BUTTON_DEFAULTS:                    "Restaurar padrões",
    BUTTON_OK:                          "OK",
    BUTTON_REPORT:                      "Relatar",
    BUTTON_SAVE:                        "Salvar",
    BUTTON_COMMIT:                      "Fazer Commit",
    BUTTON_INIT:                        "Init",
    CHANGELOG:                          "Histórico de alterações",
    CHECK_GIT_SETTINGS:                 "Verificar configurações do Git",
    COMMIT_ALL_SHORTCUT:                "Commit de todos os arquivos",
    COMMIT_CURRENT_SHORTCUT:            "Commit do arquivo atual",
    COMMIT_MESSAGE_PLACEHOLDER:         "Insira a mensagem do commit aqui\u2026",
    CREATE_NEW_BRANCH:                  "Criar um novo branch\u2026",
    CUSTOM_TERMINAL_COMMAND:            "Custom terminal command (sample: gnome-terminal --window --working-directory=$1)",
    DELETE_FILE:                        "Excluir arquivo",
    DIFF:                               "Diff",
    ENTER_PASSWORD:                     "Senha",
    ENTER_USERNAME:                     "Usuário",
    ERROR_TERMINAL_NOT_FOUND:           "Terminal was not found for your OS, you can define a custom Terminal command in the settings",
    EXTENSION_WAS_UPDATED_TITLE:        "A extensão foi atualizada para {0}",
    FEATURES:                           "Recursos",
    FILE_DELETED:                       "Exluído",
    FILE_MODIFIED:                      "Modificado",
    FILE_NEWFILE:                       "Adicionado",
    FILE_RENAMED:                       "Renomeado",
    FILE_STAGED:                        "Staged",
    FILE_UNTRACKED:                     "Untracked",
    FOR_MAC_LINUX_USERS:                "Para usuários de Mac/Linux",
    FOR_WINDOWS_USERS:                  "Para usuários de Windows",
    GIT_COMMIT:                         "Git commit\u2026",
    GIT_DIFF:                           "Git diff —",
    GIT_IS_IN_PATH:                     "O Git pode ser usado de qualquer lugar (está no caminho do sistema, pode não funcionar no Mac)",
    GIT_PULL_RESPONSE:                  "Resposta do Git Pull",
    GIT_PUSH_RESPONSE:                  "Resposta do Git Push",
    GIT_SETTINGS:                       "Configurações do Git\u2026",
    LOADING:                            "Carregando\u2026",
    MARK_MODIFIED_FILES_IN_TREE:        "Marcar arquivos modificados na árvore de arquivos",
    NOTHING_TO_COMMIT:                  "Nada para fazer commit, diretório de trabalho limpo.",
    PANEL_COMMAND:                      "Git",
    PANEL_SHORTCUT:                     "Toggle panel",
    PATH_TO_GIT_EXECUTABLE:             "Caminho para executável do Git",
    PATH_TO_GIT_MSYSGIT:                "Caminho para pasta do msysgit",
    Q_DELETE_FILE:                      "Tem certeza de que deseja excluir o arquivo {0}?",
    Q_RESTART_BRACKETS:                 "Deseja reiniciar o Brackets para aplicar as novas configurações?",
    Q_UNDO_CHANGES:                     "Redefinir alterações ao arquivo {0}?",
    REMOVE_FROM_GITIGNORE:              "Remover do .gitignore",
    RESTART:                            "Reiniciar",
    SAVE_PASSWORD_QUESTION:             "Salvar usuário/senha?",
    SET_ORIGIN_URL:                     "Definir URL da origem",
    STRIP_WHITESPACE_FROM_COMMITS:      "Remover espaços em branco extras dos commits",
    TOOLTIP_BUG:                        "Relatar erro",
    TOOLTIP_OPEN_BASH:                  "Abrir console de comandos",
    TOOLTIP_PULL:                       "Git Pull",
    TOOLTIP_PUSH:                       "Git Push",
    TOOLTIP_CLOSE_NOT_MODIFIED:         "Fechar não modificados",
    TOOLTIP_INIT:                       "Inicializar repositório",
    TOOLTIP_COMMIT:                     "Fazer Commit dos arquivos selecionados",
    TOOLTIP_SHOW_UNTRACKED:             "Mostrar untracked",
    TOOLTIP_HIDE_UNTRACKED:             "Ocultar untracked",
    TOOLTIP_REFRESH_PANEL:              "Recarregar painel",
    UNDO_CHANGES:                       "Desfazer alterações",
    URL:                                "URL",
    USE_GIT_GUTTER:                     "Usar marcas do Git na margem",
    USING_GIT_VERSION:                  "Versão do Git"
});
<commit_msg>Update strings.js<commit_after>/*global define */

define({
    ADD_ENDLINE_TO_THE_END_OF_FILE:     "Adicionar endline no fim do arquivo",
    ADD_TO_GITIGNORE:                   "Adicionar ao .gitignore",
    AMEND_COMMIT:                       "Emendar no último commit",
    AMEND_COMMIT_FORBIDDEN:             "Não é possível emendar o commit quando não tem unpushed commits",
    BASH_NOT_AVAILABLE:                 "O console de comando não está disponível ou propriamente configurado",
    BASIC_CONFIGURATION:                "Configuração básica",
    BRACKETS_GIT_ERROR:                 "Brackets Git encontrou um erro\u2026",
    BRANCH_NAME:                        "Nome do Branch",
    BUTTON_CANCEL:                      "Cancelar",
    BUTTON_CHANGELOG:                   "Histórico de alterações",
    BUTTON_CLOSE:                       "Fechar",
    BUTTON_DEFAULTS:                    "Restaurar padrões",
    BUTTON_OK:                          "OK",
    BUTTON_REPORT:                      "Relatar",
    BUTTON_SAVE:                        "Salvar",
    BUTTON_COMMIT:                      "Fazer Commit",
    BUTTON_INIT:                        "Init",
    CHANGELOG:                          "Histórico de alterações",
    CHECK_GIT_SETTINGS:                 "Verificar configurações do Git",
    COMMIT_ALL_SHORTCUT:                "Commit todos os arquivos",
    COMMIT_CURRENT_SHORTCUT:            "Commit arquivo atual",
    COMMIT_MESSAGE_PLACEHOLDER:         "Insira a mensagem do commit aqui\u2026",
    CREATE_NEW_BRANCH:                  "Criar um novo branch\u2026",
    CUSTOM_TERMINAL_COMMAND:            "Custom terminal command (sample: gnome-terminal --window --working-directory=$1)",
    DELETE_FILE:                        "Excluir arquivo",
    DIFF:                               "Diff",
    ENTER_PASSWORD:                     "Senha",
    ENTER_USERNAME:                     "Usuário",
    ERROR_TERMINAL_NOT_FOUND:           "Terminal was not found for your OS, you can define a custom Terminal command in the settings",
    EXTENSION_WAS_UPDATED_TITLE:        "A extensão foi atualizada para {0}",
    FEATURES:                           "Recursos",
    FILE_DELETED:                       "Exluído",
    FILE_MODIFIED:                      "Modificado",
    FILE_NEWFILE:                       "Adicionado",
    FILE_RENAMED:                       "Renomeado",
    FILE_STAGED:                        "Staged",
    FILE_UNTRACKED:                     "Untracked",
    FOR_MAC_LINUX_USERS:                "Para usuários de Mac/Linux",
    FOR_WINDOWS_USERS:                  "Para usuários de Windows",
    GIT_COMMIT:                         "Git commit\u2026",
    GIT_DIFF:                           "Git diff —",
    GIT_IS_IN_PATH:                     "O Git pode ser usado de qualquer lugar (está no caminho do sistema, pode não funcionar no Mac)",
    GIT_PULL_RESPONSE:                  "Resposta do Git Pull",
    GIT_PUSH_RESPONSE:                  "Resposta do Git Push",
    GIT_SETTINGS:                       "Configurações do Git\u2026",
    LOADING:                            "Carregando\u2026",
    MARK_MODIFIED_FILES_IN_TREE:        "Marcar arquivos modificados na árvore de arquivos",
    NOTHING_TO_COMMIT:                  "Nada para fazer commit, diretório de trabalho limpo.",
    PANEL_COMMAND:                      "Git",
    PANEL_SHORTCUT:                     "Toggle panel",
    PATH_TO_GIT_EXECUTABLE:             "Caminho para executável do Git",
    PATH_TO_GIT_MSYSGIT:                "Caminho para pasta do msysgit",
    Q_DELETE_FILE:                      "Tem certeza de que deseja excluir o arquivo {0}?",
    Q_RESTART_BRACKETS:                 "Deseja reiniciar o Brackets para aplicar as novas configurações?",
    Q_UNDO_CHANGES:                     "Redefinir alterações ao arquivo {0}?",
    REMOVE_FROM_GITIGNORE:              "Remover do .gitignore",
    RESTART:                            "Reiniciar",
    SAVE_PASSWORD_QUESTION:             "Salvar usuário/senha?",
    SET_ORIGIN_URL:                     "Definir URL da origem",
    STRIP_WHITESPACE_FROM_COMMITS:      "Remover espaços em branco extras dos commits",
    TOOLTIP_BUG:                        "Relatar erro",
    TOOLTIP_OPEN_BASH:                  "Abrir console de comandos",
    TOOLTIP_PULL:                       "Git Pull",
    TOOLTIP_PUSH:                       "Git Push",
    TOOLTIP_CLOSE_NOT_MODIFIED:         "Fechar não modificados",
    TOOLTIP_INIT:                       "Inicializar repositório",
    TOOLTIP_COMMIT:                     "Fazer Commit dos arquivos selecionados",
    TOOLTIP_SHOW_UNTRACKED:             "Mostrar untracked",
    TOOLTIP_HIDE_UNTRACKED:             "Ocultar untracked",
    TOOLTIP_REFRESH_PANEL:              "Recarregar painel",
    UNDO_CHANGES:                       "Desfazer alterações",
    URL:                                "URL",
    USE_GIT_GUTTER:                     "Usar marcas do Git na margem",
    USING_GIT_VERSION:                  "Versão do Git"
});
<|endoftext|>


<commit_before>const settings = require('settings-sharelatex')
const fs = require('fs')
const { promisify } = require('util')
const Stream = require('stream')
const { Storage } = require('@google-cloud/storage')
const { callbackify } = require('util')
const { WriteError, ReadError, NotFoundError } = require('./Errors')
const asyncPool = require('tiny-async-pool')
const PersistorHelper = require('./PersistorHelper')

const pipeline = promisify(Stream.pipeline)

// endpoint settings will be null by default except for tests
// that's OK - GCS uses the locally-configured service account by default
const storage = new Storage(settings.filestore.gcs.endpoint)
// workaround for broken uploads with custom endpoints:
// https://github.com/googleapis/nodejs-storage/issues/898
if (
  settings.filestore.gcs.endpoint &&
  settings.filestore.gcs.endpoint.apiEndpoint
) {
  storage.interceptors.push({
    request: function(reqOpts) {
      const url = new URL(reqOpts.uri)
      url.host = settings.filestore.gcs.endpoint.apiEndpoint
      if (settings.filestore.gcs.endpoint.apiScheme) {
        url.protocol = settings.filestore.gcs.endpoint.apiScheme
      }
      reqOpts.uri = url.toString()
      return reqOpts
    }
  })
}

const GcsPersistor = {
  sendFile: callbackify(sendFile),
  sendStream: callbackify(sendStream),
  getFileStream: callbackify(getFileStream),
  getFileMd5Hash: callbackify(getFileMd5Hash),
  deleteDirectory: callbackify(deleteDirectory),
  getFileSize: callbackify(getFileSize),
  deleteFile: callbackify(deleteFile),
  copyFile: callbackify(copyFile),
  checkIfFileExists: callbackify(checkIfFileExists),
  directorySize: callbackify(directorySize),
  promises: {
    sendFile,
    sendStream,
    getFileStream,
    getFileMd5Hash,
    deleteDirectory,
    getFileSize,
    deleteFile,
    copyFile,
    checkIfFileExists,
    directorySize
  }
}

module.exports = GcsPersistor

async function sendFile(bucketName, key, fsPath) {
  return sendStream(bucketName, key, fs.createReadStream(fsPath))
}

async function sendStream(bucketName, key, readStream, sourceMd5) {
  try {
    let hashPromise

    // if there is no supplied md5 hash, we calculate the hash as the data passes through
    if (!sourceMd5) {
      hashPromise = PersistorHelper.calculateStreamMd5(readStream)
    }

    const meteredStream = PersistorHelper.getMeteredStream(
      readStream,
      'gcs.egress' // egress from us to gcs
    )

    const writeOptions = {
      // disabling of resumable uploads is recommended by Google:
      resumable: false
    }

    if (sourceMd5) {
      writeOptions.validation = 'md5'
      writeOptions.metadata = {
        md5Hash: PersistorHelper.hexToBase64(sourceMd5)
      }
    }

    const uploadStream = storage
      .bucket(bucketName)
      .file(key)
      .createWriteStream(writeOptions)

    await pipeline(meteredStream, uploadStream)

    // if we didn't have an md5 hash, we should compare our computed one with Google's
    // as we couldn't tell GCS about it beforehand
    if (hashPromise) {
      sourceMd5 = await hashPromise
      // throws on mismatch
      await PersistorHelper.verifyMd5(GcsPersistor, bucketName, key, sourceMd5)
    }
  } catch (err) {
    throw PersistorHelper.wrapError(
      err,
      'upload to GCS failed',
      { bucketName, key },
      WriteError
    )
  }
}

async function getFileStream(bucketName, key, opts = {}) {
  if (opts.end) {
    // S3 (and http range headers) treat 'end' as inclusive, so increase this by 1
    opts.end++
  }
  const stream = storage
    .bucket(bucketName)
    .file(key)
    .createReadStream(opts)

  const meteredStream = PersistorHelper.getMeteredStream(
    stream,
    'gcs.ingress' // ingress to us from gcs
  )

  try {
    await PersistorHelper.waitForStreamReady(stream)
    return meteredStream
  } catch (err) {
    throw PersistorHelper.wrapError(
      err,
      'error reading file from GCS',
      { bucketName, key, opts },
      ReadError
    )
  }
}

async function getFileSize(bucketName, key) {
  try {
    const [metadata] = await storage
      .bucket(bucketName)
      .file(key)
      .getMetadata()
    return metadata.size
  } catch (err) {
    throw PersistorHelper.wrapError(
      err,
      'error getting size of GCS object',
      { bucketName, key },
      ReadError
    )
  }
}

async function getFileMd5Hash(bucketName, key) {
  try {
    const [metadata] = await storage
      .bucket(bucketName)
      .file(key)
      .getMetadata()
    return PersistorHelper.base64ToHex(metadata.md5Hash)
  } catch (err) {
    throw PersistorHelper.wrapError(
      err,
      'error getting hash of GCS object',
      { bucketName, key },
      ReadError
    )
  }
}

async function deleteFile(bucketName, key) {
  try {
    const file = storage.bucket(bucketName).file(key)

    if (settings.filestore.gcs.unlockBeforeDelete) {
      await file.setMetadata({ eventBasedHold: false })
    }
    if (settings.filestore.gcs.deletedBucketSuffix) {
      await file.copy(
        storage
          .bucket(`${bucketName}${settings.filestore.gcs.deletedBucketSuffix}`)
          .file(`${key}-${new Date()}`)
      )
    }
    await file.delete()
  } catch (err) {
    const error = PersistorHelper.wrapError(
      err,
      'error deleting GCS object',
      { bucketName, key },
      WriteError
    )
    if (!(error instanceof NotFoundError)) {
      throw error
    }
  }
}

async function deleteDirectory(bucketName, key) {
  try {
    const [files] = await storage
      .bucket(bucketName)
      .getFiles({ directory: key })

    await asyncPool(50, files, async file => {
      await deleteFile(bucketName, file.name)
    })
  } catch (err) {
    const error = PersistorHelper.wrapError(
      err,
      'failed to delete directory in GCS',
      { bucketName, key },
      WriteError
    )
    if (error instanceof NotFoundError) {
      return
    }
    throw error
  }
}

async function directorySize(bucketName, key) {
  let files

  try {
    const [response] = await storage
      .bucket(bucketName)
      .getFiles({ directory: key })
    files = response
  } catch (err) {
    throw PersistorHelper.wrapError(
      err,
      'failed to list objects in GCS',
      { bucketName, key },
      ReadError
    )
  }

  return files.reduce((acc, file) => Number(file.metadata.size) + acc, 0)
}

async function checkIfFileExists(bucketName, key) {
  try {
    const [response] = await storage
      .bucket(bucketName)
      .file(key)
      .exists()
    return response
  } catch (err) {
    throw PersistorHelper.wrapError(
      err,
      'error checking if file exists in GCS',
      { bucketName, key },
      ReadError
    )
  }
}

async function copyFile(bucketName, sourceKey, destKey) {
  try {
    const src = storage.bucket(bucketName).file(sourceKey)
    const dest = storage.bucket(bucketName).file(destKey)
    await src.copy(dest)
  } catch (err) {
    // fake-gcs-server has a bug that returns an invalid response when the file does not exist
    if (err.message === 'Cannot parse response as JSON: not found\n') {
      err.code = 404
    }
    throw PersistorHelper.wrapError(
      err,
      'failed to copy file in GCS',
      { bucketName, sourceKey, destKey },
      WriteError
    )
  }
}
<commit_msg>Copy-on-delete before unlocking<commit_after>const settings = require('settings-sharelatex')
const fs = require('fs')
const { promisify } = require('util')
const Stream = require('stream')
const { Storage } = require('@google-cloud/storage')
const { callbackify } = require('util')
const { WriteError, ReadError, NotFoundError } = require('./Errors')
const asyncPool = require('tiny-async-pool')
const PersistorHelper = require('./PersistorHelper')

const pipeline = promisify(Stream.pipeline)

// endpoint settings will be null by default except for tests
// that's OK - GCS uses the locally-configured service account by default
const storage = new Storage(settings.filestore.gcs.endpoint)
// workaround for broken uploads with custom endpoints:
// https://github.com/googleapis/nodejs-storage/issues/898
if (
  settings.filestore.gcs.endpoint &&
  settings.filestore.gcs.endpoint.apiEndpoint
) {
  storage.interceptors.push({
    request: function(reqOpts) {
      const url = new URL(reqOpts.uri)
      url.host = settings.filestore.gcs.endpoint.apiEndpoint
      if (settings.filestore.gcs.endpoint.apiScheme) {
        url.protocol = settings.filestore.gcs.endpoint.apiScheme
      }
      reqOpts.uri = url.toString()
      return reqOpts
    }
  })
}

const GcsPersistor = {
  sendFile: callbackify(sendFile),
  sendStream: callbackify(sendStream),
  getFileStream: callbackify(getFileStream),
  getFileMd5Hash: callbackify(getFileMd5Hash),
  deleteDirectory: callbackify(deleteDirectory),
  getFileSize: callbackify(getFileSize),
  deleteFile: callbackify(deleteFile),
  copyFile: callbackify(copyFile),
  checkIfFileExists: callbackify(checkIfFileExists),
  directorySize: callbackify(directorySize),
  promises: {
    sendFile,
    sendStream,
    getFileStream,
    getFileMd5Hash,
    deleteDirectory,
    getFileSize,
    deleteFile,
    copyFile,
    checkIfFileExists,
    directorySize
  }
}

module.exports = GcsPersistor

async function sendFile(bucketName, key, fsPath) {
  return sendStream(bucketName, key, fs.createReadStream(fsPath))
}

async function sendStream(bucketName, key, readStream, sourceMd5) {
  try {
    let hashPromise

    // if there is no supplied md5 hash, we calculate the hash as the data passes through
    if (!sourceMd5) {
      hashPromise = PersistorHelper.calculateStreamMd5(readStream)
    }

    const meteredStream = PersistorHelper.getMeteredStream(
      readStream,
      'gcs.egress' // egress from us to gcs
    )

    const writeOptions = {
      // disabling of resumable uploads is recommended by Google:
      resumable: false
    }

    if (sourceMd5) {
      writeOptions.validation = 'md5'
      writeOptions.metadata = {
        md5Hash: PersistorHelper.hexToBase64(sourceMd5)
      }
    }

    const uploadStream = storage
      .bucket(bucketName)
      .file(key)
      .createWriteStream(writeOptions)

    await pipeline(meteredStream, uploadStream)

    // if we didn't have an md5 hash, we should compare our computed one with Google's
    // as we couldn't tell GCS about it beforehand
    if (hashPromise) {
      sourceMd5 = await hashPromise
      // throws on mismatch
      await PersistorHelper.verifyMd5(GcsPersistor, bucketName, key, sourceMd5)
    }
  } catch (err) {
    throw PersistorHelper.wrapError(
      err,
      'upload to GCS failed',
      { bucketName, key },
      WriteError
    )
  }
}

async function getFileStream(bucketName, key, opts = {}) {
  if (opts.end) {
    // S3 (and http range headers) treat 'end' as inclusive, so increase this by 1
    opts.end++
  }
  const stream = storage
    .bucket(bucketName)
    .file(key)
    .createReadStream(opts)

  const meteredStream = PersistorHelper.getMeteredStream(
    stream,
    'gcs.ingress' // ingress to us from gcs
  )

  try {
    await PersistorHelper.waitForStreamReady(stream)
    return meteredStream
  } catch (err) {
    throw PersistorHelper.wrapError(
      err,
      'error reading file from GCS',
      { bucketName, key, opts },
      ReadError
    )
  }
}

async function getFileSize(bucketName, key) {
  try {
    const [metadata] = await storage
      .bucket(bucketName)
      .file(key)
      .getMetadata()
    return metadata.size
  } catch (err) {
    throw PersistorHelper.wrapError(
      err,
      'error getting size of GCS object',
      { bucketName, key },
      ReadError
    )
  }
}

async function getFileMd5Hash(bucketName, key) {
  try {
    const [metadata] = await storage
      .bucket(bucketName)
      .file(key)
      .getMetadata()
    return PersistorHelper.base64ToHex(metadata.md5Hash)
  } catch (err) {
    throw PersistorHelper.wrapError(
      err,
      'error getting hash of GCS object',
      { bucketName, key },
      ReadError
    )
  }
}

async function deleteFile(bucketName, key) {
  try {
    const file = storage.bucket(bucketName).file(key)

    if (settings.filestore.gcs.deletedBucketSuffix) {
      await file.copy(
        storage
          .bucket(`${bucketName}${settings.filestore.gcs.deletedBucketSuffix}`)
          .file(`${key}-${new Date()}`)
      )
    }
    if (settings.filestore.gcs.unlockBeforeDelete) {
      await file.setMetadata({ eventBasedHold: false })
    }
    await file.delete()
  } catch (err) {
    const error = PersistorHelper.wrapError(
      err,
      'error deleting GCS object',
      { bucketName, key },
      WriteError
    )
    if (!(error instanceof NotFoundError)) {
      throw error
    }
  }
}

async function deleteDirectory(bucketName, key) {
  try {
    const [files] = await storage
      .bucket(bucketName)
      .getFiles({ directory: key })

    await asyncPool(50, files, async file => {
      await deleteFile(bucketName, file.name)
    })
  } catch (err) {
    const error = PersistorHelper.wrapError(
      err,
      'failed to delete directory in GCS',
      { bucketName, key },
      WriteError
    )
    if (error instanceof NotFoundError) {
      return
    }
    throw error
  }
}

async function directorySize(bucketName, key) {
  let files

  try {
    const [response] = await storage
      .bucket(bucketName)
      .getFiles({ directory: key })
    files = response
  } catch (err) {
    throw PersistorHelper.wrapError(
      err,
      'failed to list objects in GCS',
      { bucketName, key },
      ReadError
    )
  }

  return files.reduce((acc, file) => Number(file.metadata.size) + acc, 0)
}

async function checkIfFileExists(bucketName, key) {
  try {
    const [response] = await storage
      .bucket(bucketName)
      .file(key)
      .exists()
    return response
  } catch (err) {
    throw PersistorHelper.wrapError(
      err,
      'error checking if file exists in GCS',
      { bucketName, key },
      ReadError
    )
  }
}

async function copyFile(bucketName, sourceKey, destKey) {
  try {
    const src = storage.bucket(bucketName).file(sourceKey)
    const dest = storage.bucket(bucketName).file(destKey)
    await src.copy(dest)
  } catch (err) {
    // fake-gcs-server has a bug that returns an invalid response when the file does not exist
    if (err.message === 'Cannot parse response as JSON: not found\n') {
      err.code = 404
    }
    throw PersistorHelper.wrapError(
      err,
      'failed to copy file in GCS',
      { bucketName, sourceKey, destKey },
      WriteError
    )
  }
}
<|endoftext|>


<commit_before>/*
 * Lunchpad Database module
 *
 * @author Markus Riegel <riegel.markius@googlemail.com>
 */

var Db = require('mongodb').Db;
var Connection = require('mongodb').Connection;
var Server = require('mongodb').Server;
var BSON = require('mongodb').BSON;
var ObjectID = require('mongodb').ObjectID;

var config = require('./config.json');

var db;

var DBase = function(){

	db = new Db(config.mongodb.db, new Server(config.mongodb.host, config.mongodb.port, {auto_reconnect: true}, {w:1, safe:true}));
	db.open(function(){});

};

DBase.prototype.gc = function(collectionName,onSuccess,onError){

	db.collection(collectionName, function(error, collection) {
		if( error ) onError(error);
		else onSuccess(collection);
	});

}

DBase.prototype.oID = function(id){
	if(typeof id !== 'string' || id.length !== 24){
		id = '000000000000000000000000';
		console.log('Trying to generate OnjectID: Invalid id');
	}
	return ObjectID(id);
};

exports.dBase = new DBase();
<commit_msg>write concern fix<commit_after>/*
 * Lunchpad Database module
 *
 * @author Markus Riegel <riegel.markius@googlemail.com>
 */

var Db = require('mongodb').Db;
var Connection = require('mongodb').Connection;
var Server = require('mongodb').Server;
var BSON = require('mongodb').BSON;
var ObjectID = require('mongodb').ObjectID;

var config = require('./config.json');

var db;

var DBase = function(){

	db = new Db(config.mongodb.db, new Server(config.mongodb.host, config.mongodb.port, {auto_reconnect: true}),{w:1});
	db.open(function(){});

};

DBase.prototype.gc = function(collectionName,onSuccess,onError){

	db.collection(collectionName, function(error, collection) {
		if( error ) onError(error);
		else onSuccess(collection);
	});

}

DBase.prototype.oID = function(id){
	if(typeof id !== 'string' || id.length !== 24){
		id = '000000000000000000000000';
		console.log('Trying to generate OnjectID: Invalid id');
	}
	return ObjectID(id);
};

exports.dBase = new DBase();
<|endoftext|>


<commit_before>const ROSLIB = require('roslib');
const personalData = require('./personalData');
const webModel = require('./webModel');
const webModelFunctions = require('./webModelFunctions');
const tts = require('./tts');
const robotModel = require('./robotModel');
// Set last movement to now to initiate the idle timer
robotModel.lastMovementTime = Date.now();

let unplug;
let pauseExplore; // Empty global for actual topic

// Copied from arloweb.js
/** @namespace personalData.rosLibDelay */
let ros; // Empty global for actual connection.
const longDelay = personalData.rosLibDelay * 1000;

// Define a list of ROS Parameters to monitor
// NOTE: Add an instance to webModel if you want this sent to the web app!
const rosParameters = {
  ignoreCliffSensors: {
    param: webModel.rosParameters.ignoreCliffSensors,
    label: 'ignoreCliffSensors',
    path: '/arlobot/ignoreCliffSensors',
  },
  ignoreProximity: {
    param: webModel.rosParameters.ignoreProximity,
    label: 'ignoreProximity',
    path: '/arlobot/ignoreProximity',
  },
  ignoreIRSensors: {
    param: webModel.rosParameters.ignoreIRSensors,
    label: 'ignoreIRSensors',
    path: '/arlobot/ignoreIRSensors',
  },
  ignoreFloorSensors: {
    param: webModel.rosParameters.ignoreFloorSensors,
    label: 'ignoreFloorSensors',
    path: '/arlobot/ignoreFloorSensors',
  },
  monitorACconnection: {
    param: webModel.rosParameters.monitorACconnection,
    label: 'monitorACconnection',
    path: '/arlobot/monitorACconnection',
  },
  mapName: {
    param: webModel.rosParameters.mapName,
    label: 'mapName',
    path: '/arlobot/mapname',
  },
  explorePaused: {
    param: webModel.rosParameters.explorePaused,
    label: 'explorePaused',
    path: '/arlobot_explore/pause',
  },
};

function unplugRobot(value) {
  if (unplug) {
    const unplugRequest = new ROSLIB.ServiceRequest({
      // args from rosservice info <service>
      unPlug: value, // Note javaScript uses true not True for bool
    });
    unplug.callService(unplugRequest, (result) => {
      console.log(result);
      // webModelFunctions.scrollingStatusUpdate(result);
    });
  }
}

function callPauseExplore(value) {
  if (pauseExplore) {
    const pauseExploreRequest = new ROSLIB.ServiceRequest({
      // args from rosservice info <service>
      pause_explorer: value, // Note javaScript uses true not True for bool
    });
    pauseExplore.callService(pauseExploreRequest, (result) => {
      console.log(result);
      // webModelFunctions.scrollingStatusUpdate(result);
    });
  }
}

function closeDeadROSConnection() {
  // TODO: Does this ever happen?
  console.log('Closing dead ROS connection.');
  if (ros !== undefined) {
    ros.close();
  }
  console.log('CLOSED dead ROS connection!');
}

function subscribeToActiveStatus() {
  // Remember to add new instances to talkToROS() at the end!
  // This should serve as a template for all topic subscriptions
  // Make sure we are still connected.

  // Make sure service exists:
  const closeDeadConnectionTime = setTimeout(closeDeadROSConnection, longDelay);
  ros.getTopics(() => {
    // Arguments: result // Unfortunately this can stall with no output!
    clearTimeout(closeDeadConnectionTime);

    // THIS is where you put the subscription code:
    const cmdActiveStatus = new ROSLIB.Topic({
      ros,
      name: '/cmd_vel_mux/active', // Obtain name by running 'rostopic list'
      messageType: 'std_msgs/String', // Obtain Type by running 'rostopic info <name>'
    }); // Obtain message.??? by running 'rosmsg show <messageType>'
    cmdActiveStatus.subscribe((message) => {
      robotModel.active_cmd = message.data;
      console.log(`Command Velocity Topic says: ${message.data}`);
      if (message.data === 'idle') {
        robotModel.cmdTopicIdle = true;
      } else {
        robotModel.cmdTopicIdle = false;
        robotModel.lastMovementTime = Date.now();
      }
    });

    // THIS is where you put the subscription code:
    const arlobotArloStatus = new ROSLIB.Topic({
      ros,
      name: '/arlo_status', // Obtain name by running 'rostopic list'
      messageType: 'arlobot_msgs/arloStatus', // Obtain Type by running 'rostopic info <name>'
    }); // Obtain message.??? by running 'rosmsg show <messageType>'
    arlobotArloStatus.subscribe((message) => {
      for (const key in message) {
        if (message.hasOwnProperty(key)) {
          webModelFunctions.updateRosTopicItem(key, message[key]);
        }
      }
    });

    // THIS is where you put the subscription code:
    const arlobotJoystick = new ROSLIB.Topic({
      ros,
      name: '/joy', // Obtain name by running 'rostopic list'
      messageType: 'sensor_msgs/Joy', // Obtain Type by running 'rostopic info <name>'
    }); // Obtain message.??? by running 'rosmsg show <messageType>'
    arlobotJoystick.subscribe((message) => {
      // console.log(message.buttons);
      // A, B, X, Y, LB, RB, BACK, START, Xbox360, LeftStick, RightStick, Left, Right, Up, Down
      if (message.buttons[0] === 1) {
        tts('Hello, my name is two flower');
      } else if (message.buttons[1] === 1) {
        tts('What is your name?');
      } else if (message.buttons[3] === 1) {
        tts('Nice to meet you.');
      } else if (message.buttons[2] === 1) {
        tts('Excuse me.');
      } else if (message.buttons[5] === 1) {
        tts('~/.arlobot/sounds/readyMaster.wav');
      } else if (message.buttons[11] === 1) {
        tts('~/.arlobot/sounds/Exterminate.wav');
      } else if (message.buttons[12] === 1) {
        tts('~/.arlobot/sounds/input1.wav');
      } else if (message.buttons[13] === 1) {
        tts('~/.arlobot/sounds/affirmative.wav');
      } else if (message.buttons[14] === 1) {
        tts('~/.arlobot/sounds/depressed.wav');
      }
      /*
       for (let key in message) {
       if (message.hasOwnProperty(key)) {
       webModelFunctions.updateRosTopicItem(key, message[key]);
       }
       }
       */
    });
  });
}

function pollParams() {
  function checkParameter(prop) {
    rosParameters[prop].param.get((value) => {
      // console.log(rosParameters[prop].label + ': ' + value);
      // Assign state to webModel object for view by web page.
      if (webModel.rosParameters.hasOwnProperty(prop)) {
        webModelFunctions.updateRosParameter(prop, value);
        // webModel.rosParameters[prop] = value;
        // console.log('For web: ' + webModel.rosParameters[prop]);
      }
    });
  }

  for (const prop in rosParameters) {
    if (rosParameters.hasOwnProperty(prop)) {
      checkParameter(prop);
    }
  }

  setTimeout(pollParams, longDelay);
}

function talkToROS() {
  // If you wanted to dump ALL params:
  // ros.getParams(function(params) {
  //    console.log('ROSLIB Params:');
  //    console.log(params);
  // });

  // Start subscriptions:
  // Each topic function will do its own checking to see if the topic is live or not.
  setTimeout(subscribeToActiveStatus, longDelay); // Start with a slight delay

  // Set up services to use
  unplug = new ROSLIB.Service({
    ros,
    name: '/arlobot_unplug', // rosservice list
    serviceType: 'arlobot_msgs/pause_explorer', // rosservice info <service>
  });

  pauseExplore = new ROSLIB.Service({
    ros,
    name: '/arlobot_explore/pause_explorer', // rosservice list
    serviceType: 'arlobot_msgs/UnPlug', // rosservice info <service>
  });

  // Enumerate parameters to watch
  for (const prop in rosParameters) {
    if (rosParameters.hasOwnProperty(prop)) {
      rosParameters[prop].param = new ROSLIB.Param({
        ros,
        name: rosParameters[prop].path,
      });
    }
  }
  // and poll them.
  pollParams();
}

function setParam(paramLabel, value) {
  if (!personalData.demoWebSite) {
    if (rosParameters.hasOwnProperty(paramLabel)) {
      if (rosParameters[paramLabel].param) {
        rosParameters[paramLabel].param.set(value);
      }
    }
  }
  // Fake update for web interface to see until polling picks it up.
  // This also covers the case if this is a demo site.
  webModelFunctions.updateRosParameter(paramLabel, value);
}

// Copied from arloweb.js
// Be sure to set url to point to localhost,
// and change any references to web objects with console.log (i.e. setActionField)
function pollROS() {
  // console.log('ROSLIB pollROS run');

  ros = new ROSLIB.Ros({
    url: 'ws://localhost:9090',
    // This eliminates a warning about utf8:
    encoding: 'ascii',
  });

  ros.on('connection', () => {
    webModelFunctions.scrollingStatusUpdate('ROSLIB Websocket connected.');
    // Set last movement to now to initiate the idle timer
    robotModel.lastMovementTime = Date.now();
    // connectRequested = true;
    // updateConnectedButton();
    // checkROSServices();
    setTimeout(talkToROS, longDelay);
  });

  ros.on('error', () => {
    // Arguments: error
    // console.log('Error connecting to websocket server: ', error);
    // console.log('ROSLIB Websocket error');
    if (ros !== undefined) {
      ros.close();
    }
  });

  ros.on('close', () => {
    // console.log('Connection to ROSLIB Websocket server closed.');
    webModelFunctions.scrollingStatusUpdate('ROSLIB Websocket closed');
    // updateConnectedButton();
    setTimeout(pollROS, longDelay);
  });
}

function start() {
  pollROS();
}

exports.start = start;
exports.setParam = setParam;
exports.unplugRobot = unplugRobot;
exports.callPauseExplore = callPauseExplore;
<commit_msg>Tell web site ROS is Running if it starts externally to the node application.<commit_after>const ROSLIB = require('roslib');
const personalData = require('./personalData');
const webModel = require('./webModel');
const webModelFunctions = require('./webModelFunctions');
const tts = require('./tts');
const robotModel = require('./robotModel');
// Set last movement to now to initiate the idle timer
robotModel.lastMovementTime = Date.now();

let unplug;
let pauseExplore; // Empty global for actual topic

// Copied from arloweb.js
/** @namespace personalData.rosLibDelay */
let ros; // Empty global for actual connection.
const longDelay = personalData.rosLibDelay * 1000;

// Define a list of ROS Parameters to monitor
// NOTE: Add an instance to webModel if you want this sent to the web app!
const rosParameters = {
  ignoreCliffSensors: {
    param: webModel.rosParameters.ignoreCliffSensors,
    label: 'ignoreCliffSensors',
    path: '/arlobot/ignoreCliffSensors',
  },
  ignoreProximity: {
    param: webModel.rosParameters.ignoreProximity,
    label: 'ignoreProximity',
    path: '/arlobot/ignoreProximity',
  },
  ignoreIRSensors: {
    param: webModel.rosParameters.ignoreIRSensors,
    label: 'ignoreIRSensors',
    path: '/arlobot/ignoreIRSensors',
  },
  ignoreFloorSensors: {
    param: webModel.rosParameters.ignoreFloorSensors,
    label: 'ignoreFloorSensors',
    path: '/arlobot/ignoreFloorSensors',
  },
  monitorACconnection: {
    param: webModel.rosParameters.monitorACconnection,
    label: 'monitorACconnection',
    path: '/arlobot/monitorACconnection',
  },
  mapName: {
    param: webModel.rosParameters.mapName,
    label: 'mapName',
    path: '/arlobot/mapname',
  },
  explorePaused: {
    param: webModel.rosParameters.explorePaused,
    label: 'explorePaused',
    path: '/arlobot_explore/pause',
  },
};

function unplugRobot(value) {
  if (unplug) {
    const unplugRequest = new ROSLIB.ServiceRequest({
      // args from rosservice info <service>
      unPlug: value, // Note javaScript uses true not True for bool
    });
    unplug.callService(unplugRequest, (result) => {
      console.log(result);
      // webModelFunctions.scrollingStatusUpdate(result);
    });
  }
}

function callPauseExplore(value) {
  if (pauseExplore) {
    const pauseExploreRequest = new ROSLIB.ServiceRequest({
      // args from rosservice info <service>
      pause_explorer: value, // Note javaScript uses true not True for bool
    });
    pauseExplore.callService(pauseExploreRequest, (result) => {
      console.log(result);
      // webModelFunctions.scrollingStatusUpdate(result);
    });
  }
}

function closeDeadROSConnection() {
  // TODO: Does this ever happen?
  console.log('Closing dead ROS connection.');
  if (ros !== undefined) {
    ros.close();
  }
  console.log('CLOSED dead ROS connection!');
}

function subscribeToActiveStatus() {
  // Remember to add new instances to talkToROS() at the end!
  // This should serve as a template for all topic subscriptions
  // Make sure we are still connected.

  // Make sure service exists:
  const closeDeadConnectionTime = setTimeout(closeDeadROSConnection, longDelay);
  ros.getTopics(() => {
    // Arguments: result // Unfortunately this can stall with no output!
    clearTimeout(closeDeadConnectionTime);

    // THIS is where you put the subscription code:
    const cmdActiveStatus = new ROSLIB.Topic({
      ros,
      name: '/cmd_vel_mux/active', // Obtain name by running 'rostopic list'
      messageType: 'std_msgs/String', // Obtain Type by running 'rostopic info <name>'
    }); // Obtain message.??? by running 'rosmsg show <messageType>'
    cmdActiveStatus.subscribe((message) => {
      robotModel.active_cmd = message.data;
      console.log(`Command Velocity Topic says: ${message.data}`);
      if (message.data === 'idle') {
        robotModel.cmdTopicIdle = true;
      } else {
        robotModel.cmdTopicIdle = false;
        robotModel.lastMovementTime = Date.now();
      }
    });

    // THIS is where you put the subscription code:
    const arlobotArloStatus = new ROSLIB.Topic({
      ros,
      name: '/arlo_status', // Obtain name by running 'rostopic list'
      messageType: 'arlobot_msgs/arloStatus', // Obtain Type by running 'rostopic info <name>'
    }); // Obtain message.??? by running 'rosmsg show <messageType>'
    arlobotArloStatus.subscribe((message) => {
      for (const key in message) {
        if (message.hasOwnProperty(key)) {
          webModelFunctions.updateRosTopicItem(key, message[key]);
        }
      }
    });

    // THIS is where you put the subscription code:
    const arlobotJoystick = new ROSLIB.Topic({
      ros,
      name: '/joy', // Obtain name by running 'rostopic list'
      messageType: 'sensor_msgs/Joy', // Obtain Type by running 'rostopic info <name>'
    }); // Obtain message.??? by running 'rosmsg show <messageType>'
    arlobotJoystick.subscribe((message) => {
      // console.log(message.buttons);
      // A, B, X, Y, LB, RB, BACK, START, Xbox360, LeftStick, RightStick, Left, Right, Up, Down
      if (message.buttons[0] === 1) {
        tts('Hello, my name is two flower');
      } else if (message.buttons[1] === 1) {
        tts('What is your name?');
      } else if (message.buttons[3] === 1) {
        tts('Nice to meet you.');
      } else if (message.buttons[2] === 1) {
        tts('Excuse me.');
      } else if (message.buttons[5] === 1) {
        tts('~/.arlobot/sounds/readyMaster.wav');
      } else if (message.buttons[11] === 1) {
        tts('~/.arlobot/sounds/Exterminate.wav');
      } else if (message.buttons[12] === 1) {
        tts('~/.arlobot/sounds/input1.wav');
      } else if (message.buttons[13] === 1) {
        tts('~/.arlobot/sounds/affirmative.wav');
      } else if (message.buttons[14] === 1) {
        tts('~/.arlobot/sounds/depressed.wav');
      }
      /*
       for (let key in message) {
       if (message.hasOwnProperty(key)) {
       webModelFunctions.updateRosTopicItem(key, message[key]);
       }
       }
       */
    });
  });
}

function pollParams() {
  function checkParameter(prop) {
    rosParameters[prop].param.get((value) => {
      // console.log(rosParameters[prop].label + ': ' + value);
      // Assign state to webModel object for view by web page.
      if (webModel.rosParameters.hasOwnProperty(prop)) {
        webModelFunctions.updateRosParameter(prop, value);
        // webModel.rosParameters[prop] = value;
        // console.log('For web: ' + webModel.rosParameters[prop]);
      }
    });
  }

  for (const prop in rosParameters) {
    if (rosParameters.hasOwnProperty(prop)) {
      checkParameter(prop);
    }
  }

  setTimeout(pollParams, longDelay);
}

function talkToROS() {
  // If you wanted to dump ALL params:
  // ros.getParams(function(params) {
  //    console.log('ROSLIB Params:');
  //    console.log(params);
  // });

  // Start subscriptions:
  // Each topic function will do its own checking to see if the topic is live or not.
  setTimeout(subscribeToActiveStatus, longDelay); // Start with a slight delay

  // Set up services to use
  unplug = new ROSLIB.Service({
    ros,
    name: '/arlobot_unplug', // rosservice list
    serviceType: 'arlobot_msgs/pause_explorer', // rosservice info <service>
  });

  pauseExplore = new ROSLIB.Service({
    ros,
    name: '/arlobot_explore/pause_explorer', // rosservice list
    serviceType: 'arlobot_msgs/UnPlug', // rosservice info <service>
  });

  // Enumerate parameters to watch
  for (const prop in rosParameters) {
    if (rosParameters.hasOwnProperty(prop)) {
      rosParameters[prop].param = new ROSLIB.Param({
        ros,
        name: rosParameters[prop].path,
      });
    }
  }
  // and poll them.
  pollParams();
}

function setParam(paramLabel, value) {
  if (!personalData.demoWebSite) {
    if (rosParameters.hasOwnProperty(paramLabel)) {
      if (rosParameters[paramLabel].param) {
        rosParameters[paramLabel].param.set(value);
      }
    }
  }
  // Fake update for web interface to see until polling picks it up.
  // This also covers the case if this is a demo site.
  webModelFunctions.updateRosParameter(paramLabel, value);
}

// Copied from arloweb.js
// Be sure to set url to point to localhost,
// and change any references to web objects with console.log (i.e. setActionField)
function pollROS() {
  // console.log('ROSLIB pollROS run');

  ros = new ROSLIB.Ros({
    url: 'ws://localhost:9090',
    // This eliminates a warning about utf8:
    encoding: 'ascii',
  });

  ros.on('connection', () => {
    webModelFunctions.scrollingStatusUpdate('ROSLIB Websocket connected.');
    // Set last movement to now to initiate the idle timer
    robotModel.lastMovementTime = Date.now();
    // Tell the system that ROS is up, even if it wasn't started via the script in Node
    webModelFunctions.update('ROSisRunning', true);
    // connectRequested = true;
    // updateConnectedButton();
    // checkROSServices();
    setTimeout(talkToROS, longDelay);
  });

  ros.on('error', () => {
    // Arguments: error
    // console.log('Error connecting to websocket server: ', error);
    // console.log('ROSLIB Websocket error');
    if (ros !== undefined) {
      ros.close();
    }
  });

  ros.on('close', () => {
    // console.log('Connection to ROSLIB Websocket server closed.');
    webModelFunctions.scrollingStatusUpdate('ROSLIB Websocket closed');
    // updateConnectedButton();
    setTimeout(pollROS, longDelay);
  });
}

function start() {
  pollROS();
}

exports.start = start;
exports.setParam = setParam;
exports.unplugRobot = unplugRobot;
exports.callPauseExplore = callPauseExplore;
<|endoftext|>


<commit_before>
(function(window, exports) {

  window.MathUtil = exports;

})(window, (function() {

  var _ = {};

  /**
   * usefull regex
   */
  _.REGEX_NUMBER   = /^[]$/;
  _.REGEX_TERM     = /^[]$/;
  _.REGEX_FUNCTION = /^[]$/;

  /**
   * bunch of functions to convert between radix
   */
  _.degtorad = function (a) {
      return (a/180)*Math.PI;
  };
  _.degtograd = function (a) {
      return (a/180)*200;
  };
  _.radtodeg = function (a) {
      return (a/Math.PI)*180;
  };
  _.radtograd = function (a) {
      return (a/Math.PI)*200;
  };
  _.gradtodeg = function (a) {
      return (a/200)*180;
  };
  _.gradtorad = function (a) {
      return (a/200)*Math.PI;
  };

  /**
   * check type of tokens
   */
  _.isNumber = function (a) {
      return !isNaN(parseFloat(a)) && isFinite(a);
  };
  _.isConstant = function (a) {
      return a in _.constants;
  };
  _.isFunction = function (a) {
      return a in _.functions;
  };
  _.isOperator = function (a) {
      return a in _.operators;
  };

  /**
   * map of specialchars
   */
  _.specialchars = {};

  /**
   * map of operators
   */
  _.operators = {};

  /**
   * map of functions
   */
  _.functions = {};

  /**
   * map of constants
   */
  _.constants = {};

  /**
   * class Mode
   */
  _.Mode = function() {};
  (function() {

  }).call(_.Mode.prototype);

  /**
   * class Token
   */
  _.Token = function() {};
  (function() {

  }).call(_.Mode.prototype);

  /**
   * class Tokenizer
   */
  _.Tokenizer = function() {};
  (function() {

  }).call(_.Mode.prototype);

  return _;

})());
<commit_msg>add controlchars- and operators-map<commit_after>
(function(window, exports) {

  window.MathUtil = exports;

})(window, (function() {

  var _ = {};

  /**
   * usefull regex
   */
  _.REGEX_NUMBER   = /(?:\d*\.)?\d+(?:e(?:\+|-)?\d+)?/g;
  _.REGEX_TERM     = /^[^=]+$/g;
  _.REGEX_FUNCTION = /^([a-z]+)\(([A-Za-z]+)\)\s*=\s*(.+)$/g;

  /**
   * bunch of functions to convert between radix
   */
  _.degtorad = function (a) {
      return (a/180)*Math.PI;
  };
  _.degtograd = function (a) {
      return (a/180)*200;
  };
  _.radtodeg = function (a) {
      return (a/Math.PI)*180;
  };
  _.radtograd = function (a) {
      return (a/Math.PI)*200;
  };
  _.gradtodeg = function (a) {
      return (a/200)*180;
  };
  _.gradtorad = function (a) {
      return (a/200)*Math.PI;
  };

  /**
   * check type of tokens
   */
  _.isNumber = function (a) {
      return !isNaN(parseFloat(a)) && isFinite(a);
  };
  _.isConstant = function (a) {
      return a in _.constants;
  };
  _.isFunction = function (a) {
      return a in _.functions;
  };
  _.isOperator = function (a) {
      return a in _.operators;
  };

  /**
   * map of controlchars
   */
  _.controlchars = { '(': 1, ',': 1, ')': 1, ';': 1 };

  /**
   * map of operators
   */
  _.operators = {
    "+": {
      assoc: "left",
      precedence: 1,
      operands: 2,
      apply: function (a, b) {
        return a + b;
      }
    },
    "-": {
      assoc: "left",
      precedence: 1,
      operands: 2,
      apply: function (a, b) {
        return b ? (a - b) : -a;
      }
    },

    "*": {
      assoc: "left",
      precedence: 2,
      operands: 2,
      apply: function (a, b) {
        return a * b;
      }
    },
    "/": {
      assoc: "left",
      precedence: 2,
      operands: 2,
      apply: function (a, b) {
        return a / b;
      }
    },
    "mod": {
      assoc: "left",
      precedence: 2,
      operands: 2,
      apply: function (a, b) {
        return a % b;
      }
    },

    "^": {
      assoc: "right",
      precedence: 3,
      operands: 2,
      apply: function (a, b) {
        return Math.pow(a, b);
      }
    },

    "%": {
      assoc: "right",
      precedence: 4,
      operands: 1,
      apply: function (a) {
        return a/100;
      }
    },
    "!": {
      assoc: "right",
      precedence: 4,
      operands: 1,
      apply: function (a) {
        return _.functions['factorial'].eval.call(this, a);
      }
    }
  };

  /**
   * map of functions
   */
  _.functions = {};

  /**
   * map of constants
   */
  _.constants = {};

  /**
   * class Mode
   */
  _.Mode = function() {
    this.rules = [];
  };
  (function() {

    this.addRule = function(type, regex) {
      this.rules.push({ type: type, regex: regex });
    };

    this.getRegex = function() {

    };

  }).call(_.Mode.prototype);

  /**
   * class Token
   */
  _.Token = function() {};
  (function() {

  }).call(_.Mode.prototype);

  /**
   * class Tokenizer
   */
  _.Tokenizer = function() {};
  (function() {

  }).call(_.Mode.prototype);

  return _;

})());
<|endoftext|>


<commit_before>"use strict";

var Promise = require('bluebird')
  , uuid = require('uuid')
  , inherits = require("util").inherits
  , models = require('../models')
  , AbstractModel = models.AbstractModel
  , FeedFactory = models.FeedFactory
  , Post = models.Post
  , mkKey = require("../support/models").mkKey
  , pubSub = models.PubSub
  , _ = require('lodash')

exports.addModel = function(database) {
  /**
   * @constructor
   */
  var Timeline = function(params) {
    Timeline.super_.call(this)

    this.id = params.id
    this.name = params.name
    this.userId = params.userId
    if (parseInt(params.createdAt, 10))
      this.createdAt = params.createdAt
    if (parseInt(params.updatedAt, 10))
      this.updatedAt = params.updatedAt
    this.offset = parseInt(params.offset, 10) || 0
    this.limit = parseInt(params.limit, 10) || 30
    this.currentUser = params.currentUser
  }

  inherits(Timeline, AbstractModel)

  Timeline.className = Timeline
  Timeline.namespace = "timeline"
  Timeline.findById = Timeline.super_.findById

  Object.defineProperty(Timeline.prototype, 'name', {
    get: function() { return this.name_ },
    set: function(newValue) {
      newValue ? this.name_ = newValue.trim() : this.name_ = ''
    }
  })

  /**
   * Adds the specified post to all timelines where it needs to appear
   * (the timelines of the feeds to which it is posted, the River of News
   * timeline of the posting user and the River of News timelines of all
   * subscribers of the feeds to which it is posted).
   */
  Timeline.publishPost = function(post) {
    var that = this
    var currentTime = new Date().getTime()

    // We can use post.timelineIds here instead of post.getPostedToIds
    // because we are about to create that post and have just received
    // a request from user, so postedToIds == timelineIds here
    return Promise.map(post.timelineIds, function(timelineId) {
      return Timeline.findById(timelineId)
    })
      .then(function(timelines) {
        return Promise.map(timelines, function(timeline) {
          return timeline.getUser()
            .then(function(user) { return user.updateLastActivityAt() })
            .then(function() { return timeline.getSubscribedTimelineIds() })
        })
      })
      .then(function(allSubscribedTimelineIds) {
        var allTimelines = _.uniq(
          _.union(post.timelineIds, _.flatten(allSubscribedTimelineIds)))
        return Promise.map(allTimelines, function(timelineId) {
          return Promise.all([
            database.zaddAsync(mkKey(['timeline', timelineId, 'posts']), currentTime, post.id),
            database.hsetAsync(mkKey(['post', post.id]), 'updatedAt', currentTime),
            database.saddAsync(mkKey(['post', post.id, 'timelines']), timelineId)
          ])
        })
      })
      .then(function() { return pubSub.newPost(post.id) })
  }

  Timeline.prototype.validate = function() {
    return new Promise(function(resolve, reject) {
      var valid

      valid = this.name
        && this.name.length > 0
        && this.userId
        && this.userId.length > 0

      valid ? resolve(valid) : reject(new Error("Invalid"))
    }.bind(this))
  }

  Timeline.prototype.validateOnCreate = function() {
    var that = this

    return new Promise(function(resolve, reject) {
      Promise.join(that.validate(),
                   that.validateUniquness(mkKey(['timeline', that.id])),
                   function(valid, idIsUnique) {
                     resolve(that)
                   })
        .catch(function(e) { reject(e) })
      })
  }

  Timeline.prototype.create = function() {
    var that = this

    return new Promise(function(resolve, reject) {
      that.createdAt = new Date().getTime()
      that.updatedAt = new Date().getTime()
      if (!that.id)
        that.id = uuid.v4()

      that.validateOnCreate()
        .then(function(timeline) {
          return Promise.all([
            database.hmsetAsync(mkKey(['user', that.userId, 'timelines']),
                                that.name, that.id),
            database.hmsetAsync(mkKey(['timeline', that.id]),
                                { 'name': that.name,
                                  'userId': that.userId,
                                  'createdAt': that.createdAt.toString(),
                                  'updatedAt': that.updatedAt.toString()
                                })
          ])
        })
        .then(function(res) { resolve(that) })
        .catch(function(e) { reject(e) })
    })
  }

  Timeline.prototype.getPostIds = function(offset, limit) {
    if (_.isUndefined(offset))
      offset = this.offset
    else
      if (offset < 0) offset = 0
    // -1 = special magic number, meaning “do not use limit defaults,
    // do not use passed in value, use 0 instead". this is at the very least
    // used in Timeline.merge()
    if (_.isUndefined(limit))
      limit = this.limit
    else
      if (limit < 0) limit = 0

    var that = this
    return new Promise(function(resolve, reject) {
      that.validateCanShow(that.currentUser)
        .then(function(valid) {
          // this is a private timeline
          if (!valid)
            limit = -1

          return database.zrevrangeAsync(mkKey(['timeline', that.id, 'posts']), offset, offset+limit-1)
        })
        .then(function(postIds) {
          that.postIds = postIds
          resolve(that.postIds)
        })
    })
  }

  Timeline.prototype.getPostIdsByScore = function(min, max) {
    var that = this

    return new Promise(function(resolve, reject) {
      database.zrevrangebyscoreAsync(mkKey(['timeline', that.id, 'posts']), min, max)
        .then(function(postIds) {
          that.postIds = postIds
          resolve(that.postIds)
        })
    })
  }

  Timeline.prototype.getPosts = function(offset, limit) {
    if (_.isUndefined(offset))
      offset = this.offset
    else
      if (offset < 0) offset = 0

    if (_.isUndefined(limit))
      limit = this.limit
    else
      if (limit < 0) limit = 0

    var that = this
    var p_post
    var p_banIds

    return new Promise(function(resolve, reject) {
      that.getPostIds(offset, limit).bind({})
        .then(function(postIds) {
          return Promise.map(postIds, function(postId) {
            return Post.findById(postId, { currentUser: that.currentUser })
          })
        })
        .then(function(posts) {
          this.posts = posts
          return that.currentUser ? models.User.findById(that.currentUser) : null
        })
        .then(function(user) {
          return user ? user.getBanIds() : []
        })
        .then(function(banIds) {
          p_banIds = banIds
          return Promise.map(this.posts, function(post) {
            return models.User.findById(post.userId).then(function(user) {
              return user.getBanIds()
            }).then(function(reverseBanIds) {
              return ((p_banIds.indexOf(post.userId) >= 0) || (reverseBanIds.indexOf(that.currentUser) >= 0)) ? null : post
            })
          })
        })
        .then(function(posts) {
          that.posts = posts.filter(Boolean)
          resolve(that.posts)
        })
    })
  }

  Timeline.prototype.merge = function(timelineId) {
    var that = this

    return new Promise(function(resolve, reject) {
      database.zunionstoreAsync(
        mkKey(['timeline', timelineId, 'posts']), 2,
        mkKey(['timeline', timelineId, 'posts']),
        mkKey(['timeline', that.id, 'posts']),
        'AGGREGATE', 'MAX')
        .then(function() { return Timeline.findById(timelineId) })
        .then(function(timeline) { return timeline.getPostIds(0, -1) })
        .then(function(postIds) {
          return Promise.map(postIds, function(postId) {
            return database.sadd(mkKey(['post', postId, 'timelines']), timelineId)
          })
        })
        .then(function(res) { resolve(res) })
    })
  }

  Timeline.prototype.unmerge = async function(timelineId) {
    // zinterstore saves results to a key. so we have to
    // create a temporary storage
    var randomKey = mkKey(['timeline', this.id, 'random', uuid.v4()])

    await database.zinterstoreAsync(
      randomKey,
      2,
      mkKey(['timeline', timelineId, 'posts']),
      mkKey(['timeline', this.id, 'posts']),
      'AGGREGATE', 'MAX')

    var postIds = await database.zrangeAsync(randomKey, 0, -1)
    await* _.flatten(postIds.map((postId) => [
      database.sremAsync(mkKey(['post', postId, 'timelines']), timelineId),
      database.zremAsync(mkKey(['timeline', timelineId, 'posts']), postId)
    ]))

    return database.delAsync(randomKey)
  }

  Timeline.prototype.getUser = function() {
    return models.FeedFactory.findById(this.userId)
  }

  /**
   * Returns the IDs of users subscribed to this timeline, as a promise.
   */
  Timeline.prototype.getSubscriberIds = function(includeSelf) {
    var that = this

    return new Promise(function(resolve, reject) {
      database.zrevrangeAsync(mkKey(['timeline', that.id, 'subscribers']), 0, -1)
        .then(function(userIds) {
          // A user is always subscribed to their own posts timeline.
          if (includeSelf && (that.isPosts() || that.isDirects())) {
            userIds = _.uniq(userIds.concat([that.userId]))
          }
          that.subscriberIds = userIds
          resolve(userIds)
        })
    })
  }

  Timeline.prototype.getSubscribers = async function(includeSelf) {
    var userIds = await this.getSubscriberIds(includeSelf)
    var promises = userIds.map((userId) => models.User.findById(userId))

    this.subscribers = await* promises

    return this.subscribers
  }

  /**
   * Returns the list of the 'River of News' timelines of all subscribers to this
   * timeline.
   */
  Timeline.prototype.getSubscribedTimelineIds = async function() {
    var subscribers = await this.getSubscribers(true);
    return await* subscribers.map((subscriber) => subscriber.getRiverOfNewsTimelineId())
  }

  Timeline.prototype.isRiverOfNews = function() {
    return this.name === "RiverOfNews"
  }

  Timeline.prototype.isPosts = function() {
    return this.name === "Posts"
  }

  Timeline.prototype.isLikes = function() {
    return this.name === "Likes"
  }

  Timeline.prototype.isComments = function() {
    return this.name === "Comments"
  }

  Timeline.prototype.isDirects = function() {
    return this.name === "Directs"
  }

  Timeline.prototype.isHides = function() {
    return this.name === "Hides"
  }

  Timeline.prototype.updatePost = async function(postId, action) {
    var currentTime = new Date().getTime()

    var score = await database.zscoreAsync(mkKey(['timeline', this.id, 'posts']), postId)

    // For the time being like does not bump post
    if (action === "like" && score != null)
      return

    await* [
      database.zaddAsync(mkKey(['timeline', this.id, 'posts']), currentTime, postId),
      database.saddAsync(mkKey(['post', postId, 'timelines']), this.id),
      database.hsetAsync(mkKey(['post', postId]), 'updatedAt', currentTime)
    ]

    var feed = await this.getUser()

    // does not update lastActivity on like
    if (action === 'like')
      return null
    else
      return feed.updateLastActivityAt()
  }

  Timeline.prototype.turnIntoPrivate = function() {
    this.posts = []
    this.postIds = []
    this.limit = 0

    return this
  }

  Timeline.prototype.validateCanShow = async function(userId) {
    // owner can read her posts
    if (this.userId === userId)
      return true

    // if post is already in user's feed then she can read it
    if (this.isDirects())
      return this.userId === userId

    // this is a public feed, anyone can read public posts, this is
    // a free country
    var user = await this.getUser()
    if (user && user.isPrivate !== '1')
      return true

    // otherwise user can view post if and only if she is subscriber
    var userIds = await this.getSubscriberIds()
    return userIds.indexOf(userId) >= 0
  }

  return Timeline
}
<commit_msg>Zzzz.<commit_after>"use strict";

var Promise = require('bluebird')
  , uuid = require('uuid')
  , inherits = require("util").inherits
  , models = require('../models')
  , AbstractModel = models.AbstractModel
  , FeedFactory = models.FeedFactory
  , Post = models.Post
  , mkKey = require("../support/models").mkKey
  , pubSub = models.PubSub
  , _ = require('lodash')

exports.addModel = function(database) {
  /**
   * @constructor
   */
  var Timeline = function(params) {
    Timeline.super_.call(this)

    this.id = params.id
    this.name = params.name
    this.userId = params.userId
    if (parseInt(params.createdAt, 10))
      this.createdAt = params.createdAt
    if (parseInt(params.updatedAt, 10))
      this.updatedAt = params.updatedAt
    this.offset = parseInt(params.offset, 10) || 0
    this.limit = parseInt(params.limit, 10) || 30
    this.currentUser = params.currentUser
  }

  inherits(Timeline, AbstractModel)

  Timeline.className = Timeline
  Timeline.namespace = "timeline"
  Timeline.findById = Timeline.super_.findById

  Object.defineProperty(Timeline.prototype, 'name', {
    get: function() { return this.name_ },
    set: function(newValue) {
      newValue ? this.name_ = newValue.trim() : this.name_ = ''
    }
  })

  /**
   * Adds the specified post to all timelines where it needs to appear
   * (the timelines of the feeds to which it is posted, the River of News
   * timeline of the posting user and the River of News timelines of all
   * subscribers of the feeds to which it is posted).
   */
  Timeline.publishPost = function(post) {
    var that = this
    var currentTime = new Date().getTime()

    // We can use post.timelineIds here instead of post.getPostedToIds
    // because we are about to create that post and have just received
    // a request from user, so postedToIds == timelineIds here
    return Promise.map(post.timelineIds, function(timelineId) {
      return Timeline.findById(timelineId)
    })
      .then(function(timelines) {
        return Promise.map(timelines, function(timeline) {
          return timeline.getUser()
            .then(function(user) { return user.updateLastActivityAt() })
            .then(function() { return timeline.getSubscribedTimelineIds() })
        })
      })
      .then(function(allSubscribedTimelineIds) {
        var allTimelines = _.uniq(
          _.union(post.timelineIds, _.flatten(allSubscribedTimelineIds)))
        return Promise.map(allTimelines, function(timelineId) {
          return Promise.all([
            database.zaddAsync(mkKey(['timeline', timelineId, 'posts']), currentTime, post.id),
            database.hsetAsync(mkKey(['post', post.id]), 'updatedAt', currentTime),
            database.saddAsync(mkKey(['post', post.id, 'timelines']), timelineId)
          ])
        })
      })
      .then(function() { return pubSub.newPost(post.id) })
  }

  Timeline.prototype.validate = function() {
    return new Promise(function(resolve, reject) {
      var valid

      valid = this.name
        && this.name.length > 0
        && this.userId
        && this.userId.length > 0

      valid ? resolve(valid) : reject(new Error("Invalid"))
    }.bind(this))
  }

  Timeline.prototype.validateOnCreate = function() {
    var that = this

    return new Promise(function(resolve, reject) {
      Promise.join(that.validate(),
                   that.validateUniquness(mkKey(['timeline', that.id])),
                   function(valid, idIsUnique) {
                     resolve(that)
                   })
        .catch(function(e) { reject(e) })
      })
  }

  Timeline.prototype.create = function() {
    var that = this

    return new Promise(function(resolve, reject) {
      that.createdAt = new Date().getTime()
      that.updatedAt = new Date().getTime()
      if (!that.id)
        that.id = uuid.v4()

      that.validateOnCreate()
        .then(function(timeline) {
          return Promise.all([
            database.hmsetAsync(mkKey(['user', that.userId, 'timelines']),
                                that.name, that.id),
            database.hmsetAsync(mkKey(['timeline', that.id]),
                                { 'name': that.name,
                                  'userId': that.userId,
                                  'createdAt': that.createdAt.toString(),
                                  'updatedAt': that.updatedAt.toString()
                                })
          ])
        })
        .then(function(res) { resolve(that) })
        .catch(function(e) { reject(e) })
    })
  }

  Timeline.prototype.getPostIds = function(offset, limit) {
    if (_.isUndefined(offset))
      offset = this.offset
    else
      if (offset < 0) offset = 0
    // -1 = special magic number, meaning “do not use limit defaults,
    // do not use passed in value, use 0 instead". this is at the very least
    // used in Timeline.merge()
    if (_.isUndefined(limit))
      limit = this.limit
    else
      if (limit < 0) limit = 0

    var that = this
    return new Promise(function(resolve, reject) {
      that.validateCanShow(that.currentUser)
        .then(function(valid) {
          // this is a private timeline, you shall not pass
          if (!valid)
            return resolve([])

          return database.zrevrangeAsync(mkKey(['timeline', that.id, 'posts']), offset, offset+limit-1)
        })
        .then(function(postIds) {
          that.postIds = postIds
          resolve(that.postIds)
        })
    })
  }

  Timeline.prototype.getPostIdsByScore = function(min, max) {
    var that = this

    return new Promise(function(resolve, reject) {
      database.zrevrangebyscoreAsync(mkKey(['timeline', that.id, 'posts']), min, max)
        .then(function(postIds) {
          that.postIds = postIds
          resolve(that.postIds)
        })
    })
  }

  Timeline.prototype.getPosts = function(offset, limit) {
    if (_.isUndefined(offset))
      offset = this.offset
    else
      if (offset < 0) offset = 0

    if (_.isUndefined(limit))
      limit = this.limit
    else
      if (limit < 0) limit = 0

    var that = this
    var p_post
    var p_banIds

    return new Promise(function(resolve, reject) {
      that.getPostIds(offset, limit).bind({})
        .then(function(postIds) {
          return Promise.map(postIds, function(postId) {
            return Post.findById(postId, { currentUser: that.currentUser })
          })
        })
        .then(function(posts) {
          this.posts = posts
          return that.currentUser ? models.User.findById(that.currentUser) : null
        })
        .then(function(user) {
          return user ? user.getBanIds() : []
        })
        .then(function(banIds) {
          p_banIds = banIds
          return Promise.map(this.posts, function(post) {
            return models.User.findById(post.userId).then(function(user) {
              return user.getBanIds()
            }).then(function(reverseBanIds) {
              return ((p_banIds.indexOf(post.userId) >= 0) || (reverseBanIds.indexOf(that.currentUser) >= 0)) ? null : post
            })
          })
        })
        .then(function(posts) {
          that.posts = posts.filter(Boolean)
          resolve(that.posts)
        })
    })
  }

  Timeline.prototype.merge = function(timelineId) {
    var that = this

    return new Promise(function(resolve, reject) {
      database.zunionstoreAsync(
        mkKey(['timeline', timelineId, 'posts']), 2,
        mkKey(['timeline', timelineId, 'posts']),
        mkKey(['timeline', that.id, 'posts']),
        'AGGREGATE', 'MAX')
        .then(function() { return Timeline.findById(timelineId) })
        .then(function(timeline) { return timeline.getPostIds(0, -1) })
        .then(function(postIds) {
          return Promise.map(postIds, function(postId) {
            return database.sadd(mkKey(['post', postId, 'timelines']), timelineId)
          })
        })
        .then(function(res) { resolve(res) })
    })
  }

  Timeline.prototype.unmerge = async function(timelineId) {
    // zinterstore saves results to a key. so we have to
    // create a temporary storage
    var randomKey = mkKey(['timeline', this.id, 'random', uuid.v4()])

    await database.zinterstoreAsync(
      randomKey,
      2,
      mkKey(['timeline', timelineId, 'posts']),
      mkKey(['timeline', this.id, 'posts']),
      'AGGREGATE', 'MAX')

    var postIds = await database.zrangeAsync(randomKey, 0, -1)
    await* _.flatten(postIds.map((postId) => [
      database.sremAsync(mkKey(['post', postId, 'timelines']), timelineId),
      database.zremAsync(mkKey(['timeline', timelineId, 'posts']), postId)
    ]))

    return database.delAsync(randomKey)
  }

  Timeline.prototype.getUser = function() {
    return models.FeedFactory.findById(this.userId)
  }

  /**
   * Returns the IDs of users subscribed to this timeline, as a promise.
   */
  Timeline.prototype.getSubscriberIds = function(includeSelf) {
    var that = this

    return new Promise(function(resolve, reject) {
      database.zrevrangeAsync(mkKey(['timeline', that.id, 'subscribers']), 0, -1)
        .then(function(userIds) {
          // A user is always subscribed to their own posts timeline.
          if (includeSelf && (that.isPosts() || that.isDirects())) {
            userIds = _.uniq(userIds.concat([that.userId]))
          }
          that.subscriberIds = userIds
          resolve(userIds)
        })
    })
  }

  Timeline.prototype.getSubscribers = async function(includeSelf) {
    var userIds = await this.getSubscriberIds(includeSelf)
    var promises = userIds.map((userId) => models.User.findById(userId))

    this.subscribers = await* promises

    return this.subscribers
  }

  /**
   * Returns the list of the 'River of News' timelines of all subscribers to this
   * timeline.
   */
  Timeline.prototype.getSubscribedTimelineIds = async function() {
    var subscribers = await this.getSubscribers(true);
    return await* subscribers.map((subscriber) => subscriber.getRiverOfNewsTimelineId())
  }

  Timeline.prototype.isRiverOfNews = function() {
    return this.name === "RiverOfNews"
  }

  Timeline.prototype.isPosts = function() {
    return this.name === "Posts"
  }

  Timeline.prototype.isLikes = function() {
    return this.name === "Likes"
  }

  Timeline.prototype.isComments = function() {
    return this.name === "Comments"
  }

  Timeline.prototype.isDirects = function() {
    return this.name === "Directs"
  }

  Timeline.prototype.isHides = function() {
    return this.name === "Hides"
  }

  Timeline.prototype.updatePost = async function(postId, action) {
    var currentTime = new Date().getTime()

    var score = await database.zscoreAsync(mkKey(['timeline', this.id, 'posts']), postId)

    // For the time being like does not bump post
    if (action === "like" && score != null)
      return

    await* [
      database.zaddAsync(mkKey(['timeline', this.id, 'posts']), currentTime, postId),
      database.saddAsync(mkKey(['post', postId, 'timelines']), this.id),
      database.hsetAsync(mkKey(['post', postId]), 'updatedAt', currentTime)
    ]

    var feed = await this.getUser()

    // does not update lastActivity on like
    if (action === 'like')
      return null
    else
      return feed.updateLastActivityAt()
  }

  Timeline.prototype.turnIntoPrivate = function() {
    this.posts = []
    this.postIds = []
    this.limit = 0

    return this
  }

  Timeline.prototype.validateCanShow = async function(userId) {
    // owner can read her posts
    if (this.userId === userId)
      return true

    // if post is already in user's feed then she can read it
    if (this.isDirects())
      return this.userId === userId

    // this is a public feed, anyone can read public posts, this is
    // a free country
    var user = await this.getUser()
    if (user && user.isPrivate !== '1')
      return true

    // otherwise user can view post if and only if she is subscriber
    var userIds = await this.getSubscriberIds()
    return userIds.indexOf(userId) >= 0
  }

  return Timeline
}
<|endoftext|>


<commit_before>// ==UserScript==
// @name         Putlocker Navigation Buttons
// @name:zh-CN   Putlocker电视剧上/下集按钮
// @namespace    http://rotar.tk/
// @version      0.1.1
// @description  Add navigation buttons (previous,next) under putlocker tvshow videos.
// @description:zh-CN  在Putlocker电视剧播放页面添加上/下集按钮。
// @author       Rotar
// @match        http://putlocker.is/watch*
// @grant        none
// @downloadURL  https://github.com/kaneorotar/putlocker-userscript/raw/master/putlocker-navi.user.js
// ==/UserScript==

(function() {
	'use strict';
	var curl = window.location.href;
	var vals = /watch-([a-z0-9\-]*)-tvshow(-season-([0-9]{1,2})-episode-([0-9]{1,2}))?-online/.exec(curl);
	var se = 0, ep = 0;
	var iurl = curl.replace(/-season-([0-9]{1,2})-episode-([0-9]{1,2})/,"");
	var retries = 3;

	var list = {
		seasons: [],
		season: function(){
			return this.seasons.length-1;
		},
		episode: function(season){
			return this.seasons[season];
		},
		next: function(season,episode){
			if((episode+1)>this.episode(season)){
				if((season+1)>this.season()){
					return null;
				}else{
					return [season+1,1];
				}
			}else{
				return [season,episode+1];
			}
		},
		previous: function(season,episode){
			if((episode-1)<1){
				if((season-1)<1){
					return null;
				}else{
					return [season-1,this.episode(season-1)];
				}
			}else{
				return [season,episode-1];
			}
		}
	};

	if(typeof(vals[2])!="undefined"){//on an episode page
		se = parseInt(vals[3]);
		ep = parseInt(vals[4]);

		var contentbox = document.getElementsByClassName("content-box")[0];
		var msgdiv = document.getElementsByClassName("message")[0];

		var navi = document.createElement("div");
		navi.id = "navi_box";
		navi.style.height = "40px";
		navi.style.position = "relative";
		navi.style.textAlign = "center";
		navi.style.fontFamily = "Arial";
		navi.style.fontSize = "22px";
		navi.style.marginTop = "-5px";
		navi.style.display = "None";

		var prev = document.createElement("a");
		prev.id = "prev_btn";
		prev.style.borderRadius = "4px";
		prev.style.position = "absolute";
		prev.style.left = "5px";
		prev.style.padding = "5px";
		prev.style.fontWeight = "bold";
		prev.style.backgroundImage = "-webkit-linear-gradient(top,#18db03 0%,#2a7a54 100%)";
		prev.style.color = "#EEEEEE";
		prev.style.cursor = "pointer";
		prev.innerHTML = "PREVIOUS";
		prev.style.display = "None";
		navi.appendChild(prev);

		var next = document.createElement("a");
		next.id = "next_btn";
		next.style.borderRadius = "4px";
		next.style.position = "absolute";
		next.style.right = "5px";
		next.style.padding = "5px";
		next.style.fontWeight = "bold";
		next.style.backgroundImage = "-webkit-linear-gradient(top,#18db03 0%,#2a7a54 100%)";
		next.style.color = "#EEEEEE";
		next.style.cursor = "pointer";
		next.innerHTML = "NEXT";
		next.style.display = "None";
		navi.appendChild(next);

		contentbox.insertBefore(navi,msgdiv);
		getIndex(iurl);
	}

	function validate(elem){
		var result;
		if(elem.innerHTML.toLowerCase().substr(0,4)=="prev"){
			result = list.previous(se,ep);
		}else{
			result = list.next(se,ep);
		}
		if(result){
			var nurl = window.location.href.replace("-season-"+se+"-episode-"+ep,"-season-"+result[0]+"-episode-"+result[1]);
			elem.href = nurl;
			if(result[0]<se){
				elem.innerHTML = "PREVIOUS SEASON";
			}else if(result[0]>se){
				elem.innerHTML = "NEXT SEASON";
			}
			elem.style.display = "block";
			navi.style.display = "block";
		}
	}

	function getIndex(lurl){
		var responseb;
		var xhr = new XMLHttpRequest();
		xhr.onloadend = function() {
			if (xhr.status == 200 || xhr.status == 304) {
				responseb = xhr.responseText;
				var dp = new DOMParser();
				var rdom = dp.parseFromString(responseb,'text/html');
				console.log("Index Successfully Fetched");

				var icontentbox = rdom.getElementsByClassName("content-box")[0];
				var tbs = icontentbox.getElementsByTagName("table");
				for(var i=0; i<(tbs.length-5); i++){//put number of episodes for each season into list.seasons
					list.seasons[i+1]=tbs[2+i].childNodes[0].childNodes.length;
				}
				validate(prev);
				validate(next);
			}else{
				console.log("[Request Error] Index fetching failed.");
				retries--;
				if(retries>=0){
					console.log("[Request Error] Retrying in 3 seconds");
					setTimeOut(function(){
						console.log("Fetching Index...");
						getIndex(lurl);
					},3000);
				}
			}
		};

		xhr.open('GET', lurl, true);
		xhr.send(null);
	}
})();<commit_msg>Removed redundant style, using existing class instead<commit_after>// ==UserScript==
// @name         Putlocker Navigation Buttons
// @name:zh-CN   Putlocker电视剧上/下集按钮
// @namespace    http://rotar.tk/
// @version      0.1.1
// @description  Add navigation buttons (previous,next) under putlocker tvshow videos.
// @description:zh-CN  在Putlocker电视剧播放页面添加上/下集按钮。
// @author       Rotar
// @match        http://putlocker.is/watch*
// @grant        none
// @downloadURL  https://github.com/kaneorotar/putlocker-userscript/raw/master/putlocker-navi.user.js
// ==/UserScript==

(function() {
	'use strict';
	var curl = window.location.href;
	var vals = /watch-([a-z0-9\-]*)-tvshow(-season-([0-9]{1,2})-episode-([0-9]{1,2}))?-online/.exec(curl);
	var se = 0, ep = 0;
	var iurl = curl.replace(/-season-([0-9]{1,2})-episode-([0-9]{1,2})/,"");
	var retries = 3;

	var list = {
		seasons: [],
		season: function(){
			return this.seasons.length-1;
		},
		episode: function(season){
			return this.seasons[season];
		},
		next: function(season,episode){
			if((episode+1)>this.episode(season)){
				if((season+1)>this.season()){
					return null;
				}else{
					return [season+1,1];
				}
			}else{
				return [season,episode+1];
			}
		},
		previous: function(season,episode){
			if((episode-1)<1){
				if((season-1)<1){
					return null;
				}else{
					return [season-1,this.episode(season-1)];
				}
			}else{
				return [season,episode-1];
			}
		}
	};

	if(typeof(vals[2])!="undefined"){//on an episode page
		se = parseInt(vals[3]);
		ep = parseInt(vals[4]);

		var contentbox = document.getElementsByClassName("content-box")[0];
		var msgdiv = document.getElementsByClassName("message")[0];

		var navi = document.createElement("div");
		navi.id = "navi_box";
		navi.style.height = "40px";
		navi.style.position = "relative";
		navi.style.textAlign = "center";
		navi.style.fontFamily = "Arial";
		navi.style.fontSize = "22px";
		navi.style.marginTop = "-5px";
		navi.style.display = "None";

		var prev = document.createElement("a");
		prev.id = "prev_btn";
		prev.style.position = "absolute";
		prev.style.left = "5px";
		prev.style.padding = "5px";
		prev.style.color = "#EEEEEE";
		prev.innerHTML = "PREVIOUS";
		prev.class = "movgr";
		prev.style.display = "None";
		navi.appendChild(prev);

		var next = document.createElement("a");
		next.id = "next_btn";
		next.style.position = "absolute";
		next.style.right = "5px";
		next.style.padding = "5px";
		next.style.color = "#EEEEEE";
		next.innerHTML = "NEXT";
		prev.class = "movgr";
		next.style.display = "None";
		navi.appendChild(next);

		contentbox.insertBefore(navi,msgdiv);
		getIndex(iurl);
	}

	function validate(elem){
		var result;
		if(elem.innerHTML.toLowerCase().substr(0,4)=="prev"){
			result = list.previous(se,ep);
		}else{
			result = list.next(se,ep);
		}
		if(result){
			var nurl = window.location.href.replace("-season-"+se+"-episode-"+ep,"-season-"+result[0]+"-episode-"+result[1]);
			elem.href = nurl;
			if(result[0]<se){
				elem.innerHTML = "PREVIOUS SEASON";
			}else if(result[0]>se){
				elem.innerHTML = "NEXT SEASON";
			}
			elem.style.display = "block";
			navi.style.display = "block";
		}
	}

	function getIndex(lurl){
		var responseb;
		var xhr = new XMLHttpRequest();
		xhr.onloadend = function() {
			if (xhr.status == 200 || xhr.status == 304) {
				responseb = xhr.responseText;
				var dp = new DOMParser();
				var rdom = dp.parseFromString(responseb,'text/html');
				console.log("Index Successfully Fetched");

				var icontentbox = rdom.getElementsByClassName("content-box")[0];
				var tbs = icontentbox.getElementsByTagName("table");
				for(var i=0; i<(tbs.length-5); i++){//put number of episodes for each season into list.seasons
					list.seasons[i+1]=tbs[2+i].childNodes[0].childNodes.length;
				}
				validate(prev);
				validate(next);
			}else{
				console.log("[Request Error] Index fetching failed.");
				retries--;
				if(retries>=0){
					console.log("[Request Error] Retrying in 3 seconds");
					setTimeOut(function(){
						console.log("Fetching Index...");
						getIndex(lurl);
					},3000);
				}
			}
		};

		xhr.open('GET', lurl, true);
		xhr.send(null);
	}
})();<|endoftext|>