<commit_before>import path from 'path';

import webpack from 'webpack';

import { skipFalsy, strToBool, ConfigWebpackPlugin } from './utils';
import RulesGenerator from './rules';

const { env } = process;
const opts = {
  dstDir: env.DST_DIR || path.join(__dirname, '..', '_dist'),
  dev: strToBool(env.DEV, true),
  livereload: strToBool(env.LIVERELOAD, false),
  hot: process.argv.indexOf('--hot') !== -1,
  hash: strToBool(env.HASH, false),
  port: env.PORT || '8080',
};

const rules = new RulesGenerator(opts);

const filename = opts.hash ? '[name]-[chunkhash]' : '[name]-dev';

const baseConfig = {
  mode: opts.dev ? 'development' : 'production',
  devtool: opts.dev ? 'inline-source-map' : 'source-map',
  output: {
    path: opts.dstDir,
    // filename: `${filename}.js`,
    filename: (chunkData) => {
      if (chunkData.chunk.name === 'bookmarklet') {
        return 'assets/js/bookmarklet-popup.js';
      }
      return `${filename}.js`;
    },
    sourceMapFilename: `[file].map`,
    devtoolModuleFilenameTemplate: '/[absolute-resource-path]',
    // Set correct globalObject for web workers:
    // see https://github.com/webpack-contrib/worker-loader/issues/166
    globalObject: 'this',
  },
  resolve: { extensions: ['.js', '.json', '.jsx'] },
  plugins: skipFalsy([
    new webpack.LoaderOptionsPlugin({ debug: opts.dev }),
    new webpack.NormalModuleReplacementPlugin(/\/iconv-loader$/, 'node-noop'),
    new webpack.DefinePlugin({
      'process.env.NODE_ENV': opts.dev ? '"development"' : '"production"',
    }),
    new ConfigWebpackPlugin(),
  ]),
};

export { baseConfig, opts, rules };
<commit_msg>Add hashes to async-chunk names<commit_after>import path from 'path';

import webpack from 'webpack';

import { skipFalsy, strToBool, ConfigWebpackPlugin } from './utils';
import RulesGenerator from './rules';

const { env } = process;
const opts = {
  dstDir: env.DST_DIR || path.join(__dirname, '..', '_dist'),
  dev: strToBool(env.DEV, true),
  livereload: strToBool(env.LIVERELOAD, false),
  hot: process.argv.indexOf('--hot') !== -1,
  hash: strToBool(env.HASH, false),
  port: env.PORT || '8080',
};

const rules = new RulesGenerator(opts);

const filename = opts.hash ? '[name]-[chunkhash]' : '[name]-dev';

const baseConfig = {
  mode: opts.dev ? 'development' : 'production',
  devtool: opts.dev ? 'inline-source-map' : 'source-map',
  output: {
    path: opts.dstDir,
    chunkFilename: `${filename}.js`, // used by non-entry chunks
    // filename: `${filename}.js`,
    filename: (chunkData) => {
      if (chunkData.chunk.name === 'bookmarklet') {
        return 'assets/js/bookmarklet-popup.js';
      }
      return `${filename}.js`;
    },
    sourceMapFilename: `[file].map`,
    devtoolModuleFilenameTemplate: '/[absolute-resource-path]',
    // Set correct globalObject for web workers:
    // see https://github.com/webpack-contrib/worker-loader/issues/166
    globalObject: 'this',
  },
  resolve: { extensions: ['.js', '.json', '.jsx'] },
  plugins: skipFalsy([
    new webpack.LoaderOptionsPlugin({ debug: opts.dev }),
    new webpack.NormalModuleReplacementPlugin(/\/iconv-loader$/, 'node-noop'),
    new webpack.DefinePlugin({
      'process.env.NODE_ENV': opts.dev ? '"development"' : '"production"',
    }),
    new ConfigWebpackPlugin(),
  ]),
};

export { baseConfig, opts, rules };
<|endoftext|>


<commit_before>/**
 * https://github.com/timonreinhard/wemo-client
 */
var Wemo = require('wemo-client');
var wemo = new Wemo();
var client;
var endDevices = [];

exports.init = function() {
    wemo.discover(function(deviceInfo) {
        client = wemo.client(deviceInfo);
        exports.reload();
    });
};

exports.reload = function(cb) {
    client.getEndDevices(function(err, data){
        endDevices = data;
        if(cb) {
            cb(data);
        }
    });
}

exports.endDevices = function() {
    return endDevices;
}

exports.toggle = function(deviceId, action, cb) {
    var actionId = action === "off" ? 0 : 1;

    client.setDeviceStatus(deviceId, 10006, actionId, function(err, resp) {
        exports.reload(function(devices) {
            var device = filterDevice(deviceId, devices);
            cb(device);
        });
    });
};

exports.dim = function(deviceId, value, time) {
    client.setDeviceStatus(deviceId, 10008, value + ":" + time, function(err, resp) {
        // TODO
    });
};

function filterDevice(deviceId, devices) {
    var device;
    for(var i = 0; i < devices.length; i++) {
        if(deviceId === devices[i].deviceId) {
            device = devices[i];
            break;
        }
    }
    return device;
}
<commit_msg>Dimming accepts callback, returns device<commit_after>/**
 * https://github.com/timonreinhard/wemo-client
 */
var Wemo = require('wemo-client');
var wemo = new Wemo();
var client;
var endDevices = [];

exports.init = function() {
    wemo.discover(function(deviceInfo) {
        client = wemo.client(deviceInfo);
        exports.reload();
    });
};

exports.reload = function(cb) {
    client.getEndDevices(function(err, data){
        endDevices = data;
        if(cb) {
            cb(data);
        }
    });
}

exports.endDevices = function() {
    return endDevices;
}

exports.toggle = function(deviceId, action, cb) {
    var actionId = action === "off" ? 0 : 1;

    client.setDeviceStatus(deviceId, 10006, actionId, function(err, resp) {
        exports.reload(function(devices) {
            var device = filterDevice(deviceId, devices);
            cb(device);
        });
    });
};

exports.dim = function(deviceId, value, time, cb) {
    client.setDeviceStatus(deviceId, 10008, value + ":" + time, function(err, resp) {
        exports.reload(function(devices) {
            var device = filterDevice(deviceId, devices);
            cb(device);
        });
    });
};

function filterDevice(deviceId, devices) {
    var device;
    for(var i = 0; i < devices.length; i++) {
        if(deviceId === devices[i].deviceId) {
            device = devices[i];
            break;
        }
    }
    return device;
}
<|endoftext|>


<commit_before>var DECtoSEX, DEGtoSEC;

// Convert WGS lat/long (° dec) to CH y
function WGStoCHy(lat, lng) {
    var lat_aux, lng_aux;
    // Converts degrees dec to sex
    lat = DECtoSEX(lat);
    lng = DECtoSEX(lng);

    // Converts degrees to seconds (sex)
    lat = DEGtoSEC(lat);
    lng = DEGtoSEC(lng);

    // Axiliary values (% Bern)
    lat_aux = (lat - 169028.66) / 10000;
    lng_aux = (lng - 26782.5) / 10000;

    // Process Y
    return 600072.37 + 211455.93 * lng_aux - 10938.51 * lng_aux * lat_aux -
        0.36 * lng_aux * Math.pow(lat_aux, 2) - 44.54 * Math.pow(lng_aux, 3);
}

// Convert WGS lat/long (° dec) to CH x
function WGStoCHx(lat, lng) {
    var lat_aux, lng_aux;
    // Converts degrees dec to sex
    lat = DECtoSEX(lat);
    lng = DECtoSEX(lng);

    // Converts degrees to seconds (sex)
    lat = DEGtoSEC(lat);
    lng = DEGtoSEC(lng);

    // Axiliary values (% Bern)
    lat_aux = (lat - 169028.66) / 10000;
    lng_aux = (lng - 26782.5) / 10000;

    // Process X
    return 200147.07 + 308807.95 * lat_aux + 3745.25 * Math.pow(lng_aux, 2) +
        76.63 * Math.pow(lat_aux, 2) - 194.56 * Math.pow(lng_aux, 2) *
        lat_aux + 119.79 * Math.pow(lat_aux, 3);
}

// Convert CH y/x to WGS lat
function CHtoWGSlat(y, x) {
    var y_aux, x_aux, lat;
    // Converts militar to civil and  to unit = 1000km
    // Axiliary values (% Bern)
    y_aux = (y - 600000) / 1000000;
    x_aux = (x - 200000) / 1000000;

    // Process lat
    lat = 16.9023892 + 3.238272 * x_aux - 0.270978 * Math.pow(y_aux, 2) -
        0.002528 * Math.pow(x_aux, 2) - 0.0447 * Math.pow(y_aux, 2) * x_aux -
        0.0140 * Math.pow(x_aux, 3);

    // Unit 10000" to 1 " and converts seconds to degrees (dec)
    return lat * 100 / 36;
}

// Convert CH y/x to WGS long
function CHtoWGSlng(y, x) {
    var y_aux, x_aux, lng;
    // Converts militar to civil and  to unit = 1000km
    // Axiliary values (% Bern)
    y_aux = (y - 600000) / 1000000;
    x_aux = (x - 200000) / 1000000;

    // Process long
    lng = 2.6779094 + 4.728982 * y_aux + 0.791484 * y_aux * x_aux + 0.1306 *
        y_aux * Math.pow(x_aux, 2) - 0.0436 * Math.pow(y_aux, 3);

    // Unit 10000" to 1 " and converts seconds to degrees (dec)
    return lng * 100 / 36;
}

// Convert SEX DMS angle to DEC
function SEXtoDEC(angle) {
    var deg, min, sec;
    // Extract DMS
    deg = parseInt(angle, 10);
    min = parseInt((angle - deg) * 100, 10);
    sec = (((angle - deg) * 100) - min) * 100;

    // Result in degrees sex (dd.mmss)
    return deg + (sec / 60 + min) / 60;
}

// Convert DEC angle to SEX DMS
function DECtoSEX(angle) {
    var deg, min, sec;
    // Extract DMS
    deg = parseInt(angle, 10);
    min = parseInt((angle - deg) * 60, 10);
    sec = (((angle - deg) * 60) - min) * 60;

    // Result in degrees sex (dd.mmss)
    return deg + min / 100 + sec / 10000;
}

// Convert Degrees angle to seconds
function DEGtoSEC(angle) {
    var deg, min, sec;
    // Extract DMS
    deg = parseInt(angle, 10);
    min = parseInt((angle - deg) * 100, 10);
    sec = (((angle - deg) * 100) - min) * 100;

    // Result in degrees sex (dd.mmss)
    return sec + min * 60 + deg * 3600;
}<commit_msg>added constructor<commit_after>var DECtoSEX, DEGtoSEC;
// Constructor
var WGS1984_CH1903 = function () {};

// Convert WGS lat/long (° dec) to CH y
function WGStoCHy(lat, lng) {
    var lat_aux, lng_aux;
    // Converts degrees dec to sex
    lat = DECtoSEX(lat);
    lng = DECtoSEX(lng);

    // Converts degrees to seconds (sex)
    lat = DEGtoSEC(lat);
    lng = DEGtoSEC(lng);

    // Axiliary values (% Bern)
    lat_aux = (lat - 169028.66) / 10000;
    lng_aux = (lng - 26782.5) / 10000;

    // Process Y
    return 600072.37 + 211455.93 * lng_aux - 10938.51 * lng_aux * lat_aux -
        0.36 * lng_aux * Math.pow(lat_aux, 2) - 44.54 * Math.pow(lng_aux, 3);
}

