<commit_before>/*
 * append to your ~/.vimperatorc
 * =====
 * javascript if (typeof hBookmark != 'undefined') liberator.loadScript('chrome://hatenabookmark/content/vimperator/plugin/hatenabookmark.js', {__proto__: this});
 * =====
 */

liberator.plugins.hBookmark = (function() {
    let p = function(msg) {
        Application.console.log('mes: ' + msg);
    }

    styles.registerSheet("chrome://hatenabookmark/skin/vimperator.css");

    let HatenaBookmark = window.hBookmark;

    const DEFAULT_SHORTCUTS = {
        hintsAdd     : 'c',
        hintsComment : 'C',
        add          : ['c'],
        comment      : ['C'],
    };

    let globalS = liberator.globalVariables.hBookmark_shortcuts;

    let shortcuts = {};
    if (globalS) {
        for (let key in DEFAULT_SHORTCUTS) {
            shortcuts[key] = globalS[key] || DEFAULT_SHORTCUTS[key];
        }
    } else {
        shortcuts = DEFAULT_SHORTCUTS;
    }

    let plugin = {};

    if (shortcuts.hintsAdd) {
        hints.addMode(shortcuts.hintsAdd, 'Hatena Bookmark - Add', function(elem) {
            let link = elem.href;
            if (link)
                plugin.showPanel(link);
        });
    }

    if (shortcuts.hintsComment) {
        hints.addMode(shortcuts.hintsComment, 'Hatena Bookmark - Comment', function(elem) {
            let link = elem.href;
            if (link)
                plugin.showComment(link);
        });
    }

    if (shortcuts.comment && shortcuts.comment.length) {
        mappings.addUserMap([modes.NORMAL],
            shortcuts.comment,
            'HatenaBookmark - Comment (toggle)',
            function() {
                plugin.toggleComment();
            }, {
            noremap: true,
            }
        );
    }

    if (shortcuts.add && shortcuts.add.length) {
        mappings.addUserMap([modes.NORMAL],
            shortcuts.add,
            'HatenaBookmark - Add',
            function() {
                plugin.showPanel();
            }, {
            noremap: true,
            }
        );
    }

    plugin.search = function(word, limit) {
        limit = limit || liberator.globalVariables.hBookmark_search_limit || 10;
        return HatenaBookmark.model('Bookmark').search(word, limit);
    }

    let BookmarkAdapter = new Struct('b');
    BookmarkAdapter.prototype.__defineGetter__('title', function() this.b.title);
    BookmarkAdapter.prototype.__defineGetter__('comment', function() this.b.comment);
    BookmarkAdapter.prototype.__defineGetter__('url', function() this.b.url);
    BookmarkAdapter.prototype.__defineGetter__('icon', function() this.b.favicon);
    BookmarkAdapter.prototype.__defineGetter__("extra", function () [
        ["comment", this.comment, "Comment"],
    ].filter(function (item) item[1]));

    plugin.command = {
        execute: function(args) {
            let url = plugin.command.genURL(args);
            liberator.open(url);
        },
        executeTab: function(args) {
            let url = plugin.command.genURL(args);
            liberator.open(url, liberator.NEW_TAB);
        },
        genURL: function(args) {
            let url = (args.string || '').replace(/\s/g, '');
            if (url.length) {
                if (args.bang) {
                    return 'http://b.hatena.ne.jp/entry/' + url.replace('#', '%23');
                } else {
                    return url;
                }
            } else {
                if (args.bang) {
                    return 'http://b.hatena.ne.jp/';
                } else {
                    return 'http://b.hatena.ne.jp/my';
                }
            }
        },
        adapter: BookmarkAdapter,
        templateDescription: function (item, text) {
           return <>
               {
                   !(item.extra && item.extra.length) ? "" :

                       {
                           template.map(item.extra, function (e)
                           <>{e[1]}</>,
                           <> </>/* Non-breaking space */)
                       }

               }
           </>
        },
        templateTitleIcon: function (item, text) {
           var simpleURL = text.replace(/^https?:\/\//, '');
           if (simpleURL.indexOf('/') == simpleURL.length-1)
               simpleURL = simpleURL.replace('/', '');
           return <>{item.icon ?  : <></>}{item.item.title}

           {
                 simpleURL
           }
           </>
        },
        options: {
            completer: function(context) {
                context.format = {
                    anchored: true,
                    title: ['URL', 'Comment'],
                    keys: { text: "url", description: "url", icon: "icon", extra: "extra"},
                    process: [
                        plugin.command.templateTitleIcon,
                        plugin.command.templateDescription,
                    ],
                }
                let word = context.filter;
                let res = plugin.search(word);
                context.filters = [];
                context.completions = res.map(function(b) new plugin.command.adapter(b));
            },
            literal: 0,
            argCount: '*',
            bang: true,
        }
    };

    plugin.toggleComment = function(url) {
        HatenaBookmark.CommentViewer.toggle(url);
    }

    plugin.showComment = function(url) {
        HatenaBookmark.CommentViewer.show(url);
    }

    plugin.showPanel = function(url, options) {
        if (url) {
            HatenaBookmark.AddPanelManager.showPanel(url, options || {});
        } else if (content.location.href.indexOf('reader.livedoor.com/reader') >= 0) {
              // domain match
              let item = content.window.wrappedJSObject.get_active_item(true);
              if (item) {
                HatenaBookmark.AddPanelManager.showPanel(item.link, {title: item.title});
              }
        } else {
            HatenaBookmark.AddPanelManager.toggle();
        }
    }

    commands.addUserCommand(
        ['hb[search]'],
        'Hatena Bookmark Search',
        plugin.command.execute,
        plugin.command.options,
        true
    );

    commands.addUserCommand(
        ['hbt[absearch]'],
        'Hatena Bookmark Search (open tab)',
        plugin.command.executeTab,
        plugin.command.options,
        true
    );

    config.guioptions['H'] = ['HatenaBookmark Toolbar','hBookmarkToolbar'];

    return plugin;
})();

<commit_msg>fix guioptions and add UrlCompleter of HatenaBookmarks<commit_after>/*
 * append to your ~/.vimperatorc
 * =====
 * javascript if (typeof hBookmark != 'undefined') liberator.loadScript('chrome://hatenabookmark/content/vimperator/plugin/hatenabookmark.js', {__proto__: this});
 * =====
 */

liberator.plugins.hBookmark = (function() {
    let p = function(msg) {
        Application.console.log('mes: ' + msg);
    }

    styles.registerSheet("chrome://hatenabookmark/skin/vimperator.css");

    let HatenaBookmark = window.hBookmark;

    const DEFAULT_SHORTCUTS = {
        hintsAdd     : 'c',
        hintsComment : 'C',
        add          : ['c'],
        comment      : ['C'],
    };

    let globalS = liberator.globalVariables.hBookmark_shortcuts;

    let shortcuts = {};
    if (globalS) {
        for (let key in DEFAULT_SHORTCUTS) {
            shortcuts[key] = globalS[key] || DEFAULT_SHORTCUTS[key];
        }
    } else {
        shortcuts = DEFAULT_SHORTCUTS;
    }

    let plugin = {};

    if (shortcuts.hintsAdd) {
        hints.addMode(shortcuts.hintsAdd, 'Hatena Bookmark - Add', function(elem) {
            let link = elem.href;
            if (link)
                plugin.showPanel(link);
        });
    }

    if (shortcuts.hintsComment) {
        hints.addMode(shortcuts.hintsComment, 'Hatena Bookmark - Comment', function(elem) {
            let link = elem.href;
            if (link)
                plugin.showComment(link);
        });
    }

    if (shortcuts.comment && shortcuts.comment.length) {
        mappings.addUserMap([modes.NORMAL],
            shortcuts.comment,
            'HatenaBookmark - Comment (toggle)',
            function() {
                plugin.toggleComment();
            }, {
            noremap: true,
            }
        );
    }

    if (shortcuts.add && shortcuts.add.length) {
        mappings.addUserMap([modes.NORMAL],
            shortcuts.add,
            'HatenaBookmark - Add',
            function() {
                plugin.showPanel();
            }, {
            noremap: true,
            }
        );
    }

    plugin.search = function(word, limit) {
        limit = limit || liberator.globalVariables.hBookmark_search_limit || 10;
        return HatenaBookmark.model('Bookmark').search(word, limit);
    }

    let BookmarkAdapter = new Struct('b');
    BookmarkAdapter.prototype.__defineGetter__('title', function() this.b.title);
    BookmarkAdapter.prototype.__defineGetter__('comment', function() this.b.comment);
    BookmarkAdapter.prototype.__defineGetter__('url', function() this.b.url);
    BookmarkAdapter.prototype.__defineGetter__('icon', function() this.b.favicon);
    BookmarkAdapter.prototype.__defineGetter__("extra", function () [
        ["comment", this.comment, "Comment"],
    ].filter(function (item) item[1]));

    plugin.command = {
        execute: function(args) {
            let url = plugin.command.genURL(args);
            liberator.open(url);
        },
        executeTab: function(args) {
            let url = plugin.command.genURL(args);
            liberator.open(url, liberator.NEW_TAB);
        },
        genURL: function(args) {
            let url = (args.string || '').replace(/\s/g, '');
            if (url.length) {
                if (args.bang) {
                    return 'http://b.hatena.ne.jp/entry/' + url.replace('#', '%23');
                } else {
                    return url;
                }
            } else {
                if (args.bang) {
                    return 'http://b.hatena.ne.jp/';
                } else {
                    return 'http://b.hatena.ne.jp/my';
                }
            }
        },
        adapter: BookmarkAdapter,
        templateDescription: function (item, text) {
           return <>
               {
                   !(item.extra && item.extra.length) ? "" :

                       {
                           template.map(item.extra, function (e)
                           <>{e[1]}</>,
                           <> </>/* Non-breaking space */)
                       }

               }
           </>
        },
        templateTitleIcon: function (item, text) {
           var simpleURL = text.replace(/^https?:\/\//, '');
           if (simpleURL.indexOf('/') == simpleURL.length-1)
               simpleURL = simpleURL.replace('/', '');
           return <>{item.icon ?  : <></>}{item.item.title}

           {
                 simpleURL
           }
           </>
        },
        options: {
            completer: null,
            literal: 0,
            argCount: '*',
            bang: true,
        },
        createCompleter: function(titles) {
            return function(context) {
                context.format = {
                    anchored: true,
                    title: titles,
                    keys: { text: "url", description: "url", icon: "icon", extra: "extra"},
                    process: [
                        plugin.command.templateTitleIcon,
                        plugin.command.templateDescription,
                    ],
                }
                let word = context.filter;
                let res = plugin.search(word);
                context.filters = [];
                context.completions = res.map(function(b) new plugin.command.adapter(b));
            }
        }
    };
	plugin.command.options.completer = plugin.command.createCompleter(['URL','Comment']);

    plugin.toggleComment = function(url) {
        HatenaBookmark.CommentViewer.toggle(url);
    }

    plugin.showComment = function(url) {
        HatenaBookmark.CommentViewer.show(url);
    }

    plugin.showPanel = function(url, options) {
        if (url) {
            HatenaBookmark.AddPanelManager.showPanel(url, options || {});
        } else if (content.location.href.indexOf('reader.livedoor.com/reader') >= 0) {
              // domain match
              let item = content.window.wrappedJSObject.get_active_item(true);
              if (item) {
                HatenaBookmark.AddPanelManager.showPanel(item.link, {title: item.title});
              }
        } else {
            HatenaBookmark.AddPanelManager.toggle();
        }
    }

    commands.addUserCommand(
        ['hb[search]'],
        'Hatena Bookmark Search',
        plugin.command.execute,
        plugin.command.options,
        true
    );

    commands.addUserCommand(
        ['hbt[absearch]'],
        'Hatena Bookmark Search (open tab)',
        plugin.command.executeTab,
        plugin.command.options,
        true
    );

    completion.addUrlCompleter("H", "Hatena Bookmarks", plugin.command.createCompleter(['Hatena Bookmark']));
    config.guioptions['H'] = ['HatenaBookmark Toolbar',['hBookmarkToolbar']];

    return plugin;
})();

<|endoftext|>


<commit_before>const pg = require('pg');
pg.defaults.parseInt8 = true;

const Sequelize = require('sequelize'),
  geo = require('./geo.js');

// Define models.
const sequelize = new Sequelize(
  process.env.POSTGRES_DB,
  process.env.POSTGRES_USER,
  process.env.POSTGRES_PASSWORD, {
    dialect: 'postgres',
    host: process.env.POSTGRES_HOST,
    port: parseInt(process.env.POSTGRES_PORT),
    logging: false
  }
);

const AGE_CHOICES = [
  'Not Specified',
  'Under 15',
  '15 to 19',
  '20 to 24',
  '25 to 34',
  '35 to 44',
  '45 to 54',
  '55 to 64',
  '65 to 74',
  '75 and older'
];

const GENDER_CHOICES = [
  'Not Specified',
  'Male',
  'Female',
  'Other'
];

const CYCLING_EXPERIENCE_CHOICES = [
  'Not Specified',
  'Beginner',
  'Intermediate',
  'Advanced'
];

const User = sequelize.define('user', {
  deviceUuid: {
    type: Sequelize.STRING,
    field: 'device_uuid',
    allowNull: false
  },
  platformName: {
    type: Sequelize.STRING,
    field: 'platform_name'
  },
  platformVersion: {
    type: Sequelize.FLOAT,
    field: 'platform_version'
  },
  gender: {
    type: Sequelize.INTEGER
  },
  age: {
    type: Sequelize.INTEGER
  },
  cyclingExperience: {
    type: Sequelize.INTEGER,
    field: 'cycling_experience'
  }
}, {
  freezeTableName: true,
  indexes: [
    {
      type: 'UNIQUE',
      fields: ['device_uuid']
    }
  ],
  underscored: true
});

User.fromMessage = function(msg) {
  // Round the version number to at most two decimal places.
  var version = (msg.platformVersion) ?
    +(Math.round(msg.platformVersion + 'e+2')  + 'e-2') : null;
  return {
    deviceUuid: msg.deviceUuid,
    platformName: msg.platformName,
    platformVersion: version,
    gender: msg.gender,
    age: msg.age,
    cyclingExperience: msg.cyclingExperience
  };
};

const Trip = sequelize.define('trip', {
  origin: {
    type: Sequelize.INTEGER,
    field: 'origin_type'
  },
  destination: {
    type: Sequelize.INTEGER,
    field: 'destination_type'
  },
  startTime: {
    type: Sequelize.DATE,
    field: 'start_time',
    allowNull: false
  },
  endTime: {
    type: Sequelize.DATE,
    field: 'end_time',
    allowNull: false
  },
  desiredAccuracy: {
    type: Sequelize.INTEGER,
    field: 'desired_accuracy',
    allowNull: false
  },
  transit: {
    type: Sequelize.BOOLEAN
  },
  geom: {
    type: Sequelize.GEOMETRY('LINESTRING', 4326),
    allowNull: false
  },
  debug: {
    type: Sequelize.BOOLEAN,
    field: 'debug'
  },
  appVersion: {
    type: Sequelize.STRING,
    field: 'app_version'
  },
  matchStatus: {
    type: Sequelize.STRING,
    field: 'match_status'
  },
  alternatives: {
    type: Sequelize.ARRAY(Sequelize.STRING)
  },
  region: {
    type: Sequelize.STRING
  },
  gender: {
    type: Sequelize.INTEGER
  },
  age: {
    type: Sequelize.INTEGER
  },
  cyclingExperience: {
    type: Sequelize.INTEGER,
    field: 'cycling_experience'
  }
}, {
  freezeTableName: true,
  indexes: [
    {
      type: 'SPATIAL',
      method: 'GIST',
      fields: ['geom']
    }
  ],
  underscored: true
});

Trip.fromMessage = function(msg, user, region) {
  return {
    origin: msg.origin,
    destination: msg.destination,
    startTime: new Date(msg.startTime.toNumber()),
    endTime: new Date(msg.endTime.toNumber()),
    desiredAccuracy: msg.desiredAccuracy,
    transit: msg.transit,
    geom: geo.toGeoJSON(msg.locations),
    user_id: user.id,
    debug: msg.debug,
    appVersion: msg.appVersion,
    region: region,
    gender: user.gender || 0,
    age: user.age || 0,
    cyclingExperience: user.cyclingExperience || 0
  };
};

const Point = sequelize.define('point', {
  accuracy: {
    type: Sequelize.DOUBLE,
    allowNull: false
  },
  altitude: {
    type: Sequelize.DOUBLE
  },
  heading: {
    type: Sequelize.DOUBLE
  },
  moving: {
    type: Sequelize.BOOLEAN
  },
  speed: {
    type: Sequelize.DOUBLE
  },
  time: {
    type: Sequelize.DATE,
    allowNull: false
  },
  'event': {
    type: Sequelize.INTEGER
  },
  activity: {
    type: Sequelize.INTEGER
  },
  confidence: {
    type: Sequelize.INTEGER
  },
  geom: {
    type: Sequelize.GEOMETRY('POINT', 4326),
    allowNull: false
  }
}, {
  freezeTableName: true,
  indexes: [
    {
      type: 'SPATIAL',
      method: 'GIST',
      fields: ['geom']
    }
  ],
  underscored: true
});

Point.fromTripMessage = function(tripMsg, tripID) {
  return tripMsg.locations.map(function(location) {
    return {
      accuracy: location.accuracy,
      altitude: location.altitude,
      heading: location.heading,
      moving: location.moving,
      speed: location.speed,
      time: new Date(location.time.toNumber()),
      event: (location.event) ? location.event : null,
      activity: (location.activity) ? location.activity : null,
      confidence: (location.confidence) ? location.confidence : null,
      geom: geo.toGeoJSON(location),
      trip_id: tripID
    };
  });
};

const Incident = sequelize.define('incident', {
  category : {
    type: Sequelize.STRING
  },
  comment:{
    type: Sequelize.TEXT
  },
  time:{
    type: Sequelize.DATE,
    allowNull: false
  },
  geom: {
    type: Sequelize.GEOMETRY("POINT", 4326)
  }
}, {
  freezeTableName: true,
  indexes: [
    {
      type: 'SPATIAL',
      method: 'GIST',
      fields: ['geom']
    }
  ],
  underscored:true
});

Incident.fromMessage = function(msg, userID){
  return {
    deviceUuid : msg.deviceUuid,
    category : msg.category,
    comment: msg.comment,
    time: new Date(msg.time.toNumber()),
    geom: geo.toGeoJSON(msg.location),
    user_id: userID
  }
};

const RouteLeg = sequelize.define('route_leg', {
  matching: {
    type: Sequelize.INTEGER,
  },
  leg: {
    type: Sequelize.INTEGER,
  },
  distance: {
    type: Sequelize.DOUBLE,
  },
  duration: {
    type: Sequelize.DOUBLE,
  },
  speed: {
    type: Sequelize.DOUBLE,
  },
  speedOutlier: {
    type: Sequelize.BOOLEAN,
    field: 'speed_outlier'
  },
  routeType: {
    type: Sequelize.STRING,
    field: 'route_type'
  },
  nodes: {
    type: Sequelize.ARRAY(Sequelize.BIGINT)
  },
  geom: {
    type: Sequelize.GEOMETRY('LINESTRING', 4326),
    allowNull: false
  }
}, {
  freezeTableName: true,
  indexes: [
    {
      type: 'SPATIAL',
      method: 'GIST',
      fields: ['geom']
    }
  ],
  underscored: true
});

const RouteTracepoint = sequelize.define('route_tracepoint', {
  geom: {
    type: Sequelize.GEOMETRY('POINT', 4326),
    allowNull: false
  }
}, {
  freezeTableName: true,
  indexes: [
    {
      type: 'SPATIAL',
      method: 'GIST',
      fields: ['geom']
    }
  ],
  underscored: true
});

const Node = sequelize.define('node', {
  id: {
    type: Sequelize.BIGINT,
    primaryKey: true
  },
  endpoint: {
    type: Sequelize.BOOLEAN
  },
  geom: {
    type: Sequelize.GEOMETRY('LINESTRING', 4326),
    allowNull: false
  }
}, {
  freezeTableName: true,
  indexes: [
    {
      type: 'SPATIAL',
      method: 'GIST',
      fields: ['geom']
    }
  ],
  underscored: true
});

const Edge = sequelize.define('edge', {
  region: {
    type: Sequelize.STRING
  },
  refs: {
    type: Sequelize.ARRAY(Sequelize.BIGINT)
  },
  geom: {
    type: Sequelize.GEOMETRY('LINESTRING', 4326),
    allowNull: false
  }
}, {
  freezeTableName: true,
  indexes: [
    {
      fields: ['region']
    },
    {
      type: 'SPATIAL',
      method: 'GIST',
      fields: ['geom']
    }
  ],
  underscored: true
});

// Set up foreign keys
Trip.belongsTo(User);
Trip.hasMany(Point);
Point.belongsTo(Trip);
Incident.belongsTo(User);
RouteLeg.belongsTo(Trip);
RouteLeg.belongsTo(Point, {as: 'startPoint'});
RouteLeg.belongsTo(Point, {as: 'endPoint'});
RouteTracepoint.belongsTo(Trip);
RouteTracepoint.belongsTo(Point);
RouteLeg.belongsToMany(Edge, {through: 'route_leg_edge'});
Edge.belongsToMany(RouteLeg, {through: 'route_leg_edge'});

function initViews() {
  let sql = `
  CREATE OR REPLACE VIEW edge_trip AS
  SELECT rle.edge_id,
      leg.route_type,
      trip.id AS trip_id,
      avg(speed) AS mean_speed
    FROM route_leg_edge AS rle
    INNER JOIN route_leg AS leg
      ON rle.route_leg_id = leg.id
    INNER JOIN trip
      ON leg.trip_id = trip.id
    GROUP BY rle.edge_id,
      leg.route_type,
      trip.id;`;

  return sequelize.query(sql, {type: sequelize.QueryTypes.RAW});
}

function getQueryOptions(options) {
  return Object.assign({
    minUsers: 0,
    region: process.env.BIKEMOVES_REGION
  }, options || {});
}

function insertIntoRouteLegEdge() {
  let sql = `
  INSERT INTO route_leg_edge (
    SELECT DISTINCT now() AS created_at,
      now() AS updated_at,
      segment.route_leg_id,
      edge.id AS edge_id
    FROM edge
    INNER JOIN (
      SELECT route_leg_id,
        node_id AS start_id,
        lead(node_id, 1) OVER
          (PARTITION BY route_leg_id ORDER BY node_idx) AS end_id
      FROM (
        SELECT route_leg_id,
          node_id,
          node_idx
        FROM (
          SELECT leg.id AS route_leg_id,
            node.id AS node_id,
            node.idx AS node_idx
          FROM route_leg AS leg,
            unnest(leg.nodes) WITH ORDINALITY node(id, idx)
          WHERE leg.id NOT IN (
            SELECT DISTINCT route_leg_id
            FROM route_leg_edge
          ) AND node.id IN (
            SELECT DISTINCT id
            FROM node
          )
        ) AS leg_node
      ) AS filtered
    ) AS segment
    ON edge.refs @> ARRAY[SEGMENT.start_id, segment.end_id]
      AND segment.end_id IS DISTINCT FROM NULL
  );`;

  return sequelize.query(sql, {type: sequelize.QueryTypes.RAW});
}

function getDemographics(trip_column, choices, options) {
  options = getQueryOptions(options);

  let sql = `
    SELECT choices.description,
      count(DISTINCT trip.user_id) AS users,
      count(DISTINCT trip.id) AS trips,
      round(sum(route_leg.distance * 0.000621371)::numeric,
        1)::double precision AS distance
    FROM unnest(ARRAY['${choices.join("', '")}'])
      WITH ORDINALITY choices(description, code)
    INNER JOIN trip
      ON coalesce(trip.${trip_column}, 0) = choices.code - 1
        AND trip.match_status = 'Matched'
        AND trip.region = '${options.region}'
    INNER JOIN route_leg
      ON route_leg.trip_id = trip.id
        AND NOT route_leg.speed_outlier
    GROUP BY choices.code,
      choices.description
    ORDER BY choices.code = 1,
      choices.code;`;

  return sequelize.query(sql, {type: sequelize.QueryTypes.SELECT});
}

function getTripCount(options) {
  options = getQueryOptions(options);

  let sql = `
    SELECT trip_count::character varying AS description,
      count(*) AS users,
      trip_count AS trips,
      NULL AS distance
    FROM (
      SELECT user_id,
        count(*) trip_count
      FROM trip
      WHERE match_status = 'Matched'
        AND region = '${options.region}'
      GROUP BY user_id
    ) AS counts
    GROUP BY trip_count
    ORDER BY trip_count;`;

  return sequelize.query(sql, {type: sequelize.QueryTypes.SELECT});
}

function getEdgeSQL(options) {
  options = getQueryOptions(options);

  let sql = `
    SELECT count(DISTINCT trip.user_id)::integer AS users,
      count(DISTINCT trip.id)::integer AS trips,
      avg(edge_trip.mean_speed) * 2.23694 AS mean_speed,
      edge.length AS length,
      edge.geom
    FROM edge
    INNER JOIN edge_trip
      ON edge_trip.edge_id = edge.id
        AND edge_trip.route_type = 'Actual'
    INNER JOIN trip
      ON edge_trip.trip_id = trip.id
        AND trip.region = '${options.region}'
    GROUP BY edge.id`;

  if (options.minUsers)
    sql += ` HAVING count(DISTINCT trip.user_id) >= ${options.minUsers}`;

  return sql;
}

function getEdgeStatistics(column, options) {
  let sql = `
    SELECT ${column}::int AS value,
      sum(length) * 0.000621371 AS count
    FROM (${getEdgeSQL(options)}) AS edge_info
    GROUP BY value
    ORDER BY value;`;

  return sequelize.query(sql, {type: sequelize.QueryTypes.SELECT});
}

function getEdgeTileSQL(options) {
  return `
    SELECT users,
      trips,
      mean_speed,
      ST_AsGeoJSON(geom) AS the_geom_geojson
    FROM (${getEdgeSQL(options)}) AS edge_info
    WHERE ST_Intersects(geom, !bbox_4326!)`;
}

// Update models.
function prepare(retries) {
  if (retries === undefined) retries = 5;
  return sequelize.authenticate()
    .then(() => sequelize.sync())
    .then(initViews)
    .catch((e) => {
      if (e.name != 'SequelizeConnectionRefusedError') throw e.name;
      if (retries === 0) return reject('Database unavailable');
      console.log('Database unavailable: ' + (retries - 1) + ' more tries');
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          prepare(retries - 1).then(resolve);
        }, 3000);
      });
    });
};

