<commit_before>import Promise from 'bluebird';
import contentTypeParser from 'content-type';
import os from 'os';
import fs from 'fs';
import _ from 'underscore';
import fsExtra from 'fs-extra';
import ApifyClient from 'apify-client';
import { ENV_VARS } from './constants';

const ensureDirPromised = Promise.promisify(fsExtra.ensureDir);

/**
 * Creates an instance of ApifyClient using options as defined in the environment variables.
 * This function is exported to enable unit testing.
 *
 * @returns {*}
 * @ignore
 */
export const newClient = () => {
    const opts = {
        userId: process.env[ENV_VARS.USER_ID] || null,
        token: process.env[ENV_VARS.TOKEN] || null,
    };

    // Only set baseUrl if overridden by env var, so that 'https://api.apify.com' is used by default.
    // This simplifies local development, which should run against production unless user wants otherwise.
    const apiBaseUrl = process.env[ENV_VARS.API_BASE_URL];
    if (apiBaseUrl) opts.baseUrl = apiBaseUrl;

    return new ApifyClient(opts);
};

/**
 * A default instance of the `ApifyClient` class provided
 * by the apify-client NPM package.
 * The instance is created when the `apify` package is first imported
 * and it is configured using the `APIFY_API_BASE_URL`, `APIFY_USER_ID` and `APIFY_TOKEN`
 * environment variables.
 *
 * After that, the instance is used for all underlying calls to the Apify API
 * in functions such as Apify.getValue()
 * or Apify.call().
 * The settings of the client can be globally altered by calling the
 * Apify.client.setOptions() function.
 * Just be careful, it might have undesired effects on other functions provided by this package.
 *
 * @memberof module:Apify
 * @name client
 * @instance
 */
export const apifyClient = newClient();

/**
 * Returns a result of `Promise.resolve()`.
 *
 * @returns {*}
 *
 * @ignore
 */
export const newPromise = () => {
    return Promise.resolve();
};

/**
 * Adds charset=utf-8 to given content type if this parameter is missing.
 *
 * @param contentType
 * @returns {string}
 *
 * @ignore
 */
export const addCharsetToContentType = (contentType) => {
    if (!contentType) return contentType;

    const parsed = contentTypeParser.parse(contentType);

    if (parsed.parameters.charset) return contentType;

    parsed.parameters.charset = 'utf-8';

    return contentTypeParser.format(parsed);
};

let isDockerPromise;
const createIsDockerPromise = () => {
    const promise1 = Promise
        .promisify(fs.stat)('/.dockerenv')
        .then(() => true)
        .catch(() => false);

    const promise2 = Promise
        .promisify(fs.readFile)('/proc/self/cgroup', 'utf8')
        .then(content => content.indexOf('docker') !== -1)
        .catch(() => false);

    return Promise
        .all([promise1, promise2])
        .then(([result1, result2]) => result1 || result2);
};

/**
 * Returns promise that resolves to true if the code is running in a Docker container.
 *
 * @return {Promise}
 *
 * @memberof module:Apify
 * @name isDocker
 * @instance
 * @function
 */
export const isDocker = (forceReset) => {
    // Parameter forceReset is just internal for unit tests.
    if (!isDockerPromise || forceReset) isDockerPromise = createIsDockerPromise();

    return isDockerPromise;
};

/**
 * Returns memory statistics of the container, which is an object with the following properties:
 *
 * ```javascript
 * {
 *   // Total memory available to the act
 *   totalBytes: Number,
 *
 *   // Amount of free memory
 *   freeBytes: Number,
 *
 *   // Amount of memory used (= totalBytes - freeBytes)
 *   usedBytes: Number,
 * }
 * ```
 *
 * @returns {Promise} Returns a promise.
 *
 * @memberof module:Apify
 * @name getMemoryInfo
 * @instance
 * @function
 */
export const getMemoryInfo = () => {
    // module.exports must be here so that we can mock it.
    return module.exports.isDocker()
        .then((isDockerVar) => {
            if (!isDockerVar) {
                const freeBytes = os.freemem();
                const totalBytes = os.totalmem();

                return Promise.resolve({ totalBytes, freeBytes, usedBytes: totalBytes - freeBytes });
            }

            // This must be promisified here so that we can Mock it.
            const readPromised = Promise.promisify(fs.readFile);

            return Promise
                .all([
                    readPromised('/sys/fs/cgroup/memory/memory.limit_in_bytes'),
                    readPromised('/sys/fs/cgroup/memory/memory.usage_in_bytes'),
                ])
                .then(([totalBytesStr, usedBytesStr]) => {
                    const totalBytes = parseInt(totalBytesStr, 10);
                    const usedBytes = parseInt(usedBytesStr, 10);

                    return { totalBytes, freeBytes: totalBytes - usedBytes, usedBytes };
                });
        });
};

/**
 * Helper function that detrermines if given parameter is an instance of Promise.
 *
 * @ignore
 */
export const isPromise = (maybePromise) => {
    return maybePromise && typeof maybePromise.then === 'function' && typeof maybePromise.catch === 'function';
};

/**
 * Helper function for validation if parameter is an instance of given prototype or multiple prototypes.
 * TODO: Move this to shared package along with checkParamOrThrow
 *
 * @ignore
 */
export const checkParamPrototypeOrThrow = (paramVal, paramName, prototypes, prototypeName, isOptional = false) => {
    if (isOptional && (paramVal === undefined || paramVal === null)) return;

    const hasCorrectPrototype = prototypes instanceof Array
        ? _.some(prototypes, prototype => paramVal instanceof prototype)
        : paramVal instanceof prototypes;

    if (!hasCorrectPrototype) throw new Error(`Parameter "${paramName}" must be an instance of ${prototypeName}`);
};

/**
 * Returns true if node is in production environment and false otherwise.
 *
 * @ignore
 */
export const isProduction = () => process.env.NODE_ENV !== 'production';

/**
 * Helper function used for local implementations. Creates dir.
 *
 * @ignore
 */
export const ensureDirExists = path => ensureDirPromised(path);

/**
 * Helper function that returns the first key from plan object.
 *
 * @ignore
 */
export const getFirstKey = (dict) => {
    for (const key in dict) { // eslint-disable-line guard-for-in, no-restricted-syntax
        return key;
    }
};

/**
 * Gets a typical path to Chrome executable, depending on the current operating system.
 * @returns {string}
 */
export const getTypicalChromeExecutablePath = () => {
    switch (os.platform()) {
    case 'darwin': return '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome';
    case 'win32': return 'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe';
    default: return 'google-chrome';
    }
};
<commit_msg>Fixing documentation<commit_after>import Promise from 'bluebird';
import contentTypeParser from 'content-type';
import os from 'os';
import fs from 'fs';
import _ from 'underscore';
import fsExtra from 'fs-extra';
import ApifyClient from 'apify-client';
import { ENV_VARS } from './constants';

const ensureDirPromised = Promise.promisify(fsExtra.ensureDir);

/**
 * Creates an instance of ApifyClient using options as defined in the environment variables.
 * This function is exported to enable unit testing.
 *
 * @returns {*}
 * @ignore
 */
export const newClient = () => {
    const opts = {
        userId: process.env[ENV_VARS.USER_ID] || null,
        token: process.env[ENV_VARS.TOKEN] || null,
    };

    // Only set baseUrl if overridden by env var, so that 'https://api.apify.com' is used by default.
    // This simplifies local development, which should run against production unless user wants otherwise.
    const apiBaseUrl = process.env[ENV_VARS.API_BASE_URL];
    if (apiBaseUrl) opts.baseUrl = apiBaseUrl;

    return new ApifyClient(opts);
};

/**
 * A default instance of the `ApifyClient` class provided
 * by the apify-client NPM package.
 * The instance is created when the `apify` package is first imported
 * and it is configured using the `APIFY_API_BASE_URL`, `APIFY_USER_ID` and `APIFY_TOKEN`
 * environment variables.
 *
 * After that, the instance is used for all underlying calls to the Apify API
 * in functions such as Apify.getValue()
 * or Apify.call().
 * The settings of the client can be globally altered by calling the
 * Apify.client.setOptions() function.
 * Just be careful, it might have undesired effects on other functions provided by this package.
 *
 * @memberof module:Apify
 * @name client
 * @instance
 */
export const apifyClient = newClient();

/**
 * Returns a result of `Promise.resolve()`.
 *
 * @returns {*}
 *
 * @ignore
 */
export const newPromise = () => {
    return Promise.resolve();
};

/**
 * Adds charset=utf-8 to given content type if this parameter is missing.
 *
 * @param contentType
 * @returns {string}
 *
 * @ignore
 */
export const addCharsetToContentType = (contentType) => {
    if (!contentType) return contentType;

    const parsed = contentTypeParser.parse(contentType);

    if (parsed.parameters.charset) return contentType;

    parsed.parameters.charset = 'utf-8';

    return contentTypeParser.format(parsed);
};

let isDockerPromise;
const createIsDockerPromise = () => {
    const promise1 = Promise
        .promisify(fs.stat)('/.dockerenv')
        .then(() => true)
        .catch(() => false);

    const promise2 = Promise
        .promisify(fs.readFile)('/proc/self/cgroup', 'utf8')
        .then(content => content.indexOf('docker') !== -1)
        .catch(() => false);

    return Promise
        .all([promise1, promise2])
        .then(([result1, result2]) => result1 || result2);
};

/**
 * Returns promise that resolves to true if the code is running in a Docker container.
 *
 * @return {Promise}
 *
 * @memberof module:Apify
 * @name isDocker
 * @instance
 * @function
 */
export const isDocker = (forceReset) => {
    // Parameter forceReset is just internal for unit tests.
    if (!isDockerPromise || forceReset) isDockerPromise = createIsDockerPromise();

    return isDockerPromise;
};

/**
 * Returns memory statistics of the container, which is an object with the following properties:
 *
 * ```javascript
 * {
 *   // Total memory available to the act
 *   totalBytes: Number,
 *
 *   // Amount of free memory
 *   freeBytes: Number,
 *
 *   // Amount of memory used (= totalBytes - freeBytes)
 *   usedBytes: Number,
 * }
 * ```
 *
 * @returns {Promise} Returns a promise.
 *
 * @memberof module:Apify
 * @name getMemoryInfo
 * @instance
 * @function
 */