// Convert WGS lat/long (° dec) to CH x
function WGStoCHx(lat, lng) {
    var lat_aux, lng_aux;
    // Converts degrees dec to sex
    lat = DECtoSEX(lat);
    lng = DECtoSEX(lng);

    // Converts degrees to seconds (sex)
    lat = DEGtoSEC(lat);
    lng = DEGtoSEC(lng);

    // Axiliary values (% Bern)
    lat_aux = (lat - 169028.66) / 10000;
    lng_aux = (lng - 26782.5) / 10000;

    // Process X
    return 200147.07 + 308807.95 * lat_aux + 3745.25 * Math.pow(lng_aux, 2) +
        76.63 * Math.pow(lat_aux, 2) - 194.56 * Math.pow(lng_aux, 2) *
        lat_aux + 119.79 * Math.pow(lat_aux, 3);
}

// Convert CH y/x to WGS lat
function CHtoWGSlat(y, x) {
    var y_aux, x_aux, lat;
    // Converts militar to civil and  to unit = 1000km
    // Axiliary values (% Bern)
    y_aux = (y - 600000) / 1000000;
    x_aux = (x - 200000) / 1000000;

    // Process lat
    lat = 16.9023892 + 3.238272 * x_aux - 0.270978 * Math.pow(y_aux, 2) -
        0.002528 * Math.pow(x_aux, 2) - 0.0447 * Math.pow(y_aux, 2) * x_aux -
        0.0140 * Math.pow(x_aux, 3);

    // Unit 10000" to 1 " and converts seconds to degrees (dec)
    return lat * 100 / 36;
}

// Convert CH y/x to WGS long
function CHtoWGSlng(y, x) {
    var y_aux, x_aux, lng;
    // Converts militar to civil and  to unit = 1000km
    // Axiliary values (% Bern)
    y_aux = (y - 600000) / 1000000;
    x_aux = (x - 200000) / 1000000;

    // Process long
    lng = 2.6779094 + 4.728982 * y_aux + 0.791484 * y_aux * x_aux + 0.1306 *
        y_aux * Math.pow(x_aux, 2) - 0.0436 * Math.pow(y_aux, 3);

    // Unit 10000" to 1 " and converts seconds to degrees (dec)
    return lng * 100 / 36;
}

// Convert SEX DMS angle to DEC
function SEXtoDEC(angle) {
    var deg, min, sec;
    // Extract DMS
    deg = parseInt(angle, 10);
    min = parseInt((angle - deg) * 100, 10);
    sec = (((angle - deg) * 100) - min) * 100;

    // Result in degrees sex (dd.mmss)
    return deg + (sec / 60 + min) / 60;
}

// Convert DEC angle to SEX DMS
function DECtoSEX(angle) {
    var deg, min, sec;
    // Extract DMS
    deg = parseInt(angle, 10);
    min = parseInt((angle - deg) * 60, 10);
    sec = (((angle - deg) * 60) - min) * 60;

    // Result in degrees sex (dd.mmss)
    return deg + min / 100 + sec / 10000;
}

// Convert Degrees angle to seconds
function DEGtoSEC(angle) {
    var deg, min, sec;
    // Extract DMS
    deg = parseInt(angle, 10);
    min = parseInt((angle - deg) * 100, 10);
    sec = (((angle - deg) * 100) - min) * 100;

    // Result in degrees sex (dd.mmss)
    return sec + min * 60 + deg * 3600;
}<|endoftext|>


<commit_before>
import type { Video } from './model/Video'

/**
 * This interface describes the methods that should be implemented by a class
 * providing the access to the videos in the storage for the purpose of showing
 * a video feed and marking videos as watched.
 */
export interface VideoStorage {
  /**
   * Fetches the videos matching the specified query (if provided), published
   * after the specified since time (inclusive) and before the specified until
   * time (exclusive).
   *
   * Only the videos uploaded to the channels subscribed to by at least one
   * enabled user account or incognito channel subscription, or were added to
   * a playlist subscribed by at least one incognito playlist subscription.
   *
   * @param query The query that the returned videos must match. The query
   *        matching method to use is left to the implementation.
   * @param since The time at or after which the returned videos must have been
   *        published.
   * @param until The time before which the returned videos must have been
   *        published.
   * @param maxVideos The maximum number of matching videos to return.
   * @param includeWatched Flag signalling whether the videos marked as watched
   *        will be included in the returned videos.
   * @return A promise that will resolve to the matched videos. The returned
   *         videos will be sorted from the most lately published to the most
   *         early published.
   */
  getVideos(
    query: ?string,
    since: Date,
    until: Date,
    maxVideos: number,
    includeWatched: boolean
  ): Promise<Array<Video>>;

  /**
   * Marks the provided video as watched in the storage.
   *
   * The method has no effect if the video is already marked as watched in the
   * storage.
   *
   * @param video The video to mark as watched.
   * @return A promise that resolves to the updated video entity re-retrieved
   *         from the storage.
   */
  markAsWatched(video: Video): Promise<Video>;
}
<commit_msg>documentation clarification<commit_after>
import type { Video } from './model/Video'

/**
 * This interface describes the methods that should be implemented by a class
 * providing the access to the videos in the storage for the purpose of showing
 * a video feed and marking videos as watched.
 */
export interface VideoStorage {
  /**
   * Fetches the videos matching the specified query (if provided), published
   * after the specified since time (inclusive) and before the specified until
   * time (exclusive).
   *
   * Only the videos uploaded to the channels subscribed to by at least one
   * enabled user account or incognito channel subscription, or were added to
   * a playlist subscribed by at least one incognito playlist subscription.
   *
   * @param query The query that the returned videos must match. The query
   *        matching method to use is left to the implementation.
   * @param since The time at or after which the returned videos must have been
   *        published.
   * @param until The time before which the returned videos must have been
   *        published.
   * @param maxVideos The maximum number of matching videos to return.
   * @param includeWatched Flag signalling whether the videos marked as watched
   *        will be included in the returned videos.
   * @return A promise that will resolve to the matched videos. The returned
   *         videos will be sorted from the most lately published to the most
   *         early published.
   */
  getVideos(
    query: ?string,
    since: Date,
    until: Date,
    maxVideos: number,
    includeWatched: boolean
  ): Promise<Array<Video>>;

  /**
   * Marks the provided video as watched in the storage.
   *
   * The method has no effect if the video is already marked as watched in the
   * storage.
   *
   * @param video The video to mark as watched.
   * @return A promise that resolves to the updated video entity re-retrieved
   *         from the storage. The promise resolves once the changes has been
   *         persisted.
   */
  markAsWatched(video: Video): Promise<Video>;
}
<|endoftext|>


<commit_before>/**
 * Created by tushar.mathur on 22/01/16.
 */

'use strict'

import PATH from 'path'
import URL from 'url'
import {Observable as O} from 'rx'
import R from 'ramda'
import * as Rx from './RxFP'
import {mux, demux} from 'muxer'
import {MTDError, FILE_SIZE_UNKNOWN} from './Error'

const first = R.nth(0)
const second = R.nth(1)
export const trace = R.curry((msg, value) => {
  console.log(msg, value)
  return value
})
export const demuxFP = R.curry((list, $) => demux($, ...list))
export const demuxFPH = R.curry((list, $) => R.head(demux($, ...list)))
export const BUFFER_SIZE = 512
export const NormalizePath = (path) => PATH.resolve(process.cwd(), path)
export const GenerateFileName = (x) => R.last(URL.parse(x).pathname.split('/')) || Date.now()
export const ResolvePath = R.compose(NormalizePath, GenerateFileName)
export const SplitRange = (totalBytes, range) => {
  const delta = Math.round(totalBytes / range)
  const start = R.times((x) => x * delta, range)
  const end = R.times((x) => (x + 1) * delta - 1, range)
  end[range - 1] = totalBytes
  return R.zip(start, end)
}
export const CreateRangeHeader = ([start, end]) => `bytes=${start}-${end}`
export const SetRangeHeader = ({request, range}) => {
  return R.set(
    R.lensPath(['headers', 'range']),
    CreateRangeHeader(range),
    R.omit(['threads', 'offsets'], request)
  )
}
export const CreateRequestParams = ({meta, index}) => {
  const range = [meta.offsets[index], second(meta.threads[index])]
  return SetRangeHeader({request: meta, range})
}
export const ToBuffer = R.curry((size, str) => {
  var buffer = CreateFilledBuffer(size)
  buffer.write(str)
  return buffer
})
export const CreateFilledBuffer = (size = BUFFER_SIZE, fill = ' ') => {
  const buffer = new Buffer(size)
  buffer.fill(fill)
  return buffer
}
export const MergeDefaultOptions = (options) => R.mergeAll([
  {mtdPath: options.path + '.mtd', range: 3, metaWrite: 300},
  options
])

// TODO: Use R.lens instead
export const GetOffset = R.curry((meta, index) => meta.offsets[index])
export const GetThread = R.curry((meta, index) => meta.threads[index])
export const GetThreadStart = R.curryN(2, R.compose(R.nth(0), GetThread))
export const GetThreadEnd = R.curryN(2, R.compose(R.nth(1), GetThread))
export const GetThreadCount = R.compose(R.length, R.prop('threads'))
export const TimesCount = R.times(R.identity)

/*
 * STREAM BASED
 */
export const GetBufferWriteOffset = ({buffer$, initialOffset}) => {
  const accumulator = ([_buffer, _offset], buffer) => [buffer, _buffer.length + _offset]
  return buffer$.scan(accumulator, [{length: 0}, initialOffset])
}
export const SetBufferParams = ({buffer$, index, meta}) => {
  const initialOffset = GetOffset(meta, index)
  const addParams = R.compose(Rx.map(R.append(index)), GetBufferWriteOffset)
  return addParams({buffer$, initialOffset})
}