exports.AGE_CHOICES = AGE_CHOICES;
exports.GENDER_CHOICES = GENDER_CHOICES;
exports.CYCLING_EXPERIENCE_CHOICES = CYCLING_EXPERIENCE_CHOICES;
exports.sequelize = sequelize;
exports.User = User;
exports.Trip = Trip;
exports.Point = Point;
exports.Incident = Incident;
exports.RouteLeg = RouteLeg;
exports.RouteTracepoint = RouteTracepoint;
exports.Edge = Edge;
exports.prepare = prepare;
exports.insertIntoRouteLegEdge = insertIntoRouteLegEdge;
exports.getDemographics = getDemographics;
exports.getTripCount = getTripCount;
exports.getEdgeStatistics = getEdgeStatistics;
exports.getEdgeTileSQL = getEdgeTileSQL;
<commit_msg>Removed region field from edge.<commit_after>const pg = require('pg');
pg.defaults.parseInt8 = true;

const Sequelize = require('sequelize'),
  geo = require('./geo.js');

// Define models.
const sequelize = new Sequelize(
  process.env.POSTGRES_DB,
  process.env.POSTGRES_USER,
  process.env.POSTGRES_PASSWORD, {
    dialect: 'postgres',
    host: process.env.POSTGRES_HOST,
    port: parseInt(process.env.POSTGRES_PORT),
    logging: false
  }
);