export const getMemoryInfo = () => {
    // module.exports must be here so that we can mock it.
    return module.exports.isDocker()
        .then((isDockerVar) => {
            if (!isDockerVar) {
                const freeBytes = os.freemem();
                const totalBytes = os.totalmem();

                return Promise.resolve({ totalBytes, freeBytes, usedBytes: totalBytes - freeBytes });
            }

            // This must be promisified here so that we can Mock it.
            const readPromised = Promise.promisify(fs.readFile);

            return Promise
                .all([
                    readPromised('/sys/fs/cgroup/memory/memory.limit_in_bytes'),
                    readPromised('/sys/fs/cgroup/memory/memory.usage_in_bytes'),
                ])
                .then(([totalBytesStr, usedBytesStr]) => {
                    const totalBytes = parseInt(totalBytesStr, 10);
                    const usedBytes = parseInt(usedBytesStr, 10);

                    return { totalBytes, freeBytes: totalBytes - usedBytes, usedBytes };
                });
        });
};

/**
 * Helper function that detrermines if given parameter is an instance of Promise.
 *
 * @ignore
 */
export const isPromise = (maybePromise) => {
    return maybePromise && typeof maybePromise.then === 'function' && typeof maybePromise.catch === 'function';
};

/**
 * Helper function for validation if parameter is an instance of given prototype or multiple prototypes.
 * TODO: Move this to shared package along with checkParamOrThrow
 *
 * @ignore
 */
export const checkParamPrototypeOrThrow = (paramVal, paramName, prototypes, prototypeName, isOptional = false) => {
    if (isOptional && (paramVal === undefined || paramVal === null)) return;

    const hasCorrectPrototype = prototypes instanceof Array
        ? _.some(prototypes, prototype => paramVal instanceof prototype)
        : paramVal instanceof prototypes;

    if (!hasCorrectPrototype) throw new Error(`Parameter "${paramName}" must be an instance of ${prototypeName}`);
};

/**
 * Returns true if node is in production environment and false otherwise.
 *
 * @ignore
 */
export const isProduction = () => process.env.NODE_ENV !== 'production';

/**
 * Helper function used for local implementations. Creates dir.
 *
 * @ignore
 */
export const ensureDirExists = path => ensureDirPromised(path);

/**
 * Helper function that returns the first key from plan object.
 *
 * @ignore
 */
export const getFirstKey = (dict) => {
    for (const key in dict) { // eslint-disable-line guard-for-in, no-restricted-syntax
        return key;
    }
};

/**
 * Gets a typical path to Chrome executable, depending on the current operating system.
 *
 * @returns {string}
 * @ignore
 */
export const getTypicalChromeExecutablePath = () => {
    switch (os.platform()) {
    case 'darwin': return '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome';
    case 'win32': return 'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe';
    default: return 'google-chrome';
    }
};
<|endoftext|>


<commit_before>// @flow

import moment from 'moment'
import {createAction, type ActionType} from 'redux-actions'

import {createVoidPayloadAction, secureFetch} from '../../common/actions'
import {isExtensionEnabled} from '../../common/util/config'
import {fetchDeployment} from './deployments'
import {fetchFeedSource} from './feeds'
import {downloadMergedFeedViaToken} from './projects'
import {downloadSnapshotViaCredentials} from '../../editor/actions/snapshots'

import type {ServerJob} from '../../types'
import type {dispatchFn, getStateFn} from '../../types/reducers'

type ErrorMessage = {
  action?: string,
  detail?: any,
  message: string,
  title?: string
}

export const clearStatusModal = createVoidPayloadAction('CLEAR_STATUS_MODAL')
const handlingFinishedJob = createAction(
  'HANDLING_FINISHED_JOB',
  (payload: ServerJob) => payload
)
export const receiveJobs = createAction(
  'RECEIVE_JOBS',
  (payload: Array<ServerJob>) => payload
)
export const removeRetiredJob = createAction(
  'REMOVE_RETIRED_JOB',
  (payload: ServerJob) => payload
)
export const setErrorMessage = createAction(
  'SET_ERROR_MESSAGE',
  (payload: ErrorMessage | Promise<ErrorMessage>) => payload
)
export const setJobMonitorTimer = createAction(
  'SET_JOBMONITOR_TIMER',
  (payload: ?IntervalID) => payload
)
export const setJobMonitorVisible = createAction(
  'SET_JOBMONITOR_VISIBLE',
  (payload: boolean) => payload
)
const setAppInfo = createAction(
  'SET_APP_INFO',
  (payload: {
    commit: string,
    repoUrl: string
  }) => payload
)

const setStatusModal = createAction(
  'SET_STATUS_MODAL',
  (payload: {
    action?: any,
    body: string,
    detail?: any,
    title: string
  }) => payload
)

export type StatusActions = ActionType<typeof clearStatusModal> |
  ActionType<typeof handlingFinishedJob> |
  ActionType<typeof receiveJobs> |
  ActionType<typeof removeRetiredJob> |
  ActionType<typeof setJobMonitorTimer> |
  ActionType<typeof setJobMonitorVisible> |
  ActionType<typeof setErrorMessage> |
  ActionType<typeof setAppInfo>

/**
 * Check status of user's jobs. Handle any finished jobs and if there are no
 * remaining active jobs, stop job monitor.
 */
export function checkJobStatus () {
  return function (dispatch: dispatchFn, getState: getStateFn) {
    return dispatch(secureFetch(`/api/manager/secure/status/jobs`))
      .then(response => response.json())
      .then(jobs => {
        // Check for any just-finished jobs
        const completedJobs = jobs
          .filter(job => job.status.completed || job.status.error)
        completedJobs
          // Retire any completed or errored jobs.
          .forEach(job => dispatch(handleFinishedJob(job)))

        const remainingJobs = jobs
          .filter(job => !job.status.completed && !job.status.error)
        if (remainingJobs.length === 0) {
          // If there are no remaining jobs, stop the timer.
          dispatch(stopJobMonitor())
        }
        // Add remaining jobs to set of active jobs. Note, this may be an empty
        // array if finished jobs have been handled, but that is the expected
        // behavior because these finished jobs will appear in a "retired" state.
        dispatch(receiveJobs(remainingJobs))
        return jobs
      })
  }
}

export function fetchAppInfo () {
  return function (dispatch: dispatchFn, getState: getStateFn) {
    // only fetch if we don't yet have server info
    if (!getState().status.appInfo) {
      // fetch info.  If an error occurs or response json doesn't match, set
      // server info to value indicating an unknown commit and repoUrl
      return dispatch(secureFetch('/api/manager/public/appinfo'))
        .then(response => response.json())
        .then(info => {
          if (info.commit && info.repoUrl) {
            info.repoUrl = info.repoUrl.replace('.git', '')
            dispatch(setAppInfo(info))
          } else {
            throw new Error('unable to interpret app info')
          }
        })
        .catch(() => {
          dispatch(setAppInfo({
            commit: 'unknown',
            repoUrl: '?'
          }))
        })
    }
  }
}

/* eslint-disable complexity */
export function handleFinishedJob (job: ServerJob) {
  return function (dispatch: dispatchFn, getState: getStateFn) {
    dispatch(handlingFinishedJob(job))
    switch (job.type) {
      case 'VALIDATE_FEED':
        if (!job.feedSourceId) {
          // FIXME use setErrorMessage instead?
          console.warn('No feedSourceId found on job')
          return
        }
        dispatch(fetchFeedSource(job.feedSourceId, true))
        if (isExtensionEnabled('mtc')) {
          const firstDate = job.validationResult && job.validationResult.firstCalendarDate
          const now = moment().startOf('day')
          if (firstDate && moment(firstDate).isAfter(now)) {
            dispatch(setStatusModal({
              title: 'Warning: Feed contains service for future dates only!',
              body: `Use the 'Merge' button to merge with the currently active feed version.`
            }))
          }
        }
        break
      // CREATE_SNAPSHOT accounts for both creating an initial snapshot from GTFS
      // as well as "restoring" a snapshot to the active buffer, which essentially
      // just creates a copy of the snapshot to restore and points at the new
      // snapshot for the new buffer.
      case 'CREATE_SNAPSHOT':
        if (!job.feedSourceId) {
          // FIXME use setErrorMessage instead?
          console.warn('No feedSourceId found on job')
          return
        }
        dispatch(fetchFeedSource(job.feedSourceId, true))
        break
      // case 'PROCESS_SNAPSHOT':
      //   dispatch(fetchSnapshots(job.feedVersion.feedSource))
      //   break
      //
      case 'CREATE_FEEDVERSION_FROM_SNAPSHOT':
        if (!job.feedSourceId) {
          // FIXME use setErrorMessage instead?
          console.warn('No feedSourceId found on job')
          return
        }
        dispatch(fetchFeedSource(job.feedSourceId, true))
        break
      case 'EXPORT_SNAPSHOT_TO_GTFS':
        if (job.parentJobId) {
          console.log('Not downloading snapshot GTFS. Export job part of feed version creation.')
        } else {
          if (job.snapshot) {
            dispatch(downloadSnapshotViaCredentials(job.snapshot, false))
          }
        }
        break
      case 'DEPLOY_TO_OTP':
        return dispatch(fetchDeployment(job.deploymentId))
      case 'MERGE_FEED_VERSIONS':
        if (job.mergeType === 'REGIONAL') {
          // If merging feeds for the project, end result is to download zip.
          if (!job.projectId) {
            // FIXME use setErrorMessage instead?
            console.warn('No project found on job')
            return
          }
          dispatch(downloadMergedFeedViaToken(job.projectId, false))
        } else {
          const result = job.mergeFeedsResult
          const details = []
          if (result) {
            // Do nothing or show merged feed modal? Feed version is be created
            details.push('Remapped ID count: ' + result.remappedReferences)
            if (Object.keys(result.remappedIds).length > 0) {
              const remappedIdStrings = []
              for (let key in result.remappedIds) {
                // Modify key to remove feed name.
                const split = key.split(':')
                const tableAndId = split.splice(1, 1)
                remappedIdStrings.push(`${tableAndId.join(':')} -> ${result.remappedIds[key]}`)
              }
              details.push('Remapped IDs: ' + remappedIdStrings.join(', '))
            }
            if (result.skippedIds.length > 0) {
              const skippedRecordsForTables = {}
              result.skippedIds.forEach(id => {
                const table = id.split(':')[0]
                if (skippedRecordsForTables[table]) {
                  skippedRecordsForTables[table] = skippedRecordsForTables[table] + 1
                } else {
                  skippedRecordsForTables[table] = 1
                }
              })
              const skippedRecordsStrings = []
              for (let key in skippedRecordsForTables) {
                skippedRecordsStrings.push(`${key} - ${skippedRecordsForTables[key]}`)
              }
              details.push('Skipped records: ' + skippedRecordsStrings.join(', '))
            }
            if (result.idConflicts.length > 0) {
              // const conflicts = result.idConflicts
              details.push('ID conflicts: ' + result.idConflicts.join(', '))
            }
            dispatch(setStatusModal({
              title: result && result.failed
                ? 'Warning: Errors encountered during feed merge!'
                : 'Feed merge was successful!',
              body: `Merge failed with ${result.errorCount} errors. ${result.failureReasons.join(', ')}`,
              detail: details.join('\n')
            }))
          }
        }
        break
      default:
        console.warn(`No completion step defined for job type ${job.type}`)
    }
  }
}