export const RequestThread = R.curry((HttpRequest, {meta, index}) => {
  const {response$, data$} = HttpRequest({meta, index})
  const buffer$ = SetBufferParams({buffer$: data$, meta, index})
  return mux({buffer$, response$})
})
export const ToJSON$ = source$ => source$.map(JSON.stringify.bind(JSON))
export const ToBuffer$ = source$ => source$.map(ToBuffer(BUFFER_SIZE))
export const JSToBuffer$ = R.compose(ToBuffer$, ToJSON$)
export const BufferToJS$ = buffer$ => buffer$.map(buffer => JSON.parse(buffer.toString()))
export const RemoteFileSize$ = ({HTTP, options}) => {
  return HTTP.requestHead(options)
    .pluck('headers', 'content-length')
    .map((x) => parseInt(x, 10))
}
export const LocalFileSize$ = ({FILE, fd$}) => FILE.fstat(fd$.map(R.of)).pluck('size')
export const PickFirst = R.map(first)
export const CreateMeta$ = ({size$, options}) => {
  return size$.map((totalBytes) => {
    if (!isFinite(totalBytes)) throw new MTDError(FILE_SIZE_UNKNOWN)
    const threads = SplitRange(totalBytes, options.range)
    return R.merge(options, {totalBytes, threads, offsets: PickFirst(threads)})
  })
}
export const ReadFileAt$ = ({FILE, fd$, position$, size = BUFFER_SIZE}) => {
  const readParams$ = O.combineLatest(position$, fd$)
  const buffer = CreateFilledBuffer(size)
  const toParam = ([position, fd]) => [fd, buffer, 0, buffer.length, position]
  return FILE.read(readParams$.map(toParam))
}
export const MetaPosition$ = ({size$}) => size$.map(R.add(-BUFFER_SIZE))
export const CreateWriteBufferAtParams = ({fd$, buffer$, position$}) => {
  const toParam = ([buffer, fd, position]) => [fd, buffer, 0, buffer.length, position]
  return O.combineLatest(buffer$, fd$, position$.first()).map(toParam)
}
export const CreateWriteBufferParams = ({fd$, buffer$, position$}) => {
  const toParams = ([fd, buffer, position]) => [fd, buffer, 0, buffer.length, position]
  return O.combineLatest(fd$, O.zip(buffer$, position$))
    .map(R.compose(toParams, R.unnest))
}
export const SetMetaOffsets = ({meta$, written$, thread$}) => {
  const offsetLens = thread => R.compose(R.lensProp('offsets'), R.lensIndex(thread))
  const start$ = meta$.map(meta => ({meta, len: 0, thread: 0})).first()
  const source$ = O.merge(
    start$,
    O.zip(written$, thread$)
      .map(R.zipObj(['len', 'thread']))
      .withLatestFrom(meta$.map(R.objOf('meta')))
      .map(R.mergeAll)
  )

  const accumulator = (previous, current) => {
    const thread = current.thread
    const pMeta = previous.meta
    const oldVal = pMeta.offsets[thread]
    const lens = offsetLens(thread)
    const meta = R.set(lens, R.add(oldVal, current.len), pMeta)
    return R.merge(current, {meta})
  }
  return source$
    .scan(accumulator)
    .skip(1)
    .pluck('meta')
}
export const ReadJSON$ = R.compose(BufferToJS$, Rx.map(second), ReadFileAt$)
export const IsOffsetInRange = R.curry((meta, i) => {
  const start = R.lte(GetThreadStart(meta, i))
  const end = R.gt(GetThreadEnd(meta, i))
  const inRange = R.allPass([start, end])
  return inRange(GetOffset(meta, i))
})
export const FlattenMeta$ = Rx.flatMap((meta) => {
  const MergeMeta = R.map(R.compose(R.merge({meta}), R.objOf('index')))
  const IsValid = R.filter(IsOffsetInRange(meta))
  return MergeMeta(IsValid(TimesCount(GetThreadCount(meta))))
})
export const RxFlatMapReplay = R.curryN(2, R.compose(Rx.shareReplay(1), Rx.flatMap))
export const RxThrottleComplete = (window$, $, sh) => {
  const selector = window => O.merge($.throttle(window, sh), $.last())
  return window$.first().flatMap(selector)
}
export const RemoveMETA = ({FILE, meta$, fd$}) => {
  const size$ = meta$.pluck('totalBytes')
  return FILE.truncate(O.combineLatest(fd$, size$).take(1))
}
export const ResetFileName = ({FILE, meta$}) => {
  const params$ = meta$.map(meta => [meta.mtdPath, meta.path]).take(1)
  return FILE.rename(params$)
}
export const IsCompleted$ = ({meta$}) => {
  const equals = R.apply(R.equals)
  const offsetsA = R.prop('offsets')
  const offsetsB = R.compose(R.map(second), R.prop('threads'))
  const isComplete = R.compose(equals, R.ap([offsetsA, offsetsB]), R.of)
  return meta$.map(isComplete).distinctUntilChanged()
}
export const FinalizeDownload = ({FILE, fd$, meta$}) => {
  const metaRemoved$ = RemoveMETA({FILE, meta$, fd$})
  const renamed$ = metaRemoved$.flatMap(() => ResetFileName({FILE, meta$}))
  return mux({metaRemoved$, renamed$})
}
export const DownloadFromMTDFile = ({FILE, HTTP, mtdPath}) => {
  /**
   * Create Request function
   */
  const HttpRequest = R.compose(demuxFPH(['data$', 'response$']), HTTP.request, CreateRequestParams)
  const HttpRequestReplay = RxFlatMapReplay(RequestThread(HttpRequest))

  /**
   * Open file to read+append
   */
  const fd$ = FILE.open(O.just([mtdPath, 'r+']))

  /**
   * Retrieve File size on disk
   */
  const size$ = LocalFileSize$({FILE, fd$})

  /**
   * Retrieve Meta info
   */
  const metaPosition$ = MetaPosition$({size$})
  const meta$ = ReadJSON$({FILE, fd$, position$: metaPosition$})

  /**
   * Flatten meta with only incomplete threads
   */
  const flattenedMeta$ = FlattenMeta$(meta$)

  /**
   * Make a HTTP request for each thread
   */
  const [{response$, buffer$}] = demux(HttpRequestReplay(flattenedMeta$), 'buffer$', 'response$')

  /**
   * Create write params and save buffer+offset to disk
   */
  const saveBuffer$ = FILE.write(CreateWriteBufferParams({
    FILE,
    fd$,
    buffer$: buffer$.map(first),
    position$: buffer$.map(second)
  }))

  /**
   * Update META info
   */
  const nMeta$ = SetMetaOffsets({
    meta$,
    written$: saveBuffer$.map(first),
    thread$: buffer$.map(R.nth(2))
  })

  /**
   * Persist META to disk
   */
  const metaWritten$ = FILE.write(CreateWriteBufferAtParams({
    fd$,
    buffer$: JSToBuffer$(RxThrottleComplete(meta$.pluck('metaWrite'), nMeta$)),
    position$: size$
  }))
  return mux({
    metaWritten$, localFileSize$: size$, fdR$: fd$, metaPosition$,
    meta$: O.merge(nMeta$, meta$),
    response$,
    completed$: IsCompleted$({meta$})
  })
}
export const CreateMTDFile = ({FILE, HTTP, options}) => {
  /**
   * Create a new file
   */
  const fd$ = FILE.open(O.just([options.mtdPath, 'w']))

  /**
   * Retreive file size on remote server
   */
  const size$ = RemoteFileSize$({HTTP, options})

  /**
   * Create initial meta data
   */
  const meta$ = CreateMeta$({options, size$})

  /**
   * Create a new file with meta info appended at the end
   */
  const written$ = FILE.write(CreateWriteBufferAtParams({
    FILE,
    fd$: fd$,
    buffer$: JSToBuffer$(meta$),
    position$: size$
  }))
  return mux({written$, meta$, remoteFileSize$: size$, fdW$: fd$})
}
<commit_msg>refactor(utils): disintegrate HttpRequest composite function<commit_after>/**
 * Created by tushar.mathur on 22/01/16.
 */

'use strict'

import PATH from 'path'
import URL from 'url'
import {Observable as O} from 'rx'
import R from 'ramda'
import * as Rx from './RxFP'
import {mux, demux} from 'muxer'
import {MTDError, FILE_SIZE_UNKNOWN} from './Error'

const first = R.nth(0)
const second = R.nth(1)
export const trace = R.curry((msg, value) => {
  console.log(msg, value)
  return value
})
export const demuxFP = R.curry((list, $) => demux($, ...list))
export const demuxFPH = R.curry((list, $) => R.head(demux($, ...list)))
export const BUFFER_SIZE = 512
export const NormalizePath = (path) => PATH.resolve(process.cwd(), path)
export const GenerateFileName = (x) => R.last(URL.parse(x).pathname.split('/')) || Date.now()
export const ResolvePath = R.compose(NormalizePath, GenerateFileName)
export const SplitRange = (totalBytes, range) => {
  const delta = Math.round(totalBytes / range)
  const start = R.times((x) => x * delta, range)
  const end = R.times((x) => (x + 1) * delta - 1, range)
  end[range - 1] = totalBytes
  return R.zip(start, end)
}
export const CreateRangeHeader = ([start, end]) => `bytes=${start}-${end}`
export const SetRangeHeader = ({request, range}) => {
  return R.set(
    R.lensPath(['headers', 'range']),
    CreateRangeHeader(range),
    R.omit(['threads', 'offsets'], request)
  )
}
export const CreateRequestParams = ({meta, index}) => {
  const range = [meta.offsets[index], second(meta.threads[index])]
  return SetRangeHeader({request: meta, range})
}
export const ToBuffer = R.curry((size, str) => {
  var buffer = CreateFilledBuffer(size)
  buffer.write(str)
  return buffer
})
export const CreateFilledBuffer = (size = BUFFER_SIZE, fill = ' ') => {
  const buffer = new Buffer(size)
  buffer.fill(fill)
  return buffer
}
export const MergeDefaultOptions = (options) => R.mergeAll([
  {mtdPath: options.path + '.mtd', range: 3, metaWrite: 300},
  options
])

// TODO: Use R.lens instead
export const GetOffset = R.curry((meta, index) => meta.offsets[index])
export const GetThread = R.curry((meta, index) => meta.threads[index])
export const GetThreadStart = R.curryN(2, R.compose(R.nth(0), GetThread))
export const GetThreadEnd = R.curryN(2, R.compose(R.nth(1), GetThread))
export const GetThreadCount = R.compose(R.length, R.prop('threads'))
export const TimesCount = R.times(R.identity)

/*
 * STREAM BASED
 */
export const GetBufferWriteOffset = ({buffer$, initialOffset}) => {
  const accumulator = ([_buffer, _offset], buffer) => [buffer, _buffer.length + _offset]
  return buffer$.scan(accumulator, [{length: 0}, initialOffset])
}
export const SetBufferParams = ({buffer$, index, meta}) => {
  const initialOffset = GetOffset(meta, index)
  const addParams = R.compose(Rx.map(R.append(index)), GetBufferWriteOffset)
  return addParams({buffer$, initialOffset})
}

