<commit_before>var Discord = require('discord.js');

var ytdl = require('ytdl-core');
var request = require('request');
var url = require('url');

var shouldDisallowQueue = require('./lib/permission-checks.js');
var VideoFormat = require('./lib/video-format.js');
var YoutubeStream = require('./lib/youtube-stream.js');
var Saved = require('./lib/saved.js');
Saved.read();

var client = new Discord.Client();

// Handle discord.js warnings
client.on('warn', (m) => console.log('[warn]', m));
client.on('debug', (m) => console.log('[debug]', m));

var playQueue = [];
var boundChannel = false;
var currentStream = false;
var currentVideo = false;

var botMention = false;

// Handling api key
if (process.argv[4]) {
  var apiKey = process.argv[4];
} else {
  var apiKey = false;
}

client.on('ready', () => {
  botMention = `<@${client.user.id}>`;
  console.log(`Bot mention: ${botMention}`);
});

client.on('message', m => {
  if (!botMention) return;

  if (m.content.startsWith(`${botMention} i`)) { // init
    if (boundChannel) return;
    var channelToJoin = spliceArguments(m.content)[1];
    for (var channel of m.channel.server.channels) {
      if (channel instanceof Discord.VoiceChannel) {
        if (!channelToJoin || channel.name === channelToJoin) {
          boundChannel = m.channel;
          client.reply(m, `Binding to text channel <#${boundChannel.id}> and voice channel **${channel.name}** \`(${channel.id})\``);
          client.joinVoiceChannel(channel).catch(error);
          break;
        }
      }
    }
  }

  if (m.content.startsWith(`${botMention} d`)) { // destroy
    if (!boundChannel) return;
    client.reply(m, `Unbinding from <#${boundChannel.id}> and destroying voice connection`);
    playQueue = [];
    client.internal.leaveVoiceChannel();
    boundChannel = false;
    currentStream = false;
    currentVideo = false;
    return;
  }

  // Only respond to other messages inside the bound channel
  if (!m.channel.equals(boundChannel)) return;

  if (m.content.startsWith(`${botMention} n`)) { // next
    playStopped();
  }

  if (m.content.startsWith(`${botMention} yq`) // youtube query
    || m.content.startsWith(`${botMention} qq`) // queue query
    || m.content.startsWith(`${botMention} pq`) // play query
    || m.content.startsWith(`${botMention} ytq`)) {

    if (apiKey == false) {
      client.reply(m, 'Search is disabled (no API KEY found).');
      return;
    }

    var args = spliceArguments(m.content)[1];

    if (!args) {
      client.reply(m, 'You need to specify a search parameter.');
      return;
    }

    var requestUrl = 'https://www.googleapis.com/youtube/v3/search' +
      `?part=snippet&q=${escape(query)}&key=${apiKey}`;

    request(requestUrl, (error, response, body) => {
      if (!error && response.statusCode == 200) {
        body = JSON.parse(body);
        if (body.items.length == 0) {
          client.reply(m, 'Your query gave 0 results.');
          return;
        }

        var vid = body.items[0].id.videoId;
        getInfoAndQueue(vid, m);
      } else {
        client.reply(m, 'There was an error searching.');
        return;
      }
    });

    return; // have to stop propagation
  }

  if (m.content.startsWith(`${botMention} pl`)) { // playlist
    if (apiKey == false) {
      client.reply(m, 'Playlist adding is disabled (no API KEY found).');
      return;
    }

    var pid = spliceArguments(m.content)[1];

    if (!pid) {
      client.reply(m, 'You need to specify a playlist ID!');
      return;
    }

    var requestUrl = 'https://www.googleapis.com/youtube/v3/playlistItems' +
      `?part=contentDetails&maxResults=50&playlistId=${pid}&key=${apiKey}`;

    request(requestUrl, (error, response, body) => {
      if (!error && response.statusCode == 200) {
        body = JSON.parse(body);
        if (body.items.length == 0) {
          client.reply(m, 'That playlist has no videos.');
          return;
        }

        client.reply(m, `Loading ${body.items.length} videos...`);
        var suppress = 0;
        body.items.forEach((elem, idx) => {
          var vid = elem.contentDetails.videoId;
          if (idx == 1) suppress = body.items.length - 2;
          if (idx == 2) suppress = -1;
          getInfoAndQueue(vid, m, suppress);
        });
      } else {
        client.reply(m, 'There was an error finding playlist with that id.');
        return;
      }
    });

    return;
  }

  if (m.content.startsWith(`${botMention} y`) // youtube
    || m.content.startsWith(`${botMention} q`) // queue
    || m.content.startsWith(`${botMention} p`)) { // play

    var vidList = spliceArguments(m.content)[1];

    var vids = vidList.split(',');
    var suppress = 0;
    vids.forEach((vid, idx) => {
      if (idx == 1) suppress = vids.length - 2;
      if (idx == 2) suppress = -1;
      parseVidAndQueue(vid, m, suppress);
    });
  }

  if (m.content.startsWith(`${botMention} r`)) { // replay
    playQueue.push(currentVideo);
  }

  if (m.content.startsWith(`${botMention} sh`)) { // shuffle
    if (playQueue.length < 2) {
      client.reply(m, 'Not enough songs in the queue.');
      return;
    } else {
      shuffle(playQueue);
      client.reply(m, 'Songs in the queue have been shuffled.');
    }

    return;
  }

  if (m.content.startsWith(`${botMention} link`)) {
    if (currentVideo) client.reply(m, `<${currentVideo.loaderUrl}>`);
    return; // stop propagation
  }

  if (m.content.startsWith(`${botMention} list s`)) { // list saved
    var formattedList = 'Here are the videos currently saved: \n';
    for (var key in Saved.saved.videos) {
      if (Saved.saved.videos.hasOwnProperty(key)) {
        formattedList += `*${key}*: ${VideoFormat.prettyPrint(Saved.saved.videos[key])}\n`;
      }
    }

    client.reply(m, formattedList);
    return; // so list doesn't get triggered
  }

  if (m.content.startsWith(`${botMention} l`)) { // list
    var formattedList = 'Here are the videos currently in the play queue, from first added to last added: \n';
    formattedList += `Currently playing: ${VideoFormat.prettyPrintWithUser(currentVideo)}\n`;

    var shouldBreak = false;

    playQueue.forEach((video, idx) => {
      if (shouldBreak) return;

      var formattedVideo = `${idx + 1}. ${VideoFormat.prettyPrintWithUser(video)}\n`;

      if ((formattedList.length + formattedVideo.length) > 1950) {
        formattedList += `... and ${playQueue.length - idx} more`;
        shouldBreak = true;
      } else {
        formattedList += formattedVideo;
      }
    });
    client.reply(m, formattedList);
  }

  if (m.content.startsWith(`${botMention} s`)) { // save
    var argument = spliceArguments(m.content)[1];
    if (!argument) {
      client.reply(m, 'You need to specify a video and a keyword!');
      return;
    }

    var splitArgs = spliceArguments(argument, 1);

    var vid = splitArgs[0];

    if (vid === 'current') {
      if (currentVideo) vid = currentVideo.vid;
      else {
        client.reply(m, `Couldn't retrieve video information of current video!`);
      }
    }

    var requestUrl = 'http://www.youtube.com/watch?v=' + vid;
    ytdl.getInfo(requestUrl, (err, info) => {
      if (err) handleYTError(err);
      else saveVideo(info, vid, splitArgs[1], m);
    });
  }

  if (m.content.startsWith(`${botMention} t`)) { // time
    var streamTime = client.internal.voiceConnection.streamTime; // in ms
    var videoTime = currentVideo.length_seconds;
    client.reply(m, `${VideoFormat.prettyTime(streamTime)} / ${VideoFormat.prettyTime(videoTime * 1000)} (${(streamTime / (videoTime * 10)).toFixed(2)} %)`);
  }
});

function parseVidAndQueue(vid, m, suppress) {
  if (/^http/.test(vid)) {
    if (url.parse(vid, true).query.v) {
      vid = url.parse(vid, true).query.v;
    }
  }

  vid = Saved.possiblyRetrieveVideo(vid);
  if (!vid) {
    client.reply(m, 'You need to specify a video!');
    return;
  }

  getInfoAndQueue(vid, m, suppress);
}

function getInfoAndQueue(vid, m, suppress) {
  requestUrl = 'http://www.youtube.com/watch?v=' + vid;
  ytdl.getInfo(requestUrl, (err, info) => {
    if (err) handleYTError(err);
    else {
      info.vid = vid;
      possiblyQueue(info, m.author.id, m, suppress);
    }
  });
}

function spliceArguments(message, after) {
  after = after || 2;
  var rest = message.split(' ');
  var removed = rest.splice(0, after);
  return [removed.join(' '), rest.join(' ')];
}

function saveVideo(video, vid, keywords, m) {
  simplified = VideoFormat.simplify(video, vid);
  if (Saved.saved.videos.hasOwnProperty(keywords)) client.reply(m, `Warning: ${VideoFormat.simplePrint(Saved.saved.videos[keywords])} is already saved as *${keywords}*! Overwriting.`);

  var key;
  if (key = Saved.isVideoSaved(vid)) client.reply(m, `Warning: This video is already saved as *${key}*! Adding it anyway as *${keywords}*.`);

  Saved.saved.videos[keywords] = simplified;
  client.reply(m, `Saved video ${VideoFormat.prettyPrint(video)} as *${keywords}*`);
  Saved.write();
}

function possiblyQueue(video, userId, m, suppress) {
  video.userId = userId;
  suppress = (suppress === undefined) ? false : suppress;
  reason = shouldDisallowQueue(playQueue, video);
  if (reason) {
    client.reply(m, `You can't queue this video right now! Reason: ${reason}`);
  } else {
    playQueue.push(video);
    if (suppress == 0) client.reply(m, `Queued ${VideoFormat.prettyPrint(video)}`);
    else if (suppress > -1) client.reply(m, `Queued ${VideoFormat.prettyPrint(video)} and ${suppress} other videos`);

    // Start playing if not playing yet
    if (!currentVideo) nextInQueue();
  }
}

function handleYTError(err) {
  if (err.toString().indexOf('Code 150') > -1) {
    // Video unavailable in country
    boundChannel.sendMessage('This video is unavailable in the country the bot is running in! Please try a different video.');
  } else {
    boundChannel.sendMessage('An error occurred while getting video information! Please try a different video.');
  }

  console.log(err.toString());
}

function playStopped() {
  if (client.internal.voiceConnection) client.internal.voiceConnection.stopPlaying();

  boundChannel.sendMessage(`Finished playing ${VideoFormat.simplePrint(currentVideo)}`);
  currentVideo = false;
  nextInQueue();
}

function play(video) {
  currentVideo = video;
  if (client.internal.voiceConnection) {
    var connection = client.internal.voiceConnection;
    currentStream = YoutubeStream.getStream(video.loaderUrl);

    currentStream.on('error', (err) => {
      boundChannel.sendMessage(`There was an error during playback! **${err}**`);
    });

    currentStream.on('end', () => setTimeout(playStopped, 8000)); // 8 second leeway for bad timing
    connection.playRawStream(currentStream).then(intent => {
      boundChannel.sendMessage(`Playing ${VideoFormat.prettyPrint(video)}`);
    });
  }
}