const AGE_CHOICES = [
  'Not Specified',
  'Under 15',
  '15 to 19',
  '20 to 24',
  '25 to 34',
  '35 to 44',
  '45 to 54',
  '55 to 64',
  '65 to 74',
  '75 and older'
];

const GENDER_CHOICES = [
  'Not Specified',
  'Male',
  'Female',
  'Other'
];

const CYCLING_EXPERIENCE_CHOICES = [
  'Not Specified',
  'Beginner',
  'Intermediate',
  'Advanced'
];

const User = sequelize.define('user', {
  deviceUuid: {
    type: Sequelize.STRING,
    field: 'device_uuid',
    allowNull: false
  },
  platformName: {
    type: Sequelize.STRING,
    field: 'platform_name'
  },
  platformVersion: {
    type: Sequelize.FLOAT,
    field: 'platform_version'
  },
  gender: {
    type: Sequelize.INTEGER
  },
  age: {
    type: Sequelize.INTEGER
  },
  cyclingExperience: {
    type: Sequelize.INTEGER,
    field: 'cycling_experience'
  }
}, {
  freezeTableName: true,
  indexes: [
    {
      type: 'UNIQUE',
      fields: ['device_uuid']
    }
  ],
  underscored: true
});

User.fromMessage = function(msg) {
  // Round the version number to at most two decimal places.
  var version = (msg.platformVersion) ?
    +(Math.round(msg.platformVersion + 'e+2')  + 'e-2') : null;
  return {
    deviceUuid: msg.deviceUuid,
    platformName: msg.platformName,
    platformVersion: version,
    gender: msg.gender,
    age: msg.age,
    cyclingExperience: msg.cyclingExperience
  };
};

const Trip = sequelize.define('trip', {
  origin: {
    type: Sequelize.INTEGER,
    field: 'origin_type'
  },
  destination: {
    type: Sequelize.INTEGER,
    field: 'destination_type'
  },
  startTime: {
    type: Sequelize.DATE,
    field: 'start_time',
    allowNull: false
  },
  endTime: {
    type: Sequelize.DATE,
    field: 'end_time',
    allowNull: false
  },
  desiredAccuracy: {
    type: Sequelize.INTEGER,
    field: 'desired_accuracy',
    allowNull: false
  },
  transit: {
    type: Sequelize.BOOLEAN
  },
  geom: {
    type: Sequelize.GEOMETRY('LINESTRING', 4326),
    allowNull: false
  },
  debug: {
    type: Sequelize.BOOLEAN,
    field: 'debug'
  },
  appVersion: {
    type: Sequelize.STRING,
    field: 'app_version'
  },
  matchStatus: {
    type: Sequelize.STRING,
    field: 'match_status'
  },
  alternatives: {
    type: Sequelize.ARRAY(Sequelize.STRING)
  },
  region: {
    type: Sequelize.STRING
  },
  gender: {
    type: Sequelize.INTEGER
  },
  age: {
    type: Sequelize.INTEGER
  },
  cyclingExperience: {
    type: Sequelize.INTEGER,
    field: 'cycling_experience'
  }
}, {
  freezeTableName: true,
  indexes: [
    {
      type: 'SPATIAL',
      method: 'GIST',
      fields: ['geom']
    }
  ],
  underscored: true
});

Trip.fromMessage = function(msg, user, region) {
  return {
    origin: msg.origin,
    destination: msg.destination,
    startTime: new Date(msg.startTime.toNumber()),
    endTime: new Date(msg.endTime.toNumber()),
    desiredAccuracy: msg.desiredAccuracy,
    transit: msg.transit,
    geom: geo.toGeoJSON(msg.locations),
    user_id: user.id,
    debug: msg.debug,
    appVersion: msg.appVersion,
    region: region,
    gender: user.gender || 0,
    age: user.age || 0,
    cyclingExperience: user.cyclingExperience || 0
  };
};

const Point = sequelize.define('point', {
  accuracy: {
    type: Sequelize.DOUBLE,
    allowNull: false
  },
  altitude: {
    type: Sequelize.DOUBLE
  },
  heading: {
    type: Sequelize.DOUBLE
  },
  moving: {
    type: Sequelize.BOOLEAN
  },
  speed: {
    type: Sequelize.DOUBLE
  },
  time: {
    type: Sequelize.DATE,
    allowNull: false
  },
  'event': {
    type: Sequelize.INTEGER
  },
  activity: {
    type: Sequelize.INTEGER
  },
  confidence: {
    type: Sequelize.INTEGER
  },
  geom: {
    type: Sequelize.GEOMETRY('POINT', 4326),
    allowNull: false
  }
}, {
  freezeTableName: true,
  indexes: [
    {
      type: 'SPATIAL',
      method: 'GIST',
      fields: ['geom']
    }
  ],
  underscored: true
});

Point.fromTripMessage = function(tripMsg, tripID) {
  return tripMsg.locations.map(function(location) {
    return {
      accuracy: location.accuracy,
      altitude: location.altitude,
      heading: location.heading,
      moving: location.moving,
      speed: location.speed,
      time: new Date(location.time.toNumber()),
      event: (location.event) ? location.event : null,
      activity: (location.activity) ? location.activity : null,
      confidence: (location.confidence) ? location.confidence : null,
      geom: geo.toGeoJSON(location),
      trip_id: tripID
    };
  });
};

const Incident = sequelize.define('incident', {
  category : {
    type: Sequelize.STRING
  },
  comment:{
    type: Sequelize.TEXT
  },
  time:{
    type: Sequelize.DATE,
    allowNull: false
  },
  geom: {
    type: Sequelize.GEOMETRY("POINT", 4326)
  }
}, {
  freezeTableName: true,
  indexes: [
    {
      type: 'SPATIAL',
      method: 'GIST',
      fields: ['geom']
    }
  ],
  underscored:true
});

Incident.fromMessage = function(msg, userID){
  return {
    deviceUuid : msg.deviceUuid,
    category : msg.category,
    comment: msg.comment,
    time: new Date(msg.time.toNumber()),
    geom: geo.toGeoJSON(msg.location),
    user_id: userID
  }
};

const RouteLeg = sequelize.define('route_leg', {
  matching: {
    type: Sequelize.INTEGER,
  },
  leg: {
    type: Sequelize.INTEGER,
  },
  distance: {
    type: Sequelize.DOUBLE,
  },
  duration: {
    type: Sequelize.DOUBLE,
  },
  speed: {
    type: Sequelize.DOUBLE,
  },
  speedOutlier: {
    type: Sequelize.BOOLEAN,
    field: 'speed_outlier'
  },
  routeType: {
    type: Sequelize.STRING,
    field: 'route_type'
  },
  nodes: {
    type: Sequelize.ARRAY(Sequelize.BIGINT)
  },
  geom: {
    type: Sequelize.GEOMETRY('LINESTRING', 4326),
    allowNull: false
  }
}, {
  freezeTableName: true,
  indexes: [
    {
      type: 'SPATIAL',
      method: 'GIST',
      fields: ['geom']
    }
  ],
  underscored: true
});

const RouteTracepoint = sequelize.define('route_tracepoint', {
  geom: {
    type: Sequelize.GEOMETRY('POINT', 4326),
    allowNull: false
  }
}, {
  freezeTableName: true,
  indexes: [
    {
      type: 'SPATIAL',
      method: 'GIST',
      fields: ['geom']
    }
  ],
  underscored: true
});