export const RequestThread = R.curry((HttpRequest, {meta, index}) => {
  const {response$, data$} = demuxFPH(['data$', 'response$'], HttpRequest({
    meta,
    index
  }))
  const buffer$ = SetBufferParams({buffer$: data$, meta, index})
  return mux({buffer$, response$})
})
export const ToJSON$ = source$ => source$.map(JSON.stringify.bind(JSON))
export const ToBuffer$ = source$ => source$.map(ToBuffer(BUFFER_SIZE))
export const JSToBuffer$ = R.compose(ToBuffer$, ToJSON$)
export const BufferToJS$ = buffer$ => buffer$.map(buffer => JSON.parse(buffer.toString()))
export const RemoteFileSize$ = ({HTTP, options}) => {
  return HTTP.requestHead(options)
    .pluck('headers', 'content-length')
    .map((x) => parseInt(x, 10))
}
export const LocalFileSize$ = ({FILE, fd$}) => FILE.fstat(fd$.map(R.of)).pluck('size')
export const PickFirst = R.map(first)
export const CreateMeta$ = ({size$, options}) => {
  return size$.map((totalBytes) => {
    if (!isFinite(totalBytes)) throw new MTDError(FILE_SIZE_UNKNOWN)
    const threads = SplitRange(totalBytes, options.range)
    return R.merge(options, {totalBytes, threads, offsets: PickFirst(threads)})
  })
}
export const ReadFileAt$ = ({FILE, fd$, position$, size = BUFFER_SIZE}) => {
  const readParams$ = O.combineLatest(position$, fd$)
  const buffer = CreateFilledBuffer(size)
  const toParam = ([position, fd]) => [fd, buffer, 0, buffer.length, position]
  return FILE.read(readParams$.map(toParam))
}
export const MetaPosition$ = ({size$}) => size$.map(R.add(-BUFFER_SIZE))
export const CreateWriteBufferAtParams = ({fd$, buffer$, position$}) => {
  const toParam = ([buffer, fd, position]) => [fd, buffer, 0, buffer.length, position]
  return O.combineLatest(buffer$, fd$, position$.first()).map(toParam)
}
export const CreateWriteBufferParams = ({fd$, buffer$, position$}) => {
  const toParams = ([fd, buffer, position]) => [fd, buffer, 0, buffer.length, position]
  return O.combineLatest(fd$, O.zip(buffer$, position$))
    .map(R.compose(toParams, R.unnest))
}
export const SetMetaOffsets = ({meta$, written$, thread$}) => {
  const offsetLens = thread => R.compose(R.lensProp('offsets'), R.lensIndex(thread))
  const start$ = meta$.map(meta => ({meta, len: 0, thread: 0})).first()
  const source$ = O.merge(
    start$,
    O.zip(written$, thread$)
      .map(R.zipObj(['len', 'thread']))
      .withLatestFrom(meta$.map(R.objOf('meta')))
      .map(R.mergeAll)
  )

  const accumulator = (previous, current) => {
    const thread = current.thread
    const pMeta = previous.meta
    const oldVal = pMeta.offsets[thread]
    const lens = offsetLens(thread)
    const meta = R.set(lens, R.add(oldVal, current.len), pMeta)
    return R.merge(current, {meta})
  }
  return source$
    .scan(accumulator)
    .skip(1)
    .pluck('meta')
}
export const ReadJSON$ = R.compose(BufferToJS$, Rx.map(second), ReadFileAt$)
export const IsOffsetInRange = R.curry((meta, i) => {
  const start = R.lte(GetThreadStart(meta, i))
  const end = R.gt(GetThreadEnd(meta, i))
  const inRange = R.allPass([start, end])
  return inRange(GetOffset(meta, i))
})
export const FlattenMeta$ = Rx.flatMap((meta) => {
  const MergeMeta = R.map(R.compose(R.merge({meta}), R.objOf('index')))
  const IsValid = R.filter(IsOffsetInRange(meta))
  return MergeMeta(IsValid(TimesCount(GetThreadCount(meta))))
})
export const RxFlatMapReplay = R.curryN(2, R.compose(Rx.shareReplay(1), Rx.flatMap))
export const RxThrottleComplete = (window$, $, sh) => {
  const selector = window => O.merge($.throttle(window, sh), $.last())
  return window$.first().flatMap(selector)
}
export const RemoveMeta = ({FILE, meta$, fd$}) => {
  const size$ = meta$.pluck('totalBytes')
  return FILE.truncate(O.combineLatest(fd$, size$).take(1))
}
export const ResetFileName = ({FILE, meta$}) => {
  const params$ = meta$.map(meta => [meta.mtdPath, meta.path]).take(1)
  return FILE.rename(params$)
}
export const IsCompleted$ = ({meta$}) => {
  const equals = R.apply(R.equals)
  const offsetsA = R.prop('offsets')
  const offsetsB = R.compose(R.map(second), R.prop('threads'))
  const isComplete = R.compose(equals, R.ap([offsetsA, offsetsB]), R.of)
  return meta$.map(isComplete).distinctUntilChanged()
}
export const FinalizeDownload = ({FILE, fd$, meta$}) => {
  const metaRemoved$ = RemoveMeta({FILE, meta$, fd$})
  const renamed$ = metaRemoved$.flatMap(() => ResetFileName({FILE, meta$}))
  return mux({metaRemoved$, renamed$})
}
export const RequestWithParams = HTTP => R.compose(HTTP.request, CreateRequestParams)
export const HttpRequest = R.compose(RxFlatMapReplay, RequestThread, RequestWithParams)
export const HttpRequestMeta$ = ({HTTP, meta$}) => {
  return R.compose(
    demuxFPH(['buffer$', 'response$']),
    HttpRequest(HTTP),
    FlattenMeta$
  )(meta$)
}
export const DownloadFromMTDFile = ({FILE, HTTP, mtdPath}) => {
  /**
   * Open file to read+append
   */
  const fd$ = FILE.open(O.just([mtdPath, 'r+']))

  /**
   * Retrieve File size on disk
   */
  const size$ = LocalFileSize$({FILE, fd$})

  /**
   * Retrieve Meta info
   */
  const metaPosition$ = MetaPosition$({size$})
  const meta$ = ReadJSON$({FILE, fd$, position$: metaPosition$})

  /**
   * Make a HTTP request for each thread
   */
  const {response$, buffer$} = HttpRequestMeta$({HTTP, meta$})

  /**
   * Create write params and save buffer+offset to disk
   */
  const saveBuffer$ = FILE.write(CreateWriteBufferParams({
    FILE,
    fd$,
    buffer$: buffer$.map(first),
    position$: buffer$.map(second)
  }))

  /**
   * Update META info
   */
  const nMeta$ = SetMetaOffsets({
    meta$,
    written$: saveBuffer$.map(first),
    thread$: buffer$.map(R.nth(2))
  })

  /**
   * Persist META to disk
   */
  const metaWritten$ = FILE.write(CreateWriteBufferAtParams({
    fd$,
    buffer$: JSToBuffer$(RxThrottleComplete(meta$.pluck('metaWrite'), nMeta$)),
    position$: size$
  }))
  return mux({
    metaWritten$, localFileSize$: size$, fdR$: fd$, metaPosition$,
    meta$: O.merge(nMeta$, meta$),
    response$,
    completed$: IsCompleted$({meta$})
  })
}
export const CreateMTDFile = ({FILE, HTTP, options}) => {
  /**
   * Create a new file
   */
  const fd$ = FILE.open(O.just([options.mtdPath, 'w']))

  /**
   * Retreive file size on remote server
   */
  const size$ = RemoteFileSize$({HTTP, options})

  /**
   * Create initial meta data
   */
  const meta$ = CreateMeta$({options, size$})

  /**
   * Create a new file with meta info appended at the end
   */
  const written$ = FILE.write(CreateWriteBufferAtParams({
    FILE,
    fd$: fd$,
    buffer$: JSToBuffer$(meta$),
    position$: size$
  }))
  return mux({written$, meta$, remoteFileSize$: size$, fdW$: fd$})
}
<|endoftext|>


<commit_before>'use strict';

function zeroesToNines(time) {
  if (time && time.match(/^0\.0+$/)) {
    return time.replaceAll('0', '9');
  }
  return time;
}

class TimerProxy {
  port_wrapper;
  profile;
  gate_watch_detectors;

  roundid;
  heat;
  lastFinishTime;
  result;

  sm;  // State machine

  detected_lane_count;
  // If not zero, a deadline for expecting heat results
  overdue_time;

  // port_wrapper is expected to be already opened
  constructor(port_wrapper, profile) {
    this.port_wrapper = port_wrapper;
    this.profile = profile;
    // start is async and runs forever
    this.start();
  }

  remote_start_profile() {
    if (Flag.fasttrack_automatic_gate_release.value && this.profile.key == "FastTrack-K") {
      return {has_remote_start: true, command: "LG"};
    } else {
      return this.profile?.remote_start;
    }
  }

  has_remote_start() {
    return this.remote_start_profile()?.has_remote_start;
  }

  async start() {
    await this.setup();

    try {
      // The polling pace, in ms.
      const kPollIntervalMs = 250;
      // This is the main timer polling loop.  Transmissions initiated by the timer (most importantly,
      // heat results) are handled asynchronously with detectors registered on the port_wrapper.
      while (true) {
        var poll_start = Date.now();
        var state = this.sm.state;

        var commands = this.profile?.poll?.[state];
        if (commands && !(Flag.fasttrack_automatic_gate_release.value && this.profile.key == "FastTrack-K")) {
          var prev = g_logger.scope;
          g_logger.scope = "profile.poll";
          try {
            await this.port_wrapper.writeCommandSequence(commands);
          } finally {
            g_logger.scope = prev;
          }
        }

        if (state == 'RUNNING' && this.overdue_time != 0 && Date.now() >= this.overdue_time) {
          this.port_wrapper.noticeContact();
          TimerEvent.send('OVERDUE');
        }

        if (this.profile?.gate_watcher && state != 'RUNNING' &&
            !Flag.no_gate_watcher.value) {
          this.port_wrapper.checkConnection();
          await this.poll_gate_once();
        }

        // Avoid polling too often
        var pause_ms = poll_start + kPollIntervalMs - Date.now();
        if (pause_ms > 0) {
          await new Promise(r => setTimeout(r, pause_ms));
        }
      }
    } finally {
      // These are global clean-ups that don't really belong here
      g_timer_proxy = null;
      $("#probe-button").prop('disabled', false);
      $("#profiles-list li").removeClass('probing chosen');
      $("#ports-list li").removeClass('probing chosen');
    }
    // Intentially not caught: "Reader is closed" thrown from PortWrapper
  }

  async poll_gate_once() {
    var deadline = Date.now() + /*POLL_RESPONSE_DEADLINE_MS*/100;

    var prev = g_logger.scope;
    g_logger.scope = "poll_gate_once";
    try {
      for (var i = 0; i < this.gate_watch_detectors; ++i) {
        this.gate_watch_detectors[i].activateUntil(deadline);
      }
      await this.port_wrapper.write(this.profile.gate_watcher.command);
      await this.port_wrapper.drainUntil(deadline);
    } finally {
      g_logger.scope = prev;
    }
  }

  async setup() {
    if (this.profile?.options?.eol !== undefined) {
      this.port_wrapper.eol = this.profile.options.eol;
    }
    if (this.profile?.matchers) {
      for (var i = 0; i < this.profile.matchers.length; ++i) {
        this.port_wrapper.detectors.push(new Detector(this.profile.matchers[i]));
      }
    }

    this.gate_watch_detectors = [];
    if (this.profile.hasOwnProperty('gate_watcher')) {
      for (var i = 0; i < this.profile.gate_watcher.matchers.length; ++i) {
        var d = new Detector(this.profile.gate_watcher.matchers[i]);
        d.activateFor(-1);
        this.gate_watch_detectors.push(d);
        this.port_wrapper.detectors.push(d);
      }
    }

    if (this.profile.hasOwnProperty('setup')) {
      await this.port_wrapper.writeCommandSequence(this.profile.setup);
    }
    // TODO Save and restore port_wrapper.detectors, as these setup queries' detectors don't need to
    // live beyond this one-time use.
    if (this.profile.hasOwnProperty('setup_queries')) {
      for (var i = 0; i < this.proffile.setup_queries.length; ++i) {
        for (var j = 0; j < this.profile.setup_queries[i].matchers.length; ++j) {
          var d = new Detector(this.profile.setup_queries[i].matchers[j]);
          d.activateFor(COMMAND_DRAIN_MS);
          this.port_wrapper.detectors.push(d);
        }
        await this.port_wrapper.write(this.profile.setup_queries[i].command);
        await this.port_wrapper.drain();
      }
    }

    this.sm = new StateMachine(this.profile.options.gate_state_is_knowable);
    TimerEvent.register(this.sm);
    TimerEvent.register(this);
  }

  async onEvent(event, args) {
    if (this.profile.hasOwnProperty('on') && this.profile.on.hasOwnProperty(event)) {
      var prev = g_logger.scope;
      g_logger.scope = "event.on";
      try {
        // Pack936 reported an issue with the 'rg' being sent too quickly after
        // gate opening, causing the previous heat's results to be sent again.
        // This 50ms pause should address that.
        await this.port_wrapper.drain(50);
        await this.port_wrapper.writeCommandSequence(this.profile.on[event]);
      } finally {
        g_logger.scope = prev;
      }
    }
    switch (event) {
    case 'PREPARE_HEAT_RECEIVED': {
      this.roundid = args[0];
      this.heat = args[1];
      var lanemask = args[2];
      var pause = Math.max(0,
                           this.lastFinishTime + Flag.delay_reset_after_race.value * 1000 - Date.now());
      TimerEvent.sendAfterMs(pause, 'MASK_LANES', [lanemask]);
      break;
    }
    case 'MASK_LANES':
      this.maskLanes(args[0]);
      break;
    case 'ABORT_HEAT_RECEIVED':
      this.lastFinishTime = 0;
      this.roundid = this.heat = 0;
      break;
    case 'START_RACE':
      {
        if (this.has_remote_start()) {
          this.port_wrapper.write(this.remote_start_profile().command);
        }
      }
      break;
    case 'RACE_STARTED':
      if (Flag.reset_after_start.value == 0) {
        this.overdue_time = 0;
      } else {
        this.overdue_time = Date.now() + Flag.reset_after_start.value * 1000;
      }
      break;
    case 'RACE_FINISHED':
      this.lastFinishTime = Date.now();
      this.roundid = 0;
      this.heat = 0;
      this.result = null;
      break;
    case 'LANE_RESULT': {
      var lane_char = args[0].charCodeAt(0);
      // ASCII 48 is '0', 57 is '9', 65 is 'A', 97 is 'a'
      var lane = (49 <= lane_char && lane_char <= 57) ?
          lane_char - 49 + 1 :
          lane_char - 65 + 1;
      var time = zeroesToNines(args[1]);
      var place = 0;
      if (args.length > 2 && args[2]) {
        // ASCII 33 is '!', signifying place
        place = args[2].charCodeAt(0) - 33 + 1;
      }

      if (this.result != null) {
        var was_filled = this.result.isFilled();
        var valid = this.result.setLane(lane, time, place);
        // Send just a single RACE_FINISHED event, even if we get some extra
        // results for masked-out lanes, etc.
        if (valid && this.result.isFilled() && !was_filled) {
          TimerEvent.send('RACE_FINISHED', [this.roundid, this.heat, this.result]);
        }
      }
      break;
    }
    case 'LANE_COUNT':
      this.detected_lane_count = args[0].charCountAt(0) - 49 + 1;
      break;
    case 'GATE_OPEN':
      break;
    case 'GATE_CLOSED':
      break;
    case 'OVERDUE':
      TimerEvent.sendAfterMs(/*GIVE_UP_AFTER_OVERDUE_MS=*/1000, 'GIVING_UP');
      break;
    case 'GIVING_UP':
      break;
    case 'LOST_CONNECTION':
      break;
    }
  }