function nextInQueue() {
  if (playQueue.length > 0) {
    next = playQueue.shift();
    play(next);
  }
}

function shuffle(array) {
  var counter = array.length;
  var temp;
  var index;

  // While there are elements in the array
  while (counter > 0) {
    index = Math.floor(Math.random() * counter);

    counter--;

    temp = array[counter];
    array[counter] = array[index];
    array[index] = temp;
  }

  return array;
}

function error(argument) {
  console.log(argument.stack);
}

// Email and password over command line
client.login(process.argv[2], process.argv[3]).catch((e) => console.log(e));
<commit_msg>Add message to replay command<commit_after>var Discord = require('discord.js');

var ytdl = require('ytdl-core');
var request = require('request');
var url = require('url');

var shouldDisallowQueue = require('./lib/permission-checks.js');
var VideoFormat = require('./lib/video-format.js');
var YoutubeStream = require('./lib/youtube-stream.js');
var Saved = require('./lib/saved.js');
Saved.read();

var client = new Discord.Client();

// Handle discord.js warnings
client.on('warn', (m) => console.log('[warn]', m));
client.on('debug', (m) => console.log('[debug]', m));

var playQueue = [];
var boundChannel = false;
var currentStream = false;
var currentVideo = false;

var botMention = false;

// Handling api key
if (process.argv[4]) {
  var apiKey = process.argv[4];
} else {
  var apiKey = false;
}

client.on('ready', () => {
  botMention = `<@${client.user.id}>`;
  console.log(`Bot mention: ${botMention}`);
});

client.on('message', m => {
  if (!botMention) return;

  if (m.content.startsWith(`${botMention} i`)) { // init
    if (boundChannel) return;
    var channelToJoin = spliceArguments(m.content)[1];
    for (var channel of m.channel.server.channels) {
      if (channel instanceof Discord.VoiceChannel) {
        if (!channelToJoin || channel.name === channelToJoin) {
          boundChannel = m.channel;
          client.reply(m, `Binding to text channel <#${boundChannel.id}> and voice channel **${channel.name}** \`(${channel.id})\``);
          client.joinVoiceChannel(channel).catch(error);
          break;
        }
      }
    }
  }

  if (m.content.startsWith(`${botMention} d`)) { // destroy
    if (!boundChannel) return;
    client.reply(m, `Unbinding from <#${boundChannel.id}> and destroying voice connection`);
    playQueue = [];
    client.internal.leaveVoiceChannel();
    boundChannel = false;
    currentStream = false;
    currentVideo = false;
    return;
  }

  // Only respond to other messages inside the bound channel
  if (!m.channel.equals(boundChannel)) return;

  if (m.content.startsWith(`${botMention} n`)) { // next
    playStopped();
  }

  if (m.content.startsWith(`${botMention} yq`) // youtube query
    || m.content.startsWith(`${botMention} qq`) // queue query
    || m.content.startsWith(`${botMention} pq`) // play query
    || m.content.startsWith(`${botMention} ytq`)) {

    if (apiKey == false) {
      client.reply(m, 'Search is disabled (no API KEY found).');
      return;
    }

    var args = spliceArguments(m.content)[1];

    if (!args) {
      client.reply(m, 'You need to specify a search parameter.');
      return;
    }

    var requestUrl = 'https://www.googleapis.com/youtube/v3/search' +
      `?part=snippet&q=${escape(args)}&key=${apiKey}`;

    request(requestUrl, (error, response, body) => {
      if (!error && response.statusCode == 200) {
        body = JSON.parse(body);
        if (body.items.length == 0) {
          client.reply(m, 'Your query gave 0 results.');
          return;
        }

        var vid = body.items[0].id.videoId;
        getInfoAndQueue(vid, m);
      } else {
        client.reply(m, 'There was an error searching.');
        return;
      }
    });

    return; // have to stop propagation
  }

  if (m.content.startsWith(`${botMention} pl`)) { // playlist
    if (apiKey == false) {
      client.reply(m, 'Playlist adding is disabled (no API KEY found).');
      return;
    }

    var pid = spliceArguments(m.content)[1];

    if (!pid) {
      client.reply(m, 'You need to specify a playlist ID!');
      return;
    }

    var requestUrl = 'https://www.googleapis.com/youtube/v3/playlistItems' +
      `?part=contentDetails&maxResults=50&playlistId=${pid}&key=${apiKey}`;

    request(requestUrl, (error, response, body) => {
      if (!error && response.statusCode == 200) {
        body = JSON.parse(body);
        if (body.items.length == 0) {
          client.reply(m, 'That playlist has no videos.');
          return;
        }

        client.reply(m, `Loading ${body.items.length} videos...`);
        var suppress = 0;
        body.items.forEach((elem, idx) => {
          var vid = elem.contentDetails.videoId;
          if (idx == 1) suppress = body.items.length - 2;
          if (idx == 2) suppress = -1;
          getInfoAndQueue(vid, m, suppress);
        });
      } else {
        client.reply(m, 'There was an error finding playlist with that id.');
        return;
      }
    });

    return;
  }

  if (m.content.startsWith(`${botMention} y`) // youtube
    || m.content.startsWith(`${botMention} q`) // queue
    || m.content.startsWith(`${botMention} p`)) { // play

    var vidList = spliceArguments(m.content)[1];

    var vids = vidList.split(',');
    var suppress = 0;
    vids.forEach((vid, idx) => {
      if (idx == 1) suppress = vids.length - 2;
      if (idx == 2) suppress = -1;
      parseVidAndQueue(vid, m, suppress);
    });
  }

  if (m.content.startsWith(`${botMention} r`)) { // replay
    playQueue.push(currentVideo);
    client.reply(m, `Queued ${VideoFormat.prettyPrint(currentVideo)}`);
  }

  if (m.content.startsWith(`${botMention} sh`)) { // shuffle
    if (playQueue.length < 2) {
      client.reply(m, 'Not enough songs in the queue.');
      return;
    } else {
      shuffle(playQueue);
      client.reply(m, 'Songs in the queue have been shuffled.');
    }

    return;
  }

  if (m.content.startsWith(`${botMention} link`)) {
    if (currentVideo) client.reply(m, `<${currentVideo.loaderUrl}>`);
    return; // stop propagation
  }

  if (m.content.startsWith(`${botMention} list s`)) { // list saved
    var formattedList = 'Here are the videos currently saved: \n';
    for (var key in Saved.saved.videos) {
      if (Saved.saved.videos.hasOwnProperty(key)) {
        formattedList += `*${key}*: ${VideoFormat.prettyPrint(Saved.saved.videos[key])}\n`;
      }
    }

    client.reply(m, formattedList);
    return; // so list doesn't get triggered
  }

  if (m.content.startsWith(`${botMention} l`)) { // list
    var formattedList = 'Here are the videos currently in the play queue, from first added to last added: \n';
    formattedList += `Currently playing: ${VideoFormat.prettyPrintWithUser(currentVideo)}\n`;

    var shouldBreak = false;

    playQueue.forEach((video, idx) => {
      if (shouldBreak) return;

      var formattedVideo = `${idx + 1}. ${VideoFormat.prettyPrintWithUser(video)}\n`;

      if ((formattedList.length + formattedVideo.length) > 1950) {
        formattedList += `... and ${playQueue.length - idx} more`;
        shouldBreak = true;
      } else {
        formattedList += formattedVideo;
      }
    });
    client.reply(m, formattedList);
  }

  if (m.content.startsWith(`${botMention} s`)) { // save
    var argument = spliceArguments(m.content)[1];
    if (!argument) {
      client.reply(m, 'You need to specify a video and a keyword!');
      return;
    }

    var splitArgs = spliceArguments(argument, 1);

    var vid = splitArgs[0];

    if (vid === 'current') {
      if (currentVideo) vid = currentVideo.vid;
      else {
        client.reply(m, `Couldn't retrieve video information of current video!`);
      }
    }

    var requestUrl = 'http://www.youtube.com/watch?v=' + vid;
    ytdl.getInfo(requestUrl, (err, info) => {
      if (err) handleYTError(err);
      else saveVideo(info, vid, splitArgs[1], m);
    });
  }

  if (m.content.startsWith(`${botMention} t`)) { // time
    var streamTime = client.internal.voiceConnection.streamTime; // in ms
    var videoTime = currentVideo.length_seconds;
    client.reply(m, `${VideoFormat.prettyTime(streamTime)} / ${VideoFormat.prettyTime(videoTime * 1000)} (${(streamTime / (videoTime * 10)).toFixed(2)} %)`);
  }
});

function parseVidAndQueue(vid, m, suppress) {
  if (/^http/.test(vid)) {
    if (url.parse(vid, true).query.v) {
      vid = url.parse(vid, true).query.v;
    }
  }

  vid = Saved.possiblyRetrieveVideo(vid);
  if (!vid) {
    client.reply(m, 'You need to specify a video!');
    return;
  }

  getInfoAndQueue(vid, m, suppress);
}

function getInfoAndQueue(vid, m, suppress) {
  requestUrl = 'http://www.youtube.com/watch?v=' + vid;
  ytdl.getInfo(requestUrl, (err, info) => {
    if (err) handleYTError(err);
    else {
      info.vid = vid;
      possiblyQueue(info, m.author.id, m, suppress);
    }
  });
}

function spliceArguments(message, after) {
  after = after || 2;
  var rest = message.split(' ');
  var removed = rest.splice(0, after);
  return [removed.join(' '), rest.join(' ')];
}

function saveVideo(video, vid, keywords, m) {
  simplified = VideoFormat.simplify(video, vid);
  if (Saved.saved.videos.hasOwnProperty(keywords)) client.reply(m, `Warning: ${VideoFormat.simplePrint(Saved.saved.videos[keywords])} is already saved as *${keywords}*! Overwriting.`);

  var key;
  if (key = Saved.isVideoSaved(vid)) client.reply(m, `Warning: This video is already saved as *${key}*! Adding it anyway as *${keywords}*.`);

  Saved.saved.videos[keywords] = simplified;
  client.reply(m, `Saved video ${VideoFormat.prettyPrint(video)} as *${keywords}*`);
  Saved.write();
}

function possiblyQueue(video, userId, m, suppress) {
  video.userId = userId;
  suppress = (suppress === undefined) ? false : suppress;
  reason = shouldDisallowQueue(playQueue, video);
  if (reason) {
    client.reply(m, `You can't queue this video right now! Reason: ${reason}`);
  } else {
    playQueue.push(video);
    if (suppress == 0) client.reply(m, `Queued ${VideoFormat.prettyPrint(video)}`);
    else if (suppress > -1) client.reply(m, `Queued ${VideoFormat.prettyPrint(video)} and ${suppress} other videos`);

    // Start playing if not playing yet
    if (!currentVideo) nextInQueue();
  }
}

function handleYTError(err) {
  if (err.toString().indexOf('Code 150') > -1) {
    // Video unavailable in country
    boundChannel.sendMessage('This video is unavailable in the country the bot is running in! Please try a different video.');
  } else {
    boundChannel.sendMessage('An error occurred while getting video information! Please try a different video.');
  }

  console.log(err.toString());
}