export function handleJobResponse (response: Response, message: string) {
  return function (dispatch: dispatchFn, getState: getStateFn) {
    if (response.status >= 400) {
      const props = message
        ? {message}
        // If no message specified, use error message supplied by JSON response
        : response.json()
      return dispatch(setErrorMessage(props))
    } else {
      // Resulting JSON contains message and job ID wich which to monitor job.
      const json: {jobId: number, message: string} = (response.json(): any)
      dispatch(startJobMonitor())
      // Return json with job ID in case it is needed upstream
      return json
    }
  }
}

/**
 * Start job monitor. This stops any existing timer function and initializes
 * the fetch user jobs status function on a timer.
 */
export function startJobMonitor (showMonitor: boolean = true) {
  return function (dispatch: dispatchFn, getState: getStateFn) {
    stopCurrentTimer(getState())

    // Check job status immediately.
    dispatch(checkJobStatus())
      .then(jobs => {
        // If there are active jobs, set up a check for every two seconds if
        // jobs are still running. Note: when the status endpoint is checked for
        // a user and completed/errored jobs are returned, the server will
        // automatically remove these jobs from the active list.
        if (jobs.length > 0) {
          const timer = setInterval(() => dispatch(checkJobStatus()), 2000)
          dispatch(setJobMonitorTimer(timer))
          if (showMonitor) dispatch(setJobMonitorVisible(true))
        }
      })
      .catch(err => console.warn('Error fetchings jobs', err))
  }
}

/**
 * Stop job time function stored with timer ID stored in state.
 */
function stopCurrentTimer (state) {
  const {timer} = state.status.jobMonitor
  if (timer) clearInterval(timer)
}

/**
 * Stop current job timer function and set job monitor (timer ID) to null.
 */
export function stopJobMonitor () {
  return function (dispatch: dispatchFn, getState: getStateFn) {
    stopCurrentTimer(getState())
    dispatch(setJobMonitorTimer(null))
  }
}
<commit_msg>fix(merge-feeds): fix success message for modal<commit_after>// @flow

import moment from 'moment'
import {createAction, type ActionType} from 'redux-actions'

import {createVoidPayloadAction, secureFetch} from '../../common/actions'
import {isExtensionEnabled} from '../../common/util/config'
import {fetchDeployment} from './deployments'
import {fetchFeedSource} from './feeds'
import {downloadMergedFeedViaToken} from './projects'
import {downloadSnapshotViaCredentials} from '../../editor/actions/snapshots'

import type {ServerJob} from '../../types'
import type {dispatchFn, getStateFn} from '../../types/reducers'

type ErrorMessage = {
  action?: string,
  detail?: any,
  message: string,
  title?: string
}

export const clearStatusModal = createVoidPayloadAction('CLEAR_STATUS_MODAL')
const handlingFinishedJob = createAction(
  'HANDLING_FINISHED_JOB',
  (payload: ServerJob) => payload
)
export const receiveJobs = createAction(
  'RECEIVE_JOBS',
  (payload: Array<ServerJob>) => payload
)
export const removeRetiredJob = createAction(
  'REMOVE_RETIRED_JOB',
  (payload: ServerJob) => payload
)
export const setErrorMessage = createAction(
  'SET_ERROR_MESSAGE',
  (payload: ErrorMessage | Promise<ErrorMessage>) => payload
)
export const setJobMonitorTimer = createAction(
  'SET_JOBMONITOR_TIMER',
  (payload: ?IntervalID) => payload
)
export const setJobMonitorVisible = createAction(
  'SET_JOBMONITOR_VISIBLE',
  (payload: boolean) => payload
)
const setAppInfo = createAction(
  'SET_APP_INFO',
  (payload: {
    commit: string,
    repoUrl: string
  }) => payload
)

const setStatusModal = createAction(
  'SET_STATUS_MODAL',
  (payload: {
    action?: any,
    body: string,
    detail?: any,
    title: string
  }) => payload
)

export type StatusActions = ActionType<typeof clearStatusModal> |
  ActionType<typeof handlingFinishedJob> |
  ActionType<typeof receiveJobs> |
  ActionType<typeof removeRetiredJob> |
  ActionType<typeof setJobMonitorTimer> |
  ActionType<typeof setJobMonitorVisible> |
  ActionType<typeof setErrorMessage> |
  ActionType<typeof setAppInfo>

/**
 * Check status of user's jobs. Handle any finished jobs and if there are no
 * remaining active jobs, stop job monitor.
 */
export function checkJobStatus () {
  return function (dispatch: dispatchFn, getState: getStateFn) {
    return dispatch(secureFetch(`/api/manager/secure/status/jobs`))
      .then(response => response.json())
      .then(jobs => {
        // Check for any just-finished jobs
        const completedJobs = jobs
          .filter(job => job.status.completed || job.status.error)
        completedJobs
          // Retire any completed or errored jobs.
          .forEach(job => dispatch(handleFinishedJob(job)))

        const remainingJobs = jobs
          .filter(job => !job.status.completed && !job.status.error)
        if (remainingJobs.length === 0) {
          // If there are no remaining jobs, stop the timer.
          dispatch(stopJobMonitor())
        }
        // Add remaining jobs to set of active jobs. Note, this may be an empty
        // array if finished jobs have been handled, but that is the expected
        // behavior because these finished jobs will appear in a "retired" state.
        dispatch(receiveJobs(remainingJobs))
        return jobs
      })
  }
}

export function fetchAppInfo () {
  return function (dispatch: dispatchFn, getState: getStateFn) {
    // only fetch if we don't yet have server info
    if (!getState().status.appInfo) {
      // fetch info.  If an error occurs or response json doesn't match, set
      // server info to value indicating an unknown commit and repoUrl
      return dispatch(secureFetch('/api/manager/public/appinfo'))
        .then(response => response.json())
        .then(info => {
          if (info.commit && info.repoUrl) {
            info.repoUrl = info.repoUrl.replace('.git', '')
            dispatch(setAppInfo(info))
          } else {
            throw new Error('unable to interpret app info')
          }
        })
        .catch(() => {
          dispatch(setAppInfo({
            commit: 'unknown',
            repoUrl: '?'
          }))
        })
    }
  }
}

/* eslint-disable complexity */
export function handleFinishedJob (job: ServerJob) {
  return function (dispatch: dispatchFn, getState: getStateFn) {
    dispatch(handlingFinishedJob(job))
    switch (job.type) {
      case 'VALIDATE_FEED':
        if (!job.feedSourceId) {
          // FIXME use setErrorMessage instead?
          console.warn('No feedSourceId found on job')
          return
        }
        dispatch(fetchFeedSource(job.feedSourceId, true))
        if (isExtensionEnabled('mtc')) {
          const firstDate = job.validationResult && job.validationResult.firstCalendarDate
          const now = moment().startOf('day')
          if (firstDate && moment(firstDate).isAfter(now)) {
            dispatch(setStatusModal({
              title: 'Warning: Feed contains service for future dates only!',
              body: `Use the 'Merge' button to merge with the currently active feed version.`
            }))
          }
        }
        break
      // CREATE_SNAPSHOT accounts for both creating an initial snapshot from GTFS
      // as well as "restoring" a snapshot to the active buffer, which essentially
      // just creates a copy of the snapshot to restore and points at the new
      // snapshot for the new buffer.
      case 'CREATE_SNAPSHOT':
        if (!job.feedSourceId) {
          // FIXME use setErrorMessage instead?
          console.warn('No feedSourceId found on job')
          return
        }
        dispatch(fetchFeedSource(job.feedSourceId, true))
        break
      // case 'PROCESS_SNAPSHOT':
      //   dispatch(fetchSnapshots(job.feedVersion.feedSource))
      //   break
      //
      case 'CREATE_FEEDVERSION_FROM_SNAPSHOT':
        if (!job.feedSourceId) {
          // FIXME use setErrorMessage instead?
          console.warn('No feedSourceId found on job')
          return
        }
        dispatch(fetchFeedSource(job.feedSourceId, true))
        break
      case 'EXPORT_SNAPSHOT_TO_GTFS':
        if (job.parentJobId) {
          console.log('Not downloading snapshot GTFS. Export job part of feed version creation.')
        } else {
          if (job.snapshot) {
            dispatch(downloadSnapshotViaCredentials(job.snapshot, false))
          }
        }
        break
      case 'DEPLOY_TO_OTP':
        return dispatch(fetchDeployment(job.deploymentId))
      case 'MERGE_FEED_VERSIONS':
        if (job.mergeType === 'REGIONAL') {
          // If merging feeds for the project, end result is to download zip.
          if (!job.projectId) {
            // FIXME use setErrorMessage instead?
            console.warn('No project found on job')
            return
          }
          dispatch(downloadMergedFeedViaToken(job.projectId, false))
        } else {
          const result = job.mergeFeedsResult
          const details = []
          if (result) {
            // Do nothing or show merged feed modal? Feed version is be created
            details.push('Remapped ID count: ' + result.remappedReferences)
            if (Object.keys(result.remappedIds).length > 0) {
              const remappedIdStrings = []
              for (let key in result.remappedIds) {
                // Modify key to remove feed name.
                const split = key.split(':')
                const tableAndId = split.splice(1, 1)
                remappedIdStrings.push(`${tableAndId.join(':')} -> ${result.remappedIds[key]}`)
              }
              details.push('Remapped IDs: ' + remappedIdStrings.join(', '))
            }
            if (result.skippedIds.length > 0) {
              const skippedRecordsForTables = {}
              result.skippedIds.forEach(id => {
                const table = id.split(':')[0]
                if (skippedRecordsForTables[table]) {
                  skippedRecordsForTables[table] = skippedRecordsForTables[table] + 1
                } else {
                  skippedRecordsForTables[table] = 1
                }
              })
              const skippedRecordsStrings = []
              for (let key in skippedRecordsForTables) {
                skippedRecordsStrings.push(`${key} - ${skippedRecordsForTables[key]}`)
              }
              details.push('Skipped records: ' + skippedRecordsStrings.join(', '))
            }
            if (result.idConflicts.length > 0) {
              // const conflicts = result.idConflicts
              details.push('ID conflicts: ' + result.idConflicts.join(', '))
            }
            dispatch(setStatusModal({
              title: result.failed
                ? 'Warning: Errors encountered during feed merge!'
                : 'Feed merge was successful!',
              body: result.failed
                ? `Merge failed with ${result.errorCount} errors. ${result.failureReasons.join(', ')}`
                : `Merge was completed successfully. A new version will be processed/validated containing the resulting feed.`
                ,
              detail: details.join('\n')
            }))
          }
        }
        break
      default:
        console.warn(`No completion step defined for job type ${job.type}`)
    }
  }
}