  async maskLanes(lanemask) {
    $("#heat-received").text('lane mask ' + lanemask);
    this.result = new HeatResult(lanemask);
    if (this.profile.hasOwnProperty('heat_prep')) {
      if (this.profile.heat_prep.hasOwnProperty('unmask')) {
        console.log('unmasking: ' + this.profile.heat_prep.unmask);
        await this.port_wrapper.write(this.profile.heat_prep.unmask);
        var nlanes = Math.max(this.detected_lane_count || 0, this.profile?.options?.max_lanes || 0);
        for (var lane = 0; lane < nlanes; ++lane) {
          if ((lanemask & (1 << lane)) == 0) {
            console.log('masking lane ' + lane + ': ' + this.profile.heat_prep.mask +
                        String.fromCharCode(this.profile.heat_prep.lane.charCodeAt(0) + lane));
            await this.port_wrapper.drain();
            await this.port_wrapper.write(
              this.profile.heat_prep.mask +
                String.fromCharCode(this.profile.heat_prep.lane.charCodeAt(0) + lane));
          }
        }
      }
      if (this.profile.heat_prep.hasOwnProperty('reset')) {
        await this.port_wrapper.drain();
        await this.port_wrapper.write(this.profile.heat_prep.reset);
      }
      this.port_wrapper.drain();
    }
  }
}
<commit_msg>Fix typo for setup-queries in timer interface in browser.<commit_after>'use strict';

function zeroesToNines(time) {
  if (time && time.match(/^0\.0+$/)) {
    return time.replaceAll('0', '9');
  }
  return time;
}

class TimerProxy {
  port_wrapper;
  profile;
  gate_watch_detectors;

  roundid;
  heat;
  lastFinishTime;
  result;

  sm;  // State machine

  detected_lane_count;
  // If not zero, a deadline for expecting heat results
  overdue_time;

  // port_wrapper is expected to be already opened
  constructor(port_wrapper, profile) {
    this.port_wrapper = port_wrapper;
    this.profile = profile;
    // start is async and runs forever
    this.start();
  }

  remote_start_profile() {
    if (Flag.fasttrack_automatic_gate_release.value && this.profile.key == "FastTrack-K") {
      return {has_remote_start: true, command: "LG"};
    } else {
      return this.profile?.remote_start;
    }
  }

  has_remote_start() {
    return this.remote_start_profile()?.has_remote_start;
  }

  async start() {
    await this.setup();

    try {
      // The polling pace, in ms.
      const kPollIntervalMs = 250;
      // This is the main timer polling loop.  Transmissions initiated by the timer (most importantly,
      // heat results) are handled asynchronously with detectors registered on the port_wrapper.
      while (true) {
        var poll_start = Date.now();
        var state = this.sm.state;

        var commands = this.profile?.poll?.[state];
        if (commands && !(Flag.fasttrack_automatic_gate_release.value && this.profile.key == "FastTrack-K")) {
          var prev = g_logger.scope;
          g_logger.scope = "profile.poll";
          try {
            await this.port_wrapper.writeCommandSequence(commands);
          } finally {
            g_logger.scope = prev;
          }
        }

        if (state == 'RUNNING' && this.overdue_time != 0 && Date.now() >= this.overdue_time) {
          this.port_wrapper.noticeContact();
          TimerEvent.send('OVERDUE');
        }

        if (this.profile?.gate_watcher && state != 'RUNNING' &&
            !Flag.no_gate_watcher.value) {
          this.port_wrapper.checkConnection();
          await this.poll_gate_once();
        }

        // Avoid polling too often
        var pause_ms = poll_start + kPollIntervalMs - Date.now();
        if (pause_ms > 0) {
          await new Promise(r => setTimeout(r, pause_ms));
        }
      }
    } finally {
      // These are global clean-ups that don't really belong here
      g_timer_proxy = null;
      $("#probe-button").prop('disabled', false);
      $("#profiles-list li").removeClass('probing chosen');
      $("#ports-list li").removeClass('probing chosen');
    }
    // Intentially not caught: "Reader is closed" thrown from PortWrapper
  }

  async poll_gate_once() {
    var deadline = Date.now() + /*POLL_RESPONSE_DEADLINE_MS*/100;

    var prev = g_logger.scope;
    g_logger.scope = "poll_gate_once";
    try {
      for (var i = 0; i < this.gate_watch_detectors; ++i) {
        this.gate_watch_detectors[i].activateUntil(deadline);
      }
      await this.port_wrapper.write(this.profile.gate_watcher.command);
      await this.port_wrapper.drainUntil(deadline);
    } finally {
      g_logger.scope = prev;
    }
  }

  async setup() {
    if (this.profile?.options?.eol !== undefined) {
      this.port_wrapper.eol = this.profile.options.eol;
    }
    if (this.profile?.matchers) {
      for (var i = 0; i < this.profile.matchers.length; ++i) {
        this.port_wrapper.detectors.push(new Detector(this.profile.matchers[i]));
      }
    }

    this.gate_watch_detectors = [];
    if (this.profile.hasOwnProperty('gate_watcher')) {
      for (var i = 0; i < this.profile.gate_watcher.matchers.length; ++i) {
        var d = new Detector(this.profile.gate_watcher.matchers[i]);
        d.activateFor(-1);
        this.gate_watch_detectors.push(d);
        this.port_wrapper.detectors.push(d);
      }
    }

    if (this.profile.hasOwnProperty('setup')) {
      await this.port_wrapper.writeCommandSequence(this.profile.setup);
    }
    // TODO Save and restore port_wrapper.detectors, as these setup queries' detectors don't need to
    // live beyond this one-time use.
    if (this.profile.hasOwnProperty('setup_queries')) {
      for (var i = 0; i < this.profile.setup_queries.length; ++i) {
        for (var j = 0; j < this.profile.setup_queries[i].matchers.length; ++j) {
          var d = new Detector(this.profile.setup_queries[i].matchers[j]);
          d.activateFor(COMMAND_DRAIN_MS);
          this.port_wrapper.detectors.push(d);
        }
        await this.port_wrapper.write(this.profile.setup_queries[i].command);
        await this.port_wrapper.drain();
      }
    }

    this.sm = new StateMachine(this.profile.options.gate_state_is_knowable);
    TimerEvent.register(this.sm);
    TimerEvent.register(this);
  }

  async onEvent(event, args) {
    if (this.profile.hasOwnProperty('on') && this.profile.on.hasOwnProperty(event)) {
      var prev = g_logger.scope;
      g_logger.scope = "event.on";
      try {
        // Pack936 reported an issue with the 'rg' being sent too quickly after
        // gate opening, causing the previous heat's results to be sent again.
        // This 50ms pause should address that.
        await this.port_wrapper.drain(50);
        await this.port_wrapper.writeCommandSequence(this.profile.on[event]);
      } finally {
        g_logger.scope = prev;
      }
    }
    switch (event) {
    case 'PREPARE_HEAT_RECEIVED': {
      this.roundid = args[0];
      this.heat = args[1];
      var lanemask = args[2];
      var pause = Math.max(0,
                           this.lastFinishTime + Flag.delay_reset_after_race.value * 1000 - Date.now());
      TimerEvent.sendAfterMs(pause, 'MASK_LANES', [lanemask]);
      break;
    }
    case 'MASK_LANES':
      this.maskLanes(args[0]);
      break;
    case 'ABORT_HEAT_RECEIVED':
      this.lastFinishTime = 0;
      this.roundid = this.heat = 0;
      break;
    case 'START_RACE':
      {
        if (this.has_remote_start()) {
          this.port_wrapper.write(this.remote_start_profile().command);
        }
      }
      break;
    case 'RACE_STARTED':
      if (Flag.reset_after_start.value == 0) {
        this.overdue_time = 0;
      } else {
        this.overdue_time = Date.now() + Flag.reset_after_start.value * 1000;
      }
      break;
    case 'RACE_FINISHED':
      this.lastFinishTime = Date.now();
      this.roundid = 0;
      this.heat = 0;
      this.result = null;
      break;
    case 'LANE_RESULT': {
      var lane_char = args[0].charCodeAt(0);
      // ASCII 48 is '0', 57 is '9', 65 is 'A', 97 is 'a'
      var lane = (49 <= lane_char && lane_char <= 57) ?
          lane_char - 49 + 1 :
          lane_char - 65 + 1;
      var time = zeroesToNines(args[1]);
      var place = 0;
      if (args.length > 2 && args[2]) {
        // ASCII 33 is '!', signifying place
        place = args[2].charCodeAt(0) - 33 + 1;
      }

      if (this.result != null) {
        var was_filled = this.result.isFilled();
        var valid = this.result.setLane(lane, time, place);
        // Send just a single RACE_FINISHED event, even if we get some extra
        // results for masked-out lanes, etc.
        if (valid && this.result.isFilled() && !was_filled) {
          TimerEvent.send('RACE_FINISHED', [this.roundid, this.heat, this.result]);
        }
      }
      break;
    }
    case 'LANE_COUNT':
      this.detected_lane_count = args[0].charCountAt(0) - 49 + 1;
      break;
    case 'GATE_OPEN':
      break;
    case 'GATE_CLOSED':
      break;
    case 'OVERDUE':
      TimerEvent.sendAfterMs(/*GIVE_UP_AFTER_OVERDUE_MS=*/1000, 'GIVING_UP');
      break;
    case 'GIVING_UP':
      break;
    case 'LOST_CONNECTION':
      break;
    }
  }

  async maskLanes(lanemask) {
    $("#heat-received").text('lane mask ' + lanemask);
    this.result = new HeatResult(lanemask);
    if (this.profile.hasOwnProperty('heat_prep')) {
      if (this.profile.heat_prep.hasOwnProperty('unmask')) {
        console.log('unmasking: ' + this.profile.heat_prep.unmask);
        await this.port_wrapper.write(this.profile.heat_prep.unmask);
        var nlanes = Math.max(this.detected_lane_count || 0, this.profile?.options?.max_lanes || 0);
        for (var lane = 0; lane < nlanes; ++lane) {
          if ((lanemask & (1 << lane)) == 0) {
            console.log('masking lane ' + lane + ': ' + this.profile.heat_prep.mask +
                        String.fromCharCode(this.profile.heat_prep.lane.charCodeAt(0) + lane));
            await this.port_wrapper.drain();
            await this.port_wrapper.write(
              this.profile.heat_prep.mask +
                String.fromCharCode(this.profile.heat_prep.lane.charCodeAt(0) + lane));
          }
        }
      }
      if (this.profile.heat_prep.hasOwnProperty('reset')) {
        await this.port_wrapper.drain();
        await this.port_wrapper.write(this.profile.heat_prep.reset);
      }
      this.port_wrapper.drain();
    }
  }
}
<|endoftext|>