function playStopped() {
  if (client.internal.voiceConnection) client.internal.voiceConnection.stopPlaying();

  boundChannel.sendMessage(`Finished playing ${VideoFormat.simplePrint(currentVideo)}`);
  currentVideo = false;
  nextInQueue();
}

function play(video) {
  currentVideo = video;
  if (client.internal.voiceConnection) {
    var connection = client.internal.voiceConnection;
    currentStream = YoutubeStream.getStream(video.loaderUrl);

    currentStream.on('error', (err) => {
      boundChannel.sendMessage(`There was an error during playback! **${err}**`);
    });

    currentStream.on('end', () => setTimeout(playStopped, 8000)); // 8 second leeway for bad timing
    connection.playRawStream(currentStream).then(intent => {
      boundChannel.sendMessage(`Playing ${VideoFormat.prettyPrint(video)}`);
    });
  }
}

function nextInQueue() {
  if (playQueue.length > 0) {
    next = playQueue.shift();
    play(next);
  }
}

function shuffle(array) {
  var counter = array.length;
  var temp;
  var index;

  // While there are elements in the array
  while (counter > 0) {
    index = Math.floor(Math.random() * counter);

    counter--;

    temp = array[counter];
    array[counter] = array[index];
    array[index] = temp;
  }

  return array;
}

function error(argument) {
  console.log(argument.stack);
}

// Email and password over command line
client.login(process.argv[2], process.argv[3]).catch((e) => console.log(e));
<|endoftext|>


<commit_before>/**
 * Mnemonist Bitwise Helpers
 * ==========================
 *
 * Miscellaneous helpers helping with bitwise operations.
 */

/**
 * Takes a 32 bits integer and returns its MSB using SWAR strategy.
 *
 * @param  {number} x - Target number.
 * @return {number}
 */
function msb32(x) {
  x |= (x >> 1);
  x |= (x >> 2);
  x |= (x >> 4);
  x |= (x >> 8);
  x |= (x >> 16);

  return (x & ~(x >> 1));
}
exports.msb32 = msb32;

/**
 * Takes a byte and returns its MSB using SWAR strategy.
 *
 * @param  {number} x - Target number.
 * @return {number}
 */
function msb8(x) {
  x |= (x >> 1);
  x |= (x >> 2);
  x |= (x >> 4);

  return (x & ~(x >> 1));
}
exports.msb8 = msb8;

/**
 * Takes a number and return bit at position.
 *
 * @param  {number} x   - Target number.
 * @param  {number} pos - Position.
 * @return {number}
 */
exports.test = function(x, pos) {
  return (x >> pos) & 1;
};

/**
 * Compare two bytes and return their critical bit.
 *
 * @param  {number} a - First byte.
 * @param  {number} b - Second byte.
 * @return {number}
 */
exports.criticalBit8 = function(a, b) {
  return msb8(a ^ b);
};
exports.criticalBit8Mask = function(a, b) {
  return (~msb8(a ^ b) >>> 0) & 0xff;
};
exports.testCriticalBit8 = function(x, mask) {
  return (1 + (x | mask)) >> 8;
};

/**
 * Takes a 32 bits integer and returns its population count (number of 1 of
 * the binary representation).
 *
 * @param  {number} x - Target number.
 * @return {number}
 */
exports.popcount = function(x) {
  x -= x >> 1 & 0x55555555;
  x = (x & 0x33333333) + (x >> 2 & 0x33333333);
  x = x + (x >> 4) & 0x0f0f0f0f;
  x += x >> 8;
  x += x >> 16;
  return x & 0x7f;
};

/**
 * Slightly faster popcount function based on a precomputed table of 8bits
 * words.
 *
 * @param  {number} x - Target number.
 * @return {number}
 */
var TABLE8 = new Uint8Array(Math.pow(2, 8));

for (var i = 0, l = TABLE8.length; i < l; i++)
  TABLE8[i] = exports.popcount(i);

exports.table8Popcount = function(x) {
  return (
    TABLE8[x & 0xff] +
    TABLE8[(x >> 8) & 0xff] +
    TABLE8[(x >> 16) & 0xff] +
    TABLE8[(x >> 24) & 0xff]
  );
};
<commit_msg>Adding criticalBit32Mask<commit_after>/**
 * Mnemonist Bitwise Helpers
 * ==========================
 *
 * Miscellaneous helpers helping with bitwise operations.
 */

/**
 * Takes a 32 bits integer and returns its MSB using SWAR strategy.
 *
 * @param  {number} x - Target number.
 * @return {number}
 */
function msb32(x) {
  x |= (x >> 1);
  x |= (x >> 2);
  x |= (x >> 4);
  x |= (x >> 8);
  x |= (x >> 16);

  return (x & ~(x >> 1));
}
exports.msb32 = msb32;

/**
 * Takes a byte and returns its MSB using SWAR strategy.
 *
 * @param  {number} x - Target number.
 * @return {number}
 */
function msb8(x) {
  x |= (x >> 1);
  x |= (x >> 2);
  x |= (x >> 4);

  return (x & ~(x >> 1));
}
exports.msb8 = msb8;

/**
 * Takes a number and return bit at position.
 *
 * @param  {number} x   - Target number.
 * @param  {number} pos - Position.
 * @return {number}
 */
exports.test = function(x, pos) {
  return (x >> pos) & 1;
};

/**
 * Compare two bytes and return their critical bit.
 *
 * @param  {number} a - First byte.
 * @param  {number} b - Second byte.
 * @return {number}
 */
exports.criticalBit8 = function(a, b) {
  return msb8(a ^ b);
};

exports.criticalBit8Mask = function(a, b) {
  return (~msb8(a ^ b) >>> 0) & 0xff;
};

exports.testCriticalBit8 = function(x, mask) {
  return (1 + (x | mask)) >> 8;
};

exports.criticalBit32Mask = function(a, b) {
  return (~msb32(a ^ b) >>> 0) & 0xffffffff;
};

/**
 * Takes a 32 bits integer and returns its population count (number of 1 of
 * the binary representation).
 *
 * @param  {number} x - Target number.
 * @return {number}
 */
exports.popcount = function(x) {
  x -= x >> 1 & 0x55555555;
  x = (x & 0x33333333) + (x >> 2 & 0x33333333);
  x = x + (x >> 4) & 0x0f0f0f0f;
  x += x >> 8;
  x += x >> 16;
  return x & 0x7f;
};

/**
 * Slightly faster popcount function based on a precomputed table of 8bits
 * words.
 *
 * @param  {number} x - Target number.
 * @return {number}
 */
var TABLE8 = new Uint8Array(Math.pow(2, 8));

for (var i = 0, l = TABLE8.length; i < l; i++)
  TABLE8[i] = exports.popcount(i);

exports.table8Popcount = function(x) {
  return (
    TABLE8[x & 0xff] +
    TABLE8[(x >> 8) & 0xff] +
    TABLE8[(x >> 16) & 0xff] +
    TABLE8[(x >> 24) & 0xff]
  );
};
<|endoftext|>


<commit_before>/**
 * lib/podcasts.js - Module for podcast-caching.
 *
 * Authors: Ian McGaunn; Dave Zimmelman
 * Modified: 13 Apr 15
 */

/**
* @callback failCallback - Local callback function used for failed requests.
* @param {Object} error - The error generated upon failure.
* @param {String} msg - Message with additional information about failure.
*
* @callback successCallback - Local callback function used for successful requests.
* @param {*} data - Passed data upon success.
*/

var _db = require('./db');
var mongoose = _db.mongoose;
var dbErr = _db.dbErrorHandler;
var saveDoc = _db.dbSaveDoc;

var request = require('request');

// Force database to update a cache after a
// certain period of time has elapsed. (seconds)
var PODCAST_CACHE_EXPIRE_TIME = 60 * 60 * 24 * 30; // 30 Days

// Define a schema for a podcast to be used
// in the document database.
var podcastSchema = mongoose.Schema({
    _id: Number,
    title: String,
    title_censored: String,
    title_uri: String,
    genre: String,
    genres: [String],
    feedUrl: String,
    viewUrl: String,
    artistId: Number,
    artistName: String,
    artistViewUrl: String,
    artistStationUrl: String,
    explicit: Boolean,
    poster30: String,
    poster60: String,
    poster100: String,
    poster600: String,
    timeAdded: Number,
    popularity: Number
});

// List of Genres names to IDs
var Genres = {
    "1301" : "Arts",
    "1321" : "Business",
    "1303" : "Comedy",
    "1304" : "Education",
    "1323" : "Games & Hobbies",
    "1325" : "Government & Organizations",
    "1307" : "Health",
    "1305" : "Kids & Family",
    "1310" : "Music",
    "1311" : "News & Politics",
    "1314" : "Religion & Spirituality",
    "1315" : "Science & Medicine",
    "1324" : "Society & Culture",
    "1316" : "Sports & Recreation",
    "1318" : "Technology",
    "1309" : "TV & Film"
}

var getGenreId = function (genre) {
    var re = RegExp(genre, 'i');
    for (var id in Genres) {
        if (Genres[id].match(re)) {
            return id;
        }
    }
    return "0";
};

/**
 * Creates a URL-safe title string from the original title.
 *
 * @param {String} str The original title.
 * @return {String} The new URL-safe title.
 */
var safeTitle = function (str) {
    str = str.replace(/\s/g, '-');
    str = str.replace(/[^a-zA-Z0-9\-]/g, '');
    return str;
};

// compile schemas into models
var Podcast = mongoose.model('Podcast', podcastSchema);

/**
 * Retrieve cached podcast data by ID.
 * @param {Number|String} id - The podcast ID.
 * @param {failCallback} failure
 * @param {successCallback} success
 */
var getPodcast = function (id, failure, success) {
    // See if podcast already exists in cache.
    id = Number(id);
    Podcast.count({'_id' : id}, function (err, count) {
        if (err) {
            // Unexpected database error.
            dbErr(err, failure);
        }
        else if (count === 0) {
            // Podcast does not exist in cache
            cachePodcast(id, failure, success);
        }
        else {
            // Podcast exists in cache; pull it out.
            Podcast.findById(id, function (err, pcast) {
                if (err) {
                    dbErr(err, failure);
                }
                else {
                    // Check time added to see if a re-caching is due.
                    var cur_time = Math.floor(new Date() / 1000);
                    if (cur_time > pcast.timeAdded + PODCAST_CACHE_EXPIRE_TIME) {
                        cachePodcast(id, failure, success);
                    }
                    else {
                        success(pcast);
                    }
                }
            });
        }
    });
};

/**
 * Retrieve list of cached podcasts by ID. (Does not force
 *  cache if podcast does not exist).
 * @param {Array} ids - The podcast IDs.
 * @param {failCallback} failure
 * @param {successCallback} success
 */
var getPodcasts = function (ids, failure, success) {
    if (!ids || ids.length < 1) success([]);
    Podcast.find({'_id' : {$in: ids}}, function (err, pcasts) {
        if (err) {
            // Unexpected database error.
            dbErr(err, failure);
        }
        else {
            success(pcasts);
        }
    });
};

/**
 * Forces cache to update any podcasts in the list of IDs that
 *  have not yet been cached.
 * @param {Array} ids - List of podcast IDs to cache.
 */