const Node = sequelize.define('node', {
  id: {
    type: Sequelize.BIGINT,
    primaryKey: true
  },
  endpoint: {
    type: Sequelize.BOOLEAN
  },
  geom: {
    type: Sequelize.GEOMETRY('LINESTRING', 4326),
    allowNull: false
  }
}, {
  freezeTableName: true,
  indexes: [
    {
      type: 'SPATIAL',
      method: 'GIST',
      fields: ['geom']
    }
  ],
  underscored: true
});

const Edge = sequelize.define('edge', {
  refs: {
    type: Sequelize.ARRAY(Sequelize.BIGINT)
  },
  geom: {
    type: Sequelize.GEOMETRY('LINESTRING', 4326),
    allowNull: false
  }
}, {
  freezeTableName: true,
  indexes: [
    {
      type: 'SPATIAL',
      method: 'GIST',
      fields: ['geom']
    }
  ],
  underscored: true
});

// Set up foreign keys
Trip.belongsTo(User);
Trip.hasMany(Point);
Point.belongsTo(Trip);
Incident.belongsTo(User);
RouteLeg.belongsTo(Trip);
RouteLeg.belongsTo(Point, {as: 'startPoint'});
RouteLeg.belongsTo(Point, {as: 'endPoint'});
RouteTracepoint.belongsTo(Trip);
RouteTracepoint.belongsTo(Point);
RouteLeg.belongsToMany(Edge, {through: 'route_leg_edge'});
Edge.belongsToMany(RouteLeg, {through: 'route_leg_edge'});

function initViews() {
  let sql = `
  CREATE OR REPLACE VIEW edge_trip AS
  SELECT rle.edge_id,
      leg.route_type,
      trip.id AS trip_id,
      avg(speed) AS mean_speed
    FROM route_leg_edge AS rle
    INNER JOIN route_leg AS leg
      ON rle.route_leg_id = leg.id
    INNER JOIN trip
      ON leg.trip_id = trip.id
    GROUP BY rle.edge_id,
      leg.route_type,
      trip.id;`;

  return sequelize.query(sql, {type: sequelize.QueryTypes.RAW});
}

function getQueryOptions(options) {
  return Object.assign({
    minUsers: 0,
    region: process.env.BIKEMOVES_REGION
  }, options || {});
}

function insertIntoRouteLegEdge() {
  let sql = `
  INSERT INTO route_leg_edge (
    SELECT DISTINCT now() AS created_at,
      now() AS updated_at,
      segment.route_leg_id,
      edge.id AS edge_id
    FROM edge
    INNER JOIN (
      SELECT route_leg_id,
        node_id AS start_id,
        lead(node_id, 1) OVER
          (PARTITION BY route_leg_id ORDER BY node_idx) AS end_id
      FROM (
        SELECT route_leg_id,
          node_id,
          node_idx
        FROM (
          SELECT leg.id AS route_leg_id,
            node.id AS node_id,
            node.idx AS node_idx
          FROM route_leg AS leg,
            unnest(leg.nodes) WITH ORDINALITY node(id, idx)
          WHERE leg.id NOT IN (
            SELECT DISTINCT route_leg_id
            FROM route_leg_edge
          ) AND node.id IN (
            SELECT DISTINCT id
            FROM node
          )
        ) AS leg_node
      ) AS filtered
    ) AS segment
    ON edge.refs @> ARRAY[SEGMENT.start_id, segment.end_id]
      AND segment.end_id IS DISTINCT FROM NULL
  );`;

  return sequelize.query(sql, {type: sequelize.QueryTypes.RAW});
}

function getDemographics(trip_column, choices, options) {
  options = getQueryOptions(options);

  let sql = `
    SELECT choices.description,
      count(DISTINCT trip.user_id) AS users,
      count(DISTINCT trip.id) AS trips,
      round(sum(route_leg.distance * 0.000621371)::numeric,
        1)::double precision AS distance
    FROM unnest(ARRAY['${choices.join("', '")}'])
      WITH ORDINALITY choices(description, code)
    INNER JOIN trip
      ON coalesce(trip.${trip_column}, 0) = choices.code - 1
        AND trip.match_status = 'Matched'
        AND trip.region = '${options.region}'
    INNER JOIN route_leg
      ON route_leg.trip_id = trip.id
        AND NOT route_leg.speed_outlier
    GROUP BY choices.code,
      choices.description
    ORDER BY choices.code = 1,
      choices.code;`;

  return sequelize.query(sql, {type: sequelize.QueryTypes.SELECT});
}

function getTripCount(options) {
  options = getQueryOptions(options);

  let sql = `
    SELECT trip_count::character varying AS description,
      count(*) AS users,
      trip_count AS trips,
      NULL AS distance
    FROM (
      SELECT user_id,
        count(*) trip_count
      FROM trip
      WHERE match_status = 'Matched'
        AND region = '${options.region}'
      GROUP BY user_id
    ) AS counts
    GROUP BY trip_count
    ORDER BY trip_count;`;

  return sequelize.query(sql, {type: sequelize.QueryTypes.SELECT});
}

function getEdgeSQL(options) {
  options = getQueryOptions(options);

  let sql = `
    SELECT count(DISTINCT trip.user_id)::integer AS users,
      count(DISTINCT trip.id)::integer AS trips,
      avg(edge_trip.mean_speed) * 2.23694 AS mean_speed,
      edge.length AS length,
      edge.geom
    FROM edge
    INNER JOIN edge_trip
      ON edge_trip.edge_id = edge.id
        AND edge_trip.route_type = 'Actual'
    INNER JOIN trip
      ON edge_trip.trip_id = trip.id
        AND trip.region = '${options.region}'
    GROUP BY edge.id`;

  if (options.minUsers)
    sql += ` HAVING count(DISTINCT trip.user_id) >= ${options.minUsers}`;

  return sql;
}

function getEdgeStatistics(column, options) {
  let sql = `
    SELECT ${column}::int AS value,
      sum(length) * 0.000621371 AS count
    FROM (${getEdgeSQL(options)}) AS edge_info
    GROUP BY value
    ORDER BY value;`;

  return sequelize.query(sql, {type: sequelize.QueryTypes.SELECT});
}

function getEdgeTileSQL(options) {
  return `
    SELECT users,
      trips,
      mean_speed,
      ST_AsGeoJSON(geom) AS the_geom_geojson
    FROM (${getEdgeSQL(options)}) AS edge_info
    WHERE ST_Intersects(geom, !bbox_4326!)`;
}

// Update models.
function prepare(retries) {
  if (retries === undefined) retries = 5;
  return sequelize.authenticate()
    .then(() => sequelize.sync())
    .then(initViews)
    .catch((e) => {
      if (e.name != 'SequelizeConnectionRefusedError') throw e.name;
      if (retries === 0) return reject('Database unavailable');
      console.log('Database unavailable: ' + (retries - 1) + ' more tries');
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          prepare(retries - 1).then(resolve);
        }, 3000);
      });
    });
};

exports.AGE_CHOICES = AGE_CHOICES;
exports.GENDER_CHOICES = GENDER_CHOICES;
exports.CYCLING_EXPERIENCE_CHOICES = CYCLING_EXPERIENCE_CHOICES;
exports.sequelize = sequelize;
exports.User = User;
exports.Trip = Trip;
exports.Point = Point;
exports.Incident = Incident;
exports.RouteLeg = RouteLeg;
exports.RouteTracepoint = RouteTracepoint;
exports.Edge = Edge;
exports.prepare = prepare;
exports.insertIntoRouteLegEdge = insertIntoRouteLegEdge;
exports.getDemographics = getDemographics;
exports.getTripCount = getTripCount;
exports.getEdgeStatistics = getEdgeStatistics;
exports.getEdgeTileSQL = getEdgeTileSQL;
<|endoftext|>


<commit_before>/**
 * Copyright 2015 Google Inc.
 *
 * 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.
 *
 * @fileoverview Live integration tests.
 */

goog.require('shaka.dash.MpdRequest');
goog.require('shaka.media.Stream');
goog.require('shaka.player.Player');
goog.require('shaka.polyfill.installAll');
goog.require('shaka.util.EventManager');

