<commit_before>/* @flow */

const _ = require('lodash')
const path = require('path')

const logger = require('../../logger')
const { id } = require('../../metadata')
const Buffer = require('./buffer')

/*::
import type Pouch from '../../pouch'
import type { AtomWatcherEvent, Batch, EventKind } from './event'
import type { Metadata } from '../../metadata'

type InitialDiffState = {
  [typeof STEP_NAME]: {
    waiting: WaitingItem[],
    renamedEvents: AtomWatcherEvent[],
    scannedPaths: Set<string>,
    byInode: Map<number|string, Metadata>,
  }
}

type WaitingItem = {
  batch: AtomWatcherEvent[],
  nbCandidates: number,
  timeout: TimeoutID
}
*/

// Wait this delay (in milliseconds) after the last event for a given file
// before pushing this event to the next steps.
// TODO tweak the value (the initial value was chosen because it looks like a
//      good value, it is not something that was computed)
const DELAY = 200
const STEP_NAME = 'initialDiff'

const log = logger({
  component: `atom/${STEP_NAME}`
})

const areParentChildPaths = (
  p /*: ?string */,
  c /*: ?string */
) /*: boolean %checks */ =>
  !!p && !!c && p !== c && `${c}${path.sep}`.startsWith(`${p}${path.sep}`)

module.exports = {
  STEP_NAME,
  loop,
  initialState,
  clearState
}

// Some files and directories can have been deleted while cozy-desktop was
// stopped. So, at the end of the initial scan, we have to do a diff between
// what was in pouchdb and the events from the local watcher to find what was
// deleted.
function loop(
  buffer /*: Buffer */,
  opts /*: { pouch: Pouch, state: InitialDiffState } */
) /*: Buffer */ {
  const out = new Buffer()
  initialDiff(buffer, out, opts.pouch, opts.state).catch(err => {
    log.error({ err })
  })
  return out
}

async function initialState(
  opts /*: { pouch: Pouch } */
) /*: Promise<InitialDiffState> */ {
  const waiting /*: WaitingItem[] */ = []
  const renamedEvents /*: AtomWatcherEvent[] */ = []
  const scannedPaths /*: Set<string> */ = new Set()

  // Using inode/fileId is more robust that using path or id for detecting
  // which files/folders have been deleted, as it is stable even if the
  // file/folder has been moved or renamed
  const byInode /*: Map<number|string, Metadata> */ = new Map()
  const docs /*: Metadata[] */ = await opts.pouch.byRecursivePathAsync('')
  for (const doc of docs) {
    if (doc.ino != null) {
      // Process only files/dirs that were created locally or synchronized
      byInode.set(doc.fileid || doc.ino, doc)
    }
  }

  return {
    [STEP_NAME]: { waiting, renamedEvents, scannedPaths, byInode }
  }
}

function clearState(state /*: InitialDiffState */) {
  const {
    [STEP_NAME]: { waiting, scannedPaths, byInode }
  } = state

  for (const item of waiting) {
    clearTimeout(item.timeout)
  }

  state[STEP_NAME].waiting = []
  state[STEP_NAME].renamedEvents = []
  scannedPaths.clear()
  byInode.clear()
}

async function initialDiff(
  buffer /*: Buffer */,
  out /*: Buffer */,
  pouch /*: Pouch */,
  state /*: InitialDiffState */
) /*: Promise<void> */ {
  // eslint-disable-next-line no-constant-condition
  while (true) {
    const events = await buffer.pop()
    const {
      [STEP_NAME]: { waiting, renamedEvents, scannedPaths, byInode }
    } = state

    let nbCandidates = 0

    debounce(waiting, events)

    const batch /*: Batch */ = []
    for (const event of events) {
      if (event.incomplete) {
        batch.push(event)
        continue
      }

      // Detect if the file was moved while the client was stopped
      if (['created', 'scan'].includes(event.action)) {
        let was /*: ?Metadata */
        if (event.stats.fileid) {
          was = byInode.get(event.stats.fileid)
        }
        if (!was) {
          was = byInode.get(event.stats.ino)
        }

        if (was && was.moveFrom && was.moveFrom.path === event.path) {
          _.set(event, [STEP_NAME, 'unappliedMoveTo'], was.path)
          event.action = 'ignored'
        } else if (was && was.path !== event.path) {
          if (kind(was) === event.kind) {
            // TODO for a directory, maybe we should check the children
            _.set(event, [STEP_NAME, 'actionConvertedFrom'], event.action)
            event.action = 'renamed'
            event.oldPath = was.path
            nbCandidates++
          } else {
            // On linux, the inodes can have been reused: a file was deleted
            // and a directory created just after while the client was stopped
            // for example.
            batch.push({
              action: 'deleted',
              kind: kind(was),
              _id: id(was.path),
              [STEP_NAME]: { inodeReuse: event },
              path: was.path
            })
          }
        } else if (foundUntouchedFile(event, was)) {
          _.set(event, [STEP_NAME, 'md5sumReusedFrom'], was.path)
          event.md5sum = was.md5sum
        }
      }

      if (
        ['created', 'modified', 'renamed', 'scan', 'ignored'].includes(
          event.action
        )
      ) {
        if (event.stats) {
          byInode.delete(event.stats.fileid)
          byInode.delete(event.stats.ino)
        }
        scannedPaths.add(event.path)
      }

      for (const renamedEvent of renamedEvents) {
        if (areParentChildPaths(renamedEvent.oldPath, event.oldPath)) {
          const oldPathFixed = event.oldPath.replace(
            renamedEvent.oldPath,
            renamedEvent.path
          )
          if (event.path === oldPathFixed) {
            event.action = 'ignored'
          } else {
            event.oldPath = oldPathFixed
          }
          _.set(
            event,
            [STEP_NAME, 'renamedAncestor'],
            _.pick(renamedEvent, ['oldPath', 'path'])
          )
        }
      }

      if (event.action === 'renamed') {
        // Needs to be pushed after the oldPath has been fixed
        renamedEvents.push(event)
      }

      if (event.action === 'initial-scan-done') {
        // Emit deleted events for all the remaining files/dirs
        for (const [, doc] of byInode) {
          if (!scannedPaths.has(doc.path)) {
            batch.push({
              action: 'deleted',
              kind: kind(doc),
              _id: id(doc.path),
              [STEP_NAME]: {
                notFound: _.defaults(
                  { kind: kind(doc) },
                  _.pick(doc, ['path', 'md5sum', 'updated_at'])
                )
              },
              path: doc.path
            })
          }
        }
        clearState(state)
      }
      batch.push(event)
    }

    // Push the new batch of events in the queue
    const timeout = setTimeout(() => {
      out.push(waiting.shift().batch)
      sendReadyBatches(waiting, out)
    }, DELAY)
    waiting.push({ batch, nbCandidates, timeout })

    // Look if some batches can be sent without waiting
    sendReadyBatches(waiting, out)
  }
}

function sendReadyBatches(waiting /*: WaitingItem[] */, out /*: Buffer */) {
  while (waiting.length > 0) {
    if (waiting[0].nbCandidates !== 0) {
      break
    }
    const item = waiting.shift()
    clearTimeout(item.timeout)
    out.push(item.batch)
  }
}

// Look if we can debounce some waiting events with the current events
function debounce(
  waiting /*: WaitingItem[] */,
  events /*: AtomWatcherEvent[] */
) {
  for (let i = 0; i < events.length; i++) {
    const event = events[i]
    if (event.incomplete) {
      continue
    }
    if (event.action === 'scan') {
      for (let j = 0; j < waiting.length; j++) {
        const w = waiting[j]
        if (w.nbCandidates === 0) {
          continue
        }
        for (let k = 0; k < w.batch.length; k++) {
          const e = w.batch[k]
          if (e.action === 'renamed' && e.path === event.path) {
            log.debug(
              { renamedEvent: e, scanEvent: event },
              `Ignore overlapping ${event.kind} ${event.action}`
            )
            events.splice(i, 1)
            w.nbCandidates--
            break
          }
        }
      }
    }
  }
}

function eventUpdateTime(event) {
  const { ctime, mtime } = event.stats
  return Math.max(ctime.getTime(), mtime.getTime())
}

function docUpdateTime(was) {
  return new Date(was.updated_at).getTime()
}

function foundUntouchedFile(event, was) /*: boolean %checks */ {
  return (
    was != null &&
    was.md5sum != null &&
    event.kind === 'file' &&
    eventUpdateTime(event) === docUpdateTime(was)
  )
}