<commit_before>#! /usr/bin/env node
/************************************************************************
 *
 *  wrapper-jats.js
 *
 *  A wrapper around MathJax-node for JATS-like files.
 *
 *  Reads an XML file that contains JATS-style *-formula elements
 *  with tex-math nodes and writes a new XML file that
 *  adds MathML representations.
 *
 * ----------------------------------------------------------------------
 *
 *  Copyright (c) 2014 The MathJax Consortium
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

var mjAPI = require("MathJax-node/lib/mj-single");
var typeset = mjAPI.typeset;
var fs = require('fs');
var path = require('path');
var async = require('async');
var libxmljs = require("libxmljs");

var argv = require("yargs")
  .strict()
  .usage("Usage: jats -i input.html -o output.html",{
    i: {
        describe: 'specify input file',
        alias: ('i','input')
    },
    o: {
        describe: 'specify output file',
        alias: ('o','output')
    },
    // TODO  implement more options
//    speech: {
//      boolean: true,
//      describe: "include speech text"
//    },
//    linebreaks: {
//      boolean: true,
//      describe: "perform automatic line-breaking on SVG (see --width)"
//    },
//    inputFormat: {
//      default: "AsciiMath,TeX,MathML", //TODO make his input agnostic
//      describe: "input format(s) to look for",
//    },
   outputFormat: {
     default: "MathML, SVG",
     describe: "output format(s) to generate; MathML, SVG, or PNG" //NOTE add multiple versions
   },
//    font: {
//      default: "TeX",
//      describe: "web font to use (for image output)"
//    },
//    ex: {
//      default: 6,
//      describe: "ex-size in pixels (for image output)"
//    },
//    width: {
//      default: 100,
//      describe: "width of container in ex (e.g., to trigger linebreaking)"
//    }
  })
  .demand(['i','o'])
  .argv;

outputFormats = argv.outputFormat.split(/ *, */);
if (argv.font === "STIX") {argv.font = "STIX-Web";}

var inputFile = fs.readFileSync(path.join(__dirname, argv.i));
var xmlDocument = libxmljs.parseXml(inputFile);

mjAPI.config({
  MathJax: {
    menuSettings: {
      semantics: true,
    }
  }
});
mjAPI.start();

function processMath(texMathNode, callback) {
    var formulaNode = texMathNode.parent();
    var texString = texMathNode.text();
    var dispStyle = (formulaNode.name()==='disp-formula');
    typeset({
          math: texString,
          format: (dispStyle ? "TeX":"inline-TeX"),
          mml: (outputFormats.indexOf('MathML') > -1),
          svg: (outputFormats.indexOf('SVG') > -1),
          png: (outputFormats.indexOf('PNG') > -1),
        }, function (data) {
          if (!data.errors) {
              if (data.svg){
                // remove ids to avoid clash; see https://github.com/pkra/lens-ams/issues/17
                  var svgNode = libxmljs.parseXml(data.svg);
                  var svgIds = svgNode.find('.//*[name()="g"][@id]');// why does //g not work? Oh well. This does work.
                  for (var idx = 0; idx < svgIds.length; idx++) {
                        var currentNode = svgIds[idx];
                        var currentId = currentNode.attr('id');
                        currentNode.attr({'xlink:type': 'resource'});
                        currentNode.defineNamespace('xlink', 'http://www.w3.org/1999/xlink')
                        currentNode.attr({'xlink:label': currentId.value()});
                        currentNode.attr({'id': ''});
                  }
                  texMathNode.addPrevSibling(svgNode.root());
              }
              if (data.mml){
                  var mmlString = data.mml.replace(/ xmlns="http:\/\/www.w3.org\/1998\/Math\/MathML"/g,'')
                                           // Work around part 1 -- for https://github.com/mathjax/MathJax-node/issues/46
                                          .replace(/<annotation encoding="application\/x-tex">(.*?)<\/annotation>/g, '<annotation encoding="application/x-tex"/>');
                                          // end work around
                  var mmlNode = libxmljs.parseXml(mmlString);
                  // Work around part 2 -- for https://github.com/mathjax/MathJax-node/issues/46
                  var annotationNode = mmlNode.find('*/annotation')[0];
                  annotationNode.text(texMathNode.text());
                  // end work around
                  var mmlIds = mmlNode.find('.//*[name()="mrow"][@id]');// why does //g not work? Oh well. This does work.
                  for (var idx = 0; idx < mmlIds.length; idx++) {
                        var currentNode = mmlIds[idx];
                        var currentId = currentNode.attr('id');
                        currentNode.attr({'xlink:type': 'resource'});
                        currentNode.defineNamespace('xlink', 'http://www.w3.org/1999/xlink')
                        currentNode.attr({'xlink:label': currentId.value()});
                  }
                  texMathNode.addPrevSibling(mmlNode.root());
              }
          }
        callback(data.errors);
    });
}

var texMathNodes = xmlDocument.find('//tex-math');

async.each(texMathNodes, processMath, function (err) {
        if (err) {throw err;}
        var mmlNodes = xmlDocument.find('//math//* | //math');
        for (var idx = 0; idx < mmlNodes.length; idx++) {
              mmlNodes[idx].namespace('mml', '');
        }
//         console.log(xmlDocument.toString());
        fs.writeFile(argv.o, xmlDocument, function (err) {
        if (err) {throw err;}
        console.log("It\'s saved!");
    });
    });
<commit_msg>refinement for Lens<commit_after>#! /usr/bin/env node
/************************************************************************
 *
 *  wrapper-jats.js
 *
 *  A wrapper around MathJax-node for JATS-like files.
 *
 *  Reads an XML file that contains JATS-style *-formula elements
 *  with tex-math nodes and writes a new XML file that
 *  adds MathML representations.
 *
 * ----------------------------------------------------------------------
 *
 *  Copyright (c) 2014 The MathJax Consortium
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

var mjAPI = require("MathJax-node/lib/mj-single");
var typeset = mjAPI.typeset;
var fs = require('fs');
var path = require('path');
var async = require('async');
var libxmljs = require("libxmljs");

var argv = require("yargs")
  .strict()
  .usage("Usage: jats -i input.html -o output.html",{
    i: {
        describe: 'specify input file',
        alias: ('i','input')
    },
    o: {
        describe: 'specify output file',
        alias: ('o','output')
    },
    // TODO  implement more options
//    speech: {
//      boolean: true,
//      describe: "include speech text"
//    },
//    linebreaks: {
//      boolean: true,
//      describe: "perform automatic line-breaking on SVG (see --width)"
//    },
//    inputFormat: {
//      default: "AsciiMath,TeX,MathML", //TODO make his input agnostic
//      describe: "input format(s) to look for",
//    },
   outputFormat: {
     default: "MathML, SVG",
     describe: "output format(s) to generate; MathML, SVG, or PNG" //NOTE add multiple versions
   },
//    font: {
//      default: "TeX",
//      describe: "web font to use (for image output)"
//    },
//    ex: {
//      default: 6,
//      describe: "ex-size in pixels (for image output)"
//    },
//    width: {
//      default: 100,
//      describe: "width of container in ex (e.g., to trigger linebreaking)"
//    }
  })
  .demand(['i','o'])
  .argv;

var outputFormats = argv.outputFormat.split(/ *, */);
if (argv.font === "STIX") {argv.font = "STIX-Web";}

var inputFile = fs.readFileSync(path.join(__dirname, argv.i));
var xmlDocument = libxmljs.parseXml(inputFile);

mjAPI.config({
  MathJax: {
    menuSettings: {
      semantics: true,
    },
    "displayAlign": "left",
    "displayIndent": "5em"
  }
});
mjAPI.start();

function processMath(texMathNode, callback) {
    var formulaNode = texMathNode.parent();
    formulaNode.addChild(libxmljs.parseXml("<alternatives/>").root());
    var alternativesTag = formulaNode.find('.//*[name()="alternatives"]')[0];
    alternativesTag.addChild(texMathNode.clone());
    texMathNode.remove();
    var thisTexMathNode = alternativesTag.find('.//*[name()="tex-math"]')[0];
    var texString = thisTexMathNode.text();
    var dispStyle = (formulaNode.name()==='disp-formula');
    typeset({
          math: texString,
          format: (dispStyle ? "TeX":"inline-TeX"),
          mml: (outputFormats.indexOf('MathML') > -1),
          svg: (outputFormats.indexOf('SVG') > -1),
          png: (outputFormats.indexOf('PNG') > -1),
        }, function (data) {
          if (!data.errors) {
              if (data.svg){
                // remove ids to avoid clash; see https://github.com/pkra/lens-ams/issues/17
                  var svgNode = libxmljs.parseXml(data.svg);
                  var svgIds = svgNode.find('.//*[name()="g"][@id]');
                  for (var idx = 0; idx < svgIds.length; idx++) {
                        var currentNode = svgIds[idx];
                        var currentId = currentNode.attr('id');
                        currentNode.attr({'xlink:type': 'resource'});
                        currentNode.defineNamespace('xlink', 'http://www.w3.org/1999/xlink');
                        currentNode.attr({'xlink:label': currentId.value()});
                        currentNode.attr({'id': ''});
                  }
                  thisTexMathNode.addPrevSibling(svgNode.root());
              }
              if (data.mml){
                  // Work around part 1 -- for https://github.com/mathjax/MathJax-node/issues/46
                  var mmlString = data.mml.replace(/<annotation encoding="application\/x-tex">(.|\n)*?<\/annotation>/g, '<annotation encoding="application/x-tex"/>');
                  // end work around
                  var mmlNode = libxmljs.parseXml(mmlString);
                  // Work around part 2 -- for https://github.com/mathjax/MathJax-node/issues/46
                  var annotationNode = mmlNode.find('.//*[name()="annotation"]')[0];
                  try { annotationNode.text(thisTexMathNode.text()); } catch(e){ console.log("annotationNode: error at: \n " + mmlNode.toString()); }
                  // end work around
                  var mmlLabels = mmlNode.find('.//*[name()="mlabeledtr"]');// adding xlink attributes
                  for (var idx = 0; idx < mmlLabels.length; idx++) {
                        var currentNode = mmlLabels[idx].child(1);
                        currentNode.attr({'xlink:type': 'resource'});
                        currentNode.defineNamespace('xlink', 'http://www.w3.org/1999/xlink');
                        currentNode.attr({'xlink:title': currentNode.child(1).text()}); // This assumes the label is simple text
                        try {
                          // check for extra ids created by \cssID{xxx}{} and only add those as xlink:label
                          var idNode = mmlNode.find('.//*[name()="mrow"][@id]')[0];
                          var currentId = idNode.attr('id');
                          currentNode.attr({'xlink:label': currentId.value()});
                        } catch(e) { console.log("no \\cssId in: " + thisTexMathNode.toString()); }
                  }
                  thisTexMathNode.addPrevSibling(mmlNode.root());
              }
          }
        callback(data.errors);
    });
}

var texMathNodes = xmlDocument.find('//tex-math');

async.each(texMathNodes, processMath, function (err) {
  if (err) {throw err;}
  fs.writeFile(argv.o, xmlDocument, function (err) {
  if (err) {throw err;}
  });
  console.log("It\'s saved!");
});
<|endoftext|>


<commit_before>/*
   Copyright (c) 2007-9, iUI Project Members
   See LICENSE.txt for licensing terms
 */