describe('Player', function() {
  var originalTimeout;
  var video;
  var videoSource;
  var player;
  var eventManager;

  const segmentDurationManifestUrl =
      'http://vm2.dashif.org/livesim/testpic_6s/Manifest.mpd';
  const segmentNumberManifestUrl =
      'http://storage.googleapis.com/' +
      'widevine-demo-media/oops-segment-timeline-number/' +
      'oops-segment-timeline-number-oops_video.mpd';
  const segmentTimeManifestUrl =
      'http://storage.googleapis.com/' +
      'widevine-demo-media/oops-segment-timeline-time/' +
      'oops-segment-timeline-time-oops_video.mpd';

  const FUDGE_FACTOR = 2;
  const SMALL_FUDGE_FACTOR = 1;
  const SEEK_OFFSET = 10000;

  beforeAll(function() {
    // Hijack assertions and convert failed assertions into failed tests.
    assertsToFailures.install();

    // Change the timeout.
    originalTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL;
    jasmine.DEFAULT_TIMEOUT_INTERVAL = 30000;  // ms

    // Install polyfills.
    shaka.polyfill.installAll();

    // Create a video tag.  This will be visible so that long tests do not
    // create the illusion of the test-runner being hung.
    video = document.createElement('video');
    video.crossOrigin = 'anonymous';
    video.width = 600;
    video.height = 400;
    // Add it to the DOM.
    document.body.appendChild(video);
  });

  beforeEach(function() {
    // Create a new player.
    player = new shaka.player.Player(video);
    player.addEventListener('error', convertErrorToTestFailure, false);

    // Disable automatic adaptation unless it is needed for a test.
    // This makes test results more reproducible.
    player.enableAdaptation(false);

    eventManager = new shaka.util.EventManager();
  });

  afterEach(function(done) {
    eventManager.destroy();
    eventManager = null;

    player.destroy().then(function() {
      player = null;
      done();
    });
  });

  afterAll(function() {
    // Remove the video tag from the DOM.
    document.body.removeChild(video);

    // Restore the timeout.
    jasmine.DEFAULT_TIMEOUT_INTERVAL = originalTimeout;

    // Restore normal assertion behavior.
    assertsToFailures.uninstall();
  });

  describe('live support for segment duration template', function() {
    beforeEach(function() {
      videoSource = newSource(segmentDurationManifestUrl);
    });

    it('plays two full segments of content', function(done) {
      const SEGMENT_LENGTH = 6;

      // Wait until onAllStreamsStarted() has been called because we need
      // to have access to the corrected SegmentIndex.
      var originalonAllStreamsStarted = videoSource.onAllStreamsStarted;
      videoSource.onAllStreamsStarted = function(segmentIndexes) {
        originalonAllStreamsStarted.call(videoSource, segmentIndexes);
        // Do this async.
        window.setTimeout(setTestExpectations.bind(null, segmentIndexes), 0);
      };

      var setTestExpectations = function(segmentIndexes) {
        console.assert(segmentIndexes.length > 0);
        var index1 = segmentIndexes[0];
        var index2 = segmentIndexes[1];

        var min = Math.min(index1.last().startTime, index2.last().startTime);
        var streamStartTime =
            Math.max(min - videoSource.manifestInfo.minBufferTime, 0);

        var targetTime = streamStartTime + (2 * SEGMENT_LENGTH);
        var waitTime = (2 * SEGMENT_LENGTH) + FUDGE_FACTOR;

        waitForTargetTime(
            video, eventManager, targetTime, waitTime).then(function() {
          done();
        }).catch(function(error) {
          fail(error);
          done();
        });
      };

      player.load(videoSource).then(function() {
        video.play();
      }).catch(function(error) {
        fail(error);
        done();
      });
    });

    it('returns to seek range when seeking before start', function(done) {
      seekBeforeRange(videoSource, done);
    });

    it('returns to end of seek range when after end', function(done) {
      seekAfterRange(videoSource, done);
    });
  });

  describe('live support for segment number template', function() {
    beforeEach(function() {
      videoSource = newSource(segmentNumberManifestUrl);
    });

    it('requests MPD update in expected time', function(done) {
      requestMpdUpdate(segmentNumberManifestUrl, videoSource, done);
    });

    it('returns to seek range when seeking before start', function(done) {
      seekBeforeRange(videoSource, done);
    });

    it('returns to end of seek range when after end', function(done) {
      seekAfterRange(videoSource, done);
    });
  });

  describe('live support for segment time template', function() {
    beforeEach(function() {
      videoSource = newSource(segmentTimeManifestUrl);
    });

    it('requests MPD update in expected time', function(done) {
      requestMpdUpdate(segmentTimeManifestUrl, videoSource, done);
    });

    it('returns to seek range when seeking before start', function(done) {
      seekBeforeRange(videoSource, done);
    });

    it('returns to end of seek range when after end', function(done) {
      seekAfterRange(videoSource, done);
    });
  });

  /**
   * Passes test when MpdRequest is sent.
   * @param {string} targetMpdUrl The url that should be used in the MpdRequest.
   * @param {!IVideoSource} videoSource
   * @param {!done} done The done function, to signal the end of this test.
   */
  function requestMpdUpdate(targetMpdUrl, videoSource, done) {
    player.load(videoSource).then(function() {
      video.play();
      // Seek back to ensure there is enough video to get an update.
      video.currentTime -= 10;
      return waitForMpdRequest(targetMpdUrl);
    }).then(function() {
      expect(video.currentTime).toBeGreaterThan(0.0);
      done();
    }).catch(function(error) {
      fail(error);
      done();
    });
  }

  /**
   * Seeks before the seek range start, then completes if video snaps back to
   * seek range start or fails otherwise.
   * @param {!IVideoSource} videoSource
   * @param {!done} done The done function, to signal the end of this test.
   */
  function seekBeforeRange(videoSource, done) {
    player.load(videoSource).then(function() {
      video.play();
      return waitForMovement(video, eventManager);
    }).then(function() {
      video.currentTime = videoSource.seekStartTime_ - SEEK_OFFSET;
      return waitForMovement(video, eventManager);
    }).then(function() {
      expect(videoSource.video.currentTime).toBeGreaterThan(
          videoSource.seekStartTime_);
      done();
    }).catch(function(error) {
      fail(error);
      done();
    });
  }

  /**
   * Seeks after the seek range end, then completes if video snaps back to seek
   * range end or fails otherwise.
   * @param {!IVideoSource} videoSource
   * @param {!done} done The done function, to signal the end of this test.
   */
  function seekAfterRange(videoSource, done) {
    player.load(videoSource).then(function() {
      video.play();
      return waitForMovement(video, eventManager);
    }).then(function() {
      video.currentTime = videoSource.seekEndTime_ + SEEK_OFFSET;
      return waitForMovement(video, eventManager);
    }).then(function() {
      expect(videoSource.video.currentTime).toBeLessThan(
          videoSource.seekEndTime_ + SMALL_FUDGE_FACTOR);
      done();
    }).catch(function(error) {
      fail(error);
      done();
    });
  }

  /**
   * @param {string} targetMpdUrl The url that should be used in the MpdRequest.
   * {!Promise} resolved when an MpdRequest has been sent.
   */
  function waitForMpdRequest(targetMpdUrl) {
    var requestStatus = new shaka.util.PublicPromise();
    var MpdRequest = shaka.dash.MpdRequest;

    spyOn(window.shaka.dash, 'MpdRequest').and.callFake(function(mpdUrl) {
      expect(mpdUrl).toEqual(targetMpdUrl);
      var request = new MpdRequest(mpdUrl);
      spyOn(request, 'send').and.callFake(function() {
        requestStatus.resolve();
        var error = new Error();
        error.type = 'fake';
        return Promise.reject(error);
      });
      return request;
    });

    return requestStatus;
  }
});
<commit_msg>Timestamp correction test for segment duration template.<commit_after>/**
 * Copyright 2015 Google Inc.
 *
 * 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.
 *
 * @fileoverview Live integration tests.
 */

goog.require('shaka.dash.MpdRequest');
goog.require('shaka.media.Stream');
goog.require('shaka.player.Player');
goog.require('shaka.polyfill.installAll');
goog.require('shaka.util.EventManager');

describe('Player', function() {
  var originalTimeout;
  var video;
  var videoSource;
  var player;
  var eventManager;

  const segmentDurationManifestUrl =
      'http://vm2.dashif.org/livesim/testpic_6s/Manifest.mpd';
  const googleStorageUrl = 'http://storage.googleapis.com/';
  const segmentTimeDir =
      googleStorageUrl + 'widevine-demo-media/oops-segment-timeline-time/';
  const segmentTimeMpd =
      segmentTimeDir + 'oops-segment-timeline-time-oops_video.mpd';
  const segmentNumberDir =
      googleStorageUrl + 'widevine-demo-media/oops-segment-timeline-number/';
  const segmentNumberMpd =
      segmentNumberDir + 'oops-segment-timeline-number-oops_video.mpd';
  const segmentNumberMinusFiveMpd =
      segmentNumberDir + 'oops-segment-timeline-number-oops_video_minus_5.mpd';
  const segmentNumberPlusFiveMpd =
      segmentNumberDir + 'oops-segment-timeline-number-oops_video_plus_5.mpd';

  const FUDGE_FACTOR = 2;
  const SMALL_FUDGE_FACTOR = 1;
  const SEEK_OFFSET = 10000;

  beforeAll(function() {
    // Hijack assertions and convert failed assertions into failed tests.
    assertsToFailures.install();

    // Change the timeout.
    originalTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL;
    jasmine.DEFAULT_TIMEOUT_INTERVAL = 30000;  // ms

    // Install polyfills.
    shaka.polyfill.installAll();

    // Create a video tag.  This will be visible so that long tests do not
    // create the illusion of the test-runner being hung.
    video = document.createElement('video');
    video.crossOrigin = 'anonymous';
    video.width = 600;
    video.height = 400;
    // Add it to the DOM.
    document.body.appendChild(video);
  });

  beforeEach(function() {
    // Create a new player.
    player = new shaka.player.Player(video);
    player.addEventListener('error', convertErrorToTestFailure, false);

    // Disable automatic adaptation unless it is needed for a test.
    // This makes test results more reproducible.
    player.enableAdaptation(false);

    eventManager = new shaka.util.EventManager();
  });

  afterEach(function(done) {
    eventManager.destroy();
    eventManager = null;

    player.destroy().then(function() {
      player = null;
      done();
    });
  });

  afterAll(function() {
    // Remove the video tag from the DOM.
    document.body.removeChild(video);

    // Restore the timeout.
    jasmine.DEFAULT_TIMEOUT_INTERVAL = originalTimeout;

    // Restore normal assertion behavior.
    assertsToFailures.uninstall();
  });

  describe('live support for segment duration template', function() {
    beforeEach(function() {
      videoSource = newSource(segmentDurationManifestUrl);
    });

    it('plays two full segments of content', function(done) {
      const SEGMENT_LENGTH = 6;

      var setTestExpectations = function(segmentIndexes) {
        console.assert(segmentIndexes.length > 0);
        var index1 = segmentIndexes[0];
        var index2 = segmentIndexes[1];

        var min = Math.min(index1.last().startTime, index2.last().startTime);
        var streamStartTime =
            Math.max(min - videoSource.manifestInfo.minBufferTime, 0);

        var targetTime = streamStartTime + (2 * SEGMENT_LENGTH);
        var waitTime = (2 * SEGMENT_LENGTH) + FUDGE_FACTOR;

        waitForTargetTime(
            video, eventManager, targetTime, waitTime).then(function() {
          done();
        }).catch(function(error) {
          fail(error);
          done();
        });
      };

      waitForOnAllStreamsStarted(setTestExpectations);

      player.load(videoSource).then(function() {
        video.play();
      }).catch(function(error) {
        fail(error);
        done();
      });
    });

    it('returns to seek range when seeking before start', function(done) {
      seekBeforeRange(videoSource, done);
    });

    it('returns to end of seek range when after end', function(done) {
      seekAfterRange(videoSource, done);
    });
  });

  describe('live support for segment number template', function() {
    beforeEach(function() {
      videoSource = newSource(segmentNumberMpd);
    });

    it('requests MPD update in expected time', function(done) {
      requestMpdUpdate(segmentNumberMpd, videoSource, done);
    });

    it('returns to seek range when seeking before start', function(done) {
      seekBeforeRange(videoSource, done);
    });

    it('returns to end of seek range when after end', function(done) {
      seekAfterRange(videoSource, done);
    });

    it('corrects a timestamp that is behind', function(done) {
      correctTimestamp(segmentNumberMinusFiveMpd, 18, done);
    });

    it('corrects a timestamp that is ahead', function(done) {
      correctTimestamp(segmentNumberPlusFiveMpd, 19, done);
    });
  });

  describe('live support for segment time template', function() {
    beforeEach(function() {
      videoSource = newSource(segmentTimeMpd);
    });

    it('requests MPD update in expected time', function(done) {
      requestMpdUpdate(segmentTimeMpd, videoSource, done);
    });

    it('returns to seek range when seeking before start', function(done) {
      seekBeforeRange(videoSource, done);
    });

    it('returns to end of seek range when after end', function(done) {
      seekAfterRange(videoSource, done);
    });
  });

  /**
   * Passes test if timestamp is corrected properly and playback starts.
   * @param {string} manifestUrl
   * @param {number} initalReference The reference at which playback will start.
   * @param {!done} done The done function, to signal the end of this test.
   */
  function correctTimestamp(manifestUrl, initialReference, done) {
    videoSource = newSource(manifestUrl);
    var setTestExpectations = function(segmentIndexes) {
      console.assert(segmentIndexes.length > 0);
      var index = segmentIndexes[0];
      expect(video.buffered.start(0)).toEqual(
          index.references[initialReference].startTime);
      waitForMovement(video, eventManager).then(function() {
        done();
      }).catch(function(error) {
        fail(error);
        done();
      });
    };

    waitForOnAllStreamsStarted(setTestExpectations);

    player.load(videoSource).then(function() {
      video.play();
    }).catch(function(error) {
      fail(error);
      done();
    });
  }

  /**
   * Passes test when MpdRequest is sent.
   * @param {string} targetMpdUrl The url that should be used in the MpdRequest.
   * @param {!IVideoSource} videoSource
   * @param {!done} done The done function, to signal the end of this test.
   */
  function requestMpdUpdate(targetMpdUrl, videoSource, done) {
    player.load(videoSource).then(function() {
      video.play();
      // Seek back to ensure there is enough video to get an update.
      video.currentTime -= 10;
      return waitForMpdRequest(targetMpdUrl);
    }).then(function() {
      expect(video.currentTime).toBeGreaterThan(0.0);
      done();
    }).catch(function(error) {
      fail(error);
      done();
    });
  }

  /**
   * Seeks before the seek range start, then completes if video snaps back to
   * seek range start or fails otherwise.
   * @param {!IVideoSource} videoSource
   * @param {!done} done The done function, to signal the end of this test.
   */
  function seekBeforeRange(videoSource, done) {
    player.load(videoSource).then(function() {
      video.play();
      return waitForMovement(video, eventManager);
    }).then(function() {
      video.currentTime = videoSource.seekStartTime_ - SEEK_OFFSET;
      return waitForMovement(video, eventManager);
    }).then(function() {
      expect(videoSource.video.currentTime).toBeGreaterThan(
          videoSource.seekStartTime_);
      done();
    }).catch(function(error) {
      fail(error);
      done();
    });
  }

  /**
   * Seeks after the seek range end, then completes if video snaps back to seek
   * range end or fails otherwise.
   * @param {!IVideoSource} videoSource
   * @param {!done} done The done function, to signal the end of this test.
   */
  function seekAfterRange(videoSource, done) {
    player.load(videoSource).then(function() {
      video.play();
      return waitForMovement(video, eventManager);
    }).then(function() {
      video.currentTime = videoSource.seekEndTime_ + SEEK_OFFSET;
      return waitForMovement(video, eventManager);
    }).then(function() {
      expect(videoSource.video.currentTime).toBeLessThan(
          videoSource.seekEndTime_ + SMALL_FUDGE_FACTOR);
      done();
    }).catch(function(error) {
      fail(error);
      done();
    });
  }

  /**
   * @param {string} targetMpdUrl The url that should be used in the MpdRequest.
   * {!Promise} resolved when an MpdRequest has been sent.
   */
  function waitForMpdRequest(targetMpdUrl) {
    var requestStatus = new shaka.util.PublicPromise();
    var MpdRequest = shaka.dash.MpdRequest;

    spyOn(window.shaka.dash, 'MpdRequest').and.callFake(function(mpdUrl) {
      expect(mpdUrl).toEqual(targetMpdUrl);
      var request = new MpdRequest(mpdUrl);
      spyOn(request, 'send').and.callFake(function() {
        requestStatus.resolve();
        var error = new Error();
        error.type = 'fake';
        return Promise.reject(error);
      });
      return request;
    });

    return requestStatus;
  }

  /**
   * Waits until onAllStreamsStarted() has been called to catch the corrected
   * segmentIndexes, then bind it to the given function and set a Timeout to
   * call asynchronously.
   * @param {Function} fn The function to be called with the segmentIndexes.
   */
  function waitForOnAllStreamsStarted(fn) {
    var originalonAllStreamsStarted = videoSource.onAllStreamsStarted;
    videoSource.onAllStreamsStarted = function(segmentIndexes) {
      originalonAllStreamsStarted.call(videoSource, segmentIndexes);
      // Do this async.
      window.setTimeout(fn.bind(null, segmentIndexes), 0);
    };
  }
});
<|endoftext|>