function kind(doc /*: Metadata */) /*: EventKind */ {
  return doc.docType === 'folder' ? 'directory' : doc.docType
}
<commit_msg>core/local/steps/initial_diff: Extract .fixPathsAfterParentMove() (#1565)<commit_after>/* @flow */

const _ = require('lodash')
const path = require('path')

const logger = require('../../logger')
const { id } = require('../../metadata')
const Buffer = require('./buffer')

/*::
import type Pouch from '../../pouch'
import type { AtomWatcherEvent, Batch, EventKind } from './event'
import type { Metadata } from '../../metadata'

type InitialDiffState = {
  [typeof STEP_NAME]: {
    waiting: WaitingItem[],
    renamedEvents: AtomWatcherEvent[],
    scannedPaths: Set<string>,
    byInode: Map<number|string, Metadata>,
  }
}

type WaitingItem = {
  batch: AtomWatcherEvent[],
  nbCandidates: number,
  timeout: TimeoutID
}
*/

// Wait this delay (in milliseconds) after the last event for a given file
// before pushing this event to the next steps.
// TODO tweak the value (the initial value was chosen because it looks like a
//      good value, it is not something that was computed)
const DELAY = 200
const STEP_NAME = 'initialDiff'

const log = logger({
  component: `atom/${STEP_NAME}`
})

const areParentChildPaths = (
  p /*: ?string */,
  c /*: ?string */
) /*: boolean %checks */ =>
  !!p && !!c && p !== c && `${c}${path.sep}`.startsWith(`${p}${path.sep}`)

module.exports = {
  STEP_NAME,
  loop,
  initialState,
  clearState
}

// Some files and directories can have been deleted while cozy-desktop was
// stopped. So, at the end of the initial scan, we have to do a diff between
// what was in pouchdb and the events from the local watcher to find what was
// deleted.
function loop(
  buffer /*: Buffer */,
  opts /*: { pouch: Pouch, state: InitialDiffState } */
) /*: Buffer */ {
  const out = new Buffer()
  initialDiff(buffer, out, opts.pouch, opts.state).catch(err => {
    log.error({ err })
  })
  return out
}

async function initialState(
  opts /*: { pouch: Pouch } */
) /*: Promise<InitialDiffState> */ {
  const waiting /*: WaitingItem[] */ = []
  const renamedEvents /*: AtomWatcherEvent[] */ = []
  const scannedPaths /*: Set<string> */ = new Set()

  // Using inode/fileId is more robust that using path or id for detecting
  // which files/folders have been deleted, as it is stable even if the
  // file/folder has been moved or renamed
  const byInode /*: Map<number|string, Metadata> */ = new Map()
  const docs /*: Metadata[] */ = await opts.pouch.byRecursivePathAsync('')
  for (const doc of docs) {
    if (doc.ino != null) {
      // Process only files/dirs that were created locally or synchronized
      byInode.set(doc.fileid || doc.ino, doc)
    }
  }

  return {
    [STEP_NAME]: { waiting, renamedEvents, scannedPaths, byInode }
  }
}

function clearState(state /*: InitialDiffState */) {
  const {
    [STEP_NAME]: { waiting, scannedPaths, byInode }
  } = state

  for (const item of waiting) {
    clearTimeout(item.timeout)
  }

  state[STEP_NAME].waiting = []
  state[STEP_NAME].renamedEvents = []
  scannedPaths.clear()
  byInode.clear()
}

async function initialDiff(
  buffer /*: Buffer */,
  out /*: Buffer */,
  pouch /*: Pouch */,
  state /*: InitialDiffState */
) /*: Promise<void> */ {
  // eslint-disable-next-line no-constant-condition
  while (true) {
    const events = await buffer.pop()
    const {
      [STEP_NAME]: { waiting, renamedEvents, scannedPaths, byInode }
    } = state

    let nbCandidates = 0

    debounce(waiting, events)

    const batch /*: Batch */ = []
    for (const event of events) {
      if (event.incomplete) {
        batch.push(event)
        continue
      }

      // Detect if the file was moved while the client was stopped
      if (['created', 'scan'].includes(event.action)) {
        let was /*: ?Metadata */
        if (event.stats.fileid) {
          was = byInode.get(event.stats.fileid)
        }
        if (!was) {
          was = byInode.get(event.stats.ino)
        }

        if (was && was.moveFrom && was.moveFrom.path === event.path) {
          _.set(event, [STEP_NAME, 'unappliedMoveTo'], was.path)
          event.action = 'ignored'
        } else if (was && was.path !== event.path) {
          if (kind(was) === event.kind) {
            // TODO for a directory, maybe we should check the children
            _.set(event, [STEP_NAME, 'actionConvertedFrom'], event.action)
            event.action = 'renamed'
            event.oldPath = was.path
            nbCandidates++
          } else {
            // On linux, the inodes can have been reused: a file was deleted
            // and a directory created just after while the client was stopped
            // for example.
            batch.push({
              action: 'deleted',
              kind: kind(was),
              _id: id(was.path),
              [STEP_NAME]: { inodeReuse: event },
              path: was.path
            })
          }
        } else if (foundUntouchedFile(event, was)) {
          _.set(event, [STEP_NAME, 'md5sumReusedFrom'], was.path)
          event.md5sum = was.md5sum
        }
      }

      if (
        ['created', 'modified', 'renamed', 'scan', 'ignored'].includes(
          event.action
        )
      ) {
        if (event.stats) {
          byInode.delete(event.stats.fileid)
          byInode.delete(event.stats.ino)
        }
        scannedPaths.add(event.path)
      }

      fixPathsAfterParentMove(renamedEvents, event)

      if (event.action === 'renamed') {
        // Needs to be pushed after the oldPath has been fixed
        renamedEvents.push(event)
      }

      if (event.action === 'initial-scan-done') {
        // Emit deleted events for all the remaining files/dirs
        for (const [, doc] of byInode) {
          if (!scannedPaths.has(doc.path)) {
            batch.push({
              action: 'deleted',
              kind: kind(doc),
              _id: id(doc.path),
              [STEP_NAME]: {
                notFound: _.defaults(
                  { kind: kind(doc) },
                  _.pick(doc, ['path', 'md5sum', 'updated_at'])
                )
              },
              path: doc.path
            })
          }
        }
        clearState(state)
      }
      batch.push(event)
    }

    // Push the new batch of events in the queue
    const timeout = setTimeout(() => {
      out.push(waiting.shift().batch)
      sendReadyBatches(waiting, out)
    }, DELAY)
    waiting.push({ batch, nbCandidates, timeout })

    // Look if some batches can be sent without waiting
    sendReadyBatches(waiting, out)
  }
}

function sendReadyBatches(waiting /*: WaitingItem[] */, out /*: Buffer */) {
  while (waiting.length > 0) {
    if (waiting[0].nbCandidates !== 0) {
      break
    }
    const item = waiting.shift()
    clearTimeout(item.timeout)
    out.push(item.batch)
  }
}

// Look if we can debounce some waiting events with the current events
function debounce(
  waiting /*: WaitingItem[] */,
  events /*: AtomWatcherEvent[] */
) {
  for (let i = 0; i < events.length; i++) {
    const event = events[i]
    if (event.incomplete) {
      continue
    }
    if (event.action === 'scan') {
      for (let j = 0; j < waiting.length; j++) {
        const w = waiting[j]
        if (w.nbCandidates === 0) {
          continue
        }
        for (let k = 0; k < w.batch.length; k++) {
          const e = w.batch[k]
          if (e.action === 'renamed' && e.path === event.path) {
            log.debug(
              { renamedEvent: e, scanEvent: event },
              `Ignore overlapping ${event.kind} ${event.action}`
            )
            events.splice(i, 1)
            w.nbCandidates--
            break
          }
        }
      }
    }
  }
}

function fixPathsAfterParentMove(renamedEvents, event) {
  for (const renamedEvent of renamedEvents) {
    if (
      event.oldPath &&
      areParentChildPaths(renamedEvent.oldPath, event.oldPath)
    ) {
      const oldPathFixed = event.oldPath.replace(
        renamedEvent.oldPath,
        renamedEvent.path
      )
      if (event.path === oldPathFixed) {
        event.action = 'ignored'
      } else {
        event.oldPath = oldPathFixed
      }
      _.set(
        event,
        [STEP_NAME, 'renamedAncestor'],
        _.pick(renamedEvent, ['oldPath', 'path'])
      )
    }
  }
}

function eventUpdateTime(event) {
  const { ctime, mtime } = event.stats
  return Math.max(ctime.getTime(), mtime.getTime())
}

function docUpdateTime(was) {
  return new Date(was.updated_at).getTime()
}

function foundUntouchedFile(event, was) /*: boolean %checks */ {
  return (
    was != null &&
    was.md5sum != null &&
    event.kind === 'file' &&
    eventUpdateTime(event) === docUpdateTime(was)
  )
}

function kind(doc /*: Metadata */) /*: EventKind */ {
  return doc.docType === 'folder' ? 'directory' : doc.docType
}
<|endoftext|>


<commit_before>/* global gapi */
import Flash from '~/flash';
import { s__, sprintf } from '~/locale';

import * as types from './mutation_types';

export const setProject = ({ commit }, selectedProject) => {
  commit(types.SET_PROJECT, selectedProject);
};

export const setZone = ({ commit }, selectedZone) => {
  commit(types.SET_ZONE, selectedZone);
};

export const setMachineType = ({ commit }, selectedMachineType) => {
  commit(types.SET_MACHINE_TYPE, selectedMachineType);
};

export const getProjects = ({ commit }) =>
  new Promise((resolve, reject) => {
    const request = gapi.client.cloudresourcemanager.projects.list();

    return request.then(
      resp => {
        commit(types.SET_PROJECTS, resp.result.projects);

        resolve();
      },
      resp => {
        if (resp.result.error) {
          Flash(
            sprintf(
              s__(
                'ClusterIntegration|An error occured while trying to fetch your projects: %{error}',
              ),
              {
                error: resp.result.error.message,
              },
            ),
          );
        }

        reject();
      },
    );
  });

export const getZones = ({ commit, state }) =>
  new Promise((resolve, reject) => {
    const request = gapi.client.compute.zones.list({
      project: state.selectedProject.projectId,
    });

    return request.then(
      resp => {
        commit(types.SET_ZONES, resp.result.items);

        resolve();
      },
      resp => {
        if (resp.result.error) {
          Flash(
            sprintf(
              s__(
                'ClusterIntegration|An error occured while trying to fetch project zones: %{error}',
              ),
              { error: resp.result.error.message },
            ),
          );
        }

        reject();
      },
    );
  });

export const getMachineTypes = ({ commit, state }) =>
  new Promise((resolve, reject) => {
    const request = gapi.client.compute.machineTypes.list({
      project: state.selectedProject.projectId,
      zone: state.selectedZone,
    });

    return request.then(
      resp => {
        commit(types.SET_MACHINE_TYPES, resp.result.items);

        resolve();
      },
      resp => {
        if (resp.result.error) {
          Flash(
            sprintf(
              s__(
                'ClusterIntegration|An error occured while trying to fetch zone machine types: %{error}',
              ),
              { error: resp.result.error.message },
            ),
          );
        }

        reject();
      },
    );
  });

// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
<commit_msg>refactor gke store actions<commit_after>/* global gapi */
import Flash from '~/flash';
import { s__, sprintf } from '~/locale';

import * as types from './mutation_types';

export const setProject = ({ commit }, selectedProject) => {
  commit(types.SET_PROJECT, selectedProject);
};

export const setZone = ({ commit }, selectedZone) => {
  commit(types.SET_ZONE, selectedZone);
};

export const setMachineType = ({ commit }, selectedMachineType) => {
  commit(types.SET_MACHINE_TYPE, selectedMachineType);
};

const displayError = (resp, errorMessage) => {
  if (resp.result && resp.result.error) {
    Flash(sprintf(s__(errorMessage), { error: resp.result.error.message }));
  }
};

const gapiRequest = ({ service, params, commit, mutation, payloadKey, errorMessage }) =>
  new Promise((resolve, reject) => {
    const request = service.list(params);

    return request.then(
      resp => {
        const { result } = resp;

        commit(mutation, result[payloadKey]);

        resolve();
      },
      resp => {
        displayError(resp, errorMessage);

        reject();
      },
    );
  });

export const getProjects = ({ commit }) =>
  gapiRequest({
    service: gapi.client.cloudresourcemanager.projects,
    params: {},
    commit,
    mutation: types.SET_PROJECTS,
    payloadKey: 'projects',
    errorMessage:
      'ClusterIntegration|An error occured while trying to fetch your projects: %{error}',
  });

export const getZones = ({ commit, state }) =>
  gapiRequest({
    service: gapi.client.compute.zones,
    params: {
      project: state.selectedProject.projectId,
    },
    commit,
    mutation: types.SET_ZONES,
    payloadKey: 'items',
    errorMessage:
      'ClusterIntegration|An error occured while trying to fetch project zones: %{error}',
  });

export const getMachineTypes = ({ commit, state }) =>
  gapiRequest({
    service: gapi.client.compute.machineTypes,
    params: {
      project: state.selectedProject.projectId,
      zone: state.selectedZone,
    },
    commit,
    mutation: types.SET_MACHINE_TYPES,
    payloadKey: 'items',
    errorMessage:
      'ClusterIntegration|An error occured while trying to fetch zone machine types: %{error}',
  });

// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
<|endoftext|>


<commit_before>/*
 Thali unit test implementation of tape. Highly inspired by wrapping-tape, and
 usage is very similar to the wrapping tape:

 var tape = require('thaliTape');

 var test = tape({
 setup: function(t) {
 // will be called after each test has started to setup the test
 // after the next line, the actual test code will be executed
 t.end();
 },
 teardown: function(t) {
 // will be called after each device has ended the test
 // do any final tear down for the test in here
 t.end();
 }
 });
 */

'use strict';

var util = require('util');
var uuid = require('node-uuid');
var tape = require('tape-catch');
var io = require('socket.io-client');
var testUtils = require('./testUtils');
var Promise = require('lie');

process.on('uncaughtException', function (err) {
  testUtils.logMessageToScreen('Uncaught Exception: ' + err);
  console.log(err.stack);
  console.log('****TEST_LOGGER:[PROCESS_ON_EXIT_FAILED]****');
  process.exit(1);
});

process.on('unhandledRejection', function (err, p) {
  testUtils.logMessageToScreen('Uncaught Promise Rejection: ' + err);
  console.trace(err);
  console.log(err.stack);
  console.log('****TEST_LOGGER:[PROCESS_ON_EXIT_FAILED]****');
  process.exit(1);
});

var tests = {};
var allSuccess = true;

var emitWhenConnected = function (socket, name, data) {
  if (socket.connected) {
    data ? socket.emit(name, data) : socket.emit(name);
  } else {
    setTimeout(function () {
      emitWhenConnected(socket, name, data);
    }, 1000);
  }
};

function declareTest(testServer, name, setup, teardown, opts, cb) {

  // test declaration is postponed until we know the order in which
  // the server wants to execute them.

  // Tape executes tests in strict declaration order once the output stream
  // starts to request results so make sure we declare everything up front
  // before asking for the first result

  // Here we declare setup and teardown functions either side of the actual test
  // They'll be executed in declaration order and will be coordinated across
  // devices by the test server emitting events at the appropriate point

  tape('setup', function (t) {
    // Run setup function when the testServer tells us
    var success = true;
    testServer.once('setup_' + name, function () {
      emitWhenConnected(testServer, util.format('setup_%s_ok', name));
      t.on('result', function (res) {
        success = success && res.ok;
      });
      t.once('end', function () {
        if (!success) {
          allSuccess = false;
        }
        emitWhenConnected(testServer, 'setup_complete',
          JSON.stringify({
            'test': name,
            'success': success,
            'data': t.data || null
          })
        );
      });
      setup(t);
    });
  });

  tape(name, function (t) {
    var success = true;

    // Listen for the test result
    t.on('result', function (res) {
      success = success && res.ok;
    });

    t.once('end', function () {
      if (!success) {
        allSuccess = false;
      }
      // Tell the server we ran the test and what the result was (true == pass)
      emitWhenConnected(testServer, 'test_complete',
        JSON.stringify({'test':name, 'success':success}));
    });

    // Run the test (cb) when the server tells us to
    testServer.once('start_test_' + name, function (data) {
      t.participants = JSON.parse(data);
      emitWhenConnected(testServer, util.format('start_test_%s_ok', name));
      cb(t);
    });
  });

  tape('teardown', function (t) {
    // Run teardown function when the server tells us
    var success = true;
    testServer.once('teardown_' + name, function () {
      emitWhenConnected(testServer, util.format('teardown_%s_ok', name));
      t.on('result', function (res) {
        success = success && res.ok;
      });
      t.once('end', function () {
        if (!success) {
          allSuccess = false;
        }
        emitWhenConnected(testServer, 'teardown_complete',
          JSON.stringify({'test':name, 'success':success}));
      });
      teardown(t);
    });
  });
}

// The running number of the test that together with the test name guarantees
// a unique identifier even if there exists multiple tests with same name
var testRunningNumber = 0;
// Flag used to check if we have completed all the tests we should run
var complete = false;
var nextTestOnly = false;
var ignoreRemainingTests = false;

var thaliTape = function (fixture) {
  // Thali_Tape - Adapt tape such that tests are executed when explicitly
  // triggered by a co-ordinating server executing (perhaps) remotely.
  // This enables us to run tests in lock step across a number of devices

  // test([name], [opts], fn)
  var addTest = function (name, opts, fn) {

    // This is the function that declares and performs the test.
    // cb is the test function. We wrap this in setup and

    if (ignoreRemainingTests) {
      return;
    }

    if (!fn) {
      fn = opts;
      opts = null;
    }

    if (nextTestOnly) {
      tests = {
        name: {
          opts: opts,
          fn: fn,
          fixture: fixture
        }
      };
      ignoreRemainingTests = true;
      return;
    }

    testRunningNumber++;
    tests[testRunningNumber + '. ' + name] = {
      opts: opts,
      fn: fn,
      fixture: fixture
    };
  };

  addTest.only = function (name, opts, fn) {
    nextTestOnly = true;
    addTest(name, opts, fn);
  };

  return addTest;
};

thaliTape.uuid = uuid.v4();

var platform =
  typeof jxcore !== 'undefined' && jxcore.utils.OSInfo().isAndroid ?
  'android' :
  'ios';

thaliTape.begin = function (version, hasRequiredHardware, nativeUTFailed) {
  console.log('ThaliTape :: Started ThaliTape');
  var serverOptions = {
    transports: ['websocket']
  };

  var connectionString = 'http://' + require('../server-address') + ':' + 3000 +
  '/';
  console.log('ThaliTape ::  Connecting to ', connectionString);
  var testServer = io(connectionString, serverOptions);

  var firstConnection = true;
  var onConnection = function () {
    if (firstConnection) {
      // Once connected, let the server know who we are and what we do
      testServer.once('schedule', function (schedule) {
        JSON.parse(schedule).forEach(function (test) {
          declareTest(
            testServer,
            test,
            tests[test].fixture.setup,
            tests[test].fixture.teardown,
            tests[test].opts,
            tests[test].fn
          );
        });
        emitWhenConnected(testServer, 'schedule_complete');
      });
    }
    firstConnection = false;

    var presentData = {
      os: platform,
      version: version,
      supportedHardware: hasRequiredHardware,
      nativeUTFailed: nativeUTFailed,
      name: testUtils.getName(),
      uuid: thaliTape.uuid,
      type: 'unittest',
      tests: Object.keys(tests)
    };
    emitWhenConnected(testServer, 'present', JSON.stringify(presentData));
  };

  // We are having similar logic in both connect reconnect
  // events, because socket.io seems to behave so that sometimes
  // we get the connect event even if we have been connected before
  // (and sometimes the reconnect event).
  testServer.on('connect_error', function (error) {
    console.log('ThaliTape :: Error when connecting to the test server '
      + error);
    testUtils.logMessageToScreen('Error when connecting to the test server '
      + error);
    onConnection();
  });
  testServer.on('connect_timeout', function () {
    console.log('ThaliTape :: Connection timeout reached when connecting '
      + 'to the test server');
    testUtils.logMessageToScreen('Connection timeout reached when connecting '
      + 'to the test server');
    onConnection();
  });
  testServer.on('connect', function () {
    console.log('ThaliTape :: Connected to the test server');
    testUtils.logMessageToScreen('Connected to the test server');
    onConnection();
  });
  testServer.on('reconnect', function () {
    console.log('ThaliTape :: Reconnected to the test server');
    testUtils.logMessageToScreen('Reconnected to the test server');
    onConnection();
  });

  testServer.once('discard', function () {
    // This device not needed, log appropriately so CI doesn't think we've
    // failed
    testUtils.logMessageToScreen('Device discarded as surplus');
    console.log('--= Surplus to requirements =--');
    console.log('****TEST_LOGGER:[PROCESS_ON_EXIT_SUCCESS]****');
  });

  testServer.once('disqualify', function () {
    testUtils.logMessageToScreen('Device disqualified');
    testUtils.returnsValidNetworkStatus()
    .then(function (validStatus) {
      if (validStatus) {
        console.log('****TEST_LOGGER:[PROCESS_ON_EXIT_SUCCESS]****');
      } else {
        console.log('****TEST_LOGGER:[PROCESS_ON_EXIT_FAILED]****');
      }
    });
  });

  testServer.on('error', function (data) {
    var errData = JSON.parse(data);
    testUtils.logMessageToScreen('Error: ' + data + ' : ' + errData.type +
      ' : ' + errData.data);
  });

  testServer.on('disconnect', function () {
    if (complete) {
      process.exit(0);
    } else {
      // Just log the error since socket.io will try
      // to reconnect.
      testUtils.logMessageToScreen('Disconnected from the test server');
    }
  });

  testServer.once('complete', function () {
    testUtils.logMessageToScreen('Tests complete');
    complete = true;
    if (allSuccess) {
      console.log('****TEST_LOGGER:[PROCESS_ON_EXIT_SUCCESS]****');
    } else {
      console.log('****TEST_LOGGER:[PROCESS_ON_EXIT_FAILED]****');
    }
  });

  testServer.once('aborted', function () {
    testUtils.logMessageToScreen('Tests aborted');
    complete = true;
    console.log('****TEST_LOGGER:[PROCESS_ON_EXIT_FAILED]****');
  });

  // Only used for testing purposes..
  thaliTape._testServer = testServer;

  return new Promise(function (resolve, reject) {
    testServer.once('complete', function () {
      if (allSuccess) {
        resolve('Successfully completed');
      } else {
        reject('Completed with errors');
      }
    });
  });
};

var objectToExport;
if (typeof jxcore === 'undefined' ||
    typeof Mobile !== 'undefined') {
  // On mobile, or outside of jxcore (some dev scenarios) we use
  // the server-coordinated thaliTape
  objectToExport = thaliTape;
  objectToExport.coordinated = true;
} else {
  // On desktop we just use simple non-coordinated tape
  objectToExport = require('./simpleTape');
  objectToExport.coordinated = false;
}

module.exports = exports = objectToExport;
<commit_msg>Added more error logs<commit_after>/*
 Thali unit test implementation of tape. Highly inspired by wrapping-tape, and
 usage is very similar to the wrapping tape:

 var tape = require('thaliTape');

 var test = tape({
 setup: function(t) {
 // will be called after each test has started to setup the test
 // after the next line, the actual test code will be executed
 t.end();
 },
 teardown: function(t) {
 // will be called after each device has ended the test
 // do any final tear down for the test in here
 t.end();
 }
 });
 */

'use strict';

var util = require('util');
var uuid = require('node-uuid');
var tape = require('tape-catch');
var io = require('socket.io-client');
var testUtils = require('./testUtils');
var Promise = require('lie');

process.on('uncaughtException', function (err) {
  testUtils.logMessageToScreen('Uncaught Exception: ' + err);
  console.log(err.stack);
  console.log('****TEST_LOGGER:[PROCESS_ON_EXIT_FAILED]****');
  process.exit(1);
});

process.on('unhandledRejection', function (err, p) {
  testUtils.logMessageToScreen('Uncaught Promise Rejection: ' + err);
  console.trace(err);
  console.log(err.stack);
  console.log('****TEST_LOGGER:[PROCESS_ON_EXIT_FAILED]****');
  process.exit(1);
});

var tests = {};
var allSuccess = true;

var emitWhenConnected = function (socket, name, data) {
  if (socket.connected) {
    data ? socket.emit(name, data) : socket.emit(name);
  } else {
    setTimeout(function () {
      emitWhenConnected(socket, name, data);
    }, 1000);
  }
};

function declareTest(testServer, name, setup, teardown, opts, cb) {

  // test declaration is postponed until we know the order in which
  // the server wants to execute them.

  // Tape executes tests in strict declaration order once the output stream
  // starts to request results so make sure we declare everything up front
  // before asking for the first result

  // Here we declare setup and teardown functions either side of the actual test
  // They'll be executed in declaration order and will be coordinated across
  // devices by the test server emitting events at the appropriate point

  tape('setup', function (t) {
    // Run setup function when the testServer tells us
    var success = true;
    testServer.once('setup_' + name, function () {
      emitWhenConnected(testServer, util.format('setup_%s_ok', name));
      t.on('result', function (res) {
        success = success && res.ok;
      });
      t.once('end', function () {
        if (!success) {
          allSuccess = false;
        }
        emitWhenConnected(testServer, 'setup_complete',
          JSON.stringify({
            'test': name,
            'success': success,
            'data': t.data || null
          })
        );
      });
      setup(t);
    });
  });

  tape(name, function (t) {
    var success = true;

    // Listen for the test result
    t.on('result', function (res) {
      success = success && res.ok;
    });

    t.once('end', function () {
      if (!success) {
        allSuccess = false;
      }
      // Tell the server we ran the test and what the result was (true == pass)
      emitWhenConnected(testServer, 'test_complete',
        JSON.stringify({'test':name, 'success':success}));
    });

    // Run the test (cb) when the server tells us to
    testServer.once('start_test_' + name, function (data) {
      t.participants = JSON.parse(data);
      emitWhenConnected(testServer, util.format('start_test_%s_ok', name));
      cb(t);
    });
  });

  tape('teardown', function (t) {
    // Run teardown function when the server tells us
    var success = true;
    testServer.once('teardown_' + name, function () {
      emitWhenConnected(testServer, util.format('teardown_%s_ok', name));
      t.on('result', function (res) {
        success = success && res.ok;
      });
      t.once('end', function () {
        if (!success) {
          allSuccess = false;
        }
        emitWhenConnected(testServer, 'teardown_complete',
          JSON.stringify({'test':name, 'success':success}));
      });
      teardown(t);
    });
  });
}

// The running number of the test that together with the test name guarantees
// a unique identifier even if there exists multiple tests with same name
var testRunningNumber = 0;
// Flag used to check if we have completed all the tests we should run
var complete = false;
var nextTestOnly = false;
var ignoreRemainingTests = false;

var thaliTape = function (fixture) {
  // Thali_Tape - Adapt tape such that tests are executed when explicitly
  // triggered by a co-ordinating server executing (perhaps) remotely.
  // This enables us to run tests in lock step across a number of devices

  // test([name], [opts], fn)
  var addTest = function (name, opts, fn) {

    // This is the function that declares and performs the test.
    // cb is the test function. We wrap this in setup and

    if (ignoreRemainingTests) {
      return;
    }

    if (!fn) {
      fn = opts;
      opts = null;
    }

    if (nextTestOnly) {
      tests = {
        name: {
          opts: opts,
          fn: fn,
          fixture: fixture
        }
      };
      ignoreRemainingTests = true;
      return;
    }

    testRunningNumber++;
    tests[testRunningNumber + '. ' + name] = {
      opts: opts,
      fn: fn,
      fixture: fixture
    };
  };

  addTest.only = function (name, opts, fn) {
    nextTestOnly = true;
    addTest(name, opts, fn);
  };

  return addTest;
};

thaliTape.uuid = uuid.v4();

var platform =
  typeof jxcore !== 'undefined' && jxcore.utils.OSInfo().isAndroid ?
  'android' :
  'ios';

thaliTape.begin = function (version, hasRequiredHardware, nativeUTFailed) {
  console.log('ThaliTape :: Started ThaliTape');
  var serverOptions = {
    transports: ['websocket']
  };

  var connectionString = 'http://' + require('../server-address') + ':' + 3000 +
  '/';
  console.log('ThaliTape ::  Connecting to ', connectionString);
  var testServer = io(connectionString, serverOptions);

  var firstConnection = true;
  var onConnection = function () {
    if (firstConnection) {
      // Once connected, let the server know who we are and what we do
      testServer.once('schedule', function (schedule) {
        JSON.parse(schedule).forEach(function (test) {
          declareTest(
            testServer,
            test,
            tests[test].fixture.setup,
            tests[test].fixture.teardown,
            tests[test].opts,
            tests[test].fn
          );
        });
        emitWhenConnected(testServer, 'schedule_complete');
      });
    }
    firstConnection = false;

    var presentData = {
      os: platform,
      version: version,
      supportedHardware: hasRequiredHardware,
      nativeUTFailed: nativeUTFailed,
      name: testUtils.getName(),
      uuid: thaliTape.uuid,
      type: 'unittest',
      tests: Object.keys(tests)
    };
    emitWhenConnected(testServer, 'present', JSON.stringify(presentData));
  };

  // We are having similar logic in both connect reconnect
  // events, because socket.io seems to behave so that sometimes
  // we get the connect event even if we have been connected before
  // (and sometimes the reconnect event).
  testServer.on('connect_error', function (error) {
    console.log('ThaliTape :: Error when connecting to the test server '
      + error);
    console.log(error.message);
    console.log(error.stack);
    testUtils.logMessageToScreen('Error when connecting to the test server '
      + error);
    onConnection();
  });
  testServer.on('connect_timeout', function () {
    console.log('ThaliTape :: Connection timeout reached when connecting '
      + 'to the test server');
    testUtils.logMessageToScreen('Connection timeout reached when connecting '
      + 'to the test server');
    onConnection();
  });
  testServer.on('connect', function () {
    console.log('ThaliTape :: Connected to the test server');
    testUtils.logMessageToScreen('Connected to the test server');
    onConnection();
  });
  testServer.on('reconnect', function () {
    console.log('ThaliTape :: Reconnected to the test server');
    testUtils.logMessageToScreen('Reconnected to the test server');
    onConnection();
  });

  testServer.once('discard', function () {
    // This device not needed, log appropriately so CI doesn't think we've
    // failed
    testUtils.logMessageToScreen('Device discarded as surplus');
    console.log('--= Surplus to requirements =--');
    console.log('****TEST_LOGGER:[PROCESS_ON_EXIT_SUCCESS]****');
  });

  testServer.once('disqualify', function () {
    testUtils.logMessageToScreen('Device disqualified');
    testUtils.returnsValidNetworkStatus()
    .then(function (validStatus) {
      if (validStatus) {
        console.log('****TEST_LOGGER:[PROCESS_ON_EXIT_SUCCESS]****');
      } else {
        console.log('****TEST_LOGGER:[PROCESS_ON_EXIT_FAILED]****');
      }
    });
  });

  testServer.on('error', function (data) {
    var errData = JSON.parse(data);
    testUtils.logMessageToScreen('Error: ' + data + ' : ' + errData.type +
      ' : ' + errData.data);
  });

  testServer.on('disconnect', function () {
    if (complete) {
      process.exit(0);
    } else {
      // Just log the error since socket.io will try
      // to reconnect.
      testUtils.logMessageToScreen('Disconnected from the test server');
    }
  });

  testServer.once('complete', function () {
    testUtils.logMessageToScreen('Tests complete');
    complete = true;
    if (allSuccess) {
      console.log('****TEST_LOGGER:[PROCESS_ON_EXIT_SUCCESS]****');
    } else {
      console.log('****TEST_LOGGER:[PROCESS_ON_EXIT_FAILED]****');
    }
  });

  testServer.once('aborted', function () {
    testUtils.logMessageToScreen('Tests aborted');
    complete = true;
    console.log('****TEST_LOGGER:[PROCESS_ON_EXIT_FAILED]****');
  });

  // Only used for testing purposes..
  thaliTape._testServer = testServer;

  return new Promise(function (resolve, reject) {
    testServer.once('complete', function () {
      if (allSuccess) {
        resolve('Successfully completed');
      } else {
        reject('Completed with errors');
      }
    });
  });
};

var objectToExport;
if (typeof jxcore === 'undefined' ||
    typeof Mobile !== 'undefined') {
  // On mobile, or outside of jxcore (some dev scenarios) we use
  // the server-coordinated thaliTape
  objectToExport = thaliTape;
  objectToExport.coordinated = true;
} else {
  // On desktop we just use simple non-coordinated tape
  objectToExport = require('./simpleTape');
  objectToExport.coordinated = false;
}

module.exports = exports = objectToExport;
<|endoftext|>


<commit_before>(function ($) {
    var options = {
    };

    /* *************************************************** */

    function DataSet (lazyLoad, events_url, color, uuid) {
        this.lazyLoad = lazyLoad;
        this.url = events_url;
        this.color = color;
        this.uuid = uuid;
        this.needsUpdate = true;
        this.data = [];
        this.label = '';
        this.parameterPk = null;
        this.axisLabel = null;
        this.axisLabelX = null;
        this.axisLabelY = null;
        this.xhr = null;
    }

    DataSet.prototype.fetch = function (successCallback) {
        var self = this;
        // cancel previous XHR
        if (this.xhr !== null) {
            this.xhr.abort();
            this.xhr = null;
        }

        // perform XHR
        this.xhr = $.get(
            this.url,
            this.lazyLoad.getExtraParams(),
            undefined,
            'json'
        )
        .done(function (data, textStatus, jqXHR) {
            self.data = data.data;
            self.label = data.label;
            if (data.parameter_pk) {
                self.parameterPk = data.parameter_pk;
            }
            if (data.axis_label) {
                self.axisLabel = data.axis_label;
            }
            if (data.axis_label_x) {
                self.axisLabelX = data.axis_label_x;
            }
            if (data.axis_label_y) {
                self.axisLabelY = data.axis_label_y;
            }
            self.needsUpdate = false;
            if (typeof successCallback !== 'undefined') {
                successCallback(self);
            }
        })
        .fail(function (jqXHR, textStatus, errorThrown) {
            if (textStatus !== 'abort') {
                console.error('Error loading data from ' + self.url + ': ' + errorThrown);
            }
        })
        .complete(function () {
            self.xhr = null;
        });
    };

    /* ************************************************************** */
    /* ************************************************************** */
    /* ************************************************************** */
    /* ************************************************************** */
    /* ************************************************************** */

    function LazyLoadBackbone (plot) {
        this.plot = plot;
        this.timeout = null;
        this.datasets = [];
        this.preventUpdates = false;
        this.graphModel = null;
        this.accountModel = null;
        this.scatterplot = false;
    }

    LazyLoadBackbone.prototype.scheduleUpdateAll = function (event) {
        if (this.preventUpdates) {
            return;
        }

        if (this.timeout != null) {
            clearTimeout(this.timeout);
            this.timeout = null;
        }
        this.timeout = setTimeout(this.fetchData.bind(this, true), 750);
    };

    LazyLoadBackbone.prototype.fetchData = function (updateAll) {
        var self = this;

        if (this.preventUpdates) {
            return;
        }

        var xAxis = this.plot.getXAxes()[0];
        var xAxisOptions = xAxis.options;
        if (this.graphModel) {
            xAxisOptions.min = this.graphModel.get('dateRange').get('start');
            xAxisOptions.max = this.graphModel.get('dateRange').get('end');
            xAxis.min = this.graphModel.get('dateRange').get('start');
            xAxis.max = this.graphModel.get('dateRange').get('end');
        }

        for (var i in this.datasets) {
            var dataset = this.datasets[i];
            if (updateAll === true || dataset.needsUpdate) {
                dataset.fetch(function () {
                    self.redraw();
                });
            }
        }
    };

    LazyLoadBackbone.prototype.redraw = function () {
        var parameterPkToYAxis = {};
        var allocatedYAxes = 0;
        var newData = [];

        // Used to determine yaxis per parameter.
        var yAxes = this.plot.getYAxes();

        for (var i in this.datasets) {
            var dataset = this.datasets[i];
            var line = {};

            // Create a new axis only when we discover a new parameter.
            var yaxis = 1;
            if (dataset.parameterPk in parameterPkToYAxis) {
                yaxis = parameterPkToYAxis[dataset.parameterPk];
            }
            else {
                // Allocate a new axis.
                yaxis = allocatedYAxes + 1;
                allocatedYAxes++;
                // Set the axisLabel of the new axis.
                yAxes[yaxis - 1].options.axisLabel = dataset.axisLabel;
                // Add the axis to a map so any following dataset can find
                // a suitable axis for their parameters.
                parameterPkToYAxis[dataset.parameterPk] = yaxis;
            }

            // Append the line.
            line.data = dataset.data;
            line.yaxis = yaxis;
            line.label = dataset.label;
            line.color = dataset.color;
            newData.push(line);
        }

        var xaxis = this.plot.getXAxes()[0];
        if (this.datasets.length == 1 && xaxis.min === null && xaxis.max === null) {
            // First and only line, so use it to determine axis bounds.
            // Note: not implemented yet, unsure if needed
        }

        this.setPreventUpdates(true);
        this.plot.setData(newData);
        this.plot.setupGrid();
        // if (this.datasets.length == 1) {
        // }
        this.plot.draw();
        this.plot.triggerRedrawOverlay();
        this.setPreventUpdates(false);
    };

    LazyLoadBackbone.prototype.setPreventUpdates = function (preventUpdates) {
        this.preventUpdates = preventUpdates;
    };

    LazyLoadBackbone.prototype.bindEvents = function (plot, eventHolder) {
        this.plot.getPlaceholder().on('plotpan.lazyload', this.plotPannedZoomed.bind(this));
        this.plot.getPlaceholder().on('plotzoom.lazyload', this.plotPannedZoomed.bind(this));
    };

    LazyLoadBackbone.prototype.shutdown = function (plot, eventHolder) {
        this.datasets = [];
        this.stopObservingGraphModel();
        this.plot.getPlaceholder().off('plotpan.lazyload');
        this.plot.getPlaceholder().off('plotzoom.lazyload');
    };

    LazyLoadBackbone.prototype.getExtraParams = function () {
        var xaxis = this.plot.getXAxes()[0];
        var minX = xaxis.min;
        var maxX = xaxis.max;
        var params = {
            eventsformat: 'flot'
        };

        var start = null;
        var end = null;
        if (minX !== -1 && maxX !== 1 && minX < maxX) {
            // use current zoom extent
            start = minX;
            end = maxX;
        }
        else if (xaxis.options.min && xaxis.options.max) {
            // no initial zoom extent set, use whatever the graph is initialized with
            start = xaxis.options.min;
            end = xaxis.options.max;
        }

        // convert back to Date objects in case of timestamps
        if (typeof start === 'number') {
            start = new Date(start);
        }
        if (typeof end === 'number') {
            end = new Date(end);
        }

        // use global start and end, instead of axes viewpoint
        if (this.scatterplot) {
            start = this.graphModel.get('dateRange').get('start');
            end = this.graphModel.get('dateRange').get('end');
        }

        if (start && end) {
            // convert to iso 8601 and append to parameters
            $.extend(params, {
                start: start.toJSON(),
                end: end.toJSON()
            });
        }

        // these are used on the server to determine Douglas-Peucker tolerance
        var width = this.plot.getPlaceholder().width();
        var height = this.plot.getPlaceholder().height();

        if (width && height) {
            // append to parameters
            $.extend(params, {
                width: width,
                height: height
            });
        }

        if (this.scatterplot) {
            var secondDataset = this.datasets[1];
            $.extend(params, {
                combine_with: secondDataset.uuid
            });
        }

        return params;
    };

    LazyLoadBackbone.prototype.addGraphItem = function (model) {
        var timeseries = model.get('timeseries');
        var eventsUrl = timeseries.get('events');
        var color = model.get('color');

        for (var i in this.datasets) {
            var dataset = this.datasets[i];
            if (dataset.url == eventsUrl) {
                // already loaded, do nothing
                return;
            }
        }

        var dataset = new DataSet(this, eventsUrl, color, timeseries.get('uuid'));
        this.datasets.push(dataset);
        this.fetchData();
    };

    LazyLoadBackbone.prototype.bbAddHandler = function (model, collection) {
        this.addGraphItem(model);
    };

    LazyLoadBackbone.prototype.bbRemoveHandler = function (model, collection) {
        var timeseries = model.get('timeseries');

        for (var i in this.datasets) {
            var dataset = this.datasets[i];
            if (dataset.url == timeseries.get('events')) {
                delete this.datasets[i];
            }
        }
        this.redraw();
    };

    LazyLoadBackbone.prototype.bbResetHandler = function (model, collection) {
        this.datasets.length = 0;
        this.redraw();
    };

    LazyLoadBackbone.prototype.observeGraphModel = function (graphModel) {
        var self = this;
        if (this.graphModel != null) {
            throw 'graphModel already defined';
        }
        this.graphModel = graphModel;

        var graphItems = graphModel.get('graphItems');

        // listen for any changes
        graphModel.get('dateRange').on('change:start change:end', this.startEndChanged, this);
        graphItems.on('add', this.bbAddHandler, this);
        graphItems.on('remove', this.bbRemoveHandler, this);
        graphItems.on('reset', this.bbResetHandler, this);

        // load the initial set
        graphItems.each(function (graphItem) {
            self.setPreventUpdates(true);
            self.addGraphItem(graphItem);
            self.setPreventUpdates(false);
        });
        this.fetchData();
    };

    LazyLoadBackbone.prototype.stopObservingGraphModel = function () {
        if (this.graphModel != null) {
            var graphModel = this.graphModel;

            var graphItems = graphModel.get('graphItems');

            // remove all model event handlers
            graphModel.get('dateRange').off('change:start change:end', this.startEndChanged, this);
            graphItems.off('add', this.bbAddHandler, this);
            graphItems.off('remove', this.bbRemoveHandler, this);
            graphItems.off('reset', this.bbResetHandler, this);

            this.graphModel = null;
        }
    };

    LazyLoadBackbone.prototype.startEndChanged = function () {
        this.scheduleUpdateAll();
    };

    LazyLoadBackbone.prototype.plotPannedZoomed = function (event, plot) {
        var xAxis = plot.getXAxes()[0];
        var xAxisOptions = xAxis.options;
        if (xAxisOptions.mode == 'time' && this.graphModel) {
            var start = xAxisOptions.min;
            var end = xAxisOptions.max;
            this.graphModel.get('dateRange').set({
                start: new Date(start),
                end: new Date(end)
            });
        }
    };

    /* ************************************************************** */
    /* ************************************************************** */
    /* ************************************************************** */
    /* ************************************************************** */
    /* ************************************************************** */

    function LazyLoadBackboneScatter (plot) {
        LazyLoadBackbone.apply(this, arguments);
        this.scatterplot = true;
    }
    LazyLoadBackboneScatter.prototype = new LazyLoadBackbone();

    LazyLoadBackboneScatter.prototype.fetchData = function (updateAll) {
        var self = this;

        if (this.preventUpdates) {
            return;
        }

        if (this.datasets.length !== 2) {
            return;
        }

        var firstDataset = this.datasets[0];
        if (updateAll === true || firstDataset.needsUpdate) {
            firstDataset.fetch(function () {
                self.redraw();
            });
        }
    };

    LazyLoadBackboneScatter.prototype.redraw = function () {
        var firstDataset = this.datasets[0];
        if (firstDataset) {
            var line = {
                yaxis: 1,
                data: firstDataset.data
            };
            this.setPreventUpdates(true);
            this.plot.getXAxes()[0].options.axisLabel = firstDataset.axisLabelX;
            this.plot.getYAxes()[0].options.axisLabel = firstDataset.axisLabelY;
            this.plot.setData([line]);
            this.plot.setupGrid();
            this.plot.draw();
            this.plot.triggerRedrawOverlay();
            this.setPreventUpdates(false);
        }
    };

    LazyLoadBackboneScatter.prototype.bindEvents = function (plot, eventHolder) {
    };

    LazyLoadBackboneScatter.prototype.shutdown = function (plot, eventHolder) {
        this.datasets = [];
        this.stopObservingGraphModel();
    };

    /* *************************************************** */

    function init (plot) {
        function processOptions (plot, options) {
            var lazyLoad = null;

            lazyLoad = new LazyLoadBackbone(plot);

            if (options.scatterplot && options.scatterplot.enabled) {
                lazyLoad = new LazyLoadBackboneScatter(plot);
            }
            plot.hooks.bindEvents.push(lazyLoad.bindEvents.bind(lazyLoad));
            plot.hooks.shutdown.push(lazyLoad.shutdown.bind(lazyLoad));

            plot.observeGraphModel = lazyLoad.observeGraphModel.bind(lazyLoad);
            plot.setPreventUpdates = lazyLoad.setPreventUpdates.bind(lazyLoad);
        }

        plot.hooks.processOptions.push(processOptions);
    }

    $.plot.plugins.push({
        init: init,
        options: options,
        name: 'lazyload-backbone',
        version: '1.0-nens'
    });
})(jQuery);
<commit_msg>add timer logging<commit_after>(function ($) {
    var options = {
    };

    /* *************************************************** */

    function DataSet (lazyLoad, events_url, color, uuid) {
        this.lazyLoad = lazyLoad;
        this.url = events_url;
        this.color = color;
        this.uuid = uuid;
        this.needsUpdate = true;
        this.data = [];
        this.label = '';
        this.parameterPk = null;
        this.axisLabel = null;
        this.axisLabelX = null;
        this.axisLabelY = null;
        this.xhr = null;
    }

    DataSet.prototype.fetch = function (successCallback) {
        var self = this;
        // cancel previous XHR
        if (this.xhr !== null) {
            this.xhr.abort();
            this.xhr = null;
        }

        // perform XHR
        this.xhr = $.get(
            this.url,
            this.lazyLoad.getExtraParams(),
            undefined,
            'json'
        )
        .done(function (data, textStatus, jqXHR) {
            self.data = data.data;
            self.label = data.label;
            if (data.parameter_pk) {
                self.parameterPk = data.parameter_pk;
            }
            if (data.axis_label) {
                self.axisLabel = data.axis_label;
            }
            if (data.axis_label_x) {
                self.axisLabelX = data.axis_label_x;
            }
            if (data.axis_label_y) {
                self.axisLabelY = data.axis_label_y;
            }
            for (var key in data) {
                if (/^timer/.test(key)) { console.log(data.label + ': ' + key + ' = ' + data[key]); }
            }
            self.needsUpdate = false;
            if (typeof successCallback !== 'undefined') {
                successCallback(self);
            }
        })
        .fail(function (jqXHR, textStatus, errorThrown) {
            if (textStatus !== 'abort') {
                console.error('Error loading data from ' + self.url + ': ' + errorThrown);
            }
        })
        .complete(function () {
            self.xhr = null;
        });
    };

    /* ************************************************************** */
    /* ************************************************************** */
    /* ************************************************************** */
    /* ************************************************************** */
    /* ************************************************************** */

    function LazyLoadBackbone (plot) {
        this.plot = plot;
        this.timeout = null;
        this.datasets = [];
        this.preventUpdates = false;
        this.graphModel = null;
        this.accountModel = null;
        this.scatterplot = false;
    }

    LazyLoadBackbone.prototype.scheduleUpdateAll = function (event) {
        if (this.preventUpdates) {
            return;
        }

        if (this.timeout != null) {
            clearTimeout(this.timeout);
            this.timeout = null;
        }
        this.timeout = setTimeout(this.fetchData.bind(this, true), 750);
    };

    LazyLoadBackbone.prototype.fetchData = function (updateAll) {
        var self = this;

        if (this.preventUpdates) {
            return;
        }

        var xAxis = this.plot.getXAxes()[0];
        var xAxisOptions = xAxis.options;
        if (this.graphModel) {
            xAxisOptions.min = this.graphModel.get('dateRange').get('start');
            xAxisOptions.max = this.graphModel.get('dateRange').get('end');
            xAxis.min = this.graphModel.get('dateRange').get('start');
            xAxis.max = this.graphModel.get('dateRange').get('end');
        }

        for (var i in this.datasets) {
            var dataset = this.datasets[i];
            if (updateAll === true || dataset.needsUpdate) {
                dataset.fetch(function () {
                    self.redraw();
                });
            }
        }
    };

    LazyLoadBackbone.prototype.redraw = function () {
        var parameterPkToYAxis = {};
        var allocatedYAxes = 0;
        var newData = [];

        // Used to determine yaxis per parameter.
        var yAxes = this.plot.getYAxes();

        for (var i in this.datasets) {
            var dataset = this.datasets[i];
            var line = {};

            // Create a new axis only when we discover a new parameter.
            var yaxis = 1;
            if (dataset.parameterPk in parameterPkToYAxis) {
                yaxis = parameterPkToYAxis[dataset.parameterPk];
            }
            else {
                // Allocate a new axis.
                yaxis = allocatedYAxes + 1;
                allocatedYAxes++;
                // Set the axisLabel of the new axis.
                yAxes[yaxis - 1].options.axisLabel = dataset.axisLabel;
                // Add the axis to a map so any following dataset can find
                // a suitable axis for their parameters.
                parameterPkToYAxis[dataset.parameterPk] = yaxis;
            }

            // Append the line.
            line.data = dataset.data;
            line.yaxis = yaxis;
            line.label = dataset.label;
            line.color = dataset.color;
            newData.push(line);
        }

        var xaxis = this.plot.getXAxes()[0];
        if (this.datasets.length == 1 && xaxis.min === null && xaxis.max === null) {
            // First and only line, so use it to determine axis bounds.
            // Note: not implemented yet, unsure if needed
        }

        this.setPreventUpdates(true);
        this.plot.setData(newData);
        this.plot.setupGrid();
        // if (this.datasets.length == 1) {
        // }
        this.plot.draw();
        this.plot.triggerRedrawOverlay();
        this.setPreventUpdates(false);
    };

    LazyLoadBackbone.prototype.setPreventUpdates = function (preventUpdates) {
        this.preventUpdates = preventUpdates;
    };

    LazyLoadBackbone.prototype.bindEvents = function (plot, eventHolder) {
        this.plot.getPlaceholder().on('plotpan.lazyload', this.plotPannedZoomed.bind(this));
        this.plot.getPlaceholder().on('plotzoom.lazyload', this.plotPannedZoomed.bind(this));
    };

    LazyLoadBackbone.prototype.shutdown = function (plot, eventHolder) {
        this.datasets = [];
        this.stopObservingGraphModel();
        this.plot.getPlaceholder().off('plotpan.lazyload');
        this.plot.getPlaceholder().off('plotzoom.lazyload');
    };

    LazyLoadBackbone.prototype.getExtraParams = function () {
        var xaxis = this.plot.getXAxes()[0];
        var minX = xaxis.min;
        var maxX = xaxis.max;
        var params = {
            eventsformat: 'flot'
        };

        var start = null;
        var end = null;
        if (minX !== -1 && maxX !== 1 && minX < maxX) {
            // use current zoom extent
            start = minX;
            end = maxX;
        }
        else if (xaxis.options.min && xaxis.options.max) {
            // no initial zoom extent set, use whatever the graph is initialized with
            start = xaxis.options.min;
            end = xaxis.options.max;
        }

        // convert back to Date objects in case of timestamps
        if (typeof start === 'number') {
            start = new Date(start);
        }
        if (typeof end === 'number') {
            end = new Date(end);
        }

        // use global start and end, instead of axes viewpoint
        if (this.scatterplot) {
            start = this.graphModel.get('dateRange').get('start');
            end = this.graphModel.get('dateRange').get('end');
        }

        if (start && end) {
            // convert to iso 8601 and append to parameters
            $.extend(params, {
                start: start.toJSON(),
                end: end.toJSON()
            });
        }

        // these are used on the server to determine Douglas-Peucker tolerance
        var width = this.plot.getPlaceholder().width();
        var height = this.plot.getPlaceholder().height();

        if (width && height) {
            // append to parameters
            $.extend(params, {
                width: width,
                height: height
            });
        }

        if (this.scatterplot) {
            var secondDataset = this.datasets[1];
            $.extend(params, {
                combine_with: secondDataset.uuid
            });
        }

        return params;
    };

    LazyLoadBackbone.prototype.addGraphItem = function (model) {
        var timeseries = model.get('timeseries');
        var eventsUrl = timeseries.get('events');
        var color = model.get('color');

        for (var i in this.datasets) {
            var dataset = this.datasets[i];
            if (dataset.url == eventsUrl) {
                // already loaded, do nothing
                return;
            }
        }

        var dataset = new DataSet(this, eventsUrl, color, timeseries.get('uuid'));
        this.datasets.push(dataset);
        this.fetchData();
    };

    LazyLoadBackbone.prototype.bbAddHandler = function (model, collection) {
        this.addGraphItem(model);
    };

    LazyLoadBackbone.prototype.bbRemoveHandler = function (model, collection) {
        var timeseries = model.get('timeseries');

        for (var i in this.datasets) {
            var dataset = this.datasets[i];
            if (dataset.url == timeseries.get('events')) {
                delete this.datasets[i];
            }
        }
        this.redraw();
    };

    LazyLoadBackbone.prototype.bbResetHandler = function (model, collection) {
        this.datasets.length = 0;
        this.redraw();
    };

    LazyLoadBackbone.prototype.observeGraphModel = function (graphModel) {
        var self = this;
        if (this.graphModel != null) {
            throw 'graphModel already defined';
        }
        this.graphModel = graphModel;

        var graphItems = graphModel.get('graphItems');

        // listen for any changes
        graphModel.get('dateRange').on('change:start change:end', this.startEndChanged, this);
        graphItems.on('add', this.bbAddHandler, this);
        graphItems.on('remove', this.bbRemoveHandler, this);
        graphItems.on('reset', this.bbResetHandler, this);

        // load the initial set
        graphItems.each(function (graphItem) {
            self.setPreventUpdates(true);
            self.addGraphItem(graphItem);
            self.setPreventUpdates(false);
        });
        this.fetchData();
    };

    LazyLoadBackbone.prototype.stopObservingGraphModel = function () {
        if (this.graphModel != null) {
            var graphModel = this.graphModel;

            var graphItems = graphModel.get('graphItems');

            // remove all model event handlers
            graphModel.get('dateRange').off('change:start change:end', this.startEndChanged, this);
            graphItems.off('add', this.bbAddHandler, this);
            graphItems.off('remove', this.bbRemoveHandler, this);
            graphItems.off('reset', this.bbResetHandler, this);

            this.graphModel = null;
        }
    };

    LazyLoadBackbone.prototype.startEndChanged = function () {
        this.scheduleUpdateAll();
    };

    LazyLoadBackbone.prototype.plotPannedZoomed = function (event, plot) {
        var xAxis = plot.getXAxes()[0];
        var xAxisOptions = xAxis.options;
        if (xAxisOptions.mode == 'time' && this.graphModel) {
            var start = xAxisOptions.min;
            var end = xAxisOptions.max;
            this.graphModel.get('dateRange').set({
                start: new Date(start),
                end: new Date(end)
            });
        }
    };

    /* ************************************************************** */
    /* ************************************************************** */
    /* ************************************************************** */
    /* ************************************************************** */
    /* ************************************************************** */

    function LazyLoadBackboneScatter (plot) {
        LazyLoadBackbone.apply(this, arguments);
        this.scatterplot = true;
    }
    LazyLoadBackboneScatter.prototype = new LazyLoadBackbone();

    LazyLoadBackboneScatter.prototype.fetchData = function (updateAll) {
        var self = this;

        if (this.preventUpdates) {
            return;
        }

        if (this.datasets.length !== 2) {
            return;
        }

        var firstDataset = this.datasets[0];
        if (updateAll === true || firstDataset.needsUpdate) {
            firstDataset.fetch(function () {
                self.redraw();
            });
        }
    };

    LazyLoadBackboneScatter.prototype.redraw = function () {
        var firstDataset = this.datasets[0];
        if (firstDataset) {
            var line = {
                yaxis: 1,
                data: firstDataset.data
            };
            this.setPreventUpdates(true);
            this.plot.getXAxes()[0].options.axisLabel = firstDataset.axisLabelX;
            this.plot.getYAxes()[0].options.axisLabel = firstDataset.axisLabelY;
            this.plot.setData([line]);
            this.plot.setupGrid();
            this.plot.draw();
            this.plot.triggerRedrawOverlay();
            this.setPreventUpdates(false);
        }
    };

    LazyLoadBackboneScatter.prototype.bindEvents = function (plot, eventHolder) {
    };

    LazyLoadBackboneScatter.prototype.shutdown = function (plot, eventHolder) {
        this.datasets = [];
        this.stopObservingGraphModel();
    };

    /* *************************************************** */

    function init (plot) {
        function processOptions (plot, options) {
            var lazyLoad = null;

            lazyLoad = new LazyLoadBackbone(plot);

            if (options.scatterplot && options.scatterplot.enabled) {
                lazyLoad = new LazyLoadBackboneScatter(plot);
            }
            plot.hooks.bindEvents.push(lazyLoad.bindEvents.bind(lazyLoad));
            plot.hooks.shutdown.push(lazyLoad.shutdown.bind(lazyLoad));

            plot.observeGraphModel = lazyLoad.observeGraphModel.bind(lazyLoad);
            plot.setPreventUpdates = lazyLoad.setPreventUpdates.bind(lazyLoad);
        }

        plot.hooks.processOptions.push(processOptions);
    }

    $.plot.plugins.push({
        init: init,
        options: options,
        name: 'lazyload-backbone',
        version: '1.0-nens'
    });
})(jQuery);
<|endoftext|>


<commit_before>#! /usr/bin/env node
"use strict";
const fs = require('fs');
const exec = require('child_process').execSync;

const dlPath = '/home/toughluck/Music';

let first = true;
let buffers = [];
process.stdin.on('readable', () => {
  let chunk = process.stdin.read();
  if (chunk !== null) {
    if (first) {
      chunk = chunk.slice(4);
      first = false;
    }
    buffers.push(chunk);
  }
});
process.stdin.on('end', () => {
  const res = Buffer.concat(buffers);
  const url = JSON.parse(res).url;
  const outTemplate = `${dlPath}/%(title)s.%(ext)s`;
  const cmdOptions = {
    shell: '/bin/bash'
  };
  const cmd = `youtube-dl --extract-audio --audio-format mp3 -o \"${outTemplate}\" ${url} &> d.txt`;
  // const args = ['--extract-audio', '--audio-format', 'mp3', '-o', outTemplate, url];
  // const cmd2 = 'youtube-dl';

  process.stderr.write('Suck it chrome');
  process.stderr.write('stderr doesnt stop host');

  exec(cmd, cmdOptions, (err, stdout, stderr) => {
    if (err) throw err;
    process.stderr.write(stdout);
    process.stderr.write(stderr);
  });

  // child.on('error', (err) => {
  //   console.log(err);
  // });
  // child.stderr.on('data', (data) => {
  //   console.log(data);
  // });
  // child.stdout.on('data', (data) => {
  //   console.log(data);
  // });

  process.stderr.write('\n Okay....');
});
<commit_msg>fixed host script to consistently work with weird stdin oddities<commit_after>#! /usr/bin/env node
"use strict";

const exec = require('child_process').execSync;

const dlPath = '/home/toughluck/Music';

process.stderr.write('Suck it chrome');

let first = true;
let buffers = [];
process.stdin.on('readable', () => {
  process.stderr.write('data received');
  let chunk;
  process.stderr.write(typeof chunk);
  while (null !== (chunk = process.stdin.read())) {
    process.stderr.write('reading chunk');
    if (first) {
      chunk = chunk.slice(4);
      first = false;
    }
    buffers.push(chunk);
  }
  const res = Buffer.concat(buffers);
  const url = JSON.parse(res).url;
  const outTemplate = `${dlPath}/%(title)s.%(ext)s`;
  const cmdOptions = {
    shell: '/bin/bash'
  };
  const cmd =`youtube-dl --extract-audio --audio-format mp3 -o \"${outTemplate}\" \"${url}\" &> \"/dev/null\"`;

  exec(cmd, cmdOptions, (err, stdout, stderr) => {
    if (err) throw err;
  });
});
<|endoftext|>


<commit_before>import Ember from 'ember';

export default Ember.Component.extend({
  didInsertElement() {
    this._super();
    Ember.run.scheduleOnce('afterRender', this, function() {
      /*
       * adding floatThead jQuery plugin to poll table
       * https://mkoryak.github.io/floatThead/
       *
       * top:
       *   Offset from the top of the `window` where the floating header will
       *   'stick' when scrolling down
       * Since we are adding a browser horizontal scrollbar on top, scrollingTop
       * has to be set to height of horizontal scrollbar which depends on
       * used browser
       */
      Ember.$('.user-selections-table').floatThead({
        top: this.getScrollbarHeight
      });

      /*
       * fix width calculation error caused by bootstrap glyphicon on webkit
       */
      Ember.$('.glyphicon').css('width', '14px');

      /*
       * scrollbar on top of table
       */
      const topScrollbarInner = Ember.$('

')
              .css('width', Ember.$('.user-selections-table').width())
              .css('height', '1px');
      const topScrollbarOuter = Ember.$('

')
              .addClass('top-scrollbar')
              .css('width', '100%')
              .css('overflow-x', 'scroll')
              .css('overflow-y', 'hidden')
              .css('position', 'relative')
              .css('z-index', '1002');
      Ember.$('.table-scroll').before(
        topScrollbarOuter.append(topScrollbarInner)
      );

      /*
       * scrollbar on top of table for thead
       */
      const topScrollbarInnerThead = Ember.$('

')
              .css('width', Ember.$('.user-selections-table').width())
              .css('height', '1px');
      const topScrollbarOuterThead = Ember.$('

')
              .addClass('top-scrollbar-floatThead')
              .css('width', Ember.$('.table-scroll').outerWidth())
              .css('overflow-x', 'scroll')
              .css('overflow-y', 'hidden')
              .css('position', 'fixed')
              .css('top', '-1px')
              .css('z-index', '1002')
              .css('margin-left', `${(Ember.$('.table-scroll').outerWidth() - Ember.$('.table-scroll').width()) / 2 * (-1)}px`)
              .css('margin-right', `${(Ember.$('.table-scroll').outerWidth() - Ember.$('.table-scroll').width()) / 2 * (-1)}px`);
      Ember.$('.table-scroll').prepend(
        topScrollbarOuterThead.append(topScrollbarInnerThead).hide()
      );

      // add listener to resize scrollbars if window get resized
      Ember.$(window).resize(this.resizeScrollbars);

      /*
       * bind scroll event on all scrollbars
       */
      Ember.$('.table-scroll').scroll(function() {
        Ember.$('.top-scrollbar').scrollLeft(Ember.$('.table-scroll').scrollLeft());
        Ember.$('.top-scrollbar-floatThead').scrollLeft(Ember.$('.table-scroll').scrollLeft());
      });
      Ember.$('.top-scrollbar').scroll(function() {
        Ember.$('.table-scroll').scrollLeft(Ember.$('.top-scrollbar').scrollLeft());
        Ember.$('.top-scrollbar-floatThead').scrollLeft(Ember.$('.top-scrollbar').scrollLeft());
      });
      Ember.$('.top-scrollbar-floatThead').scroll(function() {
        Ember.$('.table-scroll').scrollLeft(Ember.$('.top-scrollbar-floatThead').scrollLeft());
        Ember.$('.top-scrollbar').scrollLeft(Ember.$('.top-scrollbar-floatThead').scrollLeft());
      });

      /*
       * show inner scrollbar only, if header is fixed
       */
      Ember.$(window).scroll(Ember.$.proxy(this.updateScrollbarTopVisibility, this));
    });
  },

  /*
   * calculates horizontal scrollbar height depending on current browser
   */
  getScrollbarHeight() {
    const wideScrollWtml = Ember.$('
').attr('id', 'wide_scroll_div_one').css({
      'width': 50,
      'height': 50,
      'overflow-y': 'scroll',
      'position': 'absolute',
      'top': -200,
      'left': -200
    }).append(
      Ember.$('
').attr('id', 'wide_scroll_div_two').css({
        'height': '100%',
        'width': 100
      })
    );
    Ember.$('body').append(wideScrollWtml); // Append our div and add the hmtl to your document for calculations
    const scrollW1 = Ember.$('#wide_scroll_div_one').height(); // Getting the width of the surrounding(parent) div - we already know it is 50px since we styled it but just to make sure.
    const scrollW2 = Ember.$('#wide_scroll_div_two').innerHeight(); // Find the inner width of the inner(child) div.
    const scrollBarWidth = scrollW1 - scrollW2; // subtract the difference
    Ember.$('#wide_scroll_div_one').remove(); // remove the html from your document
    return scrollBarWidth;
  },

  /*
   * show / hide top scrollbar depending on window position
   * used as event callback when window is scrolled
   */
  updateScrollbarTopVisibility() {
    const windowTop = Ember.$(window).scrollTop();
    const tableTop = Ember.$('.table-scroll table').offset().top;
    if (windowTop >= tableTop - this.getScrollbarHeight()) {
      Ember.$('.top-scrollbar-floatThead').show();

      // update scroll position
      Ember.$('.top-scrollbar-floatThead').scrollLeft(Ember.$('.table-scroll').scrollLeft());
    } else {
      Ember.$('.top-scrollbar-floatThead').hide();
    }
  },

  /*
   * resize scrollbars
   * used as event callback when window is resized
   */
  resizeScrollbars() {
    Ember.$('.top-scrollbar div').css('width', Ember.$('.user-selections-table').width());
    Ember.$('.top-scrollbar-floatThead').css('width', Ember.$('.table-scroll').outerWidth());
    Ember.$('.top-scrollbar-floatThead div').css('width', Ember.$('.user-selections-table').width());
  },

  /*
   * resize scrollbars if document height might be changed
   * and therefore scrollbars might be added
   */
  triggerResizeScrollbars: Ember.observer('controller.isEvaluable', 'controller.model.users.[]', function() {
    Ember.run.next(() => {
      this.resizeScrollbars();
    });
  }),

  /*
   * clean up
   * especially remove event listeners
   */
  willDestroyElement() {
    Ember.$(window).off('resize', this.resizeScrollbars);
    Ember.$(window).off('scroll', this.updateScrollbarTopVisibility);
  }
});
<commit_msg>Fix: participation table header should not overflow container<commit_after>import Ember from 'ember';

export default Ember.Component.extend({
  didInsertElement() {
    this._super();
    Ember.run.scheduleOnce('afterRender', this, function() {
      /*
       * adding floatThead jQuery plugin to poll table
       * https://mkoryak.github.io/floatThead/
       *
       * top:
       *   Offset from the top of the `window` where the floating header will
       *   'stick' when scrolling down
       * Since we are adding a browser horizontal scrollbar on top, scrollingTop
       * has to be set to height of horizontal scrollbar which depends on
       * used browser
       */
      Ember.$('.user-selections-table').floatThead({
        position: 'absolute',
        top: this.getScrollbarHeight
      });

      /*
       * fix width calculation error caused by bootstrap glyphicon on webkit
       */
      Ember.$('.glyphicon').css('width', '14px');

      /*
       * scrollbar on top of table
       */
      const topScrollbarInner = Ember.$('

')
              .css('width', Ember.$('.user-selections-table').width())
              .css('height', '1px');
      const topScrollbarOuter = Ember.$('

')
              .addClass('top-scrollbar')
              .css('width', '100%')
              .css('overflow-x', 'scroll')
              .css('overflow-y', 'hidden')
              .css('position', 'relative')
              .css('z-index', '1002');
      Ember.$('.table-scroll').before(
        topScrollbarOuter.append(topScrollbarInner)
      );

      /*
       * scrollbar on top of table for thead
       */
      const topScrollbarInnerThead = Ember.$('

')
              .css('width', Ember.$('.user-selections-table').width())
              .css('height', '1px');
      const topScrollbarOuterThead = Ember.$('

')
              .addClass('top-scrollbar-floatThead')
              .css('width', Ember.$('.table-scroll').outerWidth())
              .css('overflow-x', 'scroll')
              .css('overflow-y', 'hidden')
              .css('position', 'fixed')
              .css('top', '-1px')
              .css('z-index', '1002')
              .css('margin-left', `${(Ember.$('.table-scroll').outerWidth() - Ember.$('.table-scroll').width()) / 2 * (-1)}px`)
              .css('margin-right', `${(Ember.$('.table-scroll').outerWidth() - Ember.$('.table-scroll').width()) / 2 * (-1)}px`);
      Ember.$('.table-scroll').prepend(
        topScrollbarOuterThead.append(topScrollbarInnerThead).hide()
      );

      // add listener to resize scrollbars if window get resized
      Ember.$(window).resize(this.resizeScrollbars);

      /*
       * bind scroll event on all scrollbars
       */
      Ember.$('.table-scroll').scroll(function() {
        Ember.$('.top-scrollbar').scrollLeft(Ember.$('.table-scroll').scrollLeft());
        Ember.$('.top-scrollbar-floatThead').scrollLeft(Ember.$('.table-scroll').scrollLeft());
      });
      Ember.$('.top-scrollbar').scroll(function() {
        Ember.$('.table-scroll').scrollLeft(Ember.$('.top-scrollbar').scrollLeft());
        Ember.$('.top-scrollbar-floatThead').scrollLeft(Ember.$('.top-scrollbar').scrollLeft());
      });
      Ember.$('.top-scrollbar-floatThead').scroll(function() {
        Ember.$('.table-scroll').scrollLeft(Ember.$('.top-scrollbar-floatThead').scrollLeft());
        Ember.$('.top-scrollbar').scrollLeft(Ember.$('.top-scrollbar-floatThead').scrollLeft());
      });

      /*
       * show inner scrollbar only, if header is fixed
       */
      Ember.$(window).scroll(Ember.$.proxy(this.updateScrollbarTopVisibility, this));
    });
  },

  /*
   * calculates horizontal scrollbar height depending on current browser
   */
  getScrollbarHeight() {
    const wideScrollWtml = Ember.$('
').attr('id', 'wide_scroll_div_one').css({
      'width': 50,
      'height': 50,
      'overflow-y': 'scroll',
      'position': 'absolute',
      'top': -200,
      'left': -200
    }).append(
      Ember.$('
').attr('id', 'wide_scroll_div_two').css({
        'height': '100%',
        'width': 100
      })
    );
    Ember.$('body').append(wideScrollWtml); // Append our div and add the hmtl to your document for calculations
    const scrollW1 = Ember.$('#wide_scroll_div_one').height(); // Getting the width of the surrounding(parent) div - we already know it is 50px since we styled it but just to make sure.
    const scrollW2 = Ember.$('#wide_scroll_div_two').innerHeight(); // Find the inner width of the inner(child) div.
    const scrollBarWidth = scrollW1 - scrollW2; // subtract the difference
    Ember.$('#wide_scroll_div_one').remove(); // remove the html from your document
    return scrollBarWidth;
  },

  /*
   * show / hide top scrollbar depending on window position
   * used as event callback when window is scrolled
   */
  updateScrollbarTopVisibility() {
    const windowTop = Ember.$(window).scrollTop();
    const tableTop = Ember.$('.table-scroll table').offset().top;
    if (windowTop >= tableTop - this.getScrollbarHeight()) {
      Ember.$('.top-scrollbar-floatThead').show();

      // update scroll position
      Ember.$('.top-scrollbar-floatThead').scrollLeft(Ember.$('.table-scroll').scrollLeft());
    } else {
      Ember.$('.top-scrollbar-floatThead').hide();
    }
  },

  /*
   * resize scrollbars
   * used as event callback when window is resized
   */
  resizeScrollbars() {
    Ember.$('.top-scrollbar div').css('width', Ember.$('.user-selections-table').width());
    Ember.$('.top-scrollbar-floatThead').css('width', Ember.$('.table-scroll').outerWidth());
    Ember.$('.top-scrollbar-floatThead div').css('width', Ember.$('.user-selections-table').width());
  },

  /*
   * resize scrollbars if document height might be changed
   * and therefore scrollbars might be added
   */
  triggerResizeScrollbars: Ember.observer('controller.isEvaluable', 'controller.model.users.[]', function() {
    Ember.run.next(() => {
      this.resizeScrollbars();
    });
  }),

  /*
   * clean up
   * especially remove event listeners
   */
  willDestroyElement() {
    Ember.$(window).off('resize', this.resizeScrollbars);
    Ember.$(window).off('scroll', this.updateScrollbarTopVisibility);
  }
});
<|endoftext|>


<commit_before>'use strict';

function requestHeaderParserHandler (req, res) {

			var data = {
				ipaddress: null,
				language: null,
				software: null
			};

			var re;

			data.ipaddress = req.ip; //express property, depends on "trust proxy" setting

			if (req.headers) {

				if (req.headers["accept-language"]){
					//refrence https://tools.ietf.org/html/rfc7231#section-5.3.1 to remove the qvalues
					re = /;(?:q=)(?:0(?:.\d\d?\d?)?|1(?:.00?0?)?)/;
					var lang = req.headers["accept-language"].replace(re, "");
					data.language = lang;
				}

				if(req.headers["user-agent"]){
					//search for (os) and extract os, else just copy user agent
					var userAgent = req.headers["user-agent"];
					re = /\((.*)\)/;
					var result = re.exec(userAgent);
					data.software = result ? result[1] : userAgent;
				}
			}

			res.json(data);
		}

module.exports = requestHeaderParserHandler;<commit_msg>made RegExp global to mach all qvalues in accept-language<commit_after>'use strict';

function requestHeaderParserHandler (req, res) {

			var data = {
				ipaddress: null,
				language: null,
				software: null
			};

			var re;

			data.ipaddress = req.ip; //express property, depends on "trust proxy" setting

			if (req.headers) {

				if (req.headers["accept-language"]){
					//refrence https://tools.ietf.org/html/rfc7231#section-5.3.1 to remove the qvalues
					re = /;(?:q=)(?:0(?:.\d\d?\d?)?|1(?:.00?0?)?)/g;
					var lang = req.headers["accept-language"].replace(re, "");
					data.language = lang;
				}

				if(req.headers["user-agent"]){
					//search for (os) and extract os, else just copy user agent
					var userAgent = req.headers["user-agent"];
					re = /\((.*)\)/;
					var result = re.exec(userAgent);
					data.software = result ? result[1] : userAgent;
				}
			}

			res.json(data);
		}

module.exports = requestHeaderParserHandler;<|endoftext|>