export function handleJobResponse (response: Response, message: string) {
  return function (dispatch: dispatchFn, getState: getStateFn) {
    if (response.status >= 400) {
      const props = message
        ? {message}
        // If no message specified, use error message supplied by JSON response
        : response.json()
      return dispatch(setErrorMessage(props))
    } else {
      // Resulting JSON contains message and job ID wich which to monitor job.
      const json: {jobId: number, message: string} = (response.json(): any)
      dispatch(startJobMonitor())
      // Return json with job ID in case it is needed upstream
      return json
    }
  }
}

/**
 * Start job monitor. This stops any existing timer function and initializes
 * the fetch user jobs status function on a timer.
 */
export function startJobMonitor (showMonitor: boolean = true) {
  return function (dispatch: dispatchFn, getState: getStateFn) {
    stopCurrentTimer(getState())

    // Check job status immediately.
    dispatch(checkJobStatus())
      .then(jobs => {
        // If there are active jobs, set up a check for every two seconds if
        // jobs are still running. Note: when the status endpoint is checked for
        // a user and completed/errored jobs are returned, the server will
        // automatically remove these jobs from the active list.
        if (jobs.length > 0) {
          const timer = setInterval(() => dispatch(checkJobStatus()), 2000)
          dispatch(setJobMonitorTimer(timer))
          if (showMonitor) dispatch(setJobMonitorVisible(true))
        }
      })
      .catch(err => console.warn('Error fetchings jobs', err))
  }
}

/**
 * Stop job time function stored with timer ID stored in state.
 */
function stopCurrentTimer (state) {
  const {timer} = state.status.jobMonitor
  if (timer) clearInterval(timer)
}

/**
 * Stop current job timer function and set job monitor (timer ID) to null.
 */
export function stopJobMonitor () {
  return function (dispatch: dispatchFn, getState: getStateFn) {
    stopCurrentTimer(getState())
    dispatch(setJobMonitorTimer(null))
  }
}
<|endoftext|>


<commit_before>const versioning = require('../../versioning');
const dockerApi = require('../../datasource/docker');
const compareVersions = require('compare-versions');

module.exports = {
  isStable,
  getPackageUpdates,
};

async function getPackageUpdates(config) {
  const {
    currentFrom,
    dockerRegistry,
    depName,
    currentDepTag,
    currentTag,
    currentValue,
    tagSuffix,
    currentDigest,
    unstablePattern,
    ignoreUnstable,
  } = config;
  logger.debug(`getPackageUpdate(${currentFrom}`);
  const { getMajor, isValid } = versioning('semver');
  const upgrades = [];
  if (currentDigest || config.pinDigests) {
    logger.debug('Checking docker pinDigests');
    const newDigest = await dockerApi.getDigest(
      dockerRegistry,
      depName,
      currentTag
    );
    if (!newDigest) {
      logger.info(
        { currentFrom, dockerRegistry, depName, currentTag },
        'Dockerfile no digest'
      );
      return [];
    }
    if (newDigest && config.currentDigest !== newDigest) {
      const upgrade = {};
      upgrade.newTag = currentTag || 'latest';
      upgrade.newDigest = newDigest;
      upgrade.newDigestShort = newDigest.slice(7, 13);
      upgrade.newValue = upgrade.newDigestShort;
      if (dockerRegistry) {
        upgrade.newFrom = `${dockerRegistry}/`;
      } else {
        upgrade.newFrom = '';
      }
      upgrade.newFrom += `${depName}:${upgrade.newTag}@${newDigest}`;

      if (currentDigest) {
        upgrade.type = 'digest';
      } else {
        upgrade.type = 'pin';
      }
      upgrades.push(upgrade);
    }
  }
  if (currentTag) {
    if (!isValid(currentValue)) {
      logger.info(
        { currentDepTag },
        'Docker tag is not valid semver - skipping'
      );
      return upgrades.map(upgrade => ({ ...upgrade, isRange: true }));
    }
    const currentMajor = getMajor(currentValue);
    const currentlyStable = isStable(currentValue, unstablePattern);
    let versionList = [];
    const allTags = await dockerApi.getTags(
      dockerRegistry,
      config.depName,
      tagSuffix
    );
    if (allTags) {
      versionList = allTags
        .filter(
          version =>
            // All stable are allowed
            isStable(version, unstablePattern) ||
            // All unstable are allowed if we aren't ignoring them
            !ignoreUnstable ||
            // Allow unstable of same major version
            (!currentlyStable && getMajor(version) === currentMajor)
        )
        .filter(
          prefix => prefix.split('.').length === currentValue.split('.').length
        )
        .filter(prefix => compareVersions(prefix, currentValue) > 0);
    }
    logger.trace({ versionList }, 'upgrades versionList');
    const versionUpgrades = {};
    for (const version of versionList) {
      const newMajor = getMajor(version);
      const type = newMajor > currentMajor ? 'major' : 'minor';
      let upgradeKey;
      if (
        !config.separateMajorMinor ||
        config.groupName ||
        config.major.automerge === true
      ) {
        // If we're not separating releases then we use a common lookup key
        upgradeKey = 'latest';
      } else if (!config.separateMultipleMajor && type === 'major') {
        upgradeKey = 'major';
      } else {
        // Use major version as lookup key
        upgradeKey = newMajor;
      }
      if (
        !versionUpgrades[upgradeKey] ||
        compareVersions(version, versionUpgrades[upgradeKey]) > 0
      ) {
        versionUpgrades[upgradeKey] = version;
      }
    }
    logger.debug({ versionUpgrades }, 'Docker versionUpgrades');
    for (const upgradeKey of Object.keys(versionUpgrades)) {
      const upgrade = {};
      const newVersion = versionUpgrades[upgradeKey];
      upgrade.newMajor = `${getMajor(newVersion)}`;
      upgrade.newTag = tagSuffix ? `${newVersion}-${tagSuffix}` : newVersion;
      upgrade.newValue = newVersion;
      upgrade.newDepTag = `${config.depName}:${upgrade.newTag}`;
      if (dockerRegistry) {
        upgrade.newFrom = `${dockerRegistry}/`;
      } else {
        upgrade.newFrom = '';
      }
      upgrade.newFrom += `${depName}:${upgrade.newTag}`;
      if (config.currentDigest || config.pinDigests) {
        upgrade.newDigest = await dockerApi.getDigest(
          dockerRegistry,
          config.depName,
          upgrade.newTag
        );
        // istanbul ignore else
        if (upgrade.newDigest) {
          upgrade.newFrom += `@${upgrade.newDigest}`;
        } else {
          logger.warn(
            { dockerRegistry, depName, tag: upgrade.newTag },
            'Dockerfile no digest'
          );
          throw new Error('registry-failure');
        }
      }
      if (upgrade.newMajor > currentMajor) {
        upgrade.type = 'major';
      } else {
        upgrade.type = 'minor';
      }
      upgrades.push(upgrade);
      logger.info(
        { currentDepTag, newDepTag: upgrade.newDepTag },
        'Docker tag version upgrade found'
      );
    }
  }
  return upgrades.filter(u => u.newDigest !== null);
}

function isStable(tag, unstablePattern) {
  return unstablePattern
    ? tag.match(new RegExp(unstablePattern)) === null
    : true;
}
<commit_msg>fix(docker): harden currentValue check<commit_after>const versioning = require('../../versioning');
const dockerApi = require('../../datasource/docker');
const compareVersions = require('compare-versions');

module.exports = {
  isStable,
  getPackageUpdates,
};