(function() {

var slideSpeed = 20;
var slideInterval = 0;

var currentPage = null;
var currentDialog = null;
var currentWidth = 0;
var currentHash = location.hash;
var hashPrefix = "#_";
var pageHistory = [];
var newPageCount = 0;
var checkTimer;
var hasOrientationEvent = false;
var portraitVal = "portrait";
var landscapeVal = "landscape";

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

window.iui =
{
    showPage: function(page, backwards)
    {
        if (page)
        {
            if (currentDialog)
            {
                currentDialog.removeAttribute("selected");
                currentDialog = null;
            }

            if (hasClass(page, "dialog"))
                showDialog(page);
            else
            {
                var fromPage = currentPage;
                currentPage = page;

                if (fromPage)
                    setTimeout(slidePages, 0, fromPage, page, backwards);
                else
                    updatePage(page, fromPage);
            }
        }
    },

    showPageById: function(pageId)
    {
        var page = $(pageId);
        if (page)
        {
            var index = pageHistory.indexOf(pageId);
            var backwards = index != -1;
            if (backwards)
                pageHistory.splice(index, pageHistory.length);

            iui.showPage(page, backwards);
        }
    },

    showPageByHref: function(href, args, method, replace, cb)
    {
        var req = new XMLHttpRequest();
        req.onerror = function()
        {
            if (cb)
                cb(false);
        };

        req.onreadystatechange = function()
        {
            if (req.readyState == 4)
            {
                if (replace)
                    replaceElementWithSource(replace, req.responseText);
                else
                {
                    var frag = document.createElement("div");
                    frag.innerHTML = req.responseText;
                    iui.insertPages(frag.childNodes);
                }
                if (cb)
                    setTimeout(cb, 1000, true);
            }
        };

        if (args)
        {
            req.open(method || "GET", href, true);
            req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
            req.setRequestHeader("Content-Length", args.length);
            req.send(args.join("&"));
        }
        else
        {
            req.open(method || "GET", href, true);
            req.send(null);
        }
    },

    insertPages: function(nodes)
    {
        var targetPage;
        for (var i = 0; i < nodes.length; ++i)
        {
            var child = nodes[i];
            if (child.nodeType == 1)
            {
                if (!child.id)
                    child.id = "__" + (++newPageCount) + "__";

                var clone = $(child.id);
                if (clone)
                    clone.parentNode.replaceChild(child, clone);
                else
                    document.body.appendChild(child);

                if (child.getAttribute("selected") == "true" || !targetPage)
                    targetPage = child;

                --i;
            }
        }

        if (targetPage)
            iui.showPage(targetPage);
    },

    getSelectedPage: function()
    {
        for (var child = document.body.firstChild; child; child = child.nextSibling)
        {
            if (child.nodeType == 1 && child.getAttribute("selected") == "true")
                return child;
        }
    },
    isNativeUrl: function(href)
    {
        for(var i = 0; i < iui.nativeUrlPatterns.length; i++)
        {
            if(href.match(iui.nativeUrlPatterns[i])) return true;
        }
        return false;
    },
    nativeUrlPatterns: [
        new RegExp("^http:\/\/maps.google.com\/maps\?"),
        new RegExp("^mailto:"),
        new RegExp("^tel:"),
        new RegExp("^http:\/\/www.youtube.com\/watch\\?v="),
        new RegExp("^http:\/\/www.youtube.com\/v\/"),
        new RegExp("^javascript:"),

    ]
};

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

addEventListener("load", function(event)
{
	var page = iui.getSelectedPage();
	var locPage = getPageFromLoc();

	if (page)
			iui.showPage(page);

	if (locPage && (locPage != page))
		iui.showPage(locPage);

	setTimeout(preloadImages, 0);
	setTimeout(checkOrientAndLocation, 0);
	checkTimer = setInterval(checkOrientAndLocation, 300);
}, false);

addEventListener("unload", function(event)
{
	return;
}, false);

addEventListener("click", function(event)
{
    var link = findParent(event.target, "a");
    if (link)
    {
        function unselect() { link.removeAttribute("selected"); }

        if (link.href && link.hash && link.hash != "#" && !link.target)
        {
            link.setAttribute("selected", "true");
            iui.showPage($(link.hash.substr(1)));
            setTimeout(unselect, 500);
        }
        else if (link == $("backButton"))
            history.back();
        else if (link.getAttribute("type") == "submit")
            submitForm(findParent(link, "form"));
        else if (link.getAttribute("type") == "cancel")
            cancelDialog(findParent(link, "form"));
        else if (link.target == "_replace")
        {
            link.setAttribute("selected", "progress");
            iui.showPageByHref(link.href, null, null, link, unselect);
        }
        else if (iui.isNativeUrl(link.href))
        {
            return;
        }
        else if (link.target == "_webapp")
        {
            location.href = link.href;
        }
        else if (!link.target)
        {
            link.setAttribute("selected", "progress");
            iui.showPageByHref(link.href, null, null, null, unselect);
        }
        else
            return;

        event.preventDefault();
    }
}, true);

addEventListener("click", function(event)
{
    var div = findParent(event.target, "div");
    if (div && hasClass(div, "toggle"))
    {
        div.setAttribute("toggled", div.getAttribute("toggled") != "true");
        event.preventDefault();
    }
}, true);

function getPageFromLoc()
{
	var page = location.hash.match(/#_([^\?_]+)/)[1];
	if (page)
		page = $(page);
  return page;
}

function orientChangeHandler()
{
  var orientation=window.orientation;
  switch(orientation)
  {
    case 0:
        setOrientation(portraitVal);
        break;

    case 90:
    case -90:
        setOrientation(landscapeVal);
        break;
  }
}

if (typeof window.onorientationchange == "object")
{
    window.onorientationchange=orientChangeHandler;
    hasOrientationEvent = true;
    setTimeout(orientChangeHandler, 0);
}

function checkOrientAndLocation()
{
    if (!hasOrientationEvent)
    {
      if (window.innerWidth != currentWidth)
      {
          currentWidth = window.innerWidth;
          var orient = currentWidth == 320 ? portraitVal : landscapeVal;
          setOrientation(orient);
      }
    }

    if (location.hash != currentHash)
    {
        var pageId = location.hash.substr(hashPrefix.length);
        iui.showPageById(pageId);
    }
}

function setOrientation(orient)
{
    document.body.setAttribute("orient", orient);
    setTimeout(scrollTo, 100, 0, 1);
}

function showDialog(page)
{
    currentDialog = page;
    page.setAttribute("selected", "true");

    if (hasClass(page, "dialog") && !page.target)
        showForm(page);
}

function showForm(form)
{
    form.onsubmit = function(event)
    {
        event.preventDefault();
        submitForm(form);
    };

    form.onclick = function(event)
    {
        if (event.target == form && hasClass(form, "dialog"))
            cancelDialog(form);
    };
}

function cancelDialog(form)
{
    form.removeAttribute("selected");
}

function updatePage(page, fromPage)
{
    if (!page.id)
        page.id = "__" + (++newPageCount) + "__";

    location.hash = currentHash = hashPrefix + page.id;
    pageHistory.push(page.id);

    var pageTitle = $("pageTitle");
    if (page.title)
        pageTitle.innerHTML = page.title;

    if (page.localName.toLowerCase() == "form" && !page.target)
        showForm(page);

    var backButton = $("backButton");
    if (backButton)
    {
        var prevPage = $(pageHistory[pageHistory.length-2]);
        if (prevPage && !page.getAttribute("hideBackButton"))
        {
            backButton.style.display = "inline";
            backButton.innerHTML = prevPage.title ? prevPage.title : "Back";
        }
        else
            backButton.style.display = "none";
    }
}

function slidePages(fromPage, toPage, backwards)
{
    var axis = (backwards ? fromPage : toPage).getAttribute("axis");

    clearInterval(checkTimer);

    if (canDoSlideAnim() && axis != 'y')
    {
      slide2(fromPage, toPage, backwards, slideDone);
    }
    else
    {
      slide1(fromPage, toPage, backwards, axis, slideDone);
    }

    function slideDone()
    {
//      console.log("slideDone");
      if (!hasClass(toPage, "dialog"))
          fromPage.removeAttribute("selected");
      checkTimer = setInterval(checkOrientAndLocation, 300);
      setTimeout(updatePage, 0, toPage, fromPage);
      fromPage.removeEventListener('webkitTransitionEnd', slideDone, false);
    }
}

function canDoSlideAnim()
{
  return (typeof WebKitCSSMatrix == "object");
}

function slide1(fromPage, toPage, backwards, axis, cb)
{
    if (axis == "y")
        (backwards ? fromPage : toPage).style.top = "100%";
    else
        toPage.style.left = "100%";

    scrollTo(0, 1);
    toPage.setAttribute("selected", "true");
    var percent = 100;
    slide();
    var timer = setInterval(slide, slideInterval);

    function slide()
    {
        percent -= slideSpeed;
        if (percent <= 0)
        {
            percent = 0;
            clearInterval(timer);
            cb();
        }

        if (axis == "y")
        {
            backwards
                ? fromPage.style.top = (100-percent) + "%"
                : toPage.style.top = percent + "%";
        }
        else
        {
            fromPage.style.left = (backwards ? (100-percent) : (percent-100)) + "%";
            toPage.style.left = (backwards ? -percent : percent) + "%";
        }
    }
}

//function durationInt(dur)
//{
//  var val = parseFloat(dur);
//  return (dur.indexOf('ms') == -1) ? val * 1000 : val;
//}

function slide2(fromPage, toPage, backwards, cb)
{
  toPage.style.webkitTransitionDuration = '0ms'; // Turn off transitions to set toPage start offset
  // fromStart is always 0% and toEnd is always 0%
  // iPhone won't take % width on toPage
  var toStart = 'translateX(' + (backwards ? '-' : '') + window.innerWidth +  'px)';
  var fromEnd = 'translateX(' + (backwards ? '100%' : '-100%') + ')';
  toPage.style.webkitTransform = toStart;
  toPage.setAttribute("selected", "true");
  toPage.style.webkitTransitionDuration = '';   // Turn transitions back on
//  var duration = durationInt(window.getComputedStyle(toPage, null).webkitTransitionDuration);
  function startTrans()
  {
    fromPage.style.webkitTransform = fromEnd;
    toPage.style.webkitTransform = 'translateX(0%)'; //toEnd
//    setTimeout(cb, duration);
  }
  fromPage.addEventListener('webkitTransitionEnd', cb, false);
  setTimeout(startTrans, 0);
}

function preloadImages()
{
    var preloader = document.createElement("div");
    preloader.id = "preloader";
    document.body.appendChild(preloader);
}

function submitForm(form)
{
    iui.showPageByHref(form.action || "POST", encodeForm(form), form.method);
}

function encodeForm(form)
{
    function encode(inputs)
    {
        for (var i = 0; i < inputs.length; ++i)
        {
            if (inputs[i].name)
                args.push(inputs[i].name + "=" + escape(inputs[i].value));
        }
    }

    var args = [];
    encode(form.getElementsByTagName("input"));
    encode(form.getElementsByTagName("textarea"));
    encode(form.getElementsByTagName("select"));
    return args;
}

function findParent(node, localName)
{
    while (node && (node.nodeType != 1 || node.localName.toLowerCase() != localName))
        node = node.parentNode;
    return node;
}

function hasClass(self, name)
{
    var re = new RegExp("(^|\\s)"+name+"($|\\s)");
    return re.exec(self.getAttribute("class")) != null;
}

function replaceElementWithSource(replace, source)
{
    var page = replace.parentNode;
    var parent = replace;
    while (page.parentNode != document.body)
    {
        page = page.parentNode;
        parent = parent.parentNode;
    }

    var frag = document.createElement(parent.localName);
    frag.innerHTML = source;

    page.removeChild(parent);

    while (frag.firstChild)
        page.appendChild(frag.firstChild);
}

function $(id) { return document.getElementById(id); }
function ddd() { console.log.apply(console, arguments); }

})();
<commit_msg>Oops, handle case in getPageFromLoc() where regex doesn't match at all<commit_after>/*
   Copyright (c) 2007-9, iUI Project Members
   See LICENSE.txt for licensing terms
 */

(function() {

var slideSpeed = 20;
var slideInterval = 0;

var currentPage = null;
var currentDialog = null;
var currentWidth = 0;
var currentHash = location.hash;
var hashPrefix = "#_";
var pageHistory = [];
var newPageCount = 0;
var checkTimer;
var hasOrientationEvent = false;
var portraitVal = "portrait";
var landscapeVal = "landscape";

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

window.iui =
{
    showPage: function(page, backwards)
    {
        if (page)
        {
            if (currentDialog)
            {
                currentDialog.removeAttribute("selected");
                currentDialog = null;
            }

            if (hasClass(page, "dialog"))
                showDialog(page);
            else
            {
                var fromPage = currentPage;
                currentPage = page;

                if (fromPage)
                    setTimeout(slidePages, 0, fromPage, page, backwards);
                else
                    updatePage(page, fromPage);
            }
        }
    },

    showPageById: function(pageId)
    {
        var page = $(pageId);
        if (page)
        {
            var index = pageHistory.indexOf(pageId);
            var backwards = index != -1;
            if (backwards)
                pageHistory.splice(index, pageHistory.length);

            iui.showPage(page, backwards);
        }
    },

    showPageByHref: function(href, args, method, replace, cb)
    {
        var req = new XMLHttpRequest();
        req.onerror = function()
        {
            if (cb)
                cb(false);
        };

        req.onreadystatechange = function()
        {
            if (req.readyState == 4)
            {
                if (replace)
                    replaceElementWithSource(replace, req.responseText);
                else
                {
                    var frag = document.createElement("div");
                    frag.innerHTML = req.responseText;
                    iui.insertPages(frag.childNodes);
                }
                if (cb)
                    setTimeout(cb, 1000, true);
            }
        };

        if (args)
        {
            req.open(method || "GET", href, true);
            req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
            req.setRequestHeader("Content-Length", args.length);
            req.send(args.join("&"));
        }
        else
        {
            req.open(method || "GET", href, true);
            req.send(null);
        }
    },

    insertPages: function(nodes)
    {
        var targetPage;
        for (var i = 0; i < nodes.length; ++i)
        {
            var child = nodes[i];
            if (child.nodeType == 1)
            {
                if (!child.id)
                    child.id = "__" + (++newPageCount) + "__";

                var clone = $(child.id);
                if (clone)
                    clone.parentNode.replaceChild(child, clone);
                else
                    document.body.appendChild(child);

                if (child.getAttribute("selected") == "true" || !targetPage)
                    targetPage = child;

                --i;
            }
        }

        if (targetPage)
            iui.showPage(targetPage);
    },

    getSelectedPage: function()
    {
        for (var child = document.body.firstChild; child; child = child.nextSibling)
        {
            if (child.nodeType == 1 && child.getAttribute("selected") == "true")
                return child;
        }
    },
    isNativeUrl: function(href)
    {
        for(var i = 0; i < iui.nativeUrlPatterns.length; i++)
        {
            if(href.match(iui.nativeUrlPatterns[i])) return true;
        }
        return false;
    },
    nativeUrlPatterns: [
        new RegExp("^http:\/\/maps.google.com\/maps\?"),
        new RegExp("^mailto:"),
        new RegExp("^tel:"),
        new RegExp("^http:\/\/www.youtube.com\/watch\\?v="),
        new RegExp("^http:\/\/www.youtube.com\/v\/"),
        new RegExp("^javascript:"),

    ]
};

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

addEventListener("load", function(event)
{
	var page = iui.getSelectedPage();
	var locPage = getPageFromLoc();

	if (page)
			iui.showPage(page);

	if (locPage && (locPage != page))
		iui.showPage(locPage);

	setTimeout(preloadImages, 0);
	setTimeout(checkOrientAndLocation, 0);
	checkTimer = setInterval(checkOrientAndLocation, 300);
}, false);

addEventListener("unload", function(event)
{
	return;
}, false);

addEventListener("click", function(event)
{
    var link = findParent(event.target, "a");
    if (link)
    {
        function unselect() { link.removeAttribute("selected"); }

        if (link.href && link.hash && link.hash != "#" && !link.target)
        {
            link.setAttribute("selected", "true");
            iui.showPage($(link.hash.substr(1)));
            setTimeout(unselect, 500);
        }
        else if (link == $("backButton"))
            history.back();
        else if (link.getAttribute("type") == "submit")
            submitForm(findParent(link, "form"));
        else if (link.getAttribute("type") == "cancel")
            cancelDialog(findParent(link, "form"));
        else if (link.target == "_replace")
        {
            link.setAttribute("selected", "progress");
            iui.showPageByHref(link.href, null, null, link, unselect);
        }
        else if (iui.isNativeUrl(link.href))
        {
            return;
        }
        else if (link.target == "_webapp")
        {
            location.href = link.href;
        }
        else if (!link.target)
        {
            link.setAttribute("selected", "progress");
            iui.showPageByHref(link.href, null, null, null, unselect);
        }
        else
            return;

        event.preventDefault();
    }
}, true);

addEventListener("click", function(event)
{
    var div = findParent(event.target, "div");
    if (div && hasClass(div, "toggle"))
    {
        div.setAttribute("toggled", div.getAttribute("toggled") != "true");
        event.preventDefault();
    }
}, true);

function getPageFromLoc()
{
	var page;
	var result = location.hash.match(/#_([^\?_]+)/);
	if (result)
		page = result[1];
	if (page)
		page = $(page);
  return page;
}

function orientChangeHandler()
{
  var orientation=window.orientation;
  switch(orientation)
  {
    case 0:
        setOrientation(portraitVal);
        break;

    case 90:
    case -90:
        setOrientation(landscapeVal);
        break;
  }
}

if (typeof window.onorientationchange == "object")
{
    window.onorientationchange=orientChangeHandler;
    hasOrientationEvent = true;
    setTimeout(orientChangeHandler, 0);
}

function checkOrientAndLocation()
{
    if (!hasOrientationEvent)
    {
      if (window.innerWidth != currentWidth)
      {
          currentWidth = window.innerWidth;
          var orient = currentWidth == 320 ? portraitVal : landscapeVal;
          setOrientation(orient);
      }
    }

    if (location.hash != currentHash)
    {
        var pageId = location.hash.substr(hashPrefix.length);
        iui.showPageById(pageId);
    }
}

function setOrientation(orient)
{
    document.body.setAttribute("orient", orient);
    setTimeout(scrollTo, 100, 0, 1);
}

function showDialog(page)
{
    currentDialog = page;
    page.setAttribute("selected", "true");

    if (hasClass(page, "dialog") && !page.target)
        showForm(page);
}

function showForm(form)
{
    form.onsubmit = function(event)
    {
        event.preventDefault();
        submitForm(form);
    };

    form.onclick = function(event)
    {
        if (event.target == form && hasClass(form, "dialog"))
            cancelDialog(form);
    };
}

function cancelDialog(form)
{
    form.removeAttribute("selected");
}

function updatePage(page, fromPage)
{
    if (!page.id)
        page.id = "__" + (++newPageCount) + "__";

    location.hash = currentHash = hashPrefix + page.id;
    pageHistory.push(page.id);

    var pageTitle = $("pageTitle");
    if (page.title)
        pageTitle.innerHTML = page.title;

    if (page.localName.toLowerCase() == "form" && !page.target)
        showForm(page);

    var backButton = $("backButton");
    if (backButton)
    {
        var prevPage = $(pageHistory[pageHistory.length-2]);
        if (prevPage && !page.getAttribute("hideBackButton"))
        {
            backButton.style.display = "inline";
            backButton.innerHTML = prevPage.title ? prevPage.title : "Back";
        }
        else
            backButton.style.display = "none";
    }
}

function slidePages(fromPage, toPage, backwards)
{
    var axis = (backwards ? fromPage : toPage).getAttribute("axis");

    clearInterval(checkTimer);

    if (canDoSlideAnim() && axis != 'y')
    {
      slide2(fromPage, toPage, backwards, slideDone);
    }
    else
    {
      slide1(fromPage, toPage, backwards, axis, slideDone);
    }

    function slideDone()
    {
//      console.log("slideDone");
      if (!hasClass(toPage, "dialog"))
          fromPage.removeAttribute("selected");
      checkTimer = setInterval(checkOrientAndLocation, 300);
      setTimeout(updatePage, 0, toPage, fromPage);
      fromPage.removeEventListener('webkitTransitionEnd', slideDone, false);
    }
}

function canDoSlideAnim()
{
  return (typeof WebKitCSSMatrix == "object");
}

function slide1(fromPage, toPage, backwards, axis, cb)
{
    if (axis == "y")
        (backwards ? fromPage : toPage).style.top = "100%";
    else
        toPage.style.left = "100%";

    scrollTo(0, 1);
    toPage.setAttribute("selected", "true");
    var percent = 100;
    slide();
    var timer = setInterval(slide, slideInterval);

    function slide()
    {
        percent -= slideSpeed;
        if (percent <= 0)
        {
            percent = 0;
            clearInterval(timer);
            cb();
        }

        if (axis == "y")
        {
            backwards
                ? fromPage.style.top = (100-percent) + "%"
                : toPage.style.top = percent + "%";
        }
        else
        {
            fromPage.style.left = (backwards ? (100-percent) : (percent-100)) + "%";
            toPage.style.left = (backwards ? -percent : percent) + "%";
        }
    }
}

//function durationInt(dur)
//{
//  var val = parseFloat(dur);
//  return (dur.indexOf('ms') == -1) ? val * 1000 : val;
//}

function slide2(fromPage, toPage, backwards, cb)
{
  toPage.style.webkitTransitionDuration = '0ms'; // Turn off transitions to set toPage start offset
  // fromStart is always 0% and toEnd is always 0%
  // iPhone won't take % width on toPage
  var toStart = 'translateX(' + (backwards ? '-' : '') + window.innerWidth +  'px)';
  var fromEnd = 'translateX(' + (backwards ? '100%' : '-100%') + ')';
  toPage.style.webkitTransform = toStart;
  toPage.setAttribute("selected", "true");
  toPage.style.webkitTransitionDuration = '';   // Turn transitions back on
//  var duration = durationInt(window.getComputedStyle(toPage, null).webkitTransitionDuration);
  function startTrans()
  {
    fromPage.style.webkitTransform = fromEnd;
    toPage.style.webkitTransform = 'translateX(0%)'; //toEnd
//    setTimeout(cb, duration);
  }
  fromPage.addEventListener('webkitTransitionEnd', cb, false);
  setTimeout(startTrans, 0);
}

function preloadImages()
{
    var preloader = document.createElement("div");
    preloader.id = "preloader";
    document.body.appendChild(preloader);
}

function submitForm(form)
{
    iui.showPageByHref(form.action || "POST", encodeForm(form), form.method);
}

function encodeForm(form)
{
    function encode(inputs)
    {
        for (var i = 0; i < inputs.length; ++i)
        {
            if (inputs[i].name)
                args.push(inputs[i].name + "=" + escape(inputs[i].value));
        }
    }

    var args = [];
    encode(form.getElementsByTagName("input"));
    encode(form.getElementsByTagName("textarea"));
    encode(form.getElementsByTagName("select"));
    return args;
}

function findParent(node, localName)
{
    while (node && (node.nodeType != 1 || node.localName.toLowerCase() != localName))
        node = node.parentNode;
    return node;
}

function hasClass(self, name)
{
    var re = new RegExp("(^|\\s)"+name+"($|\\s)");
    return re.exec(self.getAttribute("class")) != null;
}

function replaceElementWithSource(replace, source)
{
    var page = replace.parentNode;
    var parent = replace;
    while (page.parentNode != document.body)
    {
        page = page.parentNode;
        parent = parent.parentNode;
    }

    var frag = document.createElement(parent.localName);
    frag.innerHTML = source;

    page.removeChild(parent);

    while (frag.firstChild)
        page.appendChild(frag.firstChild);
}

function $(id) { return document.getElementById(id); }
function ddd() { console.log.apply(console, arguments); }

})();
<|endoftext|>