<commit_before>window.cls = window.cls || {};
cls.ResourceUtil = {}
/**
 * Utility class with static members and methods for the resource and network
 * views.
 */

/**
 * Return a human readable string representation of n bytes
 */
cls.ResourceUtil.bytes_to_human_readable = function(bytes)
{
  if (bytes >= 1048576) // megabytes
  {
    return "" + ((bytes / 1048576).toFixed(2)) + "MB";
  }
  else if (bytes >= 10240)
  {
    return "" + Math.ceil((bytes / 1024)) + "KB";
  }
  else
  {
    return "" + bytes + "B";
  }
}

/**
 * Return a human readable string representation of n millis
 */
cls.ResourceUtil.millis_to_human_readable = function(millis)
{
  if (millis > 10000) // > 10 seconds
  {
    return "" + ((millis / 1000).toFixed(1)) + "s";
  }
  else if (millis > 1000) // > 1 second
  {
    return "" + (millis / 1000).toFixed(2) + "s";
  }
  else
  {
    return "" + millis + "ms";
  }
}

/**
 * Common extensions mapped to generic type strings
 */
cls.ResourceUtil.extension_type_map = {
  png: "image",
  jpg: "image",
  bmp: "image",
  pcx: "image",
  ico: "image",
  jpeg: "image",

  oex: "extension",

  otf: "font",
  ttf: "font",
}

/**
 * Common mime types mapped to more generic strings
 */
cls.ResourceUtil.mime_type_map = {
  "text/html": "markup",
  "application/xhtml+xml": "markup",
  "application/mathml+xml": "markup",
  "application/xslt+xml": "markup",
  "text/xsl": "markup",
  "application/xml": "markup",

  "text/css": "css",

  "text/plain": "text",

  "application/x-javascript": "script",
  "application/javascript": "script",
  "text/javascript": "script",

  "image/png": "image",
  "image/gif": "image",
  "image/jpeg": "image",
  "image/x-icon": "image",
  "image/vnd.microsoft.icon": "image",
  "image/svg+xml": "image",
  "image/vnd.wap.wbmp": "image",

  "application/vnd.ms-fontobject": "font",
  "application/x-font-ttf": "font",
  "font/ttf": "font",
  "font/otf": "font",
  "font/opentype": "font",
  "application/x-woff": "font",

  "audio/mid": "audio",
  "audio/mpeg": "audio",
  "audio/ogg": "audio",
  "audio/vorbis": "audio",
  "audio/x-pn-realaudio": "audio",
  "audio/wav": "audio",
  "audio/x-wav": "audio",
  "audio/vnd.wave": "audio",
  "audio/x-ms-wax": "audio",
  "audio/x-ms-wma": "audio",

  "video/3gpp": "video",
  "video/x-matroska": "video",
  "video/mp4": "video",
  "video/mpeg": "video",
  "video/ogg": "video",
  "video/quicktime": "video",
  "video/webm": "video",
  "video/x-ms-wmv": "video",

  "application/json": "data",
  "application/rdf+xml": "data",
  "text/rdf+n3": "data",
  "application/x-turtle": "data",
  "text/turtle": "data",

  "application/atom+xml": "feed",
  "application/rss+xml": "feed",

  "application/pdf": "pdf",
  "application/postscript": "postscript",
  "application/x-shockwave-flash": "flash",
  "application/x-silverlight-app": "silverlight"
}

/**
 * Returns the most sensible way of getting this resource,
 * as datauri or string, based on the mime type.
 */
cls.ResourceUtil.mime_to_content_mode = function(mime)
{
  var type = cls.ResourceUtil.mime_to_type(mime);
  switch (type) {
    case "image":
    case "pdf":
    case "flash":
    case "font":
      return "datauri";
    case "markup":
    case "css":
    case "xml":
    case "script":
      return "text";
  }
  return "text";
}

cls.ResourceUtil.mime_to_type = function(mime, extension)
{
  return this.mime_type_map[mime];
}

cls.ResourceUtil.path_to_type = function(path)
{
  var extension = path.slice(path.lastIndexOf(".") + 1);
}

cls.ResourceUtil.url_path = function(url)
{
  if (!url) { return null; }
  var firstslash = url.replace("://", "xxx").indexOf("/");
  var querystart = url.indexOf("?");
  if (querystart == -1) { querystart = url.length; }
  var path = url.slice(firstslash, querystart);
  return path;
}

cls.ResourceUtil.url_filename = function(url)
{
  var path = cls.ResourceUtil.url_path(url);
  var lastslash = path.lastIndexOf("/");
  if (lastslash < 1) // 0 and -1 both mean there is no file name
  {
    return null;
  }
  else {
    return path.slice(lastslash+1);
  }
}