async function getPackageUpdates(config) {
  const {
    currentFrom,
    dockerRegistry,
    depName,
    currentDepTag,
    currentTag,
    currentValue,
    tagSuffix,
    currentDigest,
    unstablePattern,
    ignoreUnstable,
  } = config;
  logger.debug(`getPackageUpdate(${currentFrom}`);
  const { getMajor, isValid } = versioning('semver');
  const upgrades = [];
  if (currentDigest || config.pinDigests) {
    logger.debug('Checking docker pinDigests');
    const newDigest = await dockerApi.getDigest(
      dockerRegistry,
      depName,
      currentTag
    );
    if (!newDigest) {
      logger.info(
        { currentFrom, dockerRegistry, depName, currentTag },
        'Dockerfile no digest'
      );
      return [];
    }
    if (newDigest && config.currentDigest !== newDigest) {
      const upgrade = {};
      upgrade.newTag = currentTag || 'latest';
      upgrade.newDigest = newDigest;
      upgrade.newDigestShort = newDigest.slice(7, 13);
      upgrade.newValue = upgrade.newDigestShort;
      if (dockerRegistry) {
        upgrade.newFrom = `${dockerRegistry}/`;
      } else {
        upgrade.newFrom = '';
      }
      upgrade.newFrom += `${depName}:${upgrade.newTag}@${newDigest}`;

      if (currentDigest) {
        upgrade.type = 'digest';
      } else {
        upgrade.type = 'pin';
      }
      upgrades.push(upgrade);
    }
  }
  if (currentTag) {
    if (!(currentValue && currentValue.length && isValid(currentValue))) {
      logger.info(
        { currentDepTag },
        'Docker tag is not valid semver - skipping'
      );
      return upgrades.map(upgrade => ({ ...upgrade, isRange: true }));
    }
    const currentMajor = getMajor(currentValue);
    const currentlyStable = isStable(currentValue, unstablePattern);
    let versionList = [];
    const allTags = await dockerApi.getTags(
      dockerRegistry,
      config.depName,
      tagSuffix
    );
    if (allTags) {
      versionList = allTags
        .filter(
          version =>
            // All stable are allowed
            isStable(version, unstablePattern) ||
            // All unstable are allowed if we aren't ignoring them
            !ignoreUnstable ||
            // Allow unstable of same major version
            (!currentlyStable && getMajor(version) === currentMajor)
        )
        .filter(
          prefix => prefix.split('.').length === currentValue.split('.').length
        )
        .filter(prefix => compareVersions(prefix, currentValue) > 0);
    }
    logger.trace({ versionList }, 'upgrades versionList');
    const versionUpgrades = {};
    for (const version of versionList) {
      const newMajor = getMajor(version);
      const type = newMajor > currentMajor ? 'major' : 'minor';
      let upgradeKey;
      if (
        !config.separateMajorMinor ||
        config.groupName ||
        config.major.automerge === true
      ) {
        // If we're not separating releases then we use a common lookup key
        upgradeKey = 'latest';
      } else if (!config.separateMultipleMajor && type === 'major') {
        upgradeKey = 'major';
      } else {
        // Use major version as lookup key
        upgradeKey = newMajor;
      }
      if (
        !versionUpgrades[upgradeKey] ||
        compareVersions(version, versionUpgrades[upgradeKey]) > 0
      ) {
        versionUpgrades[upgradeKey] = version;
      }
    }
    logger.debug({ versionUpgrades }, 'Docker versionUpgrades');
    for (const upgradeKey of Object.keys(versionUpgrades)) {
      const upgrade = {};
      const newVersion = versionUpgrades[upgradeKey];
      upgrade.newMajor = `${getMajor(newVersion)}`;
      upgrade.newTag = tagSuffix ? `${newVersion}-${tagSuffix}` : newVersion;
      upgrade.newValue = newVersion;
      upgrade.newDepTag = `${config.depName}:${upgrade.newTag}`;
      if (dockerRegistry) {
        upgrade.newFrom = `${dockerRegistry}/`;
      } else {
        upgrade.newFrom = '';
      }
      upgrade.newFrom += `${depName}:${upgrade.newTag}`;
      if (config.currentDigest || config.pinDigests) {
        upgrade.newDigest = await dockerApi.getDigest(
          dockerRegistry,
          config.depName,
          upgrade.newTag
        );
        // istanbul ignore else
        if (upgrade.newDigest) {
          upgrade.newFrom += `@${upgrade.newDigest}`;
        } else {
          logger.warn(
            { dockerRegistry, depName, tag: upgrade.newTag },
            'Dockerfile no digest'
          );
          throw new Error('registry-failure');
        }
      }
      if (upgrade.newMajor > currentMajor) {
        upgrade.type = 'major';
      } else {
        upgrade.type = 'minor';
      }
      upgrades.push(upgrade);
      logger.info(
        { currentDepTag, newDepTag: upgrade.newDepTag },
        'Docker tag version upgrade found'
      );
    }
  }
  return upgrades.filter(u => u.newDigest !== null);
}

function isStable(tag, unstablePattern) {
  return unstablePattern
    ? tag.match(new RegExp(unstablePattern)) === null
    : true;
}
<|endoftext|>


<commit_before>/**
 * @flow
 * Database representation wrapper
 */
import { NativeModules, NativeEventEmitter } from 'react-native';
import { Base } from './../base';
import Snapshot from './snapshot.js';
import Reference from './reference.js';
import { promisify } from './../../utils';

const FirestackModule = NativeModules.Firestack;
const FirestackDatabase = NativeModules.FirestackDatabase;
const FirestackDatabaseEvt = new NativeEventEmitter(FirestackDatabase);

/**
 * @class Database
 */
export default class Database extends Base {
  constructor(firestack: Object, options: Object = {}) {
    super(firestack, options);
    this.subscriptions = {};
    this.transactions = {};
    this.serverTimeOffset = 0;
    this.persistenceEnabled = false;
    this.namespace = 'firestack:database';

    this.successListener = FirestackDatabaseEvt.addListener(
      'database_event',
      event => this._handleDatabaseEvent(event)
    );

    this.errorListener = FirestackDatabaseEvt.addListener(
      'database_error',
      err => this._handleDatabaseError(err)
    );

    this.transactionListener = FirestackDatabaseEvt.addListener(
      'database_transaction_update',
      event => this._handleDatabaseTransaction(event)
    );

    this.offsetRef = this.ref('.info/serverTimeOffset');
    this.offsetRef.on('value', (snapshot) => {
      this.serverTimeOffset = snapshot.val() || this.serverTimeOffset;
    });

    this.log.debug('Created new Database instance', this.options);
  }

  get ServerValue() {
    return {
      TIMESTAMP: FirestackModule.serverValueTimestamp || { '.sv': 'timestamp' },
    };
  }

  /**
   * Returns a new firestack reference instance
   * @param path
   * @returns {Reference}
   */
  ref(...path: Array<string>) {
    return new Reference(this, path);
  }

  /**
   * Enabled / disable database persistence
   * @param enable
   * @returns {*}
   */
  setPersistence(enable: boolean = true) {
    if (this.persistenceEnabled !== enable) {
      this.log.debug(`${enable ? 'Enabling' : 'Disabling'} persistence`);
      this.persistenceEnabled = enable;
      return this.whenReady(promisify('enablePersistence', FirestackDatabase)(enable));
    }

    return this.whenReady(Promise.resolve({ status: 'Already enabled' }));
  }

  /**
   *
   * @param path
   * @param modifiersString
   * @param modifiers
   * @param eventName
   * @param cb
   * @returns {*}
   */
  on(path: string, modifiersString: string, modifiers: Array<string>, eventName: string, cb: () => void) {
    const handle = this._handle(path, modifiersString);
    this.log.debug('adding on listener', handle);

    if (!this.subscriptions[handle]) this.subscriptions[handle] = {};
    if (!this.subscriptions[handle][eventName]) this.subscriptions[handle][eventName] = [];
    this.subscriptions[handle][eventName].push(cb);

    return promisify('on', FirestackDatabase)(path, modifiersString, modifiers, eventName);
  }

  /**
   *
   * @param path
   * @param modifiersString
   * @param eventName
   * @param origCB
   * @returns {*}
   */
  off(path: string, modifiersString: string, eventName?: string, origCB?: () => void) {
    const handle = this._handle(path, modifiersString);
    this.log.debug('off() : ', handle, eventName);

    if (!this.subscriptions[handle] || (eventName && !this.subscriptions[handle][eventName])) {
      this.log.warn('off() called, but not currently listening at that location (bad path)', handle, eventName);
      return Promise.resolve();
    }

    if (eventName && origCB) {
      const i = this.subscriptions[handle][eventName].indexOf(origCB);

      if (i === -1) {
        this.log.warn('off() called, but the callback specified is not listening at that location (bad path)', handle, eventName);
        return Promise.resolve();
      }

      this.subscriptions[handle][eventName].splice(i, 1);
      if (this.subscriptions[handle][eventName].length > 0) return Promise.resolve();
    } else if (eventName) {
      this.subscriptions[handle][eventName] = [];
    } else {
      this.subscriptions[handle] = {};
    }

    return promisify('off', FirestackDatabase)(path, modifiersString, eventName);
  }

  /**
   * Removes all event handlers and their native subscriptions
   * @returns {Promise.<*>}
   */
  cleanup() {
    const promises = [];
    Object.keys(this.subscriptions).forEach((handle) => {
      Object.keys(this.subscriptions[handle]).forEach((eventName) => {
        const separator = handle.indexOf('|');
        const path = handle.substring(0, separator);
        const modifiersString = handle.substring(separator + 1);
        promises.push(this.off(path, modifiersString, eventName));
      });
    });

    return Promise.all(promises);
  }

  goOnline() {
    FirestackDatabase.goOnline();
  }

  goOffline() {
    FirestackDatabase.goOffline();
  }

  addTransaction(path, updateCallback, applyLocally) {
    let id = this._generateTransactionID();
    this.transactions[id] = updateCallback;
    return promisify('beginTransaction', FirestackDatabase)(path, id, applyLocally || false)
      .then((v) => {delete this.transactions[id]; return v;},
            (e) => {delete this.transactions[id]; throw e;});
  }

  _generateTransactionID() {
    // 10 char random alphanumeric
    return Math.random().toString(36).substr(2, 10);
  }

  _handleDatabaseTransaction(event) {
    const {id, originalValue} = event;
    const updateCallback = this.transactions[id];
    const newValue = updateCallback(originalValue);
    let abort = false;
    if (newValue === undefined) {
      abort = true;
    }
    FirestackDatabase.tryCommitTransaction(id, {value: newValue}, abort);
  }