var podcastForceCache = function (ids, callback) {
    var i = ids.length;
    for (id in ids) {
        cachePodcast(ids[id], function () {
                i--;
            }, function () {
                i--;
            }, true);
    }
    if (callback) {
        var th = setInterval(function() {
            if (i === 0) {
                clearInterval(th);
                callback();
            }
            else {
                console.log("forceCache Callback: Stuck in interval. i = " + i);
            }
        }, 1000);
    }
};

/**
 * Cache podcast data results from iTunes API.
 * @param {Number|String} id - The podcast ID to cache.
 * @param {failCallback} failure
 * @param {successCallback} success
 * @param {Boolean} dont_force - Optionally, silently skip existing ids.
 */
var cachePodcast = function (id, failure, success, dont_force) {
    var error = new Error('Bad Request'),
        apiURL = 'https://itunes.apple.com/lookup?id=' + id;
    error.status = 400;

    if (dont_force) {
        Podcast.count({'_id' : id}, function (err, count) {
            if (!err && count === 0) {
                cachePodcast(id, failure, success);
            }
        });
        return;
    }

    // Get podcast data from iTunes API
    request(apiURL, function (err, response, body) {
        if (response.statusCode == 400) {
            // Bad Request
            error.err = "Bad results from: " + apiURL;
            failure(error, "Bad ID supplied to cachePodcast.");
        }
        else {
            // Parse JSON results
            var results = JSON.parse(body).results;
            if (results.length !== 1) {
                // When valid podcast ID provided, results has single-element array.
                error.err = "Bad results from: " + apiURL;
                failure(error, "Unexpected results from iTunes API.");
            }
            else {
                // Valid data returned from API.
                var pcast = parsePodcast(results[0]);
                saveDoc(pcast, failure, success);
            }
        }
    });
};

/**
 * Update database with podcast data from iTunes
 *  search result.
 * @param {Array} results - The raw json from the API request.
 * @param {failCallback} failure
 * @param {successCallback} success
 */
var cacheSearchResults = function (results, failure, success) {
    var i, th, pcast, pcasts = [];
    for (i in results) {
        pcast = parsePodcast(results[i]);
        saveDoc(pcast, function (err, msg) {
            failure(err, msg);
        }, function (pcast) {
            pcasts.push(pcast);
            if (pcasts.length == results.length) {
                success(pcasts);
            }
        });
    }
}

/**
 * Parse iTunes data into a podcast object.
 * @param {Object} r - The iTunes record to parse.
 * @returns {Podcast} - New object using the Podcast model.
 */
var parsePodcast = function (r) {
    var i, genre, explicit = (r.collectionExplicitness == "explicit") ? true : false;
    if (r.primaryGenreName) {
        if (getGenreId(r.primaryGenreName) == "0") {
            if (r.genreIds) {
                for (i in r.genreIds) {
                    if (Genres[r.genreIds[i]]) {
                        genre = Genres[r.genreIds[i]];
                        break;
                    }
                }
            }
        }
        else genre = r.primaryGenreName;
    }
    if (!genre) genre = "N/A";
    return new Podcast({
        '_id': r.collectionId,
        'title': r.collectionName,
        'title_safe': r.collectionCensoredName,
        'title_uri': safeTitle(r.collectionCensoredName),
        'genre': genre,
        'genres': r.genres,
        'feedUrl': r.feedUrl,
        'viewUrl': r.trackViewUrl,
        'artistId': r.artistId,
        'artistName': r.artistName,
        'artistStationUrl': r.radioStationUrl,
        'explicit': explicit,
        'poster30': r.artworkUrl30,
        'poster60': r.artworkUrl60,
        'poster100': r.artworkUrl100,
        'poster600': r.artworkUrl600,
        'timeAdded': Math.floor(new Date() / 1000),
        'popularity': 0
    });
};

/**
 * Searches the podcast entries' name and artist name
 *  for the given term.
 * @param {String} term - The term to search for.
 * @param {Number} limit - The maximum number of results to return. 0/false: infinite
 * @param {Function} callback - Callback function that takes the returned data set.
 */
var searchByTerm = function (term, limit, callback) {
    var query, re = new RegExp(term, 'i'),
        or_arr = [{'title': {$regex: re}},
                  {'artistName': {$regex: re}},
                  {'genre': {$regex: re}},
                  {'genres': {$regex: re}}];
    query = Podcast.find().or(or_arr).sort({'popularity': -1});
    if (limit) query = query.limit(limit);
    query.exec(function (err, pcasts) {
        callback(pcasts);
    });
};

/**
 * Get podcasts of a given cateogry.
 * @param {Mixed} genre - The genre name or id number.
 * @param {Number} limit - The maximum number of results to return. 0/false: infinite
 * @param {Array} id_nin - Optional array of IDs to filter out of results.
 * @param {Function} callback - Callback function that takes the returned data set.
 */
var getByCategory = function (genre, limit, id_nin, callback) {
    var query, re;
    if (Number(genre) == genre) {
        genre = Genres[genre];
    }
    re = new RegExp(genre, 'i');
    query = Podcast.find({'genre': {$regex: re}});
    if (id_nin && id_nin.length > 0) query = query.where('_id').nin(id_nin);
    query = query.sort({'popularity': -1});
    if (limit) query = query.limit(limit);
    query.exec(function (err, pcasts) {
        callback(pcasts);
    });
};

/**
 * Increases or decreases the popularity of a podcast.
 * @param {Number} id - The podcast ID.
 * @param {Number} points - The number of points to add or remove.
 * @param {Bool} remove - Optional flag to remove points, instead of add.
 * @param {failCallback} fail - Optional
 * @param {successCallback} pass - Optional
 */
var modifyPodcastPoints = function (id, points, remove, fail, pass) {
    // Check and set optional params.
    if (remove) points = points * -1;
    if (!fail) fail = function () {
        console.log('Error saving podcast in podcasts.modifyPodcastPoints.');
    };
    if (!pass) pass = function () {
        console.log('Successfully added ' + points +
                    ' pts to popularity to pcast(' + id + ').');
    };
    // Retrieve podcast doc
    getPodcast(id, function (err, msg) {
        // Unexpected error
        console.log('Error retrieving podcast `' + id + '` in podcasts.modifyPodcastPoints.');
        console.log(msg);
        console.log(JSON.stringify(err));
    }, function (pcast) {
        // Modify popularity points
        if (!pcast.popularity) pcast.popularity = points;
        else pcast.popularity += points;
        saveDoc(pcast, fail, pass);
    });
};

module.exports = {
    Podcast: Podcast,
    getPodcast: getPodcast,
    getPodcasts: getPodcasts,
    podcastForceCache: podcastForceCache,
    cachePodcast: cachePodcast,
    searchByTerm: searchByTerm,
    cacheSearchResults: cacheSearchResults,
    parsePodcast: parsePodcast,
    getGenreId: getGenreId,
    Genres: Genres,
    modifyPodcastPoints: modifyPodcastPoints,
    getByCategory: getByCategory
};<commit_msg>[Debug] removed console log.<commit_after>/**
 * lib/podcasts.js - Module for podcast-caching.
 *
 * Authors: Ian McGaunn; Dave Zimmelman
 * Modified: 13 Apr 15
 */

/**
* @callback failCallback - Local callback function used for failed requests.
* @param {Object} error - The error generated upon failure.
* @param {String} msg - Message with additional information about failure.
*
* @callback successCallback - Local callback function used for successful requests.
* @param {*} data - Passed data upon success.
*/

var _db = require('./db');
var mongoose = _db.mongoose;
var dbErr = _db.dbErrorHandler;
var saveDoc = _db.dbSaveDoc;

var request = require('request');

// Force database to update a cache after a
// certain period of time has elapsed. (seconds)
var PODCAST_CACHE_EXPIRE_TIME = 60 * 60 * 24 * 30; // 30 Days

// Define a schema for a podcast to be used
// in the document database.
var podcastSchema = mongoose.Schema({
    _id: Number,
    title: String,
    title_censored: String,
    title_uri: String,
    genre: String,
    genres: [String],
    feedUrl: String,
    viewUrl: String,
    artistId: Number,
    artistName: String,
    artistViewUrl: String,
    artistStationUrl: String,
    explicit: Boolean,
    poster30: String,
    poster60: String,
    poster100: String,
    poster600: String,
    timeAdded: Number,
    popularity: Number
});

// List of Genres names to IDs
var Genres = {
    "1301" : "Arts",
    "1321" : "Business",
    "1303" : "Comedy",
    "1304" : "Education",
    "1323" : "Games & Hobbies",
    "1325" : "Government & Organizations",
    "1307" : "Health",
    "1305" : "Kids & Family",
    "1310" : "Music",
    "1311" : "News & Politics",
    "1314" : "Religion & Spirituality",
    "1315" : "Science & Medicine",
    "1324" : "Society & Culture",
    "1316" : "Sports & Recreation",
    "1318" : "Technology",
    "1309" : "TV & Film"
}

var getGenreId = function (genre) {
    var re = RegExp(genre, 'i');
    for (var id in Genres) {
        if (Genres[id].match(re)) {
            return id;
        }
    }
    return "0";
};

/**
 * Creates a URL-safe title string from the original title.
 *
 * @param {String} str The original title.
 * @return {String} The new URL-safe title.
 */
var safeTitle = function (str) {
    str = str.replace(/\s/g, '-');
    str = str.replace(/[^a-zA-Z0-9\-]/g, '');
    return str;
};

// compile schemas into models
var Podcast = mongoose.model('Podcast', podcastSchema);

/**
 * Retrieve cached podcast data by ID.
 * @param {Number|String} id - The podcast ID.
 * @param {failCallback} failure
 * @param {successCallback} success
 */
var getPodcast = function (id, failure, success) {
    // See if podcast already exists in cache.
    id = Number(id);
    Podcast.count({'_id' : id}, function (err, count) {
        if (err) {
            // Unexpected database error.
            dbErr(err, failure);
        }
        else if (count === 0) {
            // Podcast does not exist in cache
            cachePodcast(id, failure, success);
        }
        else {
            // Podcast exists in cache; pull it out.
            Podcast.findById(id, function (err, pcast) {
                if (err) {
                    dbErr(err, failure);
                }
                else {
                    // Check time added to see if a re-caching is due.
                    var cur_time = Math.floor(new Date() / 1000);
                    if (cur_time > pcast.timeAdded + PODCAST_CACHE_EXPIRE_TIME) {
                        cachePodcast(id, failure, success);
                    }
                    else {
                        success(pcast);
                    }
                }
            });
        }
    });
};

/**
 * Retrieve list of cached podcasts by ID. (Does not force
 *  cache if podcast does not exist).
 * @param {Array} ids - The podcast IDs.
 * @param {failCallback} failure
 * @param {successCallback} success
 */
var getPodcasts = function (ids, failure, success) {
    if (!ids || ids.length < 1) success([]);
    Podcast.find({'_id' : {$in: ids}}, function (err, pcasts) {
        if (err) {
            // Unexpected database error.
            dbErr(err, failure);
        }
        else {
            success(pcasts);
        }
    });
};

/**
 * Forces cache to update any podcasts in the list of IDs that
 *  have not yet been cached.
 * @param {Array} ids - List of podcast IDs to cache.
 */
var podcastForceCache = function (ids, callback) {
    var i = ids.length;
    for (id in ids) {
        cachePodcast(ids[id], function () {
                i--;
            }, function () {
                i--;
            }, true);
    }
    if (callback) {
        var th = setInterval(function() {
            if (i === 0) {
                clearInterval(th);
                callback();
            }
            else {
                console.log("forceCache Callback: Stuck in interval. i = " + i);
            }
        }, 1000);
    }
};

/**
 * Cache podcast data results from iTunes API.
 * @param {Number|String} id - The podcast ID to cache.
 * @param {failCallback} failure
 * @param {successCallback} success
 * @param {Boolean} dont_force - Optionally, silently skip existing ids.
 */
var cachePodcast = function (id, failure, success, dont_force) {
    var error = new Error('Bad Request'),
        apiURL = 'https://itunes.apple.com/lookup?id=' + id;
    error.status = 400;

    if (dont_force) {
        Podcast.count({'_id' : id}, function (err, count) {
            if (!err && count === 0) {
                cachePodcast(id, failure, success);
            }
        });
        return;
    }

    // Get podcast data from iTunes API
    request(apiURL, function (err, response, body) {
        if (response.statusCode == 400) {
            // Bad Request
            error.err = "Bad results from: " + apiURL;
            failure(error, "Bad ID supplied to cachePodcast.");
        }
        else {
            // Parse JSON results
            var results = JSON.parse(body).results;
            if (results.length !== 1) {
                // When valid podcast ID provided, results has single-element array.
                error.err = "Bad results from: " + apiURL;
                failure(error, "Unexpected results from iTunes API.");
            }
            else {
                // Valid data returned from API.
                var pcast = parsePodcast(results[0]);
                saveDoc(pcast, failure, success);
            }
        }
    });
};

/**
 * Update database with podcast data from iTunes
 *  search result.
 * @param {Array} results - The raw json from the API request.
 * @param {failCallback} failure
 * @param {successCallback} success
 */
var cacheSearchResults = function (results, failure, success) {
    var i, th, pcast, pcasts = [];
    for (i in results) {
        pcast = parsePodcast(results[i]);
        saveDoc(pcast, function (err, msg) {
            failure(err, msg);
        }, function (pcast) {
            pcasts.push(pcast);
            if (pcasts.length == results.length) {
                success(pcasts);
            }
        });
    }
}

/**
 * Parse iTunes data into a podcast object.
 * @param {Object} r - The iTunes record to parse.
 * @returns {Podcast} - New object using the Podcast model.
 */
var parsePodcast = function (r) {
    var i, genre, explicit = (r.collectionExplicitness == "explicit") ? true : false;
    if (r.primaryGenreName) {
        if (getGenreId(r.primaryGenreName) == "0") {
            if (r.genreIds) {
                for (i in r.genreIds) {
                    if (Genres[r.genreIds[i]]) {
                        genre = Genres[r.genreIds[i]];
                        break;
                    }
                }
            }
        }
        else genre = r.primaryGenreName;
    }
    if (!genre) genre = "N/A";
    return new Podcast({
        '_id': r.collectionId,
        'title': r.collectionName,
        'title_safe': r.collectionCensoredName,
        'title_uri': safeTitle(r.collectionCensoredName),
        'genre': genre,
        'genres': r.genres,
        'feedUrl': r.feedUrl,
        'viewUrl': r.trackViewUrl,
        'artistId': r.artistId,
        'artistName': r.artistName,
        'artistStationUrl': r.radioStationUrl,
        'explicit': explicit,
        'poster30': r.artworkUrl30,
        'poster60': r.artworkUrl60,
        'poster100': r.artworkUrl100,
        'poster600': r.artworkUrl600,
        'timeAdded': Math.floor(new Date() / 1000),
        'popularity': 0
    });
};

/**
 * Searches the podcast entries' name and artist name
 *  for the given term.
 * @param {String} term - The term to search for.
 * @param {Number} limit - The maximum number of results to return. 0/false: infinite
 * @param {Function} callback - Callback function that takes the returned data set.
 */
var searchByTerm = function (term, limit, callback) {
    var query, re = new RegExp(term, 'i'),
        or_arr = [{'title': {$regex: re}},
                  {'artistName': {$regex: re}},
                  {'genre': {$regex: re}},
                  {'genres': {$regex: re}}];
    query = Podcast.find().or(or_arr).sort({'popularity': -1});
    if (limit) query = query.limit(limit);
    query.exec(function (err, pcasts) {
        callback(pcasts);
    });
};

/**
 * Get podcasts of a given cateogry.
 * @param {Mixed} genre - The genre name or id number.
 * @param {Number} limit - The maximum number of results to return. 0/false: infinite
 * @param {Array} id_nin - Optional array of IDs to filter out of results.
 * @param {Function} callback - Callback function that takes the returned data set.
 */
var getByCategory = function (genre, limit, id_nin, callback) {
    var query, re;
    if (Number(genre) == genre) {
        genre = Genres[genre];
    }
    re = new RegExp(genre, 'i');
    query = Podcast.find({'genre': {$regex: re}});
    if (id_nin && id_nin.length > 0) query = query.where('_id').nin(id_nin);
    query = query.sort({'popularity': -1});
    if (limit) query = query.limit(limit);
    query.exec(function (err, pcasts) {
        callback(pcasts);
    });
};

/**
 * Increases or decreases the popularity of a podcast.
 * @param {Number} id - The podcast ID.
 * @param {Number} points - The number of points to add or remove.
 * @param {Bool} remove - Optional flag to remove points, instead of add.
 * @param {failCallback} fail - Optional
 * @param {successCallback} pass - Optional
 */
var modifyPodcastPoints = function (id, points, remove, fail, pass) {
    // Check and set optional params.
    if (remove) points = points * -1;
    if (!fail) fail = function () {
        console.log('Error saving podcast in podcasts.modifyPodcastPoints.');
    };
    if (!pass) pass = function () { };
    // Retrieve podcast doc
    getPodcast(id, function (err, msg) {
        // Unexpected error
        console.log('Error retrieving podcast `' + id + '` in podcasts.modifyPodcastPoints.');
        console.log(msg);
        console.log(JSON.stringify(err));
    }, function (pcast) {
        // Modify popularity points
        if (!pcast.popularity) pcast.popularity = points;
        else pcast.popularity += points;
        saveDoc(pcast, fail, pass);
    });
};

module.exports = {
    Podcast: Podcast,
    getPodcast: getPodcast,
    getPodcasts: getPodcasts,
    podcastForceCache: podcastForceCache,
    cachePodcast: cachePodcast,
    searchByTerm: searchByTerm,
    cacheSearchResults: cacheSearchResults,
    parsePodcast: parsePodcast,
    getGenreId: getGenreId,
    Genres: Genres,
    modifyPodcastPoints: modifyPodcastPoints,
    getByCategory: getByCategory
};<|endoftext|>


<commit_before>/**
 * lib/podcasts.js - Module for podcast-caching.
 *
 * Authors: Ian McGaunn; Dave Zimmelman
 * Modified: 13 Apr 15
 */

/**
* @callback failCallback - Local callback function used for failed requests.
* @param {Object} error - The error generated upon failure.
* @param {String} msg - Message with additional information about failure.
*
* @callback successCallback - Local callback function used for successful requests.
* @param {*} data - Passed data upon success.
*/

var _db = require('./db');
var mongoose = _db.mongoose;
var dbErr = _db.dbErrorHandler;
var saveDoc = _db.dbSaveDoc;

var request = require('request');

// Force database to update a cache after a
// certain period of time has elapsed. (seconds)
var PODCAST_CACHE_EXPIRE_TIME = 60 * 60 * 24 * 30; // 30 Days

// Define a schema for a podcast to be used
// in the document database.
var podcastSchema = mongoose.Schema({
    _id: Number,
    title: String,
    title_censored: String,
    title_uri: String,
    genre: String,
    feedUrl: String,
    viewUrl: String,
    artistId: Number,
    artistName: String,
    artistViewUrl: String,
    artistStationUrl: String,
    explicit: Boolean,
    poster30: String,
    poster60: String,
    poster100: String,
    poster600: String,
    timeAdded: Number
});

module.exports = {

    // compile schemas into models
    Podcast : mongoose.model('Podcast', podcastSchema),

    /**
     * Retrieve cached podcast data by ID.
     * @param {Number|String} id - The podcast ID.
     * @param {failCallback} failure
     * @param {successCallback} success
     */
    getPodcast : function (id, falure, success) {
        // See if podcast already exists in cache.
        this.Podcast.count({'_id' : id}, function (err, count) {
            if (err) {
                // Unexpected database error.
                dbErr(err, failure);
            }
            else if (count === 0) {
                // Podcast does not exist in cache
                this.cachePodcast(id, failure, success);
            }
            else {
                // Podcast exists in cache; pull it out.
                this.Podcast.findById(id, function (err, pcast) {
                    if (err) {
                        dbErr(err, failure);
                    }
                    else {
                        // Check time added to see if a re-caching is due.
                        var cur_time = Math.floor(new Date() / 1000);
                        if (cur_time > pcast.timeAdded + PODCAST_CACHE_EXPIRE_TIME) {
                            this.cachePodcast(id, failure, success);
                        }
                        else {
                            success(pcast);
                        }
                    }
                });
            }
        });
    },

    /**
     * Forces cache to update any podcasts in the list of IDs that
     *  have not yet been cached.
     * @param {Array} ids - List of podcast IDs to cache.
     */
    podcastForceCache : function (ids) {
        for (id in ids) {
            this.Podcast.count({'_id' : id}, function (err, count) {
                if (!err && count === 0) {
                    this.cachePodcast(id, function () {}, function () {});

                }
            });
        }
    },

    /**
     * Cache podcast data results from iTunes API.
     * @param {Number|String} id - The podcast ID to cache.
     * @param {failCallback} failure
     * @param {successCallback} success
     */
    cachePodcast : function (id, failure, success) {
        var error = new Error('Bad Request'),
            apiURL = 'https://itunes.apple.com/lookup?id=' + id;
        error.status = 400;

        // Get podcast data from iTunes API
        request(apiURL, function (error, response, body) {
            if (response.statusCode == 400) {
                // Bad Request
                error.err = "Bad results from: " + apiURL;
                failure(error, "Bad ID supplied to cachePodcast.");
            }
            else {
                // Parse JSON results
                var results = JSON.parse(body).results;
                if (results.length !== 1) {
                    // When valid podcast ID provided, results has single-element array.
                    error.err = "Bad results from: " + apiURL;
                    failure(error, "Unexpected results from iTunes API.");
                }
                else {
                    // Valid data returned from API.
                    var result = results[0],
                        genre = (result.primaryGenreName) ? result.primaryGenreName : "N/A",
                        explicit = (result.collectionExplicitness == "explicit") ? true : false,
                        pcast = new this.Podcast({
                            '_id': id,
                            'title': result.collectionName,
                            'title_safe': result.collectionCensoredName,
                            'title_uri': safeTitle(result.collectionCensoredName),
                            'genre': genre,
                            'feedUrl': result.feedUrl,
                            'viewUrl': result.trackViewUrl,
                            'artistId': result.artistId,
                            'artistName': result.artistName,
                            'artistStationUrl': result.radioStationUrl,
                            'explicit': explicit,
                            'poster30': result.artworkUrl30,
                            'poster60': result.artworkUrl60,
                            'poster100': result.artworkUrl100,
                            'poster600': result.artworkUrl600,
                            'timeAdded': Math.floor(new Date() / 1000)
                        });
                        saveDoc(pcast, failure, success);
                }
            }
        });
    }
}<commit_msg>[Fix] Syntax<commit_after>/**
 * lib/podcasts.js - Module for podcast-caching.
 *
 * Authors: Ian McGaunn; Dave Zimmelman
 * Modified: 13 Apr 15
 */

/**
* @callback failCallback - Local callback function used for failed requests.
* @param {Object} error - The error generated upon failure.
* @param {String} msg - Message with additional information about failure.
*
* @callback successCallback - Local callback function used for successful requests.
* @param {*} data - Passed data upon success.
*/

var _db = require('./db');
var mongoose = _db.mongoose;
var dbErr = _db.dbErrorHandler;
var saveDoc = _db.dbSaveDoc;

var request = require('request');

// Force database to update a cache after a
// certain period of time has elapsed. (seconds)
var PODCAST_CACHE_EXPIRE_TIME = 60 * 60 * 24 * 30; // 30 Days

// Define a schema for a podcast to be used
// in the document database.
var podcastSchema = mongoose.Schema({
    _id: Number,
    title: String,
    title_censored: String,
    title_uri: String,
    genre: String,
    feedUrl: String,
    viewUrl: String,
    artistId: Number,
    artistName: String,
    artistViewUrl: String,
    artistStationUrl: String,
    explicit: Boolean,
    poster30: String,
    poster60: String,
    poster100: String,
    poster600: String,
    timeAdded: Number
});

module.exports = {

    // compile schemas into models
    Podcast : mongoose.model('Podcast', podcastSchema),

    /**
     * Retrieve cached podcast data by ID.
     * @param {Number|String} id - The podcast ID.
     * @param {failCallback} failure
     * @param {successCallback} success
     */
    getPodcast : function (id, falure, success) {
        // See if podcast already exists in cache.
        Podcast.count({'_id' : id}, function (err, count) {
            if (err) {
                // Unexpected database error.
                dbErr(err, failure);
            }
            else if (count === 0) {
                // Podcast does not exist in cache
                cachePodcast(id, failure, success);
            }
            else {
                // Podcast exists in cache; pull it out.
                this.Podcast.findById(id, function (err, pcast) {
                    if (err) {
                        dbErr(err, failure);
                    }
                    else {
                        // Check time added to see if a re-caching is due.
                        var cur_time = Math.floor(new Date() / 1000);
                        if (cur_time > pcast.timeAdded + PODCAST_CACHE_EXPIRE_TIME) {
                            cachePodcast(id, failure, success);
                        }
                        else {
                            success(pcast);
                        }
                    }
                });
            }
        });
    },

    /**
     * Forces cache to update any podcasts in the list of IDs that
     *  have not yet been cached.
     * @param {Array} ids - List of podcast IDs to cache.
     */
    podcastForceCache : function (ids) {
        for (id in ids) {
            Podcast.count({'_id' : id}, function (err, count) {
                if (!err && count === 0) {
                    cachePodcast(id, function () {}, function () {});

                }
            });
        }
    },

    /**
     * Cache podcast data results from iTunes API.
     * @param {Number|String} id - The podcast ID to cache.
     * @param {failCallback} failure
     * @param {successCallback} success
     */
    cachePodcast : function (id, failure, success) {
        var error = new Error('Bad Request'),
            apiURL = 'https://itunes.apple.com/lookup?id=' + id;
        error.status = 400;

        // Get podcast data from iTunes API
        request(apiURL, function (error, response, body) {
            if (response.statusCode == 400) {
                // Bad Request
                error.err = "Bad results from: " + apiURL;
                failure(error, "Bad ID supplied to cachePodcast.");
            }
            else {
                // Parse JSON results
                var results = JSON.parse(body).results;
                if (results.length !== 1) {
                    // When valid podcast ID provided, results has single-element array.
                    error.err = "Bad results from: " + apiURL;
                    failure(error, "Unexpected results from iTunes API.");
                }
                else {
                    // Valid data returned from API.
                    var result = results[0],
                        genre = (result.primaryGenreName) ? result.primaryGenreName : "N/A",
                        explicit = (result.collectionExplicitness == "explicit") ? true : false,
                        pcast = new Podcast({
                            '_id': id,
                            'title': result.collectionName,
                            'title_safe': result.collectionCensoredName,
                            'title_uri': safeTitle(result.collectionCensoredName),
                            'genre': genre,
                            'feedUrl': result.feedUrl,
                            'viewUrl': result.trackViewUrl,
                            'artistId': result.artistId,
                            'artistName': result.artistName,
                            'artistStationUrl': result.radioStationUrl,
                            'explicit': explicit,
                            'poster30': result.artworkUrl30,
                            'poster60': result.artworkUrl60,
                            'poster100': result.artworkUrl100,
                            'poster600': result.artworkUrl600,
                            'timeAdded': Math.floor(new Date() / 1000)
                        });
                        saveDoc(pcast, failure, success);
                }
            }
        });
    }
}<|endoftext|>


<commit_before>context('Navigation', () => {
    context('Home', () => {
        beforeEach(() => {
            cy.visit('http://localhost:8000');
        });

        it('can navigate to contact page', () => {
            cy.get('[data-cy=contact-link]').click();

            cy.location('pathname').should('include', 'contact');
        });

        it('can navigate to profile page and back', () => {
            cy.get('[data-cy=profile-link]').click();
            cy.location('pathname').should('include', 'profile');

            cy.get('[data-cy=logo-link]').click();
            cy.location('pathname').should('eq', '/');
        });
    });

    context('Offcanvas', () => {
        beforeEach(() => {
            cy.get('[data-cy=offcanvas-btn]').as('offcanvasBtn');
        });

        it('can be opened', () => {
            cy.get('@offcanvasBtn')
                .should('not.have.class', 'active')
                .click()
                .should('have.class', 'active');
        });

        it('can navigate to contact', () => {
            cy.visit('http://localhost:8000');

            cy.get('@offcanvasBtn').click();
            cy.wait(300)
                .get('[data-cy=offcanvas-contact-link]')
                .click();
            cy.location('pathname').should('include', 'contact');
        });

        it('can navigate to profile', () => {
            cy.get('@offcanvasBtn').click();
            cy.wait(300)
                .get('[data-cy=offcanvas-profile-link]')
                .click();
            cy.location('pathname').should('include', 'profile');
        });

        it('can navigate to home', () => {
            cy.get('@offcanvasBtn').click();
            cy.wait(300)
                .get('[data-cy=offcanvas-home-link]')
                .click();
            cy.location('pathname').should('eq', '/');
        });
    });

    context('Profile', () => {
        beforeEach(() => {
            cy.visit('http://localhost:8000/profile');
        });

        it('can navigate to contact page', () => {
            cy.get('[data-cy=contact-link]').click();
            cy.location('pathname').should('include', 'contact');
        });
    });
});
<commit_msg>Fix flaky tests<commit_after>context('Navigation', () => {
    context('Home', () => {
        beforeEach(() => {
            cy.visit('http://localhost:8000');
        });

        it('can navigate to contact page', () => {
            cy.get('[data-cy=contact-link]').click();

            cy.location('pathname').should('include', 'contact');
        });

        it('can navigate to profile page and back', () => {
            cy.get('[data-cy=profile-link]').click();
            cy.location('pathname').should('include', 'profile');

            cy.get('[data-cy=logo-link]').click();
            cy.location('pathname').should('eq', '/');
        });
    });

    context('Offcanvas', () => {
        beforeEach(() => {
            cy.get('[data-cy=offcanvas-btn]').as('offcanvasBtn');
        });

        it('can be opened', () => {
            cy.get('@offcanvasBtn')
                .should('not.have.class', 'active')
                .click()
                .should('have.class', 'active');
        });

        it('can navigate to contact', () => {
            cy.visit('http://localhost:8000');

            cy.get('@offcanvasBtn')
                .wait(500)
                .click();
            cy.get('[data-cy=offcanvas-contact-link]').click();
            cy.location('pathname').should('include', 'contact');
        });

        it('can navigate to profile', () => {
            cy.get('@offcanvasBtn').click();
            cy.get('[data-cy=offcanvas-profile-link]').click();
            cy.location('pathname').should('include', 'profile');
        });

        it('can navigate to home', () => {
            cy.get('@offcanvasBtn').click();
            cy.get('[data-cy=offcanvas-home-link]').click();
            cy.location('pathname').should('eq', '/');
        });
    });

    context('Profile', () => {
        beforeEach(() => {
            cy.visit('http://localhost:8000/profile');
        });

        it('can navigate to contact page', () => {
            cy.get('[data-cy=contact-link]').click();
            cy.location('pathname').should('include', 'contact');
        });
    });
});
<|endoftext|>


<commit_before>var Actions = {
    init: function() {
        selectAll = document.getElementById('action-toggle');
        if (selectAll) {
            selectAll.style.display = 'inline';
            addEvent(selectAll, 'change', function() {
                Actions.checker(this.checked);
            });
        }
    },
    checker: function(checked) {
        actionCheckboxes = document.getElementsBySelector('tr input.action-select');
        for(var i = 0; i < actionCheckboxes.length; i++) {
            actionCheckboxes[i].checked = checked;
        }
    }
}

addEvent(window, 'load', Actions.init);
<commit_msg>Fixed #10597 -- select all checkbox on admin changelist now works under IE 6 and 7. Thanks kyle.fox.<commit_after>var Actions = {
    init: function() {
        selectAll = document.getElementById('action-toggle');
        if (selectAll) {
            selectAll.style.display = 'inline';
            addEvent(selectAll, 'click', function() {
                Actions.checker(selectAll.checked);
            });
        }
    },
    checker: function(checked) {
        actionCheckboxes = document.getElementsBySelector('tr input.action-select');
        for(var i = 0; i < actionCheckboxes.length; i++) {
            actionCheckboxes[i].checked = checked;
        }
    }
}

addEvent(window, 'load', Actions.init);
<|endoftext|>


<commit_before>var async = require("async");

module.exports = function(sequalize, DataTypes) {
	var AttendanceType = sequalize.define("AttendanceType", {
		uuid: {
			type: DataTypes.UUID,
			defaultValue: DataTypes.UUIDV1,
			primaryKey: true
		},
		name: {
			type: DataTypes.STRING,
			allowNull: false,
			validate: {
				notEmpty: true
			}
		},
		pointValue: {
			type: DataTypes.INTEGER
		},
		isEnabled: { // This will be used to archive old types going into new semesters
						// it will let the old points be calculated based on the old
						// versions, but not let them appear in the new season's options.
						//nTypes should never be deleted, only archived.
			type: DataTypes.BOOLEAN,
			defaultValue: true
		},
		includeShow: { //Points will be calculated by taking the points of the show
						// and the value of the type IF this is true. Otherwise just
						// from the type.
			type: DataTypes.BOOLEAN,
			defaultValue: false
		}
	},
	{
		classMethods: {
			getRequestedType: function(callback) {
				this.findOrCreate({
	    			where: {
		    			name: "Requested"
		    		},
		    		defaults: {
		    			points: 0,
		    			isEnabled: 1,
		    			includeShow: 1
		    		}
	    		}).then(function(typeArr, created) {
	    			callback(typeArr[0], created);
	    		});
			},
			getCanceledType: function(callback) {
				this.findOrCreate({
	    			where: {
		    			name: "Canceled"
		    		},
		    		defaults: {
		    			points: 0,
		    			isEnabled: 1
					}
	    		}).then(function(cancelType, created) {
	    			callback(cancelType[0], created);
	    		});
			},
			getConfirmedType: function(callback) {
				this.findOrCreate({
	    			where: {
		    			name: "Confirmed"
		    		},
		    		defaults: {
		    			points: 0,
		    			isEnabled: 1
					}
	    		}).then(function(confirmedType, created) {
	    			callback(confirmedType[0], created);
	    		});
			},
			getCutType: function(callback) {
				this.findOrCreate({
	    			where: {
		    			name: "Cut"
		    		},
		    		defaults: {
		    			points: 20,
		    			isEnabled: 1
					}
	    		}).then(function(cutType, created) {
	    			callback(cutType[0], created);
	    		});
			},
			getAllDefaultTypesMiddleware: function(req, res, next) {
				async.parallel([
					function(cb) {
						AttendanceType.getRequestedType(function(requestType) {
			    			res.locals.requestType = requestType;
			    			cb(null);
			    		});
					},
					function(cb) {
						AttendanceType.getCanceledType(function(canceledType) {
			    			res.locals.cancelType = canceledType;
			    			cb(null);
			    		});
					},
					function(cb) {
						AttendanceType.getConfirmedType(function(confirmedType) {
			    			res.locals.confirmedType = confirmedType;
			    			cb(null);
			    		});
					},
					function(cb) {
						AttendanceType.getCutType(function(cutType) {
			    			res.locals.cutType = cutType;
			    			cb(null);
			    		});
					}
				], function() {
					next();
				});
			}
		}
	});

	return AttendanceType;
};<commit_msg>Changes to the attendance type getters and fixed naming issue<commit_after>var async = require("async");

module.exports = function(sequalize, DataTypes) {
	var AttendanceType = sequalize.define("AttendanceType", {
		uuid: {
			type: DataTypes.UUID,
			defaultValue: DataTypes.UUIDV1,
			primaryKey: true
		},
		name: {
			type: DataTypes.STRING,
			allowNull: false,
			validate: {
				notEmpty: true
			}
		},
		pointValue: {
			type: DataTypes.INTEGER
		},
		isEnabled: { // This will be used to archive old types going into new semesters
						// it will let the old points be calculated based on the old
						// versions, but not let them appear in the new season's options.
						//nTypes should never be deleted, only archived.
			type: DataTypes.BOOLEAN,
			defaultValue: true
		},
		includeShow: { //Points will be calculated by taking the points of the show
						// and the value of the type IF this is true. Otherwise just
						// from the type.
			type: DataTypes.BOOLEAN,
			defaultValue: false
		}
	},
	{
		classMethods: {
			getRequestedType: function(callback) {
				this.findOrCreate({
	    			where: {
		    			name: "Requested"
		    		},
		    		defaults: {
		    			pointValue: 0,
		    			isEnabled: 1,
		    			includeShow: 1
		    		}
	    		}).then(function(typeArr, created) {
	    			callback(typeArr[0], created);
	    		});
			},
			getCanceledType: function(callback) {
				this.findOrCreate({
	    			where: {
		    			name: "Canceled"
		    		},
		    		defaults: {
		    			pointValue: 0,
		    			isEnabled: 1
					}
	    		}).then(function(cancelType, created) {
	    			callback(cancelType[0], created);
	    		});
			},
			getConfirmedType: function(callback) {
				this.findOrCreate({
	    			where: {
		    			name: "Confirmed"
		    		},
		    		defaults: {
		    			pointValue: 0,
		    			isEnabled: 1
					}
	    		}).then(function(confirmedType, created) {
	    			callback(confirmedType[0], created);
	    		});
			},
			getCutType: function(callback) {
				this.findOrCreate({
	    			where: {
		    			name: "Cut"
		    		},
		    		defaults: {
		    			pointValue: 20,
		    			isEnabled: 1
					}
	    		}).then(function(cutType, created) {
	    			callback(cutType[0], created);
	    		});
			},
            getPresentType: function(callback) {
                this.findOrCreate({
                    where: {
                        name: "Present"
                    },
                    defaults: {
                        pointValue: 0,
                        includeShow: 1,
                        isEnabled: 1
                    }
                }).then(function(presentType, created) {
                    callback(presentType[0], created);
                })
            },
            getAllDefaultTypes: function(callback) {
                async.parallel(
                    {
                        requestType: function(cb) {
                            AttendanceType.getRequestedType(function(requestType) {
                                cb(null, requestType);
                            });
                        },
                        canceledType: function(cb) {
                            AttendanceType.getCanceledType(function(canceledType) {
                                cb(null, canceledType);
                            });
                        },
                        confirmedType: function(cb) {
                            AttendanceType.getConfirmedType(function(confirmedType) {
                                cb(null, confirmedType);
                            });
                        },
                        cutType: function(cb) {
                            AttendanceType.getCutType(function(cutType) {
                                cb(null, cutType);
                            });
                        },
                        presentType: function(cb) {
                            AttendanceType.getPresentType(function(presentType) {
                                cb(null, presentType);
                            })
                        }
                    },
                    function(err, result) {
					   callback(err, result);
				    }
                );
            },
			getAllDefaultTypesMiddleware: function(req, res, next) {
                this.getAllDefaultTypes(function(err, result) {
                    for (var type in result) {
                        result[type] = res.locals[type];
                    }
                    next();
                });
			}
		}
	});

	return AttendanceType;
};<|endoftext|>


<commit_before>
// internal libs
import { component } from 'src/app-decorators';
import Test from './testcomponent';

// external libs
import $ from 'jquery';

describe('imported component', () => {

	@component(HTMLElement)
	class Col {

		item = { name: 'A', content: 1 };

		created(){
			let testCom = Test.create(this.item);
			$(this).append(testCom);
		}

	}

	it('should create an domnode', () => {

		let x = Col.create();
		x.outerHTML.should.equal('<com-col><com-test>
1
</com-test></com-col>');

	});

});
<commit_msg>corrected name of creatd Col<commit_after>
// internal libs
import { component } from 'src/app-decorators';
import Test from './testcomponent';

// external libs
import $ from 'jquery';

describe('imported component', () => {

	@component(HTMLElement)
	class Col {

		item = { name: 'A', content: 1 };

		created(){
			let testCom = Test.create(this.item);
			$(this).append(testCom);
		}

	}

	it('should create an domnode', () => {

		let col = Col.create();
		col.outerHTML.should.equal('<com-col><com-test>
1
</com-test></com-col>');

	});

});
<|endoftext|>


<commit_before>/*
 * node-rdkafka - Node.js wrapper for RdKafka C/C++ library
 *
 * Copyright (c) 2016 Blizzard Entertainment
 *
 * This software may be modified and distributed under the terms
 * of the MIT license.  See the LICENSE.txt file for details.
 */

module.exports = Producer;

var Client = require('./client');

var util = require('util');
var Kafka = require('../librdkafka.js');
var TopicWritable = require('./util/topicWritable');
var LibrdKafkaError = require('./error');

util.inherits(Producer, Client);

/**
 * Producer class for sending messages to Kafka
 *
 * This is the main entry point for writing data to Kafka. You
 * configure this like you do any other client, with a global
 * configuration and default topic configuration.
 *
 * Once you instantiate this object, you need to connect to it first.
 * This allows you to get the metadata and make sure the connection
 * can be made before you depend on it. After that, problems with
 * the connection will by brought down by using poll, which automatically
 * runs when a transaction is made on the object.
 *
 * @param {object} conf - Key value pairs to configure the producer
 * @param {object} topicConf - Key value pairs to create a default
 * topic configuration
 * @extends Client
 * @constructor
 */
function Producer(conf, topicConf) {
  if (!(this instanceof Producer)) {
    return new Producer(conf, topicConf);
  }

  /**
   * Producer message. This is sent to the wrapper, not received from it
   *
   * @typedef {object} Producer~Message
   * @property {string|buffer} message - The buffer to send to Kafka.
   * @property {Topic} topic - The Kafka topic to produce to.
   * @property {number} partition - The partition to produce to. Defaults to
   * the partitioner
   * @property {string} key - The key string to use for the message.
   * @see Consumer~Message
   */

  var gTopic = conf.topic || false;
  var gPart = conf.partition || null;
  var dr_cb = conf.dr_cb || conf.dr_msg_cb || null;

  // delete keys we don't want to pass on
  delete conf.topic;
  delete conf.partition;

  delete conf.dr_cb;
  delete conf.dr_msg_cb;

  // client is an initialized consumer object
  // @see NodeKafka::Consumer::Init
  Client.call(this, conf, Kafka.Producer, topicConf);
  var self = this;

  // Delete these keys after saving them in vars
  this.globalConfig = conf;
  this.topicConfig = topicConf;
  this.defaultTopic = gTopic || null;
  this.defaultPartition = gPart == null ? -1 : gPart;

  this.sentMessages = 0;

  this.createdTopics = {};

  if (dr_cb) {
    this._client.onDeliveryReport(function onDeliveryReport(err, report) {
      if (err) {
        self.emit('error', LibrdKafkaError.create(err));
      }
      self.emit('delivery-report', err, report);
    });

    if (typeof dr_cb === 'function') {
      self.on('delivery-report', dr_cb);
    }

  }

}

/**
 * Create a topic if it does not exist. Otherwise, get the cached one
 *
 * @private
 */
function maybeTopic(name) {
  // create or get topic
  var topic;

  if (typeof(name) === 'string') {
    if (this.createdTopics[name]) {
      topic = this.createdTopics[name];
    } else {
      topic = this.createdTopics[name] = this.Topic(name, this.topicConfig || undefined);
    }

    return topic;
  } else {
    // this may be what we want
    return name;
  }
}

/**
 * Create a topic by topic name and config
 *
 * This is a v8 managed object. It does this to avoid creating
 * a topic configuration every time we send a message, which,
 * in our case, is tens of thousands of times per second.
 *
 * Rather than that, since topics generally don't change much,
 * we just manage them in memory for however many we are using.
 * They are pretty small and it is easy since the name can act
 * as a key.
 *
 * This is a good place to start in an upgrade across the board
 * to v4
 * @version 0.12
 *
 * @param  {string} name - the name of the topic to create
 * @param {object} config - a topic configuration.
 * @return {Topic} - a new Kafka Topic.
 */
Producer.prototype.Topic = function(name, config) {
  try {
    if (!this._isConnected) {
      throw new Error('Producer not connected');
    }
    return new Kafka.Topic(this._client, name, config || undefined);
  } catch (err) {
    err.message = 'Error creating topic "' + name + '"": ' + err.message;
    throw LibrdKafkaError.create(err);
  }
};

/**
 * Produce a message to Kafka by providing an object
 *
 * This method is deprecated and maintained only for backwards compatibility.
 * You should use the new synchronous produce method, which does not
 * do object enumeration
 *
 * @param  {object} message - Produced message object
 * @param {function} cb - Optional callback
 * @deprecated
 * @see Producer::produce
 */
Producer.prototype._produceObject = util.deprecate(function(message, cb) {
  cb = cb || function() {};

  if (!Buffer.isBuffer(message.message)) {
    message.message = new Buffer(message.message);
  }

  try {
    this.produce(message.topic, message.partition, message.message, message.key);
    setImmediate(cb);
  } catch (e) {
    setImmediate(function() {
      cb(e);
    });
  }

}, 'Producer.produce: this function no longer takes an object. Instead, it takes "topic", "partition", "message", and "key"');

/**
 * Produce a message to Kafka synchronously.
 *
 * This is the method mainly used in this class. Use it to produce
 * a message to Kafka. The first and only parameter of the synchronous
 * variant is the message object.
 *
 * When this is sent off, there is no guarantee it is delivered. If you need
 * guaranteed delivery, change your *acks* settings, or use delivery reports.
 *
 * @param {string} topic - The topic name to produce to.
 * @param {number|null} partition - The partition number to produce to.
 * @param {Buffer|null} message - The message to produce.
 * @param {string} key - The key associated with the message.
 * @param opaque - An object you want passed along with this message, if provided.
 * @throws {LibrdKafkaError} - Throws a librdkafka error if it failed.
 * @return {boolean} - returns an error if it failed, or true if not
 * @see Producer#produce
 */
Producer.prototype.produce = function(topic, partition, message, key, opaque) {
  if (!this._isConnected) {
    throw new Error('Producer not connected');
  }

  if (typeof topic === 'object' && !(topic instanceof Kafka.Topic)) {
    return this._produceObject(topic, partition);
  }

  if (!topic) {
    throw new TypeError('"topic" needs to be set');
  }

  this.sentMessages++;

  partition = partition == null ? this.defaultPartition : partition;

  topic = maybeTopic.call(this, topic);

  return this._errorWrap(
    this._client.produce(topic, partition, message, key || null, opaque || undefined));

};

/**
 * Get a write stream for the given topic.
 *
 * This stream does not run in object mode. It only takes buffers of data.
 *
 * @param {string} topic - String topic name.
 * @param {object} options - Stream options
 * @param {number} options.highWaterMark - Controls how many messages are
 * read at a time.
 * @return {TopicWritable} - returns the write stream for writing to Kafka.
 */
Producer.prototype.getWriteStream = function(topic, options) {
  return new TopicWritable(this, topic, options);
};

/**
 * Poll for events
 *
 * We need to run poll in order to learn about new events that have occurred.
 * This is done automatically every time we produce a message, but if you'd
 * like to do it more because Kafka does not have enough traffic or because
 * you need to know at certain points, you can manually poll for changes.
 *
 * @return {Producer} - returns itself.
 */
Producer.prototype.poll = function() {
  if (!this._isConnected) {
    throw new Error('Producer not connected');
  }
  this._client.poll();
  return this;
};

/**
 * Flush the producer
 *
 * Flush everything on the internal librdkafka producer buffer. Do this before
 * disconnects usually
 *
 * @return {Producer} - returns itself.
 */
Producer.prototype.flush = function(timeout) {
  if (!this._isConnected) {
    throw new Error('Producer not connected');
  }
  this._client.flush(timeout);
  return this;
};
<commit_msg>Allow undefined to pass to key in produce method without or assignment<commit_after>/*
 * node-rdkafka - Node.js wrapper for RdKafka C/C++ library
 *
 * Copyright (c) 2016 Blizzard Entertainment
 *
 * This software may be modified and distributed under the terms
 * of the MIT license.  See the LICENSE.txt file for details.
 */

module.exports = Producer;

var Client = require('./client');

var util = require('util');
var Kafka = require('../librdkafka.js');
var TopicWritable = require('./util/topicWritable');
var LibrdKafkaError = require('./error');

util.inherits(Producer, Client);

/**
 * Producer class for sending messages to Kafka
 *
 * This is the main entry point for writing data to Kafka. You
 * configure this like you do any other client, with a global
 * configuration and default topic configuration.
 *
 * Once you instantiate this object, you need to connect to it first.
 * This allows you to get the metadata and make sure the connection
 * can be made before you depend on it. After that, problems with
 * the connection will by brought down by using poll, which automatically
 * runs when a transaction is made on the object.
 *
 * @param {object} conf - Key value pairs to configure the producer
 * @param {object} topicConf - Key value pairs to create a default
 * topic configuration
 * @extends Client
 * @constructor
 */
function Producer(conf, topicConf) {
  if (!(this instanceof Producer)) {
    return new Producer(conf, topicConf);
  }

  /**
   * Producer message. This is sent to the wrapper, not received from it
   *
   * @typedef {object} Producer~Message
   * @property {string|buffer} message - The buffer to send to Kafka.
   * @property {Topic} topic - The Kafka topic to produce to.
   * @property {number} partition - The partition to produce to. Defaults to
   * the partitioner
   * @property {string} key - The key string to use for the message.
   * @see Consumer~Message
   */

  var gTopic = conf.topic || false;
  var gPart = conf.partition || null;
  var dr_cb = conf.dr_cb || conf.dr_msg_cb || null;

  // delete keys we don't want to pass on
  delete conf.topic;
  delete conf.partition;

  delete conf.dr_cb;
  delete conf.dr_msg_cb;

  // client is an initialized consumer object
  // @see NodeKafka::Consumer::Init
  Client.call(this, conf, Kafka.Producer, topicConf);
  var self = this;

  // Delete these keys after saving them in vars
  this.globalConfig = conf;
  this.topicConfig = topicConf;
  this.defaultTopic = gTopic || null;
  this.defaultPartition = gPart == null ? -1 : gPart;

  this.sentMessages = 0;

  this.createdTopics = {};

  if (dr_cb) {
    this._client.onDeliveryReport(function onDeliveryReport(err, report) {
      if (err) {
        self.emit('error', LibrdKafkaError.create(err));
      }
      self.emit('delivery-report', err, report);
    });

    if (typeof dr_cb === 'function') {
      self.on('delivery-report', dr_cb);
    }

  }

}

/**
 * Create a topic if it does not exist. Otherwise, get the cached one
 *
 * @private
 */
function maybeTopic(name) {
  // create or get topic
  var topic;

  if (typeof(name) === 'string') {
    if (this.createdTopics[name]) {
      topic = this.createdTopics[name];
    } else {
      topic = this.createdTopics[name] = this.Topic(name, this.topicConfig || undefined);
    }

    return topic;
  } else {
    // this may be what we want
    return name;
  }
}

/**
 * Create a topic by topic name and config
 *
 * This is a v8 managed object. It does this to avoid creating
 * a topic configuration every time we send a message, which,
 * in our case, is tens of thousands of times per second.
 *
 * Rather than that, since topics generally don't change much,
 * we just manage them in memory for however many we are using.
 * They are pretty small and it is easy since the name can act
 * as a key.
 *
 * This is a good place to start in an upgrade across the board
 * to v4
 * @version 0.12
 *
 * @param  {string} name - the name of the topic to create
 * @param {object} config - a topic configuration.
 * @return {Topic} - a new Kafka Topic.
 */
Producer.prototype.Topic = function(name, config) {
  try {
    if (!this._isConnected) {
      throw new Error('Producer not connected');
    }
    return new Kafka.Topic(this._client, name, config || undefined);
  } catch (err) {
    err.message = 'Error creating topic "' + name + '"": ' + err.message;
    throw LibrdKafkaError.create(err);
  }
};

/**
 * Produce a message to Kafka by providing an object
 *
 * This method is deprecated and maintained only for backwards compatibility.
 * You should use the new synchronous produce method, which does not
 * do object enumeration
 *
 * @param  {object} message - Produced message object
 * @param {function} cb - Optional callback
 * @deprecated
 * @see Producer::produce
 */
Producer.prototype._produceObject = util.deprecate(function(message, cb) {
  cb = cb || function() {};

  if (!Buffer.isBuffer(message.message)) {
    message.message = new Buffer(message.message);
  }

  try {
    this.produce(message.topic, message.partition, message.message, message.key);
    setImmediate(cb);
  } catch (e) {
    setImmediate(function() {
      cb(e);
    });
  }

}, 'Producer.produce: this function no longer takes an object. Instead, it takes "topic", "partition", "message", and "key"');

/**
 * Produce a message to Kafka synchronously.
 *
 * This is the method mainly used in this class. Use it to produce
 * a message to Kafka. The first and only parameter of the synchronous
 * variant is the message object.
 *
 * When this is sent off, there is no guarantee it is delivered. If you need
 * guaranteed delivery, change your *acks* settings, or use delivery reports.
 *
 * @param {string} topic - The topic name to produce to.
 * @param {number|null} partition - The partition number to produce to.
 * @param {Buffer|null} message - The message to produce.
 * @param {string} key - The key associated with the message.
 * @param opaque - An object you want passed along with this message, if provided.
 * @throws {LibrdKafkaError} - Throws a librdkafka error if it failed.
 * @return {boolean} - returns an error if it failed, or true if not
 * @see Producer#produce
 */
Producer.prototype.produce = function(topic, partition, message, key, opaque) {
  if (!this._isConnected) {
    throw new Error('Producer not connected');
  }

  if (typeof topic === 'object' && !(topic instanceof Kafka.Topic)) {
    return this._produceObject(topic, partition);
  }

  if (!topic) {
    throw new TypeError('"topic" needs to be set');
  }

  this.sentMessages++;

  partition = partition == null ? this.defaultPartition : partition;

  topic = maybeTopic.call(this, topic);

  return this._errorWrap(
    this._client.produce(topic, partition, message, key, opaque));

};

/**
 * Get a write stream for the given topic.
 *
 * This stream does not run in object mode. It only takes buffers of data.
 *
 * @param {string} topic - String topic name.
 * @param {object} options - Stream options
 * @param {number} options.highWaterMark - Controls how many messages are
 * read at a time.
 * @return {TopicWritable} - returns the write stream for writing to Kafka.
 */
Producer.prototype.getWriteStream = function(topic, options) {
  return new TopicWritable(this, topic, options);
};

/**
 * Poll for events
 *
 * We need to run poll in order to learn about new events that have occurred.
 * This is done automatically every time we produce a message, but if you'd
 * like to do it more because Kafka does not have enough traffic or because
 * you need to know at certain points, you can manually poll for changes.
 *
 * @return {Producer} - returns itself.
 */
Producer.prototype.poll = function() {
  if (!this._isConnected) {
    throw new Error('Producer not connected');
  }
  this._client.poll();
  return this;
};

/**
 * Flush the producer
 *
 * Flush everything on the internal librdkafka producer buffer. Do this before
 * disconnects usually
 *
 * @return {Producer} - returns itself.
 */
Producer.prototype.flush = function(timeout) {
  if (!this._isConnected) {
    throw new Error('Producer not connected');
  }
  this._client.flush(timeout);
  return this;
};
<|endoftext|>