cls.ResourceUtil.url_host = function(url)
{
  if (!url) { return null; }
  var host = url.replace(/\w+?:\/\//, "");
  var firstslash = host.indexOf("/");
  host = host.slice(0, firstslash == -1 ? host.length : firstslash);
  return host;
}

cls.ResourceUtil.header_presets = [
  {name: "None", headers: ""},
  {name: "Chrome 9", headers:
    [
      "Accept: application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5",
      "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3",
      "Accept-Encoding: gzip,deflate,sdch",
      "Accept-Language: en-GB,en-US;q=0.8,fr;q=0.6,en;q=0.4",
      "User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US) AppleWebKit/534.13 (KHTML, like Gecko) Chrome/9.0.597.98 Safari/534.13",
    ].join("\n")
  },
  {name: "Firefox 4 beta 11", headers:
    [
      "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
      "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7",
      "Accept-Encoding: gzip,deflate,sdch",
      "Accept-Language: en-gb,en;q=0.5",
      "Connection: keep-alive",
      "Keep-Alive: 115",
      "User-Agent: Mozilla/5.0 (Windows NT 6.0; rv:2.0b11) Gecko/20100101 Firefox/4.0b11",
    ].join("\n")
  },
  {name: "IE8 (compatibility mode)", headers:
    [
      "Accept: */*",
      "Accept-Encoding: gzip, deflate",
      "Accept-Language: en-gb",
      "Connection: Keep-Alive",
      "User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; Trident/4.0; chromeframe; SLCC1; .NET CLR 2.0.50727; Media Center PC 5.0; MDDC; .NET CLR 3.5.30729; .NET CLR 1.1.4322; .NET CLR 3.0.30729; .NET4.0C)",
    ].join("\n")
  },
  {name: "IE8", headers:
    [
      "Accept: image/gif, image/jpeg, image/pjpeg, application/x-ms-application, application/vnd.ms-xpsdocument, application/xaml+xml, application/x-ms-xbap, application/x-shockwave-flash, */*",
      "Accept-Encoding: gzip, deflate",
      "Accept-Language: en-gb",
      "Connection: Keep-Alive",
      "User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0; chromeframe; SLCC1; .NET CLR 2.0.50727; Media Center PC 5.0; MDDC; .NET CLR 3.5.30729; .NET CLR 1.1.4322; .NET CLR 3.0.30729; .NET4.0C)",
    ].join("\n")
  },
  {name: "IE9 platform preview", headers:
    [
      "Accept: text/html, application/xhtml+xml",
      "Accept-Encoding: gzip, deflate",
      "Accept-Language: en-gb",
      "Connection: Keep-Alive",
      "User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.0; Trident/5.0)",
    ].join("\n")
  },
  {name: "Opera 11.01", headers:
    [
      "Accept: text/html, application/xml;q=0.9, application/xhtml+xml, image/png, image/jpeg, image/gif, image/x-xbitmap, */*;q=0.1",
      "Accept-Charset: iso-8859-1, utf-8, utf-16, *;q=0.1",
      "Accept-Encoding: deflate, gzip, x-gzip, identity, *;q=0",
      "Accept-Language: en-gb,en;q=0.9",
      "Connection: Keep-Alive, TE",
      "Keep-Alive: 115",
      "Transfer-Encoding: deflate, gzip, chunked, identity, trailers",
      "User-Agent: Opera/9.80 (Windows NT 6.0; U; en-GB) Presto/2.7.62 Version/11.01",
    ].join("\n")
  },
  {name: "Safari 5", headers:
    [
      "Accept: application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5",
      "Accept-Encoding: gzip, deflate",
      "Accept-Language: en-us",
      "Connection: keep-alive",
      "User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US) AppleWebKit/533.19.4 (KHTML, like Gecko) Version/5.0.3 Safari/533.19.4",
    ].join("\n")
  },
];
<commit_msg>Fixed mime-to-type mapping for application/json. Fixes DFL-1639<commit_after>window.cls = window.cls || {};
cls.ResourceUtil = {}
/**
 * Utility class with static members and methods for the resource and network
 * views.
 */

/**
 * Return a human readable string representation of n bytes
 */
cls.ResourceUtil.bytes_to_human_readable = function(bytes)
{
  if (bytes >= 1048576) // megabytes
  {
    return "" + ((bytes / 1048576).toFixed(2)) + "MB";
  }
  else if (bytes >= 10240)
  {
    return "" + Math.ceil((bytes / 1024)) + "KB";
  }
  else
  {
    return "" + bytes + "B";
  }
}

/**
 * Return a human readable string representation of n millis
 */
cls.ResourceUtil.millis_to_human_readable = function(millis)
{
  if (millis > 10000) // > 10 seconds
  {
    return "" + ((millis / 1000).toFixed(1)) + "s";
  }
  else if (millis > 1000) // > 1 second
  {
    return "" + (millis / 1000).toFixed(2) + "s";
  }
  else
  {
    return "" + millis + "ms";
  }
}

/**
 * Common extensions mapped to generic type strings
 */
cls.ResourceUtil.extension_type_map = {
  png: "image",
  jpg: "image",
  bmp: "image",
  pcx: "image",
  ico: "image",
  jpeg: "image",

  oex: "extension",

  otf: "font",
  ttf: "font",
}

/**
 * Common mime types mapped to more generic strings
 */
cls.ResourceUtil.mime_type_map = {
  "text/html": "markup",
  "application/xhtml+xml": "markup",
  "application/mathml+xml": "markup",
  "application/xslt+xml": "markup",
  "text/xsl": "markup",
  "application/xml": "markup",

  "text/css": "css",

  "text/plain": "text",

  "application/x-javascript": "script",
  "application/javascript": "script",
  "text/javascript": "script",
  "application/json": "script",

  "image/png": "image",
  "image/gif": "image",
  "image/jpeg": "image",
  "image/x-icon": "image",
  "image/vnd.microsoft.icon": "image",
  "image/svg+xml": "image",
  "image/vnd.wap.wbmp": "image",

  "application/vnd.ms-fontobject": "font",
  "application/x-font-ttf": "font",
  "font/ttf": "font",
  "font/otf": "font",
  "font/opentype": "font",
  "application/x-woff": "font",

  "audio/mid": "audio",
  "audio/mpeg": "audio",
  "audio/ogg": "audio",
  "audio/vorbis": "audio",
  "audio/x-pn-realaudio": "audio",
  "audio/wav": "audio",
  "audio/x-wav": "audio",
  "audio/vnd.wave": "audio",
  "audio/x-ms-wax": "audio",
  "audio/x-ms-wma": "audio",

  "video/3gpp": "video",
  "video/x-matroska": "video",
  "video/mp4": "video",
  "video/mpeg": "video",
  "video/ogg": "video",
  "video/quicktime": "video",
  "video/webm": "video",
  "video/x-ms-wmv": "video",

  "application/rdf+xml": "data",
  "text/rdf+n3": "data",
  "application/x-turtle": "data",
  "text/turtle": "data",

  "application/atom+xml": "feed",
  "application/rss+xml": "feed",

  "application/pdf": "pdf",
  "application/postscript": "postscript",
  "application/x-shockwave-flash": "flash",
  "application/x-silverlight-app": "silverlight"
}

/**
 * Returns the most sensible way of getting this resource,
 * as datauri or string, based on the mime type.
 */
cls.ResourceUtil.mime_to_content_mode = function(mime)
{
  var type = cls.ResourceUtil.mime_to_type(mime);
  switch (type) {
    case "image":
    case "pdf":
    case "flash":
    case "font":
      return "datauri";
    case "markup":
    case "css":
    case "xml":
    case "script":
      return "text";
  }
  return "text";
}

cls.ResourceUtil.mime_to_type = function(mime, extension)
{
  return this.mime_type_map[mime];
}

cls.ResourceUtil.path_to_type = function(path)
{
  var extension = path.slice(path.lastIndexOf(".") + 1);
}

cls.ResourceUtil.url_path = function(url)
{
  if (!url) { return null; }
  var firstslash = url.replace("://", "xxx").indexOf("/");
  var querystart = url.indexOf("?");
  if (querystart == -1) { querystart = url.length; }
  var path = url.slice(firstslash, querystart);
  return path;
}

cls.ResourceUtil.url_filename = function(url)
{
  var path = cls.ResourceUtil.url_path(url);
  var lastslash = path.lastIndexOf("/");
  if (lastslash < 1) // 0 and -1 both mean there is no file name
  {
    return null;
  }
  else {
    return path.slice(lastslash+1);
  }
}

cls.ResourceUtil.url_host = function(url)
{
  if (!url) { return null; }
  var host = url.replace(/\w+?:\/\//, "");
  var firstslash = host.indexOf("/");
  host = host.slice(0, firstslash == -1 ? host.length : firstslash);
  return host;
}

cls.ResourceUtil.header_presets = [
  {name: "None", headers: ""},
  {name: "Chrome 9", headers:
    [
      "Accept: application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5",
      "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3",
      "Accept-Encoding: gzip,deflate,sdch",
      "Accept-Language: en-GB,en-US;q=0.8,fr;q=0.6,en;q=0.4",
      "User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US) AppleWebKit/534.13 (KHTML, like Gecko) Chrome/9.0.597.98 Safari/534.13",
    ].join("\n")
  },
  {name: "Firefox 4 beta 11", headers:
    [
      "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
      "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7",
      "Accept-Encoding: gzip,deflate,sdch",
      "Accept-Language: en-gb,en;q=0.5",
      "Connection: keep-alive",
      "Keep-Alive: 115",
      "User-Agent: Mozilla/5.0 (Windows NT 6.0; rv:2.0b11) Gecko/20100101 Firefox/4.0b11",
    ].join("\n")
  },
  {name: "IE8 (compatibility mode)", headers:
    [
      "Accept: */*",
      "Accept-Encoding: gzip, deflate",
      "Accept-Language: en-gb",
      "Connection: Keep-Alive",
      "User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; Trident/4.0; chromeframe; SLCC1; .NET CLR 2.0.50727; Media Center PC 5.0; MDDC; .NET CLR 3.5.30729; .NET CLR 1.1.4322; .NET CLR 3.0.30729; .NET4.0C)",
    ].join("\n")
  },
  {name: "IE8", headers:
    [
      "Accept: image/gif, image/jpeg, image/pjpeg, application/x-ms-application, application/vnd.ms-xpsdocument, application/xaml+xml, application/x-ms-xbap, application/x-shockwave-flash, */*",
      "Accept-Encoding: gzip, deflate",
      "Accept-Language: en-gb",
      "Connection: Keep-Alive",
      "User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0; chromeframe; SLCC1; .NET CLR 2.0.50727; Media Center PC 5.0; MDDC; .NET CLR 3.5.30729; .NET CLR 1.1.4322; .NET CLR 3.0.30729; .NET4.0C)",
    ].join("\n")
  },
  {name: "IE9 platform preview", headers:
    [
      "Accept: text/html, application/xhtml+xml",
      "Accept-Encoding: gzip, deflate",
      "Accept-Language: en-gb",
      "Connection: Keep-Alive",
      "User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.0; Trident/5.0)",
    ].join("\n")
  },
  {name: "Opera 11.01", headers:
    [
      "Accept: text/html, application/xml;q=0.9, application/xhtml+xml, image/png, image/jpeg, image/gif, image/x-xbitmap, */*;q=0.1",
      "Accept-Charset: iso-8859-1, utf-8, utf-16, *;q=0.1",
      "Accept-Encoding: deflate, gzip, x-gzip, identity, *;q=0",
      "Accept-Language: en-gb,en;q=0.9",
      "Connection: Keep-Alive, TE",
      "Keep-Alive: 115",
      "Transfer-Encoding: deflate, gzip, chunked, identity, trailers",
      "User-Agent: Opera/9.80 (Windows NT 6.0; U; en-GB) Presto/2.7.62 Version/11.01",
    ].join("\n")
  },
  {name: "Safari 5", headers:
    [
      "Accept: application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5",
      "Accept-Encoding: gzip, deflate",
      "Accept-Language: en-us",
      "Connection: keep-alive",
      "User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US) AppleWebKit/533.19.4 (KHTML, like Gecko) Version/5.0.3 Safari/533.19.4",
    ].join("\n")
  },
];
<|endoftext|>


<commit_before>/*---------------------------------------------------------------------------*\
 |  Player.js                                                                |
 |                                                                           |
 |  Copyright © 2017, Rajiv Bakulesh Shah, original author.                  |
 |                                                                           |
 |      This program is free software: you can redistribute it and/or modify |
 |      it under the terms of the GNU General Public License as published by |
 |      the Free Software Foundation, either version 3 of the License, or    |
 |      (at your option) any later version.                                  |
 |                                                                           |
 |      This program is distributed in the hope that it will be useful, but  |
 |      WITHOUT ANY WARRANTY; without even the implied warranty of           |
 |      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU    |
 |      General Public License for more details.                             |
 |                                                                           |
 |      You should have received a copy of the GNU General Public License    |
 |      along with this program.  If not, see:                               |
 |          <http://www.gnu.org/licenses/>                                   |
\*---------------------------------------------------------------------------*/

import React from 'react';
import { withRouter } from 'react-router-dom';
import './Player.css';
import './monkey';

class Player extends React.PureComponent {
  constructor(props) {
    super(props);
    this.NEXT_KEYS = [39];
    this.PREV_KEYS = [37];
    this.onKeyUp = this.onKeyUp.bind(this);
    this.onVisibilityChange = this.onVisibilityChange.bind(this);
  }

  componentDidMount() {
    document.addEventListener('keyup', this.onKeyUp);
    document.addEventListener('visibilitychange', this.onVisibilityChange);
  }

  onKeyUp(eventObject) {
    if (this.refs.buffer && document.activeElement === document.body) {
      if (this.NEXT_KEYS.includes(eventObject.which)) {
        this.refs.buffer.nextVideo();
      } else if (this.PREV_KEYS.includes(eventObject.which)) {
        this.refs.buffer.prevVideo();
      }
    }
  }

  onVisibilityChange() {
    const video = document.getElementsByTagName('video')[0];
    if (video) {
      video[document.hidden ? 'pause' : 'play']();
    }
  }

  render() {
    return <Buffer {...this.props} ref="buffer" />;
  }
}

class Buffer extends React.Component {
  constructor(props) {
    super(props);
    this.BATCH_SIZE = 60;
    this.nextVideo = this.nextVideo.bind(this);
    this.prevVideo = this.prevVideo.bind(this);
    this.videos = [];
    this.state = null;
  }

  componentDidMount() {
    this.initVideos();
  }

  componentDidUpdate(prevProps) {
    if (
      this.props.artistId !== prevProps.artistId
      || this.props.songId !== prevProps.songId
    ) {
      if (this.props.history.action !== 'REPLACE') {
        this.videos = [];
        this.initVideos();
      }
    }
  }

  initVideos() {
    if (this.videos.length === 0) {
      if (this.props.artistId && this.props.songId) {
        const { artistId, songId } = this.props;
        fetch(`${process.env.REACT_APP_API}/artists/${artistId}/songs/${songId}`)
          .then(response => response.json())
          .then(data => {
            this.videos = [data.songs[0]];
            this.moreVideos(true);
          })
          .catch(console.log);
      } else {
        this.moreVideos(true);
      }
    }
  }

  moreVideos(init) {
    fetch(`${process.env.REACT_APP_API}/songs`)
      .then(response => response.json())
      .then(data => {
        this.videos = this.videos.concat(data.songs);
        if (init) {
          this.setState({ index: 0 });
        }
      })
      .catch(console.log);
  }

  nextVideo() {
    if (this.state.index !== null) {
      if (this.state.index < this.videos.length - 1) {
        this.setState({ index: this.state.index + 1 });
      }
      if (this.state.index >= this.videos.length - this.BATCH_SIZE / 2) {
        this.moreVideos(false);
      }
    }
  }

  prevVideo() {
    if (this.state.index !== null && this.state.index > 0) {
      this.setState({ index: this.state.index - 1 });
    }
  }

  render() {
    const videos = [];
    let states;

    if (
      this.state === null
      || this.state.index === null
      || this.state.index > this.videos.length - 1
    ) {
      states = [];
    } else if (
      this.state.index === this.videos.length - 1
      || this.props.state === 'background'
    ) {
      states = [this.props.state];
    } else {
      states = ['playing', 'buffering'];
    }

    states.forEach((state, index) => {
      const video = this.videos[this.state.index + index];
      const key = `/${video.artist_id}/${video.song_id}/${state}`;
      videos.push(
        <Video
          key={key}
          video={video}
          state={state}
          nextVideo={this.nextVideo}
          resetSearch={this.props.resetSearch}
          history={this.props.history}
        />
      );
    });

    return
{videos}
;
  }
}

class Video extends React.PureComponent {
  constructor(props) {
    super(props);
    this.onMouseDown = this.onMouseDown.bind(this);
  }

  componentDidMount() {
    this.updateUrlAndTitle();
  }

  componentDidUpdate() {
    this.updateUrlAndTitle();
  }

  onMouseDown(eventObject) {
    if (this.props.state === 'playing') {
      if (eventObject.target.paused) {
        eventObject.target.play().catch(() => this.props.nextVideo());
      } else if (document.activeElement === document.body) {
        this.props.nextVideo();
      } else {
        this.props.resetSearch();
      }
    }
  }

  updateUrlAndTitle() {
    if (this.props.state === 'playing') {
      const { artist_id, song_id, artist, song } = this.props.video;
      this.props.history.replace(`/${artist_id}/${song_id}`);
      document.title = `Spool - ${artist} - ${song}`;
    }
  }

  render() {
    const className = this.props.state.capitalize();
    return (
      <video
        className={className}
        src={this.props.video.mp4_url}
        preload="auto"
        loop
        autoPlay={this.props.state === 'buffering' ? null : 'autoplay'}
        muted={this.props.state !== 'playing'}
        playsInline
        onMouseDown={this.onMouseDown}
      />
    );
  }
}

export default withRouter(Player);
<commit_msg>Shuffle songs client side<commit_after>/*---------------------------------------------------------------------------*\
 |  Player.js                                                                |
 |                                                                           |
 |  Copyright © 2017, Rajiv Bakulesh Shah, original author.                  |
 |                                                                           |
 |      This program is free software: you can redistribute it and/or modify |
 |      it under the terms of the GNU General Public License as published by |
 |      the Free Software Foundation, either version 3 of the License, or    |
 |      (at your option) any later version.                                  |
 |                                                                           |
 |      This program is distributed in the hope that it will be useful, but  |
 |      WITHOUT ANY WARRANTY; without even the implied warranty of           |
 |      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU    |
 |      General Public License for more details.                             |
 |                                                                           |
 |      You should have received a copy of the GNU General Public License    |
 |      along with this program.  If not, see:                               |
 |          <http://www.gnu.org/licenses/>                                   |
\*---------------------------------------------------------------------------*/

import React from 'react';
import { withRouter } from 'react-router-dom';
import './Player.css';
import './monkey';

class Player extends React.PureComponent {
  constructor(props) {
    super(props);
    this.NEXT_KEYS = [39];
    this.PREV_KEYS = [37];
    this.onKeyUp = this.onKeyUp.bind(this);
    this.onVisibilityChange = this.onVisibilityChange.bind(this);
  }

  componentDidMount() {
    document.addEventListener('keyup', this.onKeyUp);
    document.addEventListener('visibilitychange', this.onVisibilityChange);
  }

  onKeyUp(eventObject) {
    if (this.refs.buffer && document.activeElement === document.body) {
      if (this.NEXT_KEYS.includes(eventObject.which)) {
        this.refs.buffer.nextVideo();
      } else if (this.PREV_KEYS.includes(eventObject.which)) {
        this.refs.buffer.prevVideo();
      }
    }
  }

  onVisibilityChange() {
    const video = document.getElementsByTagName('video')[0];
    if (video) {
      video[document.hidden ? 'pause' : 'play']();
    }
  }

  render() {
    return <Buffer {...this.props} ref="buffer" />;
  }
}

class Buffer extends React.Component {
  constructor(props) {
    super(props);
    this.BATCH_SIZE = 60;
    this.nextVideo = this.nextVideo.bind(this);
    this.prevVideo = this.prevVideo.bind(this);
    this.videos = [];
    this.state = null;
  }

  componentDidMount() {
    this.initVideos();
  }

  componentDidUpdate(prevProps) {
    if (
      this.props.artistId !== prevProps.artistId
      || this.props.songId !== prevProps.songId
    ) {
      if (this.props.history.action !== 'REPLACE') {
        this.videos = [];
        this.initVideos();
      }
    }
  }

  initVideos() {
    if (this.videos.length === 0) {
      if (this.props.artistId && this.props.songId) {
        const { artistId, songId } = this.props;
        fetch(`${process.env.REACT_APP_API}/artists/${artistId}/songs/${songId}`)
          .then(response => response.json())
          .then(data => {
            this.videos = [data.songs[0]];
            this.moreVideos(true);
          })
          .catch(console.log);
      } else {
        this.moreVideos(true);
      }
    }
  }

  moreVideos(init) {
    fetch(`${process.env.REACT_APP_API}/songs`)
      .then(response => response.json())
      .then(data => {
        this.videos = this.videos.concat(data.songs.shuffle());
        if (init) {
          this.setState({ index: 0 });
        }
      })
      .catch(console.log);
  }

  nextVideo() {
    if (this.state.index !== null) {
      if (this.state.index < this.videos.length - 1) {
        this.setState({ index: this.state.index + 1 });
      }
      if (this.state.index >= this.videos.length - this.BATCH_SIZE / 2) {
        this.moreVideos(false);
      }
    }
  }

  prevVideo() {
    if (this.state.index !== null && this.state.index > 0) {
      this.setState({ index: this.state.index - 1 });
    }
  }

  render() {
    const videos = [];
    let states;

    if (
      this.state === null
      || this.state.index === null
      || this.state.index > this.videos.length - 1
    ) {
      states = [];
    } else if (
      this.state.index === this.videos.length - 1
      || this.props.state === 'background'
    ) {
      states = [this.props.state];
    } else {
      states = ['playing', 'buffering'];
    }

    states.forEach((state, index) => {
      const video = this.videos[this.state.index + index];
      const key = `/${video.artist_id}/${video.song_id}/${state}`;
      videos.push(
        <Video
          key={key}
          video={video}
          state={state}
          nextVideo={this.nextVideo}
          resetSearch={this.props.resetSearch}
          history={this.props.history}
        />
      );
    });

    return
{videos}
;
  }
}

class Video extends React.PureComponent {
  constructor(props) {
    super(props);
    this.onMouseDown = this.onMouseDown.bind(this);
  }

  componentDidMount() {
    this.updateUrlAndTitle();
  }

  componentDidUpdate() {
    this.updateUrlAndTitle();
  }

  onMouseDown(eventObject) {
    if (this.props.state === 'playing') {
      if (eventObject.target.paused) {
        eventObject.target.play().catch(() => this.props.nextVideo());
      } else if (document.activeElement === document.body) {
        this.props.nextVideo();
      } else {
        this.props.resetSearch();
      }
    }
  }

  updateUrlAndTitle() {
    if (this.props.state === 'playing') {
      const { artist_id, song_id, artist, song } = this.props.video;
      this.props.history.replace(`/${artist_id}/${song_id}`);
      document.title = `Spool - ${artist} - ${song}`;
    }
  }

  render() {
    const className = this.props.state.capitalize();
    return (
      <video
        className={className}
        src={this.props.video.mp4_url}
        preload="auto"
        loop
        autoPlay={this.props.state === 'buffering' ? null : 'autoplay'}
        muted={this.props.state !== 'playing'}
        playsInline
        onMouseDown={this.onMouseDown}
      />
    );
  }
}

export default withRouter(Player);
<|endoftext|>


<commit_before>const Logger = require("./Log");

module.exports = class Plugin {
    static get plugin() {
        return {
            name: "Plugin",
            description: "Base Plugin",
            help: "There is no need to ask for help",

            isHidden: true
        };
    }

    static get handlerNames() {
        // This is the list of supported events, mapped to their handlers.
        return {
            message: "onMessage",

            _command: "onCommand",
            _inline_command: "onInlineCommand",

            audio: "onAudio",
            callback_query: "onCallbackQuery",
            contact: "onContact",
            document: "onDocument",
            inline_query: "onInline",
            left_chat_participant: "onLeftChatParticipant",
            location: "onLocation",
            new_chat_participant: "onNewChatParticipant",
            photo: "onPhoto",
            sticker: "onSticker",
            text: "onText",
            video: "onVideo",
            video_note: "onVideoNote",
            voice: "onVoice"
        };
    }

    get plugin() {
        return this.constructor.plugin;
    }

    constructor({db, blacklist, emitter, bot, config, auth}) {
        if (new.target === Plugin) {
            throw new TypeError("Cannot construct Plugin instances directly!");
        }

        this.log = new Logger(this.plugin.name, config);
        this.listener = emitter;

        this.db = db;
        this.blacklist = new Set(blacklist); // Chats where the plugin is disabled
        this.handlers = {};

        const eventNames = Object.keys(Plugin.handlerNames);
        for (const eventName of eventNames) {
            const handlerName = Plugin.handlerNames[eventName];
            if (typeof this[handlerName] !== 'function') continue;
            const isEnabled = id => !this.blacklist.has(id); // A function that says whether the plugin is enabled or not in a given chat.
            const eventHandler = this[handlerName].bind(this); // A function that refers to the appropriate handler (this.onText, this.onCommand, etc.)
            const wrappedHandler = function({message}) {
                if (("chat" in message) && !isEnabled(message.chat.id)) // If the plugin is disabled in this chat
                    return;
                eventHandler.apply(null, arguments);
            }; // A function that receives the event, checks the message against the blacklist, and calls the appropriate handler
            this.listener.on(eventName, wrappedHandler);
            this.handlers[eventName] = wrappedHandler; // Keeps a reference to the handler so that it can be removed later
        }

        /* this.commands can contain an object, mapping command names (eg. "ping") to either:
         *
         *   - a string, in which case the string is sent as a message
         *   - an object, in which case it is sent with the appropriate message type
         */
        const shortcutHandler = ({message, command, args}) => {
            if (!this.commands) return;
            for (const trigger of Object.keys(this.commands)) {
                if (command !== trigger) continue;
                const ret = this.commands[trigger]({message, args});
                if (typeof ret === "string" || typeof ret === "number") {
                    this.sendMessage(message.chat.id, ret);
                    return;
                }
                if (typeof ret === "undefined")
                    return;
                switch (ret.type) {
                case "text": {
                    return this.sendMessage(message.chat.id, ret.text, ret.options);
                }

                case "audio": {
                    return this.sendAudio(message.chat.id, ret.audio, ret.options);
                }

                case "document": {
                    return this.sendDocument(message.chat.id, ret.document, ret.options);
                }

                case "photo": {
                    return this.sendPhoto(message.chat.id, ret.photo, ret.options);
                }

                case "sticker": {
                    return this.sendSticker(message.chat.id, ret.sticker, ret.options);
                }

                case "video": {
                    return this.sendVideo(message.chat.id, ret.video, ret.options);
                }

                case "voice": {
                    return this.sendVoice(message.chat.id, ret.voice, ret.options);
                }

                case "status": case "chatAction": {
                    return this.sendChatAction(message.chat.id, ret.status, ret.options);
                }

                default: {
                    const errorMessage = `Unrecognized reply type ${ret.type}`;
                    this.log.error(errorMessage);
                    return Promise.reject(errorMessage);
                }
                }
            }
        };
        if (this.listener) {
            this.listener.on("_command", shortcutHandler);
        }
        this.shortcutHandler = shortcutHandler;
    }

    stop() {
        const eventNames = Object.keys(this.handlers);
        if (this.listener) {
            for (const eventName of eventNames) {
                const handler = this.handlers[eventName];
                this.listener.removeListener(eventName, handler);
            }
            this.listener.removeListener("_command", this.shortcutHandler);
        }
    }
};
<commit_msg>Remove 'arguments' usage<commit_after>const Logger = require("./Log");

module.exports = class Plugin {
    static get plugin() {
        return {
            name: "Plugin",
            description: "Base Plugin",
            help: "There is no need to ask for help",

            isHidden: true
        };
    }

    static get handlerNames() {
        // This is the list of supported events, mapped to their handlers.
        return {
            message: "onMessage",

            _command: "onCommand",
            _inline_command: "onInlineCommand",

            audio: "onAudio",
            callback_query: "onCallbackQuery",
            contact: "onContact",
            document: "onDocument",
            inline_query: "onInline",
            left_chat_participant: "onLeftChatParticipant",
            location: "onLocation",
            new_chat_participant: "onNewChatParticipant",
            photo: "onPhoto",
            sticker: "onSticker",
            text: "onText",
            video: "onVideo",
            video_note: "onVideoNote",
            voice: "onVoice"
        };
    }

    get plugin() {
        return this.constructor.plugin;
    }

    constructor({db, blacklist, emitter, bot, config, auth}) {
        if (new.target === Plugin) {
            throw new TypeError("Cannot construct Plugin instances directly!");
        }

        this.log = new Logger(this.plugin.name, config);
        this.listener = emitter;

        this.db = db;
        this.blacklist = new Set(blacklist); // Chats where the plugin is disabled
        this.handlers = {};

        const eventNames = Object.keys(Plugin.handlerNames);
        for (const eventName of eventNames) {
            const handlerName = Plugin.handlerNames[eventName];
            if (typeof this[handlerName] !== 'function') continue;
            const isEnabled = id => !this.blacklist.has(id); // A function that says whether the plugin is enabled or not in a given chat.
            const eventHandler = this[handlerName].bind(this); // A function that refers to the appropriate handler (this.onText, this.onCommand, etc.)
            const wrappedHandler = function(arg) {
                if (("chat" in arg.message) && !isEnabled(arg.message.chat.id)) // If the plugin is disabled in this chat
                    return;
                eventHandler(arg);
            }; // A function that receives the event, checks the message against the blacklist, and calls the appropriate handler
            this.listener.on(eventName, wrappedHandler);
            this.handlers[eventName] = wrappedHandler; // Keeps a reference to the handler so that it can be removed later
        }

        /* this.commands can contain an object, mapping command names (eg. "ping") to either:
         *
         *   - a string, in which case the string is sent as a message
         *   - an object, in which case it is sent with the appropriate message type
         */
        const shortcutHandler = ({message, command, args}) => {
            if (!this.commands) return;
            for (const trigger of Object.keys(this.commands)) {
                if (command !== trigger) continue;
                const ret = this.commands[trigger]({message, args});
                if (typeof ret === "string" || typeof ret === "number") {
                    this.sendMessage(message.chat.id, ret);
                    return;
                }
                if (typeof ret === "undefined")
                    return;
                switch (ret.type) {
                case "text": {
                    return this.sendMessage(message.chat.id, ret.text, ret.options);
                }

                case "audio": {
                    return this.sendAudio(message.chat.id, ret.audio, ret.options);
                }

                case "document": {
                    return this.sendDocument(message.chat.id, ret.document, ret.options);
                }

                case "photo": {
                    return this.sendPhoto(message.chat.id, ret.photo, ret.options);
                }

                case "sticker": {
                    return this.sendSticker(message.chat.id, ret.sticker, ret.options);
                }

                case "video": {
                    return this.sendVideo(message.chat.id, ret.video, ret.options);
                }

                case "voice": {
                    return this.sendVoice(message.chat.id, ret.voice, ret.options);
                }

                case "status": case "chatAction": {
                    return this.sendChatAction(message.chat.id, ret.status, ret.options);
                }

                default: {
                    const errorMessage = `Unrecognized reply type ${ret.type}`;
                    this.log.error(errorMessage);
                    return Promise.reject(errorMessage);
                }
                }
            }
        };
        if (this.listener) {
            this.listener.on("_command", shortcutHandler);
        }
        this.shortcutHandler = shortcutHandler;
    }

    stop() {
        const eventNames = Object.keys(this.handlers);
        if (this.listener) {
            for (const eventName of eventNames) {
                const handler = this.handlers[eventName];
                this.listener.removeListener(eventName, handler);
            }
            this.listener.removeListener("_command", this.shortcutHandler);
        }
    }
};
<|endoftext|>


<commit_before>'use strict';

var utils = require('../../utils');
var upsert = require('pouchdb-upsert');
var callbackify = utils.callbackify;
var collate = require('pouchdb-collate');

var abstractMapper = require('./abstract-mapper');

function getKey(obj) {
  return Object.keys(obj)[0];
}

function getValue(obj) {
  return obj[getKey(obj)];
}

function getSize(obj) {
  return Object.keys(obj).length;
}

function putIfNotExists(db, doc) {
  return upsert.putIfNotExists.call(db, doc);
}

function massageIndexDef(indexDef) {
  indexDef.fields = indexDef.fields.map(function (field) {
    if (typeof field === 'string') {
      var obj = {};
      obj[field] = 'asc';
      return obj;
    }
    return field;
  });
  return indexDef;
}

function massageSelector(selector) {
  if (!selector) {
    return null;
  }
  var field = Object.keys(selector)[0];
  var matcher = selector[field];
  if (typeof matcher === 'string') {
    matcher = {$eq: matcher};
  }
  matcher = {
    operator: getKey(matcher),
    value: getValue(matcher)
  };
  return [
    {field: field, matcher: matcher}
  ];
}

function filterInclusiveStart(rows, targetValue) {
  for (var i = 0, len = rows.length; i < len; i++) {
    var row = rows[i];
    if (collate.collate(row.key, targetValue) > 0) {
      if (i > 0) {
        return rows.slice(i);
      } else {
        return rows;
      }
    }
  }
  return rows;
}

function createIndex(db, requestDef) {

  var originalIndexDef = utils.clone(requestDef.index);
  requestDef.index = massageIndexDef(requestDef.index);

  var md5 = utils.MD5(JSON.stringify(requestDef));

  var views = {};

  views[requestDef.name] = {
    map: {
      fields: utils.mergeObjects(requestDef.index.fields)
    },
    reduce: '_count',
    options: {
      def: originalIndexDef
    }
  };

  return putIfNotExists(db, {
    _id: '_design/idx-' + md5,
    views: views,
    language: 'query'
  }).then(function (res) {
    return {result: res.updated ? 'created' : 'exists'};
  });
}

function find(db, requestDef) {

  return getIndexes(db).then(function (getIndexesRes) {

    var selector = massageSelector(requestDef.selector)[0];
    var matcher = selector.matcher;

    var indexToUse;
    if (selector.field === '_id') {
      indexToUse = '_all_docs';
    } else {
      getIndexesRes.indexes.forEach(function (index) {
        if (index.def.fields.length === 1 &&
            getKey(index.def.fields[0]) === selector.field) {
          var ddoc = index.ddoc.substring(8); // remove '_design/'
          indexToUse = ddoc + '/' + index.name;
        }
      });
    }
    if (!indexToUse) {
      throw new Error('couldn\'t find any index to use');
    }

    var opts = {
      include_docs: true,
      reduce: false
    };

    if (requestDef.sort && requestDef.sort.length === 1 &&
        getSize(requestDef.sort[0]) === 1 &&
        getKey(requestDef.sort[0]) === selector.field &&
        getValue(requestDef.sort[0]) === 'desc') {
      opts.descending = true;
    }

    var inclusiveStart = true;

    switch (matcher.operator) {
      case '$eq':
        opts.key = matcher.value;
        break;
      case '$lte':
        if (opts.descending) {
          opts.startkey = matcher.value;
        } else {
          opts.endkey = matcher.value;
        }
        break;
      case '$gte':
        if (opts.descending) {
          opts.endkey = matcher.value;
        } else {
          opts.startkey = matcher.value;
        }
        break;
      case '$lt':
        if (opts.descending) {
          opts.startkey = matcher.value;
          inclusiveStart = false;
        } else {
          opts.endkey = matcher.value;
          opts.inclusive_end = false;
        }
        break;
      case '$gt':
        if (opts.descending) {
          opts.endkey = matcher.value;
          opts.inclusive_end = false;
        } else {
          opts.startkey = matcher.value;
          inclusiveStart = false;
        }
        break;
    }

    return Promise.resolve().then(function () {
      if (indexToUse === '_all_docs') {
        return db.allDocs(opts);
      } else {
        return abstractMapper.query.call(db, indexToUse, opts);
      }
    }).then(function (res) {

      if (!inclusiveStart) {
        // may have to manually filter the first one,
        // since couchdb has no inclusive_start option
        res.rows = filterInclusiveStart(res.rows, matcher.value);
      }

      return {
        docs: res.rows.map(function (row) {
          var doc = row.doc;
          if (requestDef.fields) {
            return utils.pick(doc, requestDef.fields);
          }
          return doc;
        })
      };
    });
  });
}

function getIndexes(db) {
  return db.allDocs({
    startkey: '_design/idx-',
    endkey: '_design/idx-\uffff',
    include_docs: true
  }).then(function (allDocsRes) {
    var res = {
      indexes: [{
        ddoc: null,
        name: '_all_docs',
        type: 'special',
        def: {
          fields: [{_id: 'asc'}]
        }
      }]
    };

    res.indexes = utils.flatten(res.indexes, allDocsRes.rows.map(function (row) {
      var viewNames = Object.keys(row.doc.views);

      return viewNames.map(function (viewName) {
        var view = row.doc.views[viewName];
        return {
          ddoc: row.id,
          name: viewName,
          type: 'json',
          def: massageIndexDef(view.options.def)
        };
      });
    }));

    return res;
  });
}

function deleteIndex(db, indexDef) {
  throw new Error('not implemented');
}

exports.createIndex = callbackify(createIndex);
exports.find = callbackify(find);
exports.getIndexes = callbackify(getIndexes);
exports.deleteIndex = callbackify(deleteIndex);<commit_msg>passing basic tests<commit_after>'use strict';

var utils = require('../../utils');
var upsert = require('pouchdb-upsert');
var callbackify = utils.callbackify;
var collate = require('pouchdb-collate');

var abstractMapper = require('./abstract-mapper');

function getKey(obj) {
  return Object.keys(obj)[0];
}

function getValue(obj) {
  return obj[getKey(obj)];
}

function getSize(obj) {
  return Object.keys(obj).length;
}

function putIfNotExists(db, doc) {
  return upsert.putIfNotExists.call(db, doc);
}

function massageIndexDef(indexDef) {
  indexDef.fields = indexDef.fields.map(function (field) {
    if (typeof field === 'string') {
      var obj = {};
      obj[field] = 'asc';
      return obj;
    }
    return field;
  });
  return indexDef;
}

function massageSelector(selector) {
  if (!selector) {
    return null;
  }
  var field = Object.keys(selector)[0];
  var matcher = selector[field];
  if (typeof matcher === 'string') {
    matcher = {$eq: matcher};
  }
  matcher = {
    operator: getKey(matcher),
    value: getValue(matcher)
  };
  return [
    {field: field, matcher: matcher}
  ];
}

function filterInclusiveStart(rows, targetValue) {
  for (var i = 0, len = rows.length; i < len; i++) {
    var row = rows[i];
    if (collate.collate(row.key, targetValue) > 0) {
      if (i > 0) {
        return rows.slice(i);
      } else {
        return rows;
      }
    }
  }
  return rows;
}

function createIndex(db, requestDef) {

  var originalIndexDef = utils.clone(requestDef.index);
  requestDef.index = massageIndexDef(requestDef.index);

  var md5 = utils.MD5(JSON.stringify(requestDef));

  var views = {};

  var viewName = requestDef.name || ('idx-' + md5);

  views[viewName] = {
    map: {
      fields: utils.mergeObjects(requestDef.index.fields)
    },
    reduce: '_count',
    options: {
      def: originalIndexDef
    }
  };

  return putIfNotExists(db, {
    _id: '_design/idx-' + md5,
    views: views,
    language: 'query'
  }).then(function (res) {
    return {result: res.updated ? 'created' : 'exists'};
  });
}

function find(db, requestDef) {

  var selector = massageSelector(requestDef.selector)[0];
  var matcher = selector.matcher;

  return getIndexes(db).then(function (getIndexesRes) {

    var indexToUse;
    if (selector.field === '_id') {
      indexToUse = '_all_docs';
    } else {
      getIndexesRes.indexes.forEach(function (index) {
        if (index.def.fields.length === 1 &&
            getKey(index.def.fields[0]) === selector.field) {
          var ddoc = index.ddoc.substring(8); // remove '_design/'
          indexToUse = ddoc + '/' + index.name;
        }
      });
    }
    if (!indexToUse) {
      throw new Error('couldn\'t find any index to use');
    }

    var opts = {
      include_docs: true,
      reduce: false
    };

    if (requestDef.sort && requestDef.sort.length === 1 &&
        getSize(requestDef.sort[0]) === 1 &&
        getKey(requestDef.sort[0]) === selector.field &&
        getValue(requestDef.sort[0]) === 'desc') {
      opts.descending = true;
    }

    var inclusiveStart = true;

    switch (matcher.operator) {
      case '$eq':
        opts.key = matcher.value;
        break;
      case '$lte':
        if (opts.descending) {
          opts.startkey = matcher.value;
        } else {
          opts.endkey = matcher.value;
        }
        break;
      case '$gte':
        if (opts.descending) {
          opts.endkey = matcher.value;
        } else {
          opts.startkey = matcher.value;
        }
        break;
      case '$lt':
        if (opts.descending) {
          opts.startkey = matcher.value;
          inclusiveStart = false;
        } else {
          opts.endkey = matcher.value;
          opts.inclusive_end = false;
        }
        break;
      case '$gt':
        if (opts.descending) {
          opts.endkey = matcher.value;
          opts.inclusive_end = false;
        } else {
          opts.startkey = matcher.value;
          inclusiveStart = false;
        }
        break;
    }

    return Promise.resolve().then(function () {
      if (indexToUse === '_all_docs') {
        return db.allDocs(opts);
      } else {
        return abstractMapper.query.call(db, indexToUse, opts);
      }
    }).then(function (res) {

      if (!inclusiveStart) {
        // may have to manually filter the first one,
        // since couchdb has no inclusive_start option
        res.rows = filterInclusiveStart(res.rows, matcher.value);
      }

      return {
        docs: res.rows.map(function (row) {
          var doc = row.doc;
          if (requestDef.fields) {
            return utils.pick(doc, requestDef.fields);
          }
          return doc;
        })
      };
    });
  });
}

function getIndexes(db) {
  return db.allDocs({
    startkey: '_design/idx-',
    endkey: '_design/idx-\uffff',
    include_docs: true
  }).then(function (allDocsRes) {
    var res = {
      indexes: [{
        ddoc: null,
        name: '_all_docs',
        type: 'special',
        def: {
          fields: [{_id: 'asc'}]
        }
      }]
    };

    res.indexes = utils.flatten(res.indexes, allDocsRes.rows.map(function (row) {
      var viewNames = Object.keys(row.doc.views);

      return viewNames.map(function (viewName) {
        var view = row.doc.views[viewName];
        return {
          ddoc: row.id,
          name: viewName,
          type: 'json',
          def: massageIndexDef(view.options.def)
        };
      });
    }));

    return res;
  });
}

function deleteIndex(db, index) {

  var docId = index.ddoc;

  return db.get(docId).then(function (doc) {
    return db.remove(doc);
  }).then(function () {
    return abstractMapper.viewCleanup.apply(db);
  }).then(function () {
    return {ok: true};
  });
}

exports.createIndex = callbackify(createIndex);
exports.find = callbackify(find);
exports.getIndexes = callbackify(getIndexes);
exports.deleteIndex = callbackify(deleteIndex);<|endoftext|>


<commit_before>'use babel';

import { CompositeDisposable } from 'atom';
import os from 'os';

export default {

  subscriptions: null,

  config: {
    directory: {
      type: 'string',
      default: '/tmp/',
    },
    extname: {
      type: 'string',
      default: '.md',
    },
  },

  activate(state) {
    this.subscriptions = new CompositeDisposable();

    this.subscriptions.add(atom.commands.add('atom-workspace', {
      'atom-open-daily-memo:open-memo': () => this.openMemo()
    }));
  },

  deactivate() {
    this.subscriptions.dispose();
  },

  openMemo() {
    atom.workspace.open(this.memoFile());
  },

  memoFile() {
    const directory = atom.config.get('atom-open-daily-memo.directory');
    const date = new Date().toISOString().slice(0,10).replace(/-/g,"");
    const extname = atom.config.get('atom-open-daily-memo.extname');
    return `${directory}/${date}${extname}`;
  }

};
<commit_msg>expand home directory<commit_after>'use babel';

import { CompositeDisposable } from 'atom';
import expandHomeDir from 'expand-home-dir';

export default {

  subscriptions: null,

  config: {
    directory: {
      type: 'string',
      default: '~/Documents/',
    },
    extname: {
      type: 'string',
      default: '.md',
    },
  },

  activate(state) {
    this.subscriptions = new CompositeDisposable();

    this.subscriptions.add(atom.commands.add('atom-workspace', {
      'atom-open-daily-memo:open-memo': () => this.openMemo()
    }));
  },

  deactivate() {
    this.subscriptions.dispose();
  },

  openMemo() {
    atom.workspace.open(this.memoFile());
  },

  memoFile() {
    const directory = expandHomeDir(atom.config.get('atom-open-daily-memo.directory'));
    const date = new Date().toISOString().slice(0,10).replace(/-/g,"");
    const extname = atom.config.get('atom-open-daily-memo.extname');
    return `${directory}/${date}${extname}`;
  }

};
<|endoftext|>


<commit_before>var Fiber = Npm.require("fibers");

bulkCollectionUpdate = function(collection, data, options) {

  // set defaults
  var options = options || {};
  options["deleteMissing"] = options["deleteMissing"] || false;
  options["primaryKey"] = options["primaryKey"] || "_id";

  // holds all data elements, where the primaryKey is the key - for easy access at the delete routine
  var objByKey = {};

  // validation of parameters
  if (typeof collection !== "object" || typeof collection._collection !== "object") {
    throw '"collection" is no valid meteor collection';
  }
  if (typeof data !== "object" || Object.prototype.toString.call(data) !== "[object Array]") {
    throw '"data" is not of type array';
  }
  if (typeof options.primaryKey !== "string" || typeof data[0][options.primaryKey] !== "string") {
    throw '"primaryKey" is no valid object property';
  }

  Fiber(function () {

    // iterate through all elements of data
    _.each(data, function (document) {

      // save elements with the primary key as property name for later use
      objByKey[document[options.primaryKey]] = document;

      // build the condition for upsert
      var condition = {};
      condition[options.primaryKey] = document[options.primaryKey];

      // insert or update, depending on the previosuly built condition (=primaryKey exists)
      collection.upsert(
        condition,
        {
          $set: document
        }
      );
    });

    // if we should delete elements which were not contained in data
    if (options.deleteMissing === true) {

      //limit returned fields to only _id and the primaryKey
      var fields = {};
      fields[options.primaryKey] = true;

      //iterate through all returned documents
      _.each(collection.find({},{ fields: fields }).fetch(), function(document) {

        // if document was not contained in data
        if (typeof objByKey[document[options.primaryKey]] === "undefined") {

          //delete from collection
          collection.remove(document._id);
        }
      });
    }

    // fire callback
    if (typeof options.callback === "function") {
      options.callback();
    }

  }).run();

};<commit_msg>added whitespaces in comments<commit_after>var Fiber = Npm.require("fibers");

bulkCollectionUpdate = function(collection, data, options) {

  // set defaults
  var options = options || {};
  options["deleteMissing"] = options["deleteMissing"] || false;
  options["primaryKey"] = options["primaryKey"] || "_id";

  // holds all data elements, where the primaryKey is the key - for easy access at the delete routine
  var objByKey = {};

  // validation of parameters
  if (typeof collection !== "object" || typeof collection._collection !== "object") {
    throw '"collection" is no valid meteor collection';
  }
  if (typeof data !== "object" || Object.prototype.toString.call(data) !== "[object Array]") {
    throw '"data" is not of type array';
  }
  if (typeof options.primaryKey !== "string" || typeof data[0][options.primaryKey] !== "string") {
    throw '"primaryKey" is no valid object property';
  }

  Fiber(function () {

    // iterate through all elements of data
    _.each(data, function (document) {

      // save elements with the primary key as property name for later use
      objByKey[document[options.primaryKey]] = document;

      // build the condition for upsert
      var condition = {};
      condition[options.primaryKey] = document[options.primaryKey];

      // insert or update, depending on the previosuly built condition (=primaryKey exists)
      collection.upsert(
        condition,
        {
          $set: document
        }
      );
    });

    // if we should delete elements which were not contained in data
    if (options.deleteMissing === true) {

      // limit returned fields to only _id and the primaryKey
      var fields = {};
      fields[options.primaryKey] = true;

      // iterate through all returned documents
      _.each(collection.find({},{ fields: fields }).fetch(), function(document) {

        // if document was not contained in data
        if (typeof objByKey[document[options.primaryKey]] === "undefined") {

          // delete from collection
          collection.remove(document._id);
        }
      });
    }

    // fire callback
    if (typeof options.callback === "function") {
      options.callback();
    }

  }).run();

};<|endoftext|>


<commit_before>var tree = require('../tree');
var _ = global._ || require('underscore');

tree.Filterset = function Filterset() {
    this.filters = {};
};

tree.Filterset.prototype.toXML = function(env) {
    var filters = [];
    for (var id in this.filters) {
        filters.push('(' + this.filters[id].toXML(env).trim() + ')');
    }
    if (filters.length) {
        return '    <Filter>' + filters.join(' and ') + '</Filter>\n';
    } else {
        return '';
    }
};

tree.Filterset.prototype.toString = function() {
    var arr = [];
    for (var id in this.filters) arr.push(this.filters[id].id);
    return arr.sort().join('\t');
};

tree.Filterset.prototype.ev = function(env) {
    for (var i in this.filters) {
        this.filters[i].ev(env);
    }
    return this;
};

tree.Filterset.prototype.clone = function() {
    var clone = new tree.Filterset();
    for (var id in this.filters) {
        clone.filters[id] = this.filters[id];
    }
    return clone;
};

// Note: other has to be a tree.Filterset.
tree.Filterset.prototype.cloneWith = function(other) {
    var additions = [];
    for (var id in other.filters) {
        var status = this.addable(other.filters[id]);
        // status is true, false or null. if it's null we don't fail this
        // clone nor do we add the filter.
        if (status === false) {
            return false;
        }
        if (status === true) {
            // Adding the filter will override another value.
            additions.push(other.filters[id]);
        }
    }

    // Adding the other filters doesn't make this filterset invalid, but it
    // doesn't add anything to it either.
    if (!additions.length) {
        return null;
    }

    // We can successfully add all filters. Now clone the filterset and add the
    // new rules.
    var clone = new tree.Filterset();

    // We can add the rules that are already present without going through the
    // add function as a Filterset is always in it's simplest canonical form.
    for (id in this.filters) {
        clone.filters[id] = this.filters[id];
    }

    // Only add new filters that actually change the filter.
    while (id = additions.shift()) {
        clone.add(id);
    }

    return clone;
};

tree.Filterset.prototype.toJS = function(env) {
  var opMap = {
    '=': '==='
  };
  return _.map(this.filters, function(filter) {
    var op = filter.op;
    if(op in opMap) {
      op = opMap[op];
    }
    var val = filter.val;
    if(filter._val !== undefined) {
      val = filter._val.toString(true);
    }
    var attrs = "data";
    if (op === '=~') {
      return "(" + attrs + "['" + filter.key.value  + "'] + '').match(" + (val.is === 'string' ? "'" + val.toString().replace(/'/g, "\\'") + "'" : val) + ")";
    }
    return attrs + "['" + filter.key.value  + "'] " + op + " " + (val.is === 'string' ? "'" + val.toString().replace(/'/g, "\\'") + "'" : val);
  }).join(' && ');
};

// Returns true when the new filter can be added, false otherwise.
// It can also return null, and on the other side we test for === true or
// false
tree.Filterset.prototype.addable = function(filter) {
    var key = filter.key.toString(),
        value = filter.val.toString();

    if (value.match(/^[0-9]+(\.[0-9]*)?$/)) value = parseFloat(value);

    switch (filter.op) {
        case '=':
            // if there is already foo= and we're adding foo=
            if (this.filters[key + '='] !== undefined) {
                if (this.filters[key + '='].val.toString() != value) {
                    return false;
                } else {
                    return null;
                }
            }
            if (this.filters[key + '!=' + value] !== undefined) return false;
            if (this.filters[key + '>'] !== undefined && this.filters[key + '>'].val >= value) return false;
            if (this.filters[key + '<'] !== undefined && this.filters[key + '<'].val <= value) return false;
            if (this.filters[key + '>='] !== undefined  && this.filters[key + '>='].val > value) return false;
            if (this.filters[key + '<='] !== undefined  && this.filters[key + '<='].val < value) return false;
            return true;

        case '=~':
            return true;

        case '!=':
            if (this.filters[key + '='] !== undefined) return (this.filters[key + '='].val == value) ? false : null;
            if (this.filters[key + '!=' + value] !== undefined) return null;
            if (this.filters[key + '>'] !== undefined && this.filters[key + '>'].val >= value) return null;
            if (this.filters[key + '<'] !== undefined && this.filters[key + '<'].val <= value) return null;
            if (this.filters[key + '>='] !== undefined && this.filters[key + '>='].val > value) return null;
            if (this.filters[key + '<='] !== undefined && this.filters[key + '<='].val < value) return null;
            return true;

        case '>':
            if (key + '=' in this.filters) {
                if (this.filters[key + '='].val <= value) {
                    return false;
                } else {
                    return null;
                }
            }
            if (this.filters[key + '<'] !== undefined && this.filters[key + '<'].val <= value) return false;
            if (this.filters[key + '<='] !== undefined  && this.filters[key + '<='].val <= value) return false;
            if (this.filters[key + '>'] !== undefined && this.filters[key + '>'].val >= value) return null;
            if (this.filters[key + '>='] !== undefined  && this.filters[key + '>='].val > value) return null;
            return true;

        case '>=':
            if (this.filters[key + '=' ] !== undefined) return (this.filters[key + '='].val < value) ? false : null;
            if (this.filters[key + '<' ] !== undefined && this.filters[key + '<'].val <= value) return false;
            if (this.filters[key + '<='] !== undefined && this.filters[key + '<='].val < value) return false;
            if (this.filters[key + '>' ] !== undefined && this.filters[key + '>'].val >= value) return null;
            if (this.filters[key + '>='] !== undefined && this.filters[key + '>='].val >= value) return null;
            return true;

        case '<':
            if (this.filters[key + '=' ] !== undefined) return (this.filters[key + '='].val >= value) ? false : null;
            if (this.filters[key + '>' ] !== undefined && this.filters[key + '>'].val >= value) return false;
            if (this.filters[key + '>='] !== undefined && this.filters[key + '>='].val >= value) return false;
            if (this.filters[key + '<' ] !== undefined && this.filters[key + '<'].val <= value) return null;
            if (this.filters[key + '<='] !== undefined && this.filters[key + '<='].val < value) return null;
            return true;

        case '<=':
            if (this.filters[key + '=' ] !== undefined) return (this.filters[key + '='].val > value) ? false : null;
            if (this.filters[key + '>' ] !== undefined && this.filters[key + '>'].val >= value) return false;
            if (this.filters[key + '>='] !== undefined && this.filters[key + '>='].val > value) return false;
            if (this.filters[key + '<' ] !== undefined && this.filters[key + '<'].val <= value) return null;
            if (this.filters[key + '<='] !== undefined && this.filters[key + '<='].val <= value) return null;
            return true;
    }
};

// Does the new filter constitute a conflict?
tree.Filterset.prototype.conflict = function(filter) {
    var key = filter.key.toString(),
        value = filter.val.toString();

    if (!isNaN(parseFloat(value))) value = parseFloat(value);

    // if (a=b) && (a=c)
    // if (a=b) && (a!=b)
    // or (a!=b) && (a=b)
    if ((filter.op === '=' && this.filters[key + '='] !== undefined &&
        value != this.filters[key + '='].val.toString()) ||
        (filter.op === '!=' && this.filters[key + '='] !== undefined &&
        value == this.filters[key + '='].val.toString()) ||
        (filter.op === '=' && this.filters[key + '!='] !== undefined &&
        value == this.filters[key + '!='].val.toString())) {
        return filter.toString() + ' added to ' + this.toString() + ' produces an invalid filter';
    }

    return false;
};

// Only call this function for filters that have been cleared by .addable().
tree.Filterset.prototype.add = function(filter, env) {
    var key = filter.key.toString(),
        id,
        op = filter.op,
        conflict = this.conflict(filter),
        numval;

    if (conflict) return conflict;

    if (op === '=') {
        for (var i in this.filters) {
            if (this.filters[i].key == key) delete this.filters[i];
        }
        this.filters[key + '='] = filter;
    } else if (op === '!=') {
        this.filters[key + '!=' + filter.val] = filter;
    } else if (op === '=~') {
        this.filters[key + '=~' + filter.val] = filter;
    } else if (op === '>') {
        // If there are other filters that are also >
        // but are less than this one, they don't matter, so
        // remove them.
        for (var j in this.filters) {
            if (this.filters[j].key == key && this.filters[j].val <= filter.val) {
                delete this.filters[j];
            }
        }
        this.filters[key + '>'] = filter;
    } else if (op === '>=') {
        for (var k in this.filters) {
            numval = (+this.filters[k].val.toString());
            if (this.filters[k].key == key && numval < filter.val) {
                delete this.filters[k];
            }
        }
        if (this.filters[key + '!=' + filter.val] !== undefined) {
            delete this.filters[key + '!=' + filter.val];
            filter.op = '>';
            this.filters[key + '>'] = filter;
        }
        else {
            this.filters[key + '>='] = filter;
        }
    } else if (op === '<') {
        for (var l in this.filters) {
            numval = (+this.filters[l].val.toString());
            if (this.filters[l].key == key && numval >= filter.val) {
                delete this.filters[l];
            }
        }
        this.filters[key + '<'] = filter;
    } else if (op === '<=') {
        for (var m in this.filters) {
            numval = (+this.filters[m].val.toString());
            if (this.filters[m].key == key && numval > filter.val) {
                delete this.filters[m];
            }
        }
        if (this.filters[key + '!=' + filter.val] !== undefined) {
            delete this.filters[key + '!=' + filter.val];
            filter.op = '<';
            this.filters[key + '<'] = filter;
        }
        else {
            this.filters[key + '<='] = filter;
        }
    }
};
<commit_msg>Fix for ampersand on values (JS only)<commit_after>var tree = require('../tree');
var _ = global._ || require('underscore');

tree.Filterset = function Filterset() {
    this.filters = {};
};

tree.Filterset.prototype.toXML = function(env) {
    var filters = [];
    for (var id in this.filters) {
        filters.push('(' + this.filters[id].toXML(env).trim() + ')');
    }
    if (filters.length) {
        return '    <Filter>' + filters.join(' and ') + '</Filter>\n';
    } else {
        return '';
    }
};

tree.Filterset.prototype.toString = function() {
    var arr = [];
    for (var id in this.filters) arr.push(this.filters[id].id);
    return arr.sort().join('\t');
};

tree.Filterset.prototype.ev = function(env) {
    for (var i in this.filters) {
        this.filters[i].ev(env);
    }
    return this;
};

tree.Filterset.prototype.clone = function() {
    var clone = new tree.Filterset();
    for (var id in this.filters) {
        clone.filters[id] = this.filters[id];
    }
    return clone;
};

// Note: other has to be a tree.Filterset.
tree.Filterset.prototype.cloneWith = function(other) {
    var additions = [];
    for (var id in other.filters) {
        var status = this.addable(other.filters[id]);
        // status is true, false or null. if it's null we don't fail this
        // clone nor do we add the filter.
        if (status === false) {
            return false;
        }
        if (status === true) {
            // Adding the filter will override another value.
            additions.push(other.filters[id]);
        }
    }

    // Adding the other filters doesn't make this filterset invalid, but it
    // doesn't add anything to it either.
    if (!additions.length) {
        return null;
    }

    // We can successfully add all filters. Now clone the filterset and add the
    // new rules.
    var clone = new tree.Filterset();

    // We can add the rules that are already present without going through the
    // add function as a Filterset is always in it's simplest canonical form.
    for (id in this.filters) {
        clone.filters[id] = this.filters[id];
    }

    // Only add new filters that actually change the filter.
    while (id = additions.shift()) {
        clone.add(id);
    }

    return clone;
};

tree.Filterset.prototype.toJS = function(env) {
  var opMap = {
    '=': '==='
  };
  return _.map(this.filters, function(filter) {
    var op = filter.op;
    if(op in opMap) {
      op = opMap[op];
    }
    var val = filter.val;
    if(filter._val !== undefined) {
      val = filter._val.toString(true);
    }
    var attrs = "data";
    if (op === '=~') {
      return "(" + attrs + "['" + filter.key.value  + "'] + '').match(" + (val.is === 'string' ? "'" + val.toString().replace(/'/g, "\\'").replace(/&/g, '&') + "'" : val) + ")";
    }
    return attrs + "['" + filter.key.value  + "'] " + op + " " + (val.is === 'string' ? "'" + val.toString().replace(/'/g, "\\'").replace(/&/g, '&') + "'" : val);
  }).join(' && ');
};

// Returns true when the new filter can be added, false otherwise.
// It can also return null, and on the other side we test for === true or
// false
tree.Filterset.prototype.addable = function(filter) {
    var key = filter.key.toString(),
        value = filter.val.toString();

    if (value.match(/^[0-9]+(\.[0-9]*)?$/)) value = parseFloat(value);

    switch (filter.op) {
        case '=':
            // if there is already foo= and we're adding foo=
            if (this.filters[key + '='] !== undefined) {
                if (this.filters[key + '='].val.toString() != value) {
                    return false;
                } else {
                    return null;
                }
            }
            if (this.filters[key + '!=' + value] !== undefined) return false;
            if (this.filters[key + '>'] !== undefined && this.filters[key + '>'].val >= value) return false;
            if (this.filters[key + '<'] !== undefined && this.filters[key + '<'].val <= value) return false;
            if (this.filters[key + '>='] !== undefined  && this.filters[key + '>='].val > value) return false;
            if (this.filters[key + '<='] !== undefined  && this.filters[key + '<='].val < value) return false;
            return true;

        case '=~':
            return true;

        case '!=':
            if (this.filters[key + '='] !== undefined) return (this.filters[key + '='].val == value) ? false : null;
            if (this.filters[key + '!=' + value] !== undefined) return null;
            if (this.filters[key + '>'] !== undefined && this.filters[key + '>'].val >= value) return null;
            if (this.filters[key + '<'] !== undefined && this.filters[key + '<'].val <= value) return null;
            if (this.filters[key + '>='] !== undefined && this.filters[key + '>='].val > value) return null;
            if (this.filters[key + '<='] !== undefined && this.filters[key + '<='].val < value) return null;
            return true;

        case '>':
            if (key + '=' in this.filters) {
                if (this.filters[key + '='].val <= value) {
                    return false;
                } else {
                    return null;
                }
            }
            if (this.filters[key + '<'] !== undefined && this.filters[key + '<'].val <= value) return false;
            if (this.filters[key + '<='] !== undefined  && this.filters[key + '<='].val <= value) return false;
            if (this.filters[key + '>'] !== undefined && this.filters[key + '>'].val >= value) return null;
            if (this.filters[key + '>='] !== undefined  && this.filters[key + '>='].val > value) return null;
            return true;

        case '>=':
            if (this.filters[key + '=' ] !== undefined) return (this.filters[key + '='].val < value) ? false : null;
            if (this.filters[key + '<' ] !== undefined && this.filters[key + '<'].val <= value) return false;
            if (this.filters[key + '<='] !== undefined && this.filters[key + '<='].val < value) return false;
            if (this.filters[key + '>' ] !== undefined && this.filters[key + '>'].val >= value) return null;
            if (this.filters[key + '>='] !== undefined && this.filters[key + '>='].val >= value) return null;
            return true;

        case '<':
            if (this.filters[key + '=' ] !== undefined) return (this.filters[key + '='].val >= value) ? false : null;
            if (this.filters[key + '>' ] !== undefined && this.filters[key + '>'].val >= value) return false;
            if (this.filters[key + '>='] !== undefined && this.filters[key + '>='].val >= value) return false;
            if (this.filters[key + '<' ] !== undefined && this.filters[key + '<'].val <= value) return null;
            if (this.filters[key + '<='] !== undefined && this.filters[key + '<='].val < value) return null;
            return true;

        case '<=':
            if (this.filters[key + '=' ] !== undefined) return (this.filters[key + '='].val > value) ? false : null;
            if (this.filters[key + '>' ] !== undefined && this.filters[key + '>'].val >= value) return false;
            if (this.filters[key + '>='] !== undefined && this.filters[key + '>='].val > value) return false;
            if (this.filters[key + '<' ] !== undefined && this.filters[key + '<'].val <= value) return null;
            if (this.filters[key + '<='] !== undefined && this.filters[key + '<='].val <= value) return null;
            return true;
    }
};

// Does the new filter constitute a conflict?
tree.Filterset.prototype.conflict = function(filter) {
    var key = filter.key.toString(),
        value = filter.val.toString();

    if (!isNaN(parseFloat(value))) value = parseFloat(value);

    // if (a=b) && (a=c)
    // if (a=b) && (a!=b)
    // or (a!=b) && (a=b)
    if ((filter.op === '=' && this.filters[key + '='] !== undefined &&
        value != this.filters[key + '='].val.toString()) ||
        (filter.op === '!=' && this.filters[key + '='] !== undefined &&
        value == this.filters[key + '='].val.toString()) ||
        (filter.op === '=' && this.filters[key + '!='] !== undefined &&
        value == this.filters[key + '!='].val.toString())) {
        return filter.toString() + ' added to ' + this.toString() + ' produces an invalid filter';
    }

    return false;
};

// Only call this function for filters that have been cleared by .addable().
tree.Filterset.prototype.add = function(filter, env) {
    var key = filter.key.toString(),
        id,
        op = filter.op,
        conflict = this.conflict(filter),
        numval;

    if (conflict) return conflict;

    if (op === '=') {
        for (var i in this.filters) {
            if (this.filters[i].key == key) delete this.filters[i];
        }
        this.filters[key + '='] = filter;
    } else if (op === '!=') {
        this.filters[key + '!=' + filter.val] = filter;
    } else if (op === '=~') {
        this.filters[key + '=~' + filter.val] = filter;
    } else if (op === '>') {
        // If there are other filters that are also >
        // but are less than this one, they don't matter, so
        // remove them.
        for (var j in this.filters) {
            if (this.filters[j].key == key && this.filters[j].val <= filter.val) {
                delete this.filters[j];
            }
        }
        this.filters[key + '>'] = filter;
    } else if (op === '>=') {
        for (var k in this.filters) {
            numval = (+this.filters[k].val.toString());
            if (this.filters[k].key == key && numval < filter.val) {
                delete this.filters[k];
            }
        }
        if (this.filters[key + '!=' + filter.val] !== undefined) {
            delete this.filters[key + '!=' + filter.val];
            filter.op = '>';
            this.filters[key + '>'] = filter;
        }
        else {
            this.filters[key + '>='] = filter;
        }
    } else if (op === '<') {
        for (var l in this.filters) {
            numval = (+this.filters[l].val.toString());
            if (this.filters[l].key == key && numval >= filter.val) {
                delete this.filters[l];
            }
        }
        this.filters[key + '<'] = filter;
    } else if (op === '<=') {
        for (var m in this.filters) {
            numval = (+this.filters[m].val.toString());
            if (this.filters[m].key == key && numval > filter.val) {
                delete this.filters[m];
            }
        }
        if (this.filters[key + '!=' + filter.val] !== undefined) {
            delete this.filters[key + '!=' + filter.val];
            filter.op = '<';
            this.filters[key + '<'] = filter;
        }
        else {
            this.filters[key + '<='] = filter;
        }
    }
};
<|endoftext|>