  /**
   *  INTERNALS
   */
  _getServerTime() {
    return new Date().getTime() + this.serverTimeOffset;
  }

  /**
   *
   * @param path
   * @param modifiersString
   * @returns {string}
   * @private
   */
  _handle(path: string = '', modifiersString: string = '') {
    return `${path}|${modifiersString}`;
  }

  /**
   *
   * @param event
   * @private
   */
  _handleDatabaseEvent(event: Object) {
    const body = event.body || {};
    const { path, modifiersString, eventName, snapshot } = body;
    const handle = this._handle(path, modifiersString);

    this.log.debug('_handleDatabaseEvent: ', handle, eventName, snapshot && snapshot.key);

    if (this.subscriptions[handle] && this.subscriptions[handle][eventName]) {
      this.subscriptions[handle][eventName].forEach((cb) => {
        cb(new Snapshot(new Reference(this, path.split('/'), modifiersString.split('|')), snapshot), body);
      });
    } else {
      FirestackDatabase.off(path, modifiersString, eventName, () => {
        this.log.debug('_handleDatabaseEvent: No JS listener registered, removed native listener', handle, eventName);
      });
    }
  }

  /**
   *
   * @param err
   * @private
   */
  _handleDatabaseError(err: Object) {
    this.log.debug('_handleDatabaseError ->', err);
  }
}
<commit_msg>Wrap update function in try block.<commit_after>/**
 * @flow
 * Database representation wrapper
 */
import { NativeModules, NativeEventEmitter } from 'react-native';
import { Base } from './../base';
import Snapshot from './snapshot.js';
import Reference from './reference.js';
import { promisify } from './../../utils';

const FirestackModule = NativeModules.Firestack;
const FirestackDatabase = NativeModules.FirestackDatabase;
const FirestackDatabaseEvt = new NativeEventEmitter(FirestackDatabase);

/**
 * @class Database
 */
export default class Database extends Base {
  constructor(firestack: Object, options: Object = {}) {
    super(firestack, options);
    this.subscriptions = {};
    this.transactions = {};
    this.serverTimeOffset = 0;
    this.persistenceEnabled = false;
    this.namespace = 'firestack:database';

    this.successListener = FirestackDatabaseEvt.addListener(
      'database_event',
      event => this._handleDatabaseEvent(event)
    );

    this.errorListener = FirestackDatabaseEvt.addListener(
      'database_error',
      err => this._handleDatabaseError(err)
    );

    this.transactionListener = FirestackDatabaseEvt.addListener(
      'database_transaction_update',
      event => this._handleDatabaseTransaction(event)
    );

    this.offsetRef = this.ref('.info/serverTimeOffset');
    this.offsetRef.on('value', (snapshot) => {
      this.serverTimeOffset = snapshot.val() || this.serverTimeOffset;
    });

    this.log.debug('Created new Database instance', this.options);
  }

  get ServerValue() {
    return {
      TIMESTAMP: FirestackModule.serverValueTimestamp || { '.sv': 'timestamp' },
    };
  }

  /**
   * Returns a new firestack reference instance
   * @param path
   * @returns {Reference}
   */
  ref(...path: Array<string>) {
    return new Reference(this, path);
  }

  /**
   * Enabled / disable database persistence
   * @param enable
   * @returns {*}
   */
  setPersistence(enable: boolean = true) {
    if (this.persistenceEnabled !== enable) {
      this.log.debug(`${enable ? 'Enabling' : 'Disabling'} persistence`);
      this.persistenceEnabled = enable;
      return this.whenReady(promisify('enablePersistence', FirestackDatabase)(enable));
    }

    return this.whenReady(Promise.resolve({ status: 'Already enabled' }));
  }

  /**
   *
   * @param path
   * @param modifiersString
   * @param modifiers
   * @param eventName
   * @param cb
   * @returns {*}
   */
  on(path: string, modifiersString: string, modifiers: Array<string>, eventName: string, cb: () => void) {
    const handle = this._handle(path, modifiersString);
    this.log.debug('adding on listener', handle);

    if (!this.subscriptions[handle]) this.subscriptions[handle] = {};
    if (!this.subscriptions[handle][eventName]) this.subscriptions[handle][eventName] = [];
    this.subscriptions[handle][eventName].push(cb);

    return promisify('on', FirestackDatabase)(path, modifiersString, modifiers, eventName);
  }

  /**
   *
   * @param path
   * @param modifiersString
   * @param eventName
   * @param origCB
   * @returns {*}
   */
  off(path: string, modifiersString: string, eventName?: string, origCB?: () => void) {
    const handle = this._handle(path, modifiersString);
    this.log.debug('off() : ', handle, eventName);

    if (!this.subscriptions[handle] || (eventName && !this.subscriptions[handle][eventName])) {
      this.log.warn('off() called, but not currently listening at that location (bad path)', handle, eventName);
      return Promise.resolve();
    }

    if (eventName && origCB) {
      const i = this.subscriptions[handle][eventName].indexOf(origCB);

      if (i === -1) {
        this.log.warn('off() called, but the callback specified is not listening at that location (bad path)', handle, eventName);
        return Promise.resolve();
      }

      this.subscriptions[handle][eventName].splice(i, 1);
      if (this.subscriptions[handle][eventName].length > 0) return Promise.resolve();
    } else if (eventName) {
      this.subscriptions[handle][eventName] = [];
    } else {
      this.subscriptions[handle] = {};
    }

    return promisify('off', FirestackDatabase)(path, modifiersString, eventName);
  }

  /**
   * Removes all event handlers and their native subscriptions
   * @returns {Promise.<*>}
   */
  cleanup() {
    const promises = [];
    Object.keys(this.subscriptions).forEach((handle) => {
      Object.keys(this.subscriptions[handle]).forEach((eventName) => {
        const separator = handle.indexOf('|');
        const path = handle.substring(0, separator);
        const modifiersString = handle.substring(separator + 1);
        promises.push(this.off(path, modifiersString, eventName));
      });
    });

    return Promise.all(promises);
  }

  goOnline() {
    FirestackDatabase.goOnline();
  }

  goOffline() {
    FirestackDatabase.goOffline();
  }

  addTransaction(path, updateCallback, applyLocally) {
    let id = this._generateTransactionID();
    this.transactions[id] = updateCallback;
    return promisify('beginTransaction', FirestackDatabase)(path, id, applyLocally || false)
      .then((v) => {delete this.transactions[id]; return v;},
            (e) => {delete this.transactions[id]; throw e;});
  }

  _generateTransactionID() {
    // 10 char random alphanumeric
    return Math.random().toString(36).substr(2, 10);
  }

  _handleDatabaseTransaction(event) {
    const {id, originalValue} = event;
    let newValue;
    try {
      const updateCallback = this.transactions[id];
      newValue = updateCallback(originalValue);
    } finally {
      let abort = false;
      if (newValue === undefined) {
        abort = true;
      }
      FirestackDatabase.tryCommitTransaction(id, {value: newValue}, abort);
    }
  }

  /**
   *  INTERNALS
   */
  _getServerTime() {
    return new Date().getTime() + this.serverTimeOffset;
  }

  /**
   *
   * @param path
   * @param modifiersString
   * @returns {string}
   * @private
   */
  _handle(path: string = '', modifiersString: string = '') {
    return `${path}|${modifiersString}`;
  }

  /**
   *
   * @param event
   * @private
   */
  _handleDatabaseEvent(event: Object) {
    const body = event.body || {};
    const { path, modifiersString, eventName, snapshot } = body;
    const handle = this._handle(path, modifiersString);

    this.log.debug('_handleDatabaseEvent: ', handle, eventName, snapshot && snapshot.key);

    if (this.subscriptions[handle] && this.subscriptions[handle][eventName]) {
      this.subscriptions[handle][eventName].forEach((cb) => {
        cb(new Snapshot(new Reference(this, path.split('/'), modifiersString.split('|')), snapshot), body);
      });
    } else {
      FirestackDatabase.off(path, modifiersString, eventName, () => {
        this.log.debug('_handleDatabaseEvent: No JS listener registered, removed native listener', handle, eventName);
      });
    }
  }

  /**
   *
   * @param err
   * @private
   */
  _handleDatabaseError(err: Object) {
    this.log.debug('_handleDatabaseError ->', err);
  }
}
<|endoftext|>


<commit_before>import { createElement, PureComponent } from 'react';
import { PropTypes } from 'prop-types';
import { connect } from 'react-redux';

import actions from './share-actions';
import reducers, { initialState } from './share-reducers';
import ShareComponent from './share-component';

const mapStateToProps = ({ share, location }) => ({
  open: share.open,
  selected: share.selected,
  copied: share.copied,
  data: share.data,
  loading: share.loading,
  location
});

class ShareContainer extends PureComponent {
  handleCopyToClipboard = input => {
    const { setShareCopied } = this.props;
    input.select();

    try {
      document.execCommand('copy');
      setShareCopied();
    } catch (err) {
      alert('This browser does not support clipboard access');
    }
  };

  handleFocus = event => {
    event.target.select();
  };

  render() {
    return createElement(ShareComponent, {
      ...this.props,
      handleCopyToClipboard: this.handleCopyToClipboard,
      handleFocus: this.handleFocus
    });
  }
}

ShareContainer.propTypes = {
  setShareCopied: PropTypes.func.isRequired
};

export { actions, reducers, initialState };

export default connect(mapStateToProps, actions)(ShareContainer);
<commit_msg>show copied when clicked<commit_after>import { createElement, PureComponent } from 'react';
import { PropTypes } from 'prop-types';
import { connect } from 'react-redux';

import actions from './share-actions';
import reducers, { initialState } from './share-reducers';
import ShareComponent from './share-component';

const mapStateToProps = ({ share, location }) => ({
  open: share.open,
  selected: share.selected,
  copied: share.copied,
  data: share.data,
  loading: share.loading,
  location
});

class ShareContainer extends PureComponent {
  handleCopyToClipboard = input => {
    const { setShareCopied } = this.props;
    input.select();

    try {
      document.execCommand('copy');
      setShareCopied();
    } catch (err) {
      alert('This browser does not support clipboard access'); // eslint-disable-line
    }
  };

  handleFocus = event => {
    event.target.select();
  };

  render() {
    return createElement(ShareComponent, {
      ...this.props,
      handleCopyToClipboard: this.handleCopyToClipboard,
      handleFocus: this.handleFocus
    });
  }
}

ShareContainer.propTypes = {
  setShareCopied: PropTypes.func.isRequired
};

export { actions, reducers, initialState };

export default connect(mapStateToProps, actions)(ShareContainer);
<|endoftext|>


<commit_before>
// WebGL implementation

var vertexShaderSource = ""+
"uniform vec2 res;"+ // canvas size
"uniform vec2 imgRes;"+ // image size
"uniform vec2 pos;"+ // bound position
"uniform vec2 dim;"+ // bound size
"attribute vec2 p;"+
"varying vec2 uv;"+
"void main() {"+
  "uv = (p * vec2(1.0, -1.0)+1.0)/2.0;"+
  "uv = pos/imgRes + uv * (dim/imgRes);"+
  "gl_Position = vec4(p, 0.0, 1.0);"+
"}";
var fragmentShaderSource = ""+
"#ifdef GL_ES\n"+
"precision highp float;\n"+
"#endif\n"+
"uniform sampler2D img;"+
"varying vec2 uv;"+
"void main() {"+
  "gl_FragColor = texture2D(img, uv);"+
"}"+
"";

function KenBurnsWebGLTrait (canvas) {
  this.canvas = canvas;
  var gl = this.gl = canvas.getContext("webgl");

  var program = this.program = gl.createProgram();

  var vertexShader = gl.createShader(gl.VERTEX_SHADER);
  gl.shaderSource(vertexShader, vertexShaderSource);
  gl.compileShader(vertexShader);
  gl.attachShader(program, vertexShader);

  var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
  gl.shaderSource(fragmentShader, fragmentShaderSource);
  gl.compileShader(fragmentShader);
  gl.attachShader(program, fragmentShader);

  gl.linkProgram(program);
  gl.useProgram(program);

  var positionLocation = gl.getAttribLocation(program, "p");
  this.resL = gl.getUniformLocation(program, "res");
  this.imgResL = gl.getUniformLocation(program, "imgRes");
  this.posL = gl.getUniformLocation(program, "pos");
  this.dimL = gl.getUniformLocation(program, "dim");

  var buffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
  gl.bufferData(
      gl.ARRAY_BUFFER,
      new Float32Array([
        -1.0, -1.0,
        1.0, -1.0,
        -1.0,  1.0,
        -1.0,  1.0,
        1.0, -1.0,
        1.0,  1.0]),
      gl.STATIC_DRAW);
  gl.enableVertexAttribArray(positionLocation);
  gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);
}
KenBurnsWebGLTrait.prototype = {
  runStart: function (image) {
    var gl = this.gl;
    var canvas = this.canvas;
    gl.useProgram(this.program);

    gl.uniform2f(this.imgResL, image.width, image.height);
    gl.uniform2f(this.resL, canvas.width, canvas.height);

    var texture = this.texture = gl.createTexture();
    gl.bindTexture(gl.TEXTURE_2D, texture);

    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);

    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
  },
  runEnd: function () {
    this.gl.deleteTexture(this.texture);
  },
  draw: function (image, bound) {
    var gl = this.gl;
    gl.uniform2f(this.posL, bound[0], bound[1]);
    gl.uniform2f(this.dimL, bound[2], bound[3]);
    gl.drawArrays(gl.TRIANGLES, 0, 6);
  },
  destroy: function () {
    this.gl.deleteProgram(this.program);
  }
};

module.exports = KenBurnsWebGLTrait;
<commit_msg>negative coding<commit_after>
// WebGL implementation

var vertexShaderSource = ""+
"uniform vec2 imgRes;"+ // image size
"uniform vec2 pos;"+ // bound position
"uniform vec2 dim;"+ // bound size
"attribute vec2 p;"+
"varying vec2 uv;"+
"void main() {"+
  "uv = pos/imgRes + ((p * vec2(1.0, -1.0)+1.0)/2.0) * (dim/imgRes);"+
  "gl_Position = vec4(p, 0.0, 1.0);"+
"}";
var fragmentShaderSource = ""+
"#ifdef GL_ES\n"+
"precision highp float;\n"+
"#endif\n"+
"uniform sampler2D img;"+
"varying vec2 uv;"+
"void main() {"+
  "gl_FragColor = texture2D(img, uv);"+
"}"+
"";

function KenBurnsWebGLTrait (canvas) {
  this.canvas = canvas;
  var gl = this.gl = canvas.getContext("webgl");

  var program = this.program = gl.createProgram();

  var vertexShader = gl.createShader(gl.VERTEX_SHADER);
  gl.shaderSource(vertexShader, vertexShaderSource);
  gl.compileShader(vertexShader);
  gl.attachShader(program, vertexShader);

  var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
  gl.shaderSource(fragmentShader, fragmentShaderSource);
  gl.compileShader(fragmentShader);
  gl.attachShader(program, fragmentShader);

  gl.linkProgram(program);
  gl.useProgram(program);

  var positionLocation = gl.getAttribLocation(program, "p");
  this.imgResL = gl.getUniformLocation(program, "imgRes");
  this.posL = gl.getUniformLocation(program, "pos");
  this.dimL = gl.getUniformLocation(program, "dim");

  var buffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
  gl.bufferData(
      gl.ARRAY_BUFFER,
      new Float32Array([
        -1.0, -1.0,
        1.0, -1.0,
        -1.0,  1.0,
        -1.0,  1.0,
        1.0, -1.0,
        1.0,  1.0]),
      gl.STATIC_DRAW);
  gl.enableVertexAttribArray(positionLocation);
  gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);
}
KenBurnsWebGLTrait.prototype = {
  runStart: function (image) {
    var gl = this.gl;
    var canvas = this.canvas;
    gl.useProgram(this.program);

    gl.uniform2f(this.imgResL, image.width, image.height);

    var texture = this.texture = gl.createTexture();
    gl.bindTexture(gl.TEXTURE_2D, texture);

    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);

    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
  },
  runEnd: function () {
    this.gl.deleteTexture(this.texture);
  },
  draw: function (image, bound) {
    var gl = this.gl;
    gl.uniform2f(this.posL, bound[0], bound[1]);
    gl.uniform2f(this.dimL, bound[2], bound[3]);
    gl.drawArrays(gl.TRIANGLES, 0, 6);
  },
  destroy: function () {
    this.gl.deleteProgram(this.program);
  }
};

module.exports = KenBurnsWebGLTrait;
<|endoftext|>


<commit_before>/*global LowLatencyAudio,cordova,Media,LocalFileSystem,buzz,console*/
/**
 * Wedge.js - Tiny audio library that provides HTML5 apps with low latency audio
 * if available, and falls back to Buzz.js
 *
 * Named after a wedge monitor speaker that you'd get onstage at a gig!
 *
 * This means you can write your HTML5 application to play sound using Wedge.js, and if
 * accelerated audio is availble through Cordova, that will be used. Otherwise,
 * it will fall back to Buzz!. This means you can develop on your desktop and
 * still hear audio, whilst still getting the benefits of low latency audio
 * when you compile.
 *
 * Under the hood, it uses the LowLatencyAudio plugin for Cordova to provide
 * much more responsive sound than the native HTML5 web audio API.
 *
 * Dependencies:
 * * Buzz! - http://buzz.jaysalvat.com/ - this is the fallback option. This means
 *   you can still test audio even when you're just working in the web browser.
 *
 * Optional Dependencies:
 * * Apache Cordova - http://cordova.apache.org/ - if you're building HTML5 for
 *   native
 * * Cordova Low Latency Audio plugin - https://github.com/phonegap/phonegap-plugins/tree/master/iPhone/LowLatencyAudio -
 *   low latency audio for iOS and Android
 *
 * Usage:
 *
 * Loads an object "wedge" onto the window object.
 *
 * See documentation (supplied with project) for detailed examples, but you
 * can play a sample by doing something like:
 *
 * wedge.preload("foo/bar.wav");
 * wedge.play("foo/bar.wav");
 *
 * License:
 *
 * MIT licensed
 *
 * Support:
 * * Tested with iOS/Cordova
 * * Should also work with Android
 */
(function() {
    var Wedge = function() {

        // Check dependencies
        if (typeof buzz === 'undefined') {
            throw new Error('Missing dependency: Buzz!. You can download Buzz! from http://buzz.jaysalvat.com/');
        }

        // only used if not accelerated
        this.samples = {};

        // determine if Low Latency Audio is supplied by Cordova. If not, everything
        // will fall back to Buzz.
        this.accelAudioAvailable = false;
        if(typeof cordova !== "undefined" && typeof cordova.exec !== "undefined" && typeof LowLatencyAudio !== "undefined") {
            this.accelAudioAvailable = true;
        }
    };

    /**
     * Error message. Displayed on the console.
     *
     * @param msg String The message to display
     */
    function wedgeError(msg) {
        if(typeof console !== "undefined") {
            console.log(msg);
        }
    }

    /**
     * Get a sound from the internal collection.
     *
     * Not compatible with LowLatencyAudio.
     *
     * @param wedge Wedge We pass in the Wedge object so that this function is
     * not a "public method" on Wedge. Otherwise, it would cloud the interface.
     * @param path String e.g. "sfx/foo.wav"
     */
    function getSound(wedge, path) {
        var sound = wedge.samples[path];
        if(sound === null || typeof sound === 'undefined') {
            wedgeError("Wedge.js: Could not play sample " + path + "; perhaps it has not been preloaded?");
            return false;
        }

        return sound;
    }

    /**
     * Preload a sample into Wedge. You should not try to play a sample that you
     * have not preloaded.
     *
     * @param path String e.g. "sfx/foo.wav"
     *
     * @return Wedge For chaining method calls
     */
    Wedge.prototype.preload = function(path) {

        if(this.accelAudioAvailable) {
            LowLatencyAudio.preloadAudio(path, path, 2);
        } else {
            this.samples[path] = new buzz.sound(path);
        }

        return this;
    };

    /**
     * Play a sample identified by its path.
     * This will play using accelerated audio if available.
     * @param path String e.g. "sfx/foo.wav"
     *
     * @return Wedge For chaining method calls
     */
    Wedge.prototype.play = function(path) {

        if(this.accelAudioAvailable) {
            LowLatencyAudio.play(path);
        } else {
            var sound = getSound(this, path);
            if(!sound) {
                return this;
            }

            // Trigger the sound from the start
            if(sound.getPercent() === '--' || sound.getPercent() === 0 || sound.getPercent() === 100) {
                sound.play();
            } else {
                sound.setPercent(0);
            }
        }

        return this;
    };

    /**
     * Start looping a sample identified by its path
     * This will play using accelerated audio if available.
     * @param path String e.g. "sfx/foo.wav"
     *
     * @return Wedge For chaining method calls
     */
    Wedge.prototype.loop = function(path) {

        if(this.accelAudioAvailable) {
            LowLatencyAudio.loop(path);
        } else {
            var sound = getSound(this, path);
            if(!sound) {
                return this;
            }

            sound.play().loop();
        }

        return this;
    };

    /**
     * Stop a sample identified by its path
     * @param path String e.g. "sfx/foo.wav"
     *
     * @return Wedge For chaining method calls
     */
    Wedge.prototype.stop = function(path) {

        if(this.accelAudioAvailable) {
            LowLatencyAudio.stop(path);
        } else {
            var sound = getSound(this, path);
            if(!sound) {
                return this;
            }

            sound.stop();
        }

        return this;
    };

    // export an instance of Wedge to the global object
    window.wedge = new Wedge();
})(window);<commit_msg>Adding the semicolon to before the closure<commit_after>/*global LowLatencyAudio,cordova,Media,LocalFileSystem,buzz,console*/
/**
 * Wedge.js - Tiny audio library that provides HTML5 apps with low latency audio
 * if available, and falls back to Buzz.js
 *
 * Named after a wedge monitor speaker that you'd get onstage at a gig!
 *
 * This means you can write your HTML5 application to play sound using Wedge.js, and if
 * accelerated audio is availble through Cordova, that will be used. Otherwise,
 * it will fall back to Buzz!. This means you can develop on your desktop and
 * still hear audio, whilst still getting the benefits of low latency audio
 * when you compile.
 *
 * Under the hood, it uses the LowLatencyAudio plugin for Cordova to provide
 * much more responsive sound than the native HTML5 web audio API.
 *
 * Dependencies:
 * * Buzz! - http://buzz.jaysalvat.com/ - this is the fallback option. This means
 *   you can still test audio even when you're just working in the web browser.
 *
 * Optional Dependencies:
 * * Apache Cordova - http://cordova.apache.org/ - if you're building HTML5 for
 *   native
 * * Cordova Low Latency Audio plugin - https://github.com/phonegap/phonegap-plugins/tree/master/iPhone/LowLatencyAudio -
 *   low latency audio for iOS and Android
 *
 * Usage:
 *
 * Loads an object "wedge" onto the window object.
 *
 * See documentation (supplied with project) for detailed examples, but you
 * can play a sample by doing something like:
 *
 * wedge.preload("foo/bar.wav");
 * wedge.play("foo/bar.wav");
 *
 * License:
 *
 * MIT licensed
 *
 * Support:
 * * Tested with iOS/Cordova
 * * Should also work with Android
 */
;(function() {
    var Wedge = function() {

        // Check dependencies
        if (typeof buzz === 'undefined') {
            throw new Error('Missing dependency: Buzz!. You can download Buzz! from http://buzz.jaysalvat.com/');
        }

        // only used if not accelerated
        this.samples = {};

        // determine if Low Latency Audio is supplied by Cordova. If not, everything
        // will fall back to Buzz.
        this.accelAudioAvailable = false;
        if(typeof cordova !== "undefined" && typeof cordova.exec !== "undefined" && typeof LowLatencyAudio !== "undefined") {
            this.accelAudioAvailable = true;
        }
    };

    /**
     * Error message. Displayed on the console.
     *
     * @param msg String The message to display
     */
    function wedgeError(msg) {
        if(typeof console !== "undefined") {
            console.log(msg);
        }
    }

    /**
     * Get a sound from the internal collection.
     *
     * Not compatible with LowLatencyAudio.
     *
     * @param wedge Wedge We pass in the Wedge object so that this function is
     * not a "public method" on Wedge. Otherwise, it would cloud the interface.
     * @param path String e.g. "sfx/foo.wav"
     */
    function getSound(wedge, path) {
        var sound = wedge.samples[path];
        if(sound === null || typeof sound === 'undefined') {
            wedgeError("Wedge.js: Could not play sample " + path + "; perhaps it has not been preloaded?");
            return false;
        }

        return sound;
    }

    /**
     * Preload a sample into Wedge. You should not try to play a sample that you
     * have not preloaded.
     *
     * @param path String e.g. "sfx/foo.wav"
     *
     * @return Wedge For chaining method calls
     */
    Wedge.prototype.preload = function(path) {

        if(this.accelAudioAvailable) {
            LowLatencyAudio.preloadAudio(path, path, 2);
        } else {
            this.samples[path] = new buzz.sound(path);
        }

        return this;
    };

    /**
     * Play a sample identified by its path.
     * This will play using accelerated audio if available.
     * @param path String e.g. "sfx/foo.wav"
     *
     * @return Wedge For chaining method calls
     */
    Wedge.prototype.play = function(path) {

        if(this.accelAudioAvailable) {
            LowLatencyAudio.play(path);
        } else {
            var sound = getSound(this, path);
            if(!sound) {
                return this;
            }

            // Trigger the sound from the start
            if(sound.getPercent() === '--' || sound.getPercent() === 0 || sound.getPercent() === 100) {
                sound.play();
            } else {
                sound.setPercent(0);
            }
        }

        return this;
    };

    /**
     * Start looping a sample identified by its path
     * This will play using accelerated audio if available.
     * @param path String e.g. "sfx/foo.wav"
     *
     * @return Wedge For chaining method calls
     */
    Wedge.prototype.loop = function(path) {

        if(this.accelAudioAvailable) {
            LowLatencyAudio.loop(path);
        } else {
            var sound = getSound(this, path);
            if(!sound) {
                return this;
            }

            sound.play().loop();
        }

        return this;
    };

    /**
     * Stop a sample identified by its path
     * @param path String e.g. "sfx/foo.wav"
     *
     * @return Wedge For chaining method calls
     */
    Wedge.prototype.stop = function(path) {

        if(this.accelAudioAvailable) {
            LowLatencyAudio.stop(path);
        } else {
            var sound = getSound(this, path);
            if(!sound) {
                return this;
            }

            sound.stop();
        }

        return this;
    };

    // export an instance of Wedge to the global object
    window.wedge = new Wedge();
})(window);
<|endoftext|>


<commit_before>// Dependencies
const ParseMethod = require("./method")
    , Enny = require("enny")
    , Ul = require("ul")
    , Typpy = require("typpy")
    , flowTypes = require("engine-flow-types")
    , parseCommand = require("./flow-command")
    , deffy = require("deffy")
    ;

/**
 * flowComponent
 * Parses the flow components.
 *
 * @name flowComponent
 * @function
 * @param {Object} _input Raw engine-syntax flow component.
 * @param {String} instName The instance name.
 * @return {FlowComponent} The parsed input.
 */
module.exports = function (_input, instName) {
    var input = Ul.clone(_input);

    if (Typpy(input, String)) {
        input = [input];
    }

    var pInput = parseCommand(input[0]);
    if (pInput.error) {
        // TODO Not sure how to handle such errors
        throw pInput.error;
    }

    pInput.instance = deffy(pInput.instance, instName);
    if (pInput.command == "reset") debugger

    switch (pInput.type.func) {
        case flowTypes.DataHandler:
        case flowTypes.StreamHandler:
            return new pInput.type.func(
                pInput.command
              , {
                    to: pInput.instance
                  , once: pInput.type.type === "once"
                  , leaking: pInput.type.type === "leaking"
                }
              , input[1]
            );
        case flowTypes.Emit:
            pInput.instance = deffy(input[1] && input[1].to, pInput.instance)
            return new flowTypes.Emit(
                pInput.command
              , {
                    to: pInput.instance
                  , net: pInput.type.type === "net"
                  , leaking: pInput.type.type === "leaking"
                }
            );
        default:
            throw new Error("Unsupported type.");
    }
};
<commit_msg>Removed debugger<commit_after>// Dependencies
const ParseMethod = require("./method")
    , Enny = require("enny")
    , Ul = require("ul")
    , Typpy = require("typpy")
    , flowTypes = require("engine-flow-types")
    , parseCommand = require("./flow-command")
    , deffy = require("deffy")
    ;

/**
 * flowComponent
 * Parses the flow components.
 *
 * @name flowComponent
 * @function
 * @param {Object} _input Raw engine-syntax flow component.
 * @param {String} instName The instance name.
 * @return {FlowComponent} The parsed input.
 */
module.exports = function (_input, instName) {
    var input = Ul.clone(_input);

    if (Typpy(input, String)) {
        input = [input];
    }

    var pInput = parseCommand(input[0]);
    if (pInput.error) {
        // TODO Not sure how to handle such errors
        throw pInput.error;
    }

    pInput.instance = deffy(pInput.instance, instName);

    switch (pInput.type.func) {
        case flowTypes.DataHandler:
        case flowTypes.StreamHandler:
            return new pInput.type.func(
                pInput.command
              , {
                    to: pInput.instance
                  , once: pInput.type.type === "once"
                  , leaking: pInput.type.type === "leaking"
                }
              , input[1]
            );
        case flowTypes.Emit:
            pInput.instance = deffy(input[1] && input[1].to, pInput.instance)
            return new flowTypes.Emit(
                pInput.command
              , {
                    to: pInput.instance
                  , net: pInput.type.type === "net"
                  , leaking: pInput.type.type === "leaking"
                }
            );
        default:
            throw new Error("Unsupported type.");
    }
};
<|endoftext|>