<commit_before>(function(angular) {
'use strict';

  angular.module('app.<%= moduleName %>s', [])
  .config(function($stateProvider) {
    $stateProvider
    .state({
      name: 'app.<%= moduleName %>s',
      url: '/<%= moduleName %>s',
      abstract: true
    })
    .state({
      name: 'app.<%= moduleName %>s.list',
      url: '/',
      templateUrl: '/app/modules/<%= moduleName %>/templates/<%= moduleName %>.list.html',
      controller: '<%= moduleNameCap %>ListController',
      controllerAs: 'vm'
    })
    .state({
      name: 'app.<%= moduleName %>s.create',
      url: '/create',
      templateUrl: '/app/modules/<%= moduleName %>/templates/<%= moduleName %>.update.html',
      controller: '<%= moduleNameCap %>UpdateController',
      resolve: {
        <%= moduleName %>Resolve: new<%= moduleName %>
      },
      controllerAs: 'vm'
    })
    .state({
      name: 'app.<%= moduleName %>s.view',
      url: '/view/{id}',
      templateUrl: '/app/modules/<%= moduleName %>/templates/<%= moduleName %>.view.html',
      controller: '<%= moduleNameCap %>ViewController',
      controllerAs: 'vm'
    })
    .state({
      name: 'app.<%= moduleName %>s.update',
      url: '/update/{id}',
      templateUrl: '/app/modules/<%= moduleName %>/templates/<%= moduleName %>.update.html',
      controller: '<%= moduleNameCap %>UpdateController',
      resolve: {
        <%= moduleName %>Resolve: get<%= moduleName %>
      },
      controllerAs: 'vm'
    });
  });

  new<%= moduleNameCap %>.$inject = ['<%= moduleNameCap %>sService'];

  function new<%= moduleNameCap %>(<%= moduleNameCap %>sService) {
    return new <%= moduleNameCap %>sService();
  }

  get<%= moduleNameCap %>.$inject = ['$stateParams', '<%= moduleNameCap %>sService'];

  function get<%= moduleNameCap %>($stateParams, <%= moduleNameCap %>sService) {
    return <%= moduleNameCap %>sService.get({
      id: $stateParams.id
    }).$promise;
  }
})(angular);<commit_msg>bug fix<commit_after>(function(angular) {
'use strict';

  angular.module('app.<%= moduleName %>s', [])
  .config(function($stateProvider) {
    $stateProvider
    .state({
      name: 'app.<%= moduleName %>s',
      url: '/<%= moduleName %>s',
      abstract: true
    })
    .state({
      name: 'app.<%= moduleName %>s.list',
      url: '/',
      templateUrl: '/app/modules/<%= moduleName %>/templates/<%= moduleName %>.list.html',
      controller: '<%= moduleNameCap %>ListController',
      controllerAs: 'vm'
    })
    .state({
      name: 'app.<%= moduleName %>s.create',
      url: '/create',
      templateUrl: '/app/modules/<%= moduleName %>/templates/<%= moduleName %>.update.html',
      controller: '<%= moduleNameCap %>UpdateController',
      resolve: {
        <%= moduleName %>Resolve: new<%= moduleNameCap %>
      },
      controllerAs: 'vm'
    })
    .state({
      name: 'app.<%= moduleName %>s.view',
      url: '/view/{id}',
      templateUrl: '/app/modules/<%= moduleName %>/templates/<%= moduleName %>.view.html',
      controller: '<%= moduleNameCap %>ViewController',
      controllerAs: 'vm'
    })
    .state({
      name: 'app.<%= moduleName %>s.update',
      url: '/update/{id}',
      templateUrl: '/app/modules/<%= moduleName %>/templates/<%= moduleName %>.update.html',
      controller: '<%= moduleNameCap %>UpdateController',
      resolve: {
        <%= moduleName %>Resolve: get<%= moduleNameCap %>
      },
      controllerAs: 'vm'
    });
  });

  new<%= moduleNameCap %>.$inject = ['<%= moduleNameCap %>sService'];

  function new<%= moduleNameCap %>(<%= moduleNameCap %>sService) {
    return new <%= moduleNameCap %>sService();
  }

  get<%= moduleNameCap %>.$inject = ['$stateParams', '<%= moduleNameCap %>sService'];

  function get<%= moduleNameCap %>($stateParams, <%= moduleNameCap %>sService) {
    return <%= moduleNameCap %>sService.get({
      id: $stateParams.id
    }).$promise;
  }
})(angular);<|endoftext|>


<commit_before>/*
 *  WinkBridge.js
 *
 *  David Janes
 *  IOTDB.org
 *  2015-04-19
 *
 *  Copyright [2013-2015] [David P. Janes]
 *
 *  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.
 */

"use strict";

var iotdb = require('iotdb');
var _ = iotdb._;
var bunyan = iotdb.bunyan;

var WinkAPI = require('node-winkapi');

var logger = bunyan.createLogger({
    name: 'homestar-wink',
    module: 'WinkBridge',
});

/**
 *  See {iotdb.bridge.Bridge#Bridge} for documentation.
 *

 *  @param {object|undefined} native
 *  only used for instances, should be
 */
var WinkBridge = function (initd, native) {
    var self = this;

    self.initd = _.defaults(initd,
        iotdb.keystore().get("bridges/WinkBridge/initd"), {
            poll: 30
        }
    );
    self.native = native;   // the thing that does the work - keep this name

    if (self.native) {
        self.queue = _.queue("WinkBridge");
    }
};

WinkBridge.prototype = new iotdb.Bridge();

WinkBridge.prototype.name = function () {
    return "WinkBridge";
};

/* --- lifecycle --- */

/**
 *  See {iotdb.bridge.Bridge#XXX} for documentation.
 */
WinkBridge.prototype.discover = function () {
    var self = this;

    logger.info({
        method: "discover"
    }, "called");

    /*
     *  This is the core bit of discovery. As you find new
     *  things, make a new WinkBridge and call 'discovered'.
     *  The first argument should be self.initd, the second
     *  the thing that you do work with
     */
    var cfgd = iotdb.keystore().get("bridges/WinkBridge");
    if (!cfgd) {
        logger.error({
            method: "discover",
        }, "WinkBridge is not configured");
        return;
    }

    var wink_api = new WinkAPI.WinkAPI({
        clientID: cfgd.client_id,
        clientSecret: cfgd.client_secret,
    });
    wink_api.login(cfgd.username, cfgd.password, function(error) {
        if (error) {
            logger.error({
                method: "discover/login",
                error: error,
            }, "WinkBridge could not login");
            return;
        }

        wink_api.getDevices(function(error, devices) {
            if (error) {
                logger.error({
                    method: "discover/login/getDevices",
                    error: error,
                }, "WinkBridge could not getDevices");
                return;
            }

            for (var di in devices) {
                self.discovered(new WinkBridge(self.initd, devices[di]));
            }
        });

    });
};

/**
 *  See {iotdb.bridge.Bridge#XXX} for documentation.
 */
WinkBridge.prototype.connect = function (connectd) {
    var self = this;
    if (!self.native) {
        return;
    }

    self._setup_polling();
    self.pull();
};

WinkBridge.prototype._setup_polling = function () {
    var self = this;
    if (!self.initd.poll) {
        return;
    }

    var timer = setInterval(function () {
        if (!self.native) {
            clearInterval(timer);
            return;
        }

        self.pull();
    }, self.initd.poll * 1000);
};

WinkBridge.prototype._forget = function () {
    var self = this;
    if (!self.native) {
        return;
    }

    logger.info({
        method: "_forget"
    }, "called");

    self.native = null;
    self.pulled();
};

/**
 *  See {iotdb.bridge.Bridge#XXX} for documentation.
 */
WinkBridge.prototype.disconnect = function () {
    var self = this;
    if (!self.native || !self.native) {
        return;
    }

    self._forget();
};

/* --- data --- */

/**
 *  See {iotdb.bridge.Bridge#XXX} for documentation.
 */
WinkBridge.prototype.push = function (pushd) {
    var self = this;
    if (!self.native) {
        return;
    }

    logger.info({
        method: "push",
        putd: putd
    }, "push");

    var qitem = {
        // if you set "id", new pushes will unqueue old pushes with the same id
        // id: self.number,
        run: function () {
            self._pushd(pushd);
            self.queue.finished(qitem);
        }
    };
    self.queue.add(qitem);
};

/**
 *  Do the work of pushing. If you don't need queueing
 *  consider just moving this up into push
 */
WinkBridge.prototype._push = function (pushd) {
    if (pushd.on !== undefined) {
    }
};

/**
 *  See {iotdb.bridge.Bridge#XXX} for documentation.
 */
WinkBridge.prototype.pull = function () {
    var self = this;
    if (!self.native) {
        return;
    }
};

/* --- state --- */

/**
 *  See {iotdb.bridge.Bridge#XXX} for documentation.
 */
WinkBridge.prototype.meta = function () {
    var self = this;
    if (!self.native) {
        return;
    }

    return {
        "iot:thing": _.id.thing_urn.unique("Wink", "hub-" + self.native.props.hub_id, "id-" + self.native.id),
        "schema:name": self.native.name || "Wink",

        // other possibilites
        // "iot:thing": _.id.thing_urn.unique("Wink", self.native.uuid, self.initd.number),
        // "iot:number": self.initd.number,
        // "iot:device": _.id.thing_urn.unique("Wink", self.native.uuid),
        "schema:manufacturer": self.native.props.device_manufacturer,
        'iot:vendor/type': self.native.type,
        'iot:vendor/model': self.native.props.model_name,
    };

};

/**
 *  See {iotdb.bridge.Bridge#XXX} for documentation.
 */
WinkBridge.prototype.reachable = function () {
    return this.native !== null;
};

/**
 *  See {iotdb.bridge.Bridge#XXX} for documentation.
 */
WinkBridge.prototype.configure = function (app) {};

/* -- internals -- */

/*
 *  API
 */
exports.Bridge = WinkBridge;
<commit_msg>deal with 'connectd'<commit_after>/*
 *  WinkBridge.js
 *
 *  David Janes
 *  IOTDB.org
 *  2015-04-19
 *
 *  Copyright [2013-2015] [David P. Janes]
 *
 *  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.
 */

"use strict";

var iotdb = require('iotdb');
var _ = iotdb._;
var bunyan = iotdb.bunyan;

var WinkAPI = require('node-winkapi');

var logger = bunyan.createLogger({
    name: 'homestar-wink',
    module: 'WinkBridge',
});

/**
 *  See {iotdb.bridge.Bridge#Bridge} for documentation.
 *

 *  @param {object|undefined} native
 *  only used for instances, should be
 */
var WinkBridge = function (initd, native) {
    var self = this;

    self.initd = _.defaults(initd,
        iotdb.keystore().get("bridges/WinkBridge/initd"), {
            poll: 30
        }
    );
    self.native = native;   // the thing that does the work - keep this name

    if (self.native) {
        self.queue = _.queue("WinkBridge");
    }
};

WinkBridge.prototype = new iotdb.Bridge();

WinkBridge.prototype.name = function () {
    return "WinkBridge";
};

/* --- lifecycle --- */

/**
 *  See {iotdb.bridge.Bridge#XXX} for documentation.
 */
WinkBridge.prototype.discover = function () {
    var self = this;

    logger.info({
        method: "discover"
    }, "called");

    /*
     *  This is the core bit of discovery. As you find new
     *  things, make a new WinkBridge and call 'discovered'.
     *  The first argument should be self.initd, the second
     *  the thing that you do work with
     */
    var cfgd = iotdb.keystore().get("bridges/WinkBridge");
    if (!cfgd) {
        logger.error({
            method: "discover",
        }, "WinkBridge is not configured");
        return;
    }

    var wink_api = new WinkAPI.WinkAPI({
        clientID: cfgd.client_id,
        clientSecret: cfgd.client_secret,
    });
    wink_api.login(cfgd.username, cfgd.password, function(error) {
        if (error) {
            logger.error({
                method: "discover/login",
                error: error,
            }, "WinkBridge could not login");
            return;
        }

        wink_api.getDevices(function(error, devices) {
            if (error) {
                logger.error({
                    method: "discover/login/getDevices",
                    error: error,
                }, "WinkBridge could not getDevices");
                return;
            }

            for (var di in devices) {
                self.discovered(new WinkBridge(self.initd, devices[di]));
            }
        });

    });
};

/**
 *  See {iotdb.bridge.Bridge#XXX} for documentation.
 */
WinkBridge.prototype.connect = function (connectd) {
    var self = this;
    if (!self.native) {
        return;
    }

    self._validate_connect(connectd);

    self._setup_polling();
    self.pull();
};

WinkBridge.prototype._setup_polling = function () {
    var self = this;
    if (!self.initd.poll) {
        return;
    }

    var timer = setInterval(function () {
        if (!self.native) {
            clearInterval(timer);
            return;
        }

        self.pull();
    }, self.initd.poll * 1000);
};

WinkBridge.prototype._forget = function () {
    var self = this;
    if (!self.native) {
        return;
    }

    logger.info({
        method: "_forget"
    }, "called");

    self.native = null;
    self.pulled();
};

/**
 *  See {iotdb.bridge.Bridge#XXX} for documentation.
 */
WinkBridge.prototype.disconnect = function () {
    var self = this;
    if (!self.native || !self.native) {
        return;
    }

    self._forget();
};

/* --- data --- */

/**
 *  See {iotdb.bridge.Bridge#XXX} for documentation.
 */
WinkBridge.prototype.push = function (pushd) {
    var self = this;
    if (!self.native) {
        return;
    }

    logger.info({
        method: "push",
        putd: putd
    }, "push");

    var qitem = {
        // if you set "id", new pushes will unqueue old pushes with the same id
        // id: self.number,
        run: function () {
            self._pushd(pushd);
            self.queue.finished(qitem);
        }
    };
    self.queue.add(qitem);
};

/**
 *  Do the work of pushing. If you don't need queueing
 *  consider just moving this up into push
 */
WinkBridge.prototype._push = function (pushd) {
    if (pushd.on !== undefined) {
    }
};

/**
 *  See {iotdb.bridge.Bridge#XXX} for documentation.
 */
WinkBridge.prototype.pull = function () {
    var self = this;
    if (!self.native) {
        return;
    }
};

/* --- state --- */

/**
 *  See {iotdb.bridge.Bridge#XXX} for documentation.
 */
WinkBridge.prototype.meta = function () {
    var self = this;
    if (!self.native) {
        return;
    }

    return {
        "iot:thing": _.id.thing_urn.unique("Wink", "hub-" + self.native.props.hub_id, "id-" + self.native.id),
        "schema:name": self.native.name || "Wink",

        // other possibilites
        // "iot:thing": _.id.thing_urn.unique("Wink", self.native.uuid, self.initd.number),
        // "iot:number": self.initd.number,
        // "iot:device": _.id.thing_urn.unique("Wink", self.native.uuid),
        "schema:manufacturer": self.native.props.device_manufacturer,
        'iot:vendor/type': self.native.type,
        'iot:vendor/model': self.native.props.model_name,
    };

};

/**
 *  See {iotdb.bridge.Bridge#XXX} for documentation.
 */
WinkBridge.prototype.reachable = function () {
    return this.native !== null;
};

/**
 *  See {iotdb.bridge.Bridge#XXX} for documentation.
 */
WinkBridge.prototype.configure = function (app) {};

/* -- internals -- */

/*
 *  API
 */
exports.Bridge = WinkBridge;
<|endoftext|>


<commit_before>var _ = require("underscore");
var moment = require("moment");
var q = require("q");
var logger = require("../Application/Logger");
var util = require("util");

var Lock = require("../Application/ExecuteLocked");
var lock = new Lock();

var partitions = [];
var cleanIdlePartitionsAfterMinutes;

function Partition(partitionId, worker) {
    var self = this;
    this.partitionId = partitionId;
    this.worker = worker;
    this.updatedAt = moment().utc().format();

    setInterval(function() {
        logger.info(util.format("Partition cleanup fired for %d", partitionId));
        if(self.updatedAt < moment().utc().subtract(cleanIdlePartitionsAfterMinutes, 'minutes').format())
            lock.execWrite(function(){
                return q.Promise(function(resolve){
                  partitions.splice(_.findIndex(partitions, self), 1);
                  logger.info(util.format("Partition %d cleaned", partitionId));
                  resolve();
                });
            });
        logger.info(util.format("Partition cleanup completed for %d", partitionId));
    }, cleanIdlePartitionsAfterMinutes * 60 * 60);
}

function PartitionService(cleanIdlePartitions){
    if(cleanIdlePartitions === null || cleanIdlePartitions === undefined || isNaN(parseInt(cleanIdlePartitions)) || cleanIdlePartitions <= 0)
        throw new Error("cleanIdlePartitionsAfterMinutes required integer greater than 0");

    cleanIdlePartitionsAfterMinutes = cleanIdlePartitions;
}

PartitionService.prototype.get = function(partitionId) {
    return lock.execRead(function(){
       return q.Promise(function(resolve, reject){
            try {
                var maybePartition = _.findWhere(partitions, {partitionId: partitionId});

                if(maybePartition == undefined)
                    return resolve(null);

                maybePartition.updatedAt = moment().utc().format();

                resolve(maybePartition);

            } catch(ex){
                logger.error(ex);
                reject(ex);
            }
        });
    });
};

PartitionService.prototype.push = function(partitionId, worker) {
    return lock.execWrite(function() {
        return q.Promise(function(resolve){
            var partition = new Partition(partitionId, worker);
            partitions.push(partition);
            resolve(partition);
        });
    });
};

module.exports = PartitionService;<commit_msg>bugfix<commit_after>var _ = require("underscore");
var moment = require("moment");
var q = require("q");
var logger = require("../Application/Logger");
var util = require("util");

var Lock = require("../Application/ExecuteLocked");
var lock = new Lock();

var partitions = [];
var cleanIdlePartitionsAfterMinutes;

function Partition(partitionId, worker) {
    var self = this;
    this.partitionId = partitionId;
    this.worker = worker;
    this.updatedAt = moment().utc().format();

    setInterval(function() {
        logger.info(util.format("Partition cleanup fired for %d", partitionId));
        if(self.updatedAt < moment().utc().subtract(cleanIdlePartitionsAfterMinutes, 'minutes').format())
            lock.execWrite(function(){
                return q.Promise(function(resolve){
                  partitions.splice(_.findIndex(partitions, self), 1);
                  logger.info(util.format("Partition %d cleaned", partitionId));
                  resolve();
                });
            });
        logger.info(util.format("Partition cleanup completed for %d", partitionId));
    }, cleanIdlePartitionsAfterMinutes * 60 * 1000);
}

function PartitionService(cleanIdlePartitions){
    if(cleanIdlePartitions === null || cleanIdlePartitions === undefined || isNaN(parseInt(cleanIdlePartitions)) || cleanIdlePartitions <= 0)
        throw new Error("cleanIdlePartitionsAfterMinutes required integer greater than 0");

    cleanIdlePartitionsAfterMinutes = cleanIdlePartitions;
}

PartitionService.prototype.get = function(partitionId) {
    return lock.execRead(function(){
       return q.Promise(function(resolve, reject){
            try {
                var maybePartition = _.findWhere(partitions, {partitionId: partitionId});

                if(maybePartition == undefined)
                    return resolve(null);

                maybePartition.updatedAt = moment().utc().format();

                resolve(maybePartition);

            } catch(ex){
                logger.error(ex);
                reject(ex);
            }
        });
    });
};

PartitionService.prototype.push = function(partitionId, worker) {
    return lock.execWrite(function() {
        return q.Promise(function(resolve){
            var partition = new Partition(partitionId, worker);
            partitions.push(partition);
            resolve(partition);
        });
    });
};

module.exports = PartitionService;<|endoftext|>


<commit_before>// EXEC UTILITY
// exec is not a task. But it is still a very useful command for quickly execute
// any kind of the Teleport methods. Look at the jest tests.
// - tpt -e --script build --type staging --server express-webrouter, for instance
// will execute the scripts/staging_build.sh script found in your express webrouter server.
// - if no script arg is specified, then you can mention a method, and Teleport will execute it
// like for instance tpt -e --method getConfig
// - you can also specify the python language '--lang py', and in that case, it will a python
// method from the python teleport part that can be called

import childProcess from 'child_process'
import fs from 'fs'
import path from 'path'

const platformScriptNames = [
  'build',
  'deploy',
  'push',
  'run'
]

export function exec () {
  const { app, program, project, server, type } = this
  let command, scriptFile
  // execute a script
  const script = program.script
  if (typeof script !== 'undefined' && server) {
    let platform = program.platform
    let typedScript = script
    // look how many platforms we have
    if (typeof project.platformTemplateNames === 'undefined') {
      project.platformTemplateNames = this.getPlatformTemplates()
    }
    if (project.platformTemplateNames.length === 1 && platform !== 'heroku') {
      platform = project.platformTemplateNames[0]
    }
    // check
    if (
      platformScriptNames.includes(script) && platform &&
      // NOTE : this is where we need to do a little hacky workaround.
      // our default platform value is heroku, but our scripts in our templates
      // are by default kubernetes if they don't have a prefix platform
      (platform !== 'kubernetes' || platform !== 'teleport-snips')
    ) {
      typedScript = `${platform}_${script}`
    }
    if (type) {
      typedScript = `${type.name}_${typedScript}`
    }
    // find the file
    const scriptFileDir = path.join(server.dir, 'scripts', `${typedScript}.sh`)
    scriptFile = fs.readFileSync(scriptFileDir).toString('utf-8')
    command = `cd ${server.dir} && sh scripts/${typedScript}.sh`
  } else if (typeof program.method !== 'string') {
    console.log('You didn\'t specify a method to be called, please use the --method option for that')
    return
  // check if it is a python command
  } else if (program.lang === 'py') {
    command = `python ${app.pythonDir} ${program.method.replace(/[ ]*,[ ]*|[ ]+/g, ' ')}`
  } else {
    // then it is not a script or python command to process
    // it is a node method to call
    // we can return after
    this.consoleInfo(`Let\'s exec this method ${program.method}`)
    return this[program.method]()
  }
  // venv check
  if (app.venvDir) {
    command = `source ${app.venvDir}/bin/activate && ${command}`
  }
  if (program.shell !== 'concurrently') {
    this.consoleInfo('Let\'s exec this command !')
    this.consoleLog(command)
    if (scriptFile) {
      this.consoleInfo('Which corresponds to : ')
      this.consoleLog(scriptFile)
    }
    childProcess.execSync(command, { stdio: [0, 1, 2] })
  } else if (script) {
    this.execAddConcurrently(script, command)
    // execute the whole if it is the end of the combination
    if (this.mapIndex === (this.mapLength -1)) {
      this.execConcurrentlyOrDirectly(script)
    }
  } else {
    childProcess.execSync(command, { stdio: [0, 1, 2] })
  }
}

export function execAddConcurrently (script, command) {
  const concurrentlyCommandsString = `${script}ConcurrentlyCommands`
  if (typeof this[concurrentlyCommandsString] === 'undefined') (
    this[concurrentlyCommandsString] = []
  )
  this[`${script}ConcurrentlyCommands`].push(command)
}

export function execConcurrentlyOrDirectly(script) {
  const { app, program } = this
  if (program.shell !== 'concurrently') {
    this.consoleInfo(`Let\'s ${script}`)
    this.consoleLog(command)
    childProcess.execSync(command, { stdio: [0, 1, 2] })
  }
  else if (program.process === 'sync') {
    // NOTE: unfortunately, when we need to deploy several servers,
    // we cannot throw the heroku deploy commands
    // into parallel concurrently processed.
    // So we keep using concurrently to still display on the fly
    // the logs from the child processes but they are still sync.
    const command = this[`${script}ConcurrentlyCommands`]
      .map(concurrentlyCommand => `${app.concurrentlyDir} \"${concurrentlyCommand}\"`)
      .join(' && ')
    this.consoleLog(command)
    childProcess.execSync(command, { stdio: [0, 1, 2] })
  } else {
    const concurrentlyCommandsString = this[`${script}ConcurrentlyCommands`]
      .map(concurrentlyCommand => `\"${concurrentlyCommand}\"`)
      .join(' ')
    const command = `${app.concurrentlyDir} --kill-others ${concurrentlyCommandsString}`
    this.consoleLog(command)
    childProcess.execSync(command, { stdio: [0, 1, 2] })
  }
}
<commit_msg>fixup to make tpt chose automatically the good platform option<commit_after>// EXEC UTILITY
// exec is not a task. But it is still a very useful command for quickly execute
// any kind of the Teleport methods. Look at the jest tests.
// - tpt -e --script build --type staging --server express-webrouter, for instance
// will execute the scripts/staging_build.sh script found in your express webrouter server.
// - if no script arg is specified, then you can mention a method, and Teleport will execute it
// like for instance tpt -e --method getConfig
// - you can also specify the python language '--lang py', and in that case, it will a python
// method from the python teleport part that can be called

import childProcess from 'child_process'
import fs from 'fs'
import path from 'path'

const platformScriptNames = [
  'build',
  'deploy',
  'push',
  'run'
]

export function exec () {
  const { app, program, project, server, type } = this
  let command, scriptFile
  // execute a script
  const script = program.script
  if (typeof script !== 'undefined' && server) {
    let platform = program.platform
    let typedScript = script
    // look how many platforms we have
    if (typeof project.platformTemplateNames === 'undefined') {
      project.platformTemplateNames = this.getPlatformTemplates()
    }
    // if there is only one platform to deploy, then necessary force
    // the deploy on that one
    if (project.platformTemplateNames.length === 1) {
      platform = project.platformTemplateNames[0]
    }
    // check
    if (
      platformScriptNames.includes(script) && platform &&
      // NOTE : this is where we need to do a little hacky workaround.
      // our default platform value is heroku, but our scripts in our templates
      // are by default kubernetes if they don't have a prefix platform
      platform !== 'kubernetes' && platform !== 'teleport-snips'
    ) {
      typedScript = `${platform}_${script}`
    }
    if (type) {
      typedScript = `${type.name}_${typedScript}`
    }
    // find the file
    const scriptFileDir = path.join(server.dir, 'scripts', `${typedScript}.sh`)
    scriptFile = fs.readFileSync(scriptFileDir).toString('utf-8')
    command = `cd ${server.dir} && sh scripts/${typedScript}.sh`
  } else if (typeof program.method !== 'string') {
    console.log('You didn\'t specify a method to be called, please use the --method option for that')
    return
  // check if it is a python command
  } else if (program.lang === 'py') {
    command = `python ${app.pythonDir} ${program.method.replace(/[ ]*,[ ]*|[ ]+/g, ' ')}`
  } else {
    // then it is not a script or python command to process
    // it is a node method to call
    // we can return after
    this.consoleInfo(`Let\'s exec this method ${program.method}`)
    return this[program.method]()
  }
  // venv check
  if (app.venvDir) {
    command = `source ${app.venvDir}/bin/activate && ${command}`
  }
  if (program.shell !== 'concurrently') {
    this.consoleInfo('Let\'s exec this command !')
    this.consoleLog(command)
    if (scriptFile) {
      this.consoleInfo('Which corresponds to : ')
      this.consoleLog(scriptFile)
    }
    childProcess.execSync(command, { stdio: [0, 1, 2] })
  } else if (script) {
    this.execAddConcurrently(script, command)
    // execute the whole if it is the end of the combination
    if (this.mapIndex === (this.mapLength -1)) {
      this.execConcurrentlyOrDirectly(script)
    }
  } else {
    childProcess.execSync(command, { stdio: [0, 1, 2] })
  }
}

export function execAddConcurrently (script, command) {
  const concurrentlyCommandsString = `${script}ConcurrentlyCommands`
  if (typeof this[concurrentlyCommandsString] === 'undefined') (
    this[concurrentlyCommandsString] = []
  )
  this[`${script}ConcurrentlyCommands`].push(command)
}

export function execConcurrentlyOrDirectly(script) {
  const { app, program } = this
  if (program.shell !== 'concurrently') {
    this.consoleInfo(`Let\'s ${script}`)
    this.consoleLog(command)
    childProcess.execSync(command, { stdio: [0, 1, 2] })
  }
  else if (program.process === 'sync') {
    // NOTE: unfortunately, when we need to deploy several servers,
    // we cannot throw the heroku deploy commands
    // into parallel concurrently processed.
    // So we keep using concurrently to still display on the fly
    // the logs from the child processes but they are still sync.
    const command = this[`${script}ConcurrentlyCommands`]
      .map(concurrentlyCommand => `${app.concurrentlyDir} \"${concurrentlyCommand}\"`)
      .join(' && ')
    this.consoleLog(command)
    childProcess.execSync(command, { stdio: [0, 1, 2] })
  } else {
    const concurrentlyCommandsString = this[`${script}ConcurrentlyCommands`]
      .map(concurrentlyCommand => `\"${concurrentlyCommand}\"`)
      .join(' ')
    const command = `${app.concurrentlyDir} --kill-others ${concurrentlyCommandsString}`
    this.consoleLog(command)
    childProcess.execSync(command, { stdio: [0, 1, 2] })
  }
}
<|endoftext|>


<commit_before>import csjs from 'csjs-inject';
import React, { createElement as r } from 'react';
import Frame from 'react-frame-component';

import { Button, IFrame, Link, Spacer, Step } from '../components/index.js';
import renderBack from '../lib/render-back';
import renderFront from '../lib/render-front';

export default class PreviewStep extends React.Component {

  render () {
    const { address, image, message, preview  } = this.props.postcard;
    const size = this.props.postcard.size.sizes[this.props.postcard.size.selectedIndex];
    const fromAddress = address.addresses[address.selectedFromIndex];
    const toAddress = address.addresses[address.selectedToIndex];

    const scale = 800 / 100 / size.width;
    const topBottom = -(size.height * 100 - size.height * 100 * scale) / 2;
    const leftRight = -(size.width  * 100 - size.width  * 100 * scale) / 2;
    const styles = csjs`
      .frame {
        border: 1px solid black;
        margin: ${topBottom}px ${leftRight}px;
        transform: scale(${scale}, ${scale});
      }

      .sideLabel {
        font-style: italic;
      }
    `;

    const render = preview.side === 'front'
      ? renderFront({ image, size, isPreview: true })
      : renderBack({ size, message, fromAddress, toAddress, isPreview: true });

    return r(Step, { title: 'preview postcard' },
      r(Spacer, { height: '5px' }),
      r(Frame, { className: styles.frame, width: size.width * 100, height: size.height * 100 }, render),
      r(Spacer, { height: '5px' }),
      r('div', null,
        r('p', { className: styles.sideLabel }, preview.side),
        r(Spacer, { height: '5px' }),
        r(Button, { onClick: this.handleFlipClick.bind(this) }, 'flip')
      ),
      r(Spacer),
      r(Link, { to: '/to' }, 'back'),
      r(Link, { to: '/send' }, 'next')
    );
  }

  handleFlipClick () {
    const newSide = this.props.postcard.preview.side === 'front' ? 'back' : 'front';
    this.props.changeSelectedPreviewSide(newSide);
  }

}
<commit_msg>fixed scaling<commit_after>import csjs from 'csjs-inject';
import React, { createElement as r } from 'react';
import Frame from 'react-frame-component';

import { Button, IFrame, Link, Spacer, Step } from '../components/index.js';
import renderBack from '../lib/render-back';
import renderFront from '../lib/render-front';

export default class PreviewStep extends React.Component {

  render () {
    const { address, image, message, preview  } = this.props.postcard;
    const size = this.props.postcard.size.sizes[this.props.postcard.size.selectedIndex];
    const fromAddress = address.addresses[address.selectedFromIndex];
    const toAddress = address.addresses[address.selectedToIndex];

    const scale = 300 / 100 / size.width;
    const topBottom = -(size.height * 100 - size.height * 100 * scale) / 2;
    const leftRight = -(size.width  * 100 - size.width  * 100 * scale) / 2;
    const styles = csjs`
      .frame {
        border: 1px solid black;
        margin: ${topBottom}px ${leftRight}px;
        transform: scale(${scale}, ${scale});
      }

      .sideLabel {
        font-style: italic;
      }
    `;

    const render = preview.side === 'front'
      ? renderFront({ image, size, isPreview: true })
      : renderBack({ size, message, fromAddress, toAddress, isPreview: true });

    return r(Step, { title: 'preview postcard' },
      r(Spacer, { height: '5px' }),
      r(Frame, { className: styles.frame, width: size.width * 100, height: size.height * 100 }, render),
      r(Spacer, { height: '5px' }),
      r('div', null,
        r('p', { className: styles.sideLabel }, preview.side),
        r(Spacer, { height: '5px' }),
        r(Button, { onClick: this.handleFlipClick.bind(this) }, 'flip')
      ),
      r(Spacer),
      r(Link, { to: '/to' }, 'back'),
      r(Link, { to: '/send' }, 'next')
    );
  }

  handleFlipClick () {
    const newSide = this.props.postcard.preview.side === 'front' ? 'back' : 'front';
    this.props.changeSelectedPreviewSide(newSide);
  }

}
<|endoftext|>