<commit_before>// pagetwitter.js
//  Info about trust among twitter community members
//
// dec 30 2017 by jhr

/*
 *  Some global imports
 */

var Ui1Server;
var Kudo;
var ui;
var l;
var l8;
var de;
var nde = false;
var trace;
var bug;
var mand;
var assert;
var _;

function process_kudo_imports( kudo_scope ){
// This function fill the global Kudo map and init global variables with
// stuff imported from elsewhere.
  Kudo    = kudo_scope;
  ui      = Kudo.ui;
  l       = ui.l;
  l8      = Kudo.l8;
  _       = Kudo._;
  // My de&&bug() and de&&mand() darlings
  de      = true;
  trace   = Kudo.trace;
  bug     = trace;
  mand    = Kudo.assert;
  assert  = Kudo.assert;
  // More imports
  // ui1core stuff
  Session    = Kudo.Session;
  // Exports
}

function format_status( status ){

  var text = status.text;
  var buf = "";
  buf += '
';
  buf += '
';
  buf += text;
  buf += ' Tweet';
  buf += '

';
  return buf;

}

function page_twittrust( page_name, p1, p2, p3 ){

  var session = this.session;

  // Header
  this.set(
    ui.page_style( "twittrust" ),
    ui.page_header(
      _,
      ui.link_to_twitter_filter( "#corse", "", true /* no_icon */ ),
      _
    )
  );

  session.needs_twitter = true;

  var target;
  var actor;
  var name = "all";
  if( p1 && p1 !== "all" ){
    target = Kudo.TwitterUser.find( p1 );
    if( target ){
      name = target.screen_name;
      actor = Kudo.TrustActor.find( target.id );
    }else{
      name = "all";
    }
  }

  var top_n;
  top_n = parseInt( p2, 10 );
  if( !top_n ){
    top_n = 100;
  }

  var criteria = p3;
  if( !criteria ){
    criteria = "2";
  }
  var depth;
  depth = parseInt( p3, 10 );
  if( !depth ){
    depth = criteria;
  }

  var msg = new ui.Builder();

  msg.push(
    "
", ui.icon( "home" ), " ",
    ui.link_to_page( "index" )
  );
  if( session.domain ){
    msg.push( " ", session.domain );
  }
  msg.push( " ", ui.link_to_wiki_icon( "HomePage" ) );

  this.open_div( "twittrust_view" );

  if( target ){

    var twitter_data = target.twitter_user_data;
    var label = ( twitter_data && twitter_data.name ) || name;
    this.open_div( "top_twitter" );

    var profile
    = ' '
    + ui.avatar( target.screen_name, 126 )
    + ' ';

    this.push( profile )
    .h2( label )
    .push(
      " ",
      ui.link_to_twitter_user( target.screen_name ),
      ' - recommendations de lecture'
    ).br();
    var rank = ( actor && actor.get_rank() ) || 0;
    if( rank ){
      this.push( "top " + rank + ". " );
    }
    if( false ){
      if( target.friends_count ){
        this.push( target.friends_count, " amis suivis. " );
      }
      if( target.followers_count ){
        this.push( target.followers_count, " abonnés. " );
      }
    }
    if( twitter_data && twitter_data.followers_count ){
      this.push( twitter_data.followers_count, " abonnés Twitter. " );
    }
    this.push(
      Math.round( target.action_rate ),
      " interventions par semaine en moyenne. "
    ).br().br();

    var statuses = target.get_last_statuses();
    for( ii = 0 ; ii < statuses.length ; ii++ ){
      var status = statuses[ ii ];
      this.push( format_status( status ) );
    }
    this.br().br().close_div();
  }

  var community_size
  = Kudo.TwitterUser.get_community_size();
  var community_friendships_count
  = Kudo.TwitterUser.get_community_friendships_count();

  //
 Comptes Twitter
  var link = function( p, l ){
    return  " " + ui.link_to_page( page_name, name +  " " + p, l );
  }
  this.open_div( "top_twitter", "", "hide" ).h2(
    "Le palmarès des comptes Twitter en Corse"
  ).push(
    " - "
    + link(   "10 " +  depth,         "10" )
    + link(  "100 " +  depth,         "100" )
    // + link( "1000 " +  depth,         "1000" )
    // + link( top_n   +   " 1",         "x1" )
    // + link( top_n   +   " 2",         "x2" )
    // + link( top_n   +   " 3",         "x3" )
    + link( top_n   +   " pagerank",  "influents" )
    + link( top_n   +   " followers", "abonnés" )
    + link( top_n   +   " rate",      "actifs" )
    // + link( top_n   +   " locality",  "corse" )
    // + " - " + ui.link_to_page( "delegates", "all all", l( "votes" ) )
    + " - "
    + ui.link_to_page(
      "toptweets", "all " + top_n + " pagerank", "top tweets"
    )
    + "
",
    "établi sur la base des ",
    community_friendships_count,
    " relations entre les ",
    community_size,
    " membres de la communauté ",
    ui.link_to_twitter_user( "@suvranu" ),
    ". Une initiative de l'Institut Mariani.",
    "
"
  );

  var trust = Kudo.TrustActor.get_ranked( top_n, depth, target );

  this.br().push( "Les ", top_n, " " );
  if( depth === "followers" ){
    this.push( "ayant le plus d'abonnés" );
  }else if( depth === "rate" ){
    this.push( "plus actifs" );
  }else if( depth === "locality" ){
    this.push( "actifs qui suivent en priorité les membres de la communauté" );
  }else{
    this.push( "plus influents en Corse" );
  }
  this.push( " sont : " );
  var actors = trust.actors;
  if( !actors ){
    trace( "BUG? bad actor list from .get_ranked()" );
    actors = [];
  }
  community_size = Kudo.TwitterUser.get_community_size();
  var actor;
  var twitter_user;
  var data;
  for( var ii = 0 ; ii < actors.length ; ii++ ){

    actor = actors[ ii ];
    twitter_user = actor.twitter_user;

    this.push(
      "
" + ( ii + 1 ),
      " - "
    );

    data = actor.twitter_user.twitter_user_data;
    if( data ){
      this.push(
        " ",
        ui.link_to_page(
          page_name,
          twitter_user.screen_name + " " + top_n + " " +  depth,
          twitter_user.twitter_user_data.name
        )
      );
    }else{
      this.push( "" + actor.id );
    }

    if( depth !== "pagerank" ){
      var rank = actor.get_rank();
      if( rank ){
        this.push( " top " + rank );
      }
    }

    if( false ){
      var percent
      = Math.round( twitter_user.friends_count * 10 * 100 / community_size  ) / 10;
      if( percent ){
        this.push( "" + percent + "% en ami, " );
      }else{
        // this.push( "sans amis" );
      }
      percent
      = Math.round( actor.followers_count * 10 * 100 / community_size  ) / 10;
      if( actor.followers_count ){
        this.push( "suivi à ", percent, "%" );
      }
    }

    if( top_n < 100 ){
      this.push( format_status( actor.twitter_user.get_last_status() ) );
    }
  }

  this.close_div();

  this.close_div();

  this.push(  "
", ui.page_footer() );

}

function page_toptweets( page_name, p1, p2, p3 ){

  var session = this.session;

  // Header
  this.set(
    ui.page_style( "twittrust" ),
    ui.page_header(
      _,
      ui.link_to_twitter_filter( "#corse", "", true /* no_icon */ ),
      _
    )
  );

  session.needs_twitter = true;

  var target;
  var name = "all";
  if( p1 && p1 !== "all" ){
    target = Kudo.TwitterUser.find( p1 );
    if( target ){
      name = target.screen_name;
    }else{
      name = "all";
    }
  }

  var top_n;
  top_n = parseInt( p2, 10 );
  if( !top_n ){
    top_n = 100;
  }

  var criteria = p3;
  if( !criteria ){
    criteria = "pagerank";
  }
  var depth;
  depth = parseInt( p3, 10 );
  if( !depth ){
    depth = criteria;
  }

  var msg = new ui.Builder();

  msg.push(
    "
", ui.icon( "home" ), " ",
    ui.link_to_page( "index" )
  );
  if( session.domain ){
    msg.push( " ", session.domain );
  }
  msg.push( " ", ui.link_to_wiki_icon( "HomePage" ) );

  this.open_div( "twittrust_view" );

  //
 Comptes Twitter
  var community_size
  = Kudo.TwitterUser.get_community_size();
  var community_friendships_count
  = Kudo.TwitterUser.get_community_friendships_count();
  var link = function( p, l ){
    return  " " + ui.link_to_page( page_name, name +  " " + p, l );
  };
  this.open_div( "top_twitter", "", "hide" ).h2(
    "Le palmarès des tweets en Corse"
  ).push(
    " - "
    + link(   "10 " +  depth,       "10"  )
    + link(  "100 " +  depth,       "100" )
    + link( top_n   + " t24h",      "24h" )
    + link( top_n   +  " t7j",      "7j" )
    + link( top_n   + " t28j",      "28j" )
    //+ link( top_n   +   " followers", "abonnés" )
    //+ link( top_n   +   " rate",      "actifs" )
    + " - "
    + ui.link_to_page(
      "twittrust", "all " + top_n + " pagerank", "top comptes"
    )
    + "
",
    "établi sur la base des ",
    community_friendships_count,
    " relations entre les ",
    community_size,
    " membres de la communauté ",
    ui.link_to_twitter_user( "@suvranu" ),
    ". Une initiative de l'Institut Mariani.",
    "
"
  );

  var trust = Kudo.TrustTweet.get_ranked( top_n, depth, target );

  this.br().push( "Les ", top_n, " " );
  if( depth === "followers" ){
    this.push( "ayant le plus d'abonnés" );
  }else if( depth === "rate" ){
    this.push( "plus actifs" );
  }else if( depth === "locality" ){
    this.push( "actifs qui suivent en priorité les membres de la communauté" );
  }else{
    this.push( "plus influents en Corse" );
  }
  this.push( " sont : " );
  var tweets = trust;
  if( !tweets ){
    trace( "BUG? bad tweets list from .get_ranked()" );
    tweets = [];
  }
  community_size = Kudo.TwitterUser.get_community_size();
  var tweet;
  var twitter_user;
  var data;
  for( var ii = 0 ; ii < tweets.length ; ii++ ){

    tweet = tweets[ ii ];
    twitter_user = tweet.actor.twitter_user;

    this.push(
      "
" + ( ii + 1 ),
      " - "
    );
    data = twitter_user.twitter_user_data;
    if( data ){
      this.push(
        " ",
        ui.link_to_page(
          page_name,
          twitter_user.screen_name + " " + top_n + " " +  depth,
          twitter_user.twitter_user_data.name
        )
      );
    }else{
      this.push( "" + twitter_user.id );
    }

    var rank = tweet.actor.get_rank();
    if( rank ){
      this.push( " top " + rank );
    }

    this.push( format_status( tweet.status ) );
  }

  this.close_div();

  this.close_div();

  this.push(  "
", ui.page_footer() );

}

exports.start = function( kudo_scope ){

  // Import stuff from main.js, shared with ui1_server defined in ui1core.js
  process_kudo_imports( kudo_scope );

  Kudo.ui.register_page( "twittrust", page_twittrust );
  Kudo.ui.register_page( "toptweets", page_toptweets );

};
<commit_msg>Signal when top rank is better than number of followers<commit_after>// pagetwitter.js
//  Info about trust among twitter community members
//
// dec 30 2017 by jhr

/*
 *  Some global imports
 */

var Ui1Server;
var Kudo;
var ui;
var l;
var l8;
var de;
var nde = false;
var trace;
var bug;
var mand;
var assert;
var _;

function process_kudo_imports( kudo_scope ){
// This function fill the global Kudo map and init global variables with
// stuff imported from elsewhere.
  Kudo    = kudo_scope;
  ui      = Kudo.ui;
  l       = ui.l;
  l8      = Kudo.l8;
  _       = Kudo._;
  // My de&&bug() and de&&mand() darlings
  de      = true;
  trace   = Kudo.trace;
  bug     = trace;
  mand    = Kudo.assert;
  assert  = Kudo.assert;
  // More imports
  // ui1core stuff
  Session    = Kudo.Session;
  // Exports
}

function format_status( status ){

  var text = status.text;
  var buf = "";
  buf += '
';
  buf += '
';
  buf += text;
  buf += ' Tweet';
  buf += '

';
  return buf;

}

function page_twittrust( page_name, p1, p2, p3 ){

  var session = this.session;

  // Header
  this.set(
    ui.page_style( "twittrust" ),
    ui.page_header(
      _,
      ui.link_to_twitter_filter( "#corse", "", true /* no_icon */ ),
      _
    )
  );

  session.needs_twitter = true;

  var target;
  var actor;
  var name = "all";
  if( p1 && p1 !== "all" ){
    target = Kudo.TwitterUser.find( p1 );
    if( target ){
      name = target.screen_name;
      actor = Kudo.TrustActor.find( target.id );
    }else{
      name = "all";
    }
  }

  var top_n;
  top_n = parseInt( p2, 10 );
  if( !top_n ){
    top_n = 100;
  }

  var criteria = p3;
  if( !criteria ){
    criteria = "2";
  }
  var depth;
  depth = parseInt( p3, 10 );
  if( !depth ){
    depth = criteria;
  }

  var msg = new ui.Builder();

  msg.push(
    "
", ui.icon( "home" ), " ",
    ui.link_to_page( "index" )
  );
  if( session.domain ){
    msg.push( " ", session.domain );
  }
  msg.push( " ", ui.link_to_wiki_icon( "HomePage" ) );

  this.open_div( "twittrust_view" );

  if( target ){

    var twitter_data = target.twitter_user_data;
    var label = ( twitter_data && twitter_data.name ) || name;
    this.open_div( "top_twitter" );

    var profile
    = ' '
    + ui.avatar( target.screen_name, 126 )
    + ' ';

    this.push( profile )
    .h2( label )
    .push(
      " ",
      ui.link_to_twitter_user( target.screen_name ),
      ' - recommendations de lecture'
    ).br();
    var rank = ( actor && actor.get_rank() ) || 0;
    if( rank ){
      this.push( "top " + rank + ". " );
    }
    if( false ){
      if( target.friends_count ){
        this.push( target.friends_count, " amis suivis. " );
      }
      if( target.followers_count ){
        this.push( target.followers_count, " abonnés. " );
      }
    }
    if( twitter_data && twitter_data.followers_count ){
      this.push( twitter_data.followers_count, " abonnés Twitter. " );
    }
    this.push(
      Math.round( target.action_rate ),
      " interventions par semaine en moyenne. "
    ).br().br();

    var statuses = target.get_last_statuses();
    for( ii = 0 ; ii < statuses.length ; ii++ ){
      var status = statuses[ ii ];
      this.push( format_status( status ) );
    }
    this.br().br().close_div();
  }

  var community_size
  = Kudo.TwitterUser.get_community_size();
  var community_friendships_count
  = Kudo.TwitterUser.get_community_friendships_count();

  //
 Comptes Twitter
  var link = function( p, l ){
    return  " " + ui.link_to_page( page_name, name +  " " + p, l );
  }
  this.open_div( "top_twitter", "", "hide" ).h2(
    "Le palmarès des comptes Twitter en Corse"
  ).push(
    " - "
    + link(   "10 " +  depth,         "10" )
    + link(  "100 " +  depth,         "100" )
    // + link( "1000 " +  depth,         "1000" )
    // + link( top_n   +   " 1",         "x1" )
    // + link( top_n   +   " 2",         "x2" )
    // + link( top_n   +   " 3",         "x3" )
    + link( top_n   +   " pagerank",  "influents" )
    + link( top_n   +   " followers", "abonnés" )
    + link( top_n   +   " rate",      "actifs" )
    // + link( top_n   +   " locality",  "corse" )
    // + " - " + ui.link_to_page( "delegates", "all all", l( "votes" ) )
    + " - "
    + ui.link_to_page(
      "toptweets", "all " + top_n + " pagerank", "top tweets"
    )
    + "
",
    "établi sur la base des ",
    community_friendships_count,
    " relations entre les ",
    community_size,
    " membres de la communauté ",
    ui.link_to_twitter_user( "@suvranu" ),
    ". Une initiative de l'Institut Mariani.",
    "
"
  );

  var trust = Kudo.TrustActor.get_ranked( top_n, depth, target );

  this.br().push( "Les ", top_n, " " );
  if( depth === "followers" ){
    this.push( "ayant le plus d'abonnés" );
  }else if( depth === "rate" ){
    this.push( "plus actifs" );
  }else if( depth === "locality" ){
    this.push( "actifs qui suivent en priorité les membres de la communauté" );
  }else{
    this.push( "plus influents en Corse" );
  }
  this.push( " sont : " );
  var actors = trust.actors;
  if( !actors ){
    trace( "BUG? bad actor list from .get_ranked()" );
    actors = [];
  }
  community_size = Kudo.TwitterUser.get_community_size();
  var actor;
  var twitter_user;
  var data;
  for( var ii = 0 ; ii < actors.length ; ii++ ){

    actor = actors[ ii ];
    twitter_user = actor.twitter_user;

    this.push(
      "
" + ( ii + 1 ),
      " - "
    );

    data = actor.twitter_user.twitter_user_data;
    if( data ){
      this.push(
        " ",
        ui.link_to_page(
          page_name,
          twitter_user.screen_name + " " + top_n + " " +  depth,
          twitter_user.twitter_user_data.name
        )
      );
    }else{
      this.push( "" + actor.id );
    }

    if( depth !== "pagerank" ){
      var rank = actor.get_rank();
      if( rank ){
        this.push( " top " + rank );
        if( rank < ii ){
          this.push( " *" );
        }
      }
    }

    if( false ){
      var percent
      = Math.round( twitter_user.friends_count * 10 * 100 / community_size  ) / 10;
      if( percent ){
        this.push( "" + percent + "% en ami, " );
      }else{
        // this.push( "sans amis" );
      }
      percent
      = Math.round( actor.followers_count * 10 * 100 / community_size  ) / 10;
      if( actor.followers_count ){
        this.push( "suivi à ", percent, "%" );
      }
    }

    if( top_n < 100 ){
      this.push( format_status( actor.twitter_user.get_last_status() ) );
    }
  }

  this.close_div();

  this.close_div();

  this.push(  "
", ui.page_footer() );

}

function page_toptweets( page_name, p1, p2, p3 ){

  var session = this.session;

  // Header
  this.set(
    ui.page_style( "twittrust" ),
    ui.page_header(
      _,
      ui.link_to_twitter_filter( "#corse", "", true /* no_icon */ ),
      _
    )
  );

  session.needs_twitter = true;

  var target;
  var name = "all";
  if( p1 && p1 !== "all" ){
    target = Kudo.TwitterUser.find( p1 );
    if( target ){
      name = target.screen_name;
    }else{
      name = "all";
    }
  }

  var top_n;
  top_n = parseInt( p2, 10 );
  if( !top_n ){
    top_n = 100;
  }

  var criteria = p3;
  if( !criteria ){
    criteria = "pagerank";
  }
  var depth;
  depth = parseInt( p3, 10 );
  if( !depth ){
    depth = criteria;
  }

  var msg = new ui.Builder();

  msg.push(
    "
", ui.icon( "home" ), " ",
    ui.link_to_page( "index" )
  );
  if( session.domain ){
    msg.push( " ", session.domain );
  }
  msg.push( " ", ui.link_to_wiki_icon( "HomePage" ) );

  this.open_div( "twittrust_view" );

  //
 Comptes Twitter
  var community_size
  = Kudo.TwitterUser.get_community_size();
  var community_friendships_count
  = Kudo.TwitterUser.get_community_friendships_count();
  var link = function( p, l ){
    return  " " + ui.link_to_page( page_name, name +  " " + p, l );
  };
  this.open_div( "top_twitter", "", "hide" ).h2(
    "Le palmarès des tweets en Corse"
  ).push(
    " - "
    + link(   "10 " +  depth,       "10"  )
    + link(  "100 " +  depth,       "100" )
    + link( top_n   + " t24h",      "24h" )
    + link( top_n   +  " t7j",      "7j" )
    + link( top_n   + " t28j",      "28j" )
    //+ link( top_n   +   " followers", "abonnés" )
    //+ link( top_n   +   " rate",      "actifs" )
    + " - "
    + ui.link_to_page(
      "twittrust", "all " + top_n + " pagerank", "top comptes"
    )
    + "
",
    "établi sur la base des ",
    community_friendships_count,
    " relations entre les ",
    community_size,
    " membres de la communauté ",
    ui.link_to_twitter_user( "@suvranu" ),
    ". Une initiative de l'Institut Mariani.",
    "
"
  );

  var trust = Kudo.TrustTweet.get_ranked( top_n, depth, target );

  this.br().push( "Les ", top_n, " " );
  if( depth === "followers" ){
    this.push( "ayant le plus d'abonnés" );
  }else if( depth === "rate" ){
    this.push( "plus actifs" );
  }else if( depth === "locality" ){
    this.push( "actifs qui suivent en priorité les membres de la communauté" );
  }else{
    this.push( "plus influents en Corse" );
  }
  this.push( " sont : " );
  var tweets = trust;
  if( !tweets ){
    trace( "BUG? bad tweets list from .get_ranked()" );
    tweets = [];
  }
  community_size = Kudo.TwitterUser.get_community_size();
  var tweet;
  var twitter_user;
  var data;
  for( var ii = 0 ; ii < tweets.length ; ii++ ){

    tweet = tweets[ ii ];
    twitter_user = tweet.actor.twitter_user;

    this.push(
      "
" + ( ii + 1 ),
      " - "
    );
    data = twitter_user.twitter_user_data;
    if( data ){
      this.push(
        " ",
        ui.link_to_page(
          page_name,
          twitter_user.screen_name + " " + top_n + " " +  depth,
          twitter_user.twitter_user_data.name
        )
      );
    }else{
      this.push( "" + twitter_user.id );
    }

    var rank = tweet.actor.get_rank();
    if( rank ){
      this.push( " top " + rank );
    }

    this.push( format_status( tweet.status ) );
  }

  this.close_div();

  this.close_div();

  this.push(  "
", ui.page_footer() );

}

exports.start = function( kudo_scope ){

  // Import stuff from main.js, shared with ui1_server defined in ui1core.js
  process_kudo_imports( kudo_scope );

  Kudo.ui.register_page( "twittrust", page_twittrust );
  Kudo.ui.register_page( "toptweets", page_toptweets );

};
<|endoftext|>


<commit_before>var path = require('path');
var fs = require('fs');
var url = require('url');
var crypto = require('crypto');
var get = require('get');
var Step = require('step');
var sqlite3 = require('sqlite3');

function getMimeType(data) {
    if (data[0] === 0x89 && data[1] === 0x50 && data[2] === 0x4E &&
        data[3] === 0x47 && data[4] === 0x0D && data[5] === 0x0A &&
        data[6] === 0x1A && data[7] === 0x0A) {
        return 'image/png';
    } else if (data[0] === 0xFF && data[1] === 0xD8 &&
        data[data.length - 2] === 0xFF && data[data.length - 1] === 0xD9) {
        return 'image/jpeg';
    } else if (data[0] === 0x47 && data[1] === 0x49 && data[2] === 0x46 &&
        data[3] === 0x38 && (data[4] === 0x39 || data[4] === 0x37) &&
        data[5] === 0x61) {
        return 'image/gif';
    }
};

function md5(data) {
    return crypto.createHash('md5').update(data).digest('hex');
}

function getExpires(headers) {
    if (headers['expires']) {
        var expires = +new Date(headers['expires']);
    } else if (headers['cache-control']) {
        var expires = Date.now() + +'max-age=600'.replace('max-age=', '') * 1000;
    }
    return expires || (Date.now() + 3600 * 1000);
}

var cache = {};

module.exports = TileJSON;
require('util').inherits(TileJSON, require('events').EventEmitter)
function TileJSON(uri, callback) {
    if (typeof callback !== 'function') throw new Error('callback required');
    if (typeof uri === 'string') uri = url.parse(uri, true);
    else if (typeof uri.query === 'string') uri.query = qs.parse(uri.query);

    if (!uri.pathname) {
        callback(new Error('Invalid URI ' + url.format(uri)));
        return;
    }

    if (uri.hostname === '.' || uri.hostname == '..') {
        uri.pathname = uri.hostname + uri.pathname;
        delete uri.hostname;
        delete uri.host;
    }
    uri.query = uri.query || {};

    var key = url.format(uri);
    if (!cache[key]) {
        cache[key] = this;
        this._open(uri);
    }

    var tilejson = cache[key];
    if (!tilejson.open) {
        tilejson.once('open', callback);
    } else {
        callback(null, tilejson);
    }
    return undefined;
}

TileJSON.prototype._open = function(uri) {
    var tilejson = this;
    function error(err) {
        process.nextTick(function() {
            tilejson._close();
            tilejson.emit('open', err);
        });
    }

    var key = url.format(uri);
    this._deleteInterval = setInterval(this._deleteExpired.bind(this), 60000);

    Step(function() {
        if (uri.protocol === 'http:' || uri.protocol === 'https:') {
            new get(url.format(uri)).asString(this);
        } else {
            tilejson.filename = uri.pathname;
            fs.watchFile(uri.pathname, function(cur, prev) {
                // Make sure we throw away this object when the file changed.
                cache[key]._close();
                delete cache[key];
            });

            fs.readFile(uri.pathname, 'utf8', this);
        }
    }, function(err, data) {
        if (err) return error(err);
        data = data.replace(/^\s*\w+\s*\(\s*|\s*\)\s*;?\s*$/g, '');
        try { tilejson.data = JSON.parse(data); }
        catch(err) { return error(err); }

        if (!tilejson.data.id) {
            tilejson.data.id = path.basename(uri.pathname, path.extname(uri.pathname));
        }

        // Create a file based anonymous database as cache..
        tilejson.cache = new sqlite3.Database('', this);
    }, function(err) {
        if (err) return error(err);
        fs.readFile(__dirname + '/schema.sql', 'utf8', this);
    }, function(err, schema) {
        if (err) return error(err);
        tilejson.cache.exec(schema, this);
    }, function(err) {
        if (err) return error(err);
        tilejson._getTile = tilejson.cache.prepare('SELECT data, headers FROM tile WHERE z = ? AND x = ? AND y = ? AND expires > ?');
        tilejson._putTile = tilejson.cache.prepare('REPLACE INTO tile (z, x, y, data, headers, expires) VALUES (?, ?, ?, ?, ?, ?)');
        tilejson._getGrid = tilejson.cache.prepare('SELECT data, headers FROM grid WHERE z = ? AND x = ? AND y = ? AND expires > ?');
        tilejson._putGrid = tilejson.cache.prepare('REPLACE INTO grid (z, x, y, data, headers, expires) VALUES (?, ?, ?, ?, ?, ?)');
        tilejson.open = true;
        tilejson.emit('open', null, tilejson);
    });
}

TileJSON.prototype._close = function() {
    if (this.filename) {
        fs.unwatchFile(this.filename);
        clearInterval(this._deleteInterval);
    }
};

TileJSON.prototype._deleteExpired = function() {
    this.cache.run('DELETE FROM tile WHERE expires <= ?', Date.now());
    this.cache.run('DELETE FROM grid WHERE expires <= ?', Date.now());
};

TileJSON.registerProtocols = function(tilelive) {
    tilelive.protocols['tilejson:'] = TileJSON;
};

TileJSON.list = function(filepath, callback) {
    filepath = path.resolve(filepath);
    fs.readdir(filepath, function(err, files) {
        if (err) return callback(err);
        for (var result = {}, i = 0; i < files.length; i++) {
            var name = files[i].match(/^([\w-]+)\.tilejson$/);
            if (name) result[name[1]] = 'tilejson://' + path.join(filepath, name[0]);
        }
        callback(null, result);
    });
};

TileJSON.findID = function(filepath, id, callback) {
    filepath = path.resolve(filepath);
    var file = path.join(filepath, id + '.tilejson');
    fs.stat(file, function(err, stats) {
        if (err) callback(err);
        else callback(null, 'tilejson://' + file);
    });
};

TileJSON.prototype.getInfo = function(callback) {
    if (!this.data || !this.cache) callback(new Error('Tilesource not loaded'));
    else callback(null, this.data);
};

// z, x, y are XYZ coordinates.
TileJSON.prototype.getTile = function(z, x, y, callback) {
    if (!this.data || !this.cache) return callback(new Error('Tilesource not loaded'));
    if (!this.data.tiles) return callback(new Error('Tile does not exist'));

    var tilejson = this;
    tilejson._getTile.get(z, x, y, Date.now(), function(err, row) {
        if (err) return callback(err);
        if (row) return callback(null, row.data, JSON.parse(row.headers));

        // The tile wasn't found in the database or expired already.
        // Download it from the source.
        tilejson._downloadTile(z, x, y, callback);
    });
};

TileJSON.prototype._prepareURL = function(url, z, x, y) {
    return (url
        .replace(/\{z\}/g, z)
        .replace(/\{x\}/g, x)
        .replace(/\{y\}/g, (this.data.scheme === 'tms') ? (1 << z) - 1 - y : y));
};

TileJSON.prototype._downloadTile = function(z, x, y, callback) {
    var tilejson = this;

    var url = this._prepareURL(this.data.tiles[0], z, x, y);
    new get(url).asBuffer(function(err, data, headers) {
        if (err) return callback(new Error('Tile does not exist'));

        var expires = getExpires(headers);
        var modified = headers['last-modified'] ? new Date(headers['last-modified']) : new Date;
        var headers = {
            'Content-Type': getMimeType(data),
            'Last-Modified': modified,
            'ETag': headers['etag'] || (headers['content-length'] + '-' + +modified),
            'Cache-Control': headers['cache-control']
        };

        tilejson._putTile.run(z, x, y, data, JSON.stringify(headers), expires);
        callback(null, data, headers);
    });
};

// z, x, y are XYZ coordinates.
TileJSON.prototype.getGrid = function(z, x, y, callback) {
    if (!this.data || !this.cache) return callback(new Error('Gridsource not loaded'));
    if (!this.data.grids) return callback(new Error('Grid does not exist'));

    var tilejson = this;
    tilejson._getGrid.get(z, x, y, Date.now(), function(err, row) {
        if (err) return callback(err);
        if (row) return callback(null, JSON.parse(row.data), JSON.parse(row.headers));

        // The grid wasn't found in the database or expired already.
        // Download it from the source.
        tilejson._downloadGrid(z, x, y, callback);
    });
};

TileJSON.prototype._downloadGrid = function(z, x, y, callback) {
    var tilejson = this;

    var url = this._prepareURL(this.data.grids[0], z, x, y);
    new get(url).asString(function(err, grid, headers) {
        if (err) return callback(new Error('Grid does not exist'));

        var expires = getExpires(headers);
        var modified = headers['last-modified'] ? new Date(headers['last-modified']) : new Date;
        var headers = {
            'Content-Type': 'application/json',
            'Last-Modified': modified,
            'ETag': headers['etag'] || (headers['content-length'] + '-' + +modified)
        };

        // TODO: compression
        grid = grid.replace(/^\s*\w+\s*\(|\)\s*;?\s*$/g, '');

        tilejson._putGrid.run(z, x, y, grid, JSON.stringify(headers), expires);
        callback(null, JSON.parse(grid), headers);
    });
};
<commit_msg>don't set empty cache-control<commit_after>var path = require('path');
var fs = require('fs');
var url = require('url');
var crypto = require('crypto');
var get = require('get');
var Step = require('step');
var sqlite3 = require('sqlite3');

function getMimeType(data) {
    if (data[0] === 0x89 && data[1] === 0x50 && data[2] === 0x4E &&
        data[3] === 0x47 && data[4] === 0x0D && data[5] === 0x0A &&
        data[6] === 0x1A && data[7] === 0x0A) {
        return 'image/png';
    } else if (data[0] === 0xFF && data[1] === 0xD8 &&
        data[data.length - 2] === 0xFF && data[data.length - 1] === 0xD9) {
        return 'image/jpeg';
    } else if (data[0] === 0x47 && data[1] === 0x49 && data[2] === 0x46 &&
        data[3] === 0x38 && (data[4] === 0x39 || data[4] === 0x37) &&
        data[5] === 0x61) {
        return 'image/gif';
    }
};

function md5(data) {
    return crypto.createHash('md5').update(data).digest('hex');
}

function getExpires(headers) {
    if (headers['expires']) {
        var expires = +new Date(headers['expires']);
    } else if (headers['cache-control']) {
        var expires = Date.now() + +'max-age=600'.replace('max-age=', '') * 1000;
    }
    return expires || (Date.now() + 3600 * 1000);
}

var cache = {};

module.exports = TileJSON;
require('util').inherits(TileJSON, require('events').EventEmitter)
function TileJSON(uri, callback) {
    if (typeof callback !== 'function') throw new Error('callback required');
    if (typeof uri === 'string') uri = url.parse(uri, true);
    else if (typeof uri.query === 'string') uri.query = qs.parse(uri.query);

    if (!uri.pathname) {
        callback(new Error('Invalid URI ' + url.format(uri)));
        return;
    }

    if (uri.hostname === '.' || uri.hostname == '..') {
        uri.pathname = uri.hostname + uri.pathname;
        delete uri.hostname;
        delete uri.host;
    }
    uri.query = uri.query || {};

    var key = url.format(uri);
    if (!cache[key]) {
        cache[key] = this;
        this._open(uri);
    }

    var tilejson = cache[key];
    if (!tilejson.open) {
        tilejson.once('open', callback);
    } else {
        callback(null, tilejson);
    }
    return undefined;
}

TileJSON.prototype._open = function(uri) {
    var tilejson = this;
    function error(err) {
        process.nextTick(function() {
            tilejson._close();
            tilejson.emit('open', err);
        });
    }

    var key = url.format(uri);
    this._deleteInterval = setInterval(this._deleteExpired.bind(this), 60000);

    Step(function() {
        if (uri.protocol === 'http:' || uri.protocol === 'https:') {
            new get(url.format(uri)).asString(this);
        } else {
            tilejson.filename = uri.pathname;
            fs.watchFile(uri.pathname, function(cur, prev) {
                // Make sure we throw away this object when the file changed.
                cache[key]._close();
                delete cache[key];
            });

            fs.readFile(uri.pathname, 'utf8', this);
        }
    }, function(err, data) {
        if (err) return error(err);
        data = data.replace(/^\s*\w+\s*\(\s*|\s*\)\s*;?\s*$/g, '');
        try { tilejson.data = JSON.parse(data); }
        catch(err) { return error(err); }

        if (!tilejson.data.id) {
            tilejson.data.id = path.basename(uri.pathname, path.extname(uri.pathname));
        }

        // Create a file based anonymous database as cache..
        tilejson.cache = new sqlite3.Database('', this);
    }, function(err) {
        if (err) return error(err);
        fs.readFile(__dirname + '/schema.sql', 'utf8', this);
    }, function(err, schema) {
        if (err) return error(err);
        tilejson.cache.exec(schema, this);
    }, function(err) {
        if (err) return error(err);
        tilejson._getTile = tilejson.cache.prepare('SELECT data, headers FROM tile WHERE z = ? AND x = ? AND y = ? AND expires > ?');
        tilejson._putTile = tilejson.cache.prepare('REPLACE INTO tile (z, x, y, data, headers, expires) VALUES (?, ?, ?, ?, ?, ?)');
        tilejson._getGrid = tilejson.cache.prepare('SELECT data, headers FROM grid WHERE z = ? AND x = ? AND y = ? AND expires > ?');
        tilejson._putGrid = tilejson.cache.prepare('REPLACE INTO grid (z, x, y, data, headers, expires) VALUES (?, ?, ?, ?, ?, ?)');
        tilejson.open = true;
        tilejson.emit('open', null, tilejson);
    });
}

TileJSON.prototype._close = function() {
    if (this.filename) {
        fs.unwatchFile(this.filename);
        clearInterval(this._deleteInterval);
    }
};

TileJSON.prototype._deleteExpired = function() {
    this.cache.run('DELETE FROM tile WHERE expires <= ?', Date.now());
    this.cache.run('DELETE FROM grid WHERE expires <= ?', Date.now());
};

TileJSON.registerProtocols = function(tilelive) {
    tilelive.protocols['tilejson:'] = TileJSON;
};

TileJSON.list = function(filepath, callback) {
    filepath = path.resolve(filepath);
    fs.readdir(filepath, function(err, files) {
        if (err) return callback(err);
        for (var result = {}, i = 0; i < files.length; i++) {
            var name = files[i].match(/^([\w-]+)\.tilejson$/);
            if (name) result[name[1]] = 'tilejson://' + path.join(filepath, name[0]);
        }
        callback(null, result);
    });
};

TileJSON.findID = function(filepath, id, callback) {
    filepath = path.resolve(filepath);
    var file = path.join(filepath, id + '.tilejson');
    fs.stat(file, function(err, stats) {
        if (err) callback(err);
        else callback(null, 'tilejson://' + file);
    });
};

TileJSON.prototype.getInfo = function(callback) {
    if (!this.data || !this.cache) callback(new Error('Tilesource not loaded'));
    else callback(null, this.data);
};

// z, x, y are XYZ coordinates.
TileJSON.prototype.getTile = function(z, x, y, callback) {
    if (!this.data || !this.cache) return callback(new Error('Tilesource not loaded'));
    if (!this.data.tiles) return callback(new Error('Tile does not exist'));

    var tilejson = this;
    tilejson._getTile.get(z, x, y, Date.now(), function(err, row) {
        if (err) return callback(err);
        if (row) return callback(null, row.data, JSON.parse(row.headers));

        // The tile wasn't found in the database or expired already.
        // Download it from the source.
        tilejson._downloadTile(z, x, y, callback);
    });
};

TileJSON.prototype._prepareURL = function(url, z, x, y) {
    return (url
        .replace(/\{z\}/g, z)
        .replace(/\{x\}/g, x)
        .replace(/\{y\}/g, (this.data.scheme === 'tms') ? (1 << z) - 1 - y : y));
};

TileJSON.prototype._downloadTile = function(z, x, y, callback) {
    var tilejson = this;

    var url = this._prepareURL(this.data.tiles[0], z, x, y);
    new get(url).asBuffer(function(err, data, headers) {
        if (err) return callback(new Error('Tile does not exist'));

        var expires = getExpires(headers);
        var modified = headers['last-modified'] ? new Date(headers['last-modified']) : new Date;
        var maxage = headers['cache-control'] || null;
        var headers = {
            'Content-Type': getMimeType(data),
            'Last-Modified': modified,
            'ETag': headers['etag'] || (headers['content-length'] + '-' + +modified),
            'Cache-Control': headers['cache-control']
        };
        maxage && headers['Cache-Control'] = maxage;

        tilejson._putTile.run(z, x, y, data, JSON.stringify(headers), expires);
        callback(null, data, headers);
    });
};

// z, x, y are XYZ coordinates.
TileJSON.prototype.getGrid = function(z, x, y, callback) {
    if (!this.data || !this.cache) return callback(new Error('Gridsource not loaded'));
    if (!this.data.grids) return callback(new Error('Grid does not exist'));

    var tilejson = this;
    tilejson._getGrid.get(z, x, y, Date.now(), function(err, row) {
        if (err) return callback(err);
        if (row) return callback(null, JSON.parse(row.data), JSON.parse(row.headers));

        // The grid wasn't found in the database or expired already.
        // Download it from the source.
        tilejson._downloadGrid(z, x, y, callback);
    });
};

TileJSON.prototype._downloadGrid = function(z, x, y, callback) {
    var tilejson = this;

    var url = this._prepareURL(this.data.grids[0], z, x, y);
    new get(url).asString(function(err, grid, headers) {
        if (err) return callback(new Error('Grid does not exist'));

        var expires = getExpires(headers);
        var modified = headers['last-modified'] ? new Date(headers['last-modified']) : new Date;
        var headers = {
            'Content-Type': 'application/json',
            'Last-Modified': modified,
            'ETag': headers['etag'] || (headers['content-length'] + '-' + +modified)
        };

        // TODO: compression
        grid = grid.replace(/^\s*\w+\s*\(|\)\s*;?\s*$/g, '');

        tilejson._putGrid.run(z, x, y, grid, JSON.stringify(headers), expires);
        callback(null, JSON.parse(grid), headers);
    });
};
<|endoftext|>


<commit_before>var tilelive = exports;
var _ = require('underscore');
var path = require('path');
var url = require('url');
var util = require('util');
var os = require('os');
var Queue = require('./queue');
var EventEmitter = require('events').EventEmitter;
var Step = require('step');
var sm = new (require('sphericalmercator'));

tilelive.protocols = {
    // Add your protocol handlers here.
    // 'mbtiles:': require('mbtiles')
};

tilelive.defaults = {
    id: null,
    name: '',
    description: '',
    version: "1.0.0",
    legend: null,
    scheme: "xyz",
    minzoom: 0,
    maxzoom: 22,
    bounds: [-180, -85.05112877980659, 180, 85.05112877980659],
    center: null
};

// List all tile source URIs from all tile sources.
tilelive.list = function(source, callback) {
    if (typeof callback !== 'function') {
        throw new Error('Callback required as second argument');
    }

    if (!Object.keys(tilelive.protocols).length) {
        return callback(new Error('No tilesource protocols defined'));
    }

    Step(function() {
        var group = this.group();
        for (var name in tilelive.protocols) {
            tilelive.protocols[name].list(source, group());
        }
    }, function(err, uris) {
        if (err) return callback(err);

        // Combine lists from all protocol sources, eliminating duplicate IDs.
        var result = {};
        for (var i = 0; i < uris.length; i++) {
            _.defaults(result, uris[i]);
        }
        callback(null, result);
    });
};

// Obtains a tile source URI from an ID, checking all tile source protocols
// until one is found.
tilelive.findID = function(source, id, callback) {
    if (typeof callback !== 'function') {
        throw new Error('Callback required as third argument');
    }
    var protocols = Object.keys(tilelive.protocols);
    check();
    function check(err, uri) {
        if (!protocols.length) {
            return callback(new Error('Tileset does not exist'));
        }
        tilelive.protocols[protocols.shift()].findID(source, id, function(err, uri) {
            if (err) check();
            else callback(null, uri);
        });
    }
};

tilelive.load = function(uri, callback) {
    if (typeof callback !== 'function') {
        throw new Error('Callback required as second argument');
    }

    if (typeof uri === 'string') uri = url.parse(uri, true);

    // Handle uris in the format /path/to/dir?id=bar
    if (!uri.protocol && uri.pathname && uri.query.id) {
        tilelive.findID(uri.pathname, uri.query.id, function(err, uri) {
            if (err) callback(err);
            else tilelive.load(uri, callback);
        });
        return;
    }

    if (!tilelive.protocols[uri.protocol]) {
        return callback(new Error('Invalid tilesource protocol'));
    }

    var handler = new tilelive.protocols[uri.protocol](uri, callback);
};

// Load a tilesource and retrieve metadata
tilelive.info = function(uri, callback) {
    if (typeof callback !== 'function') {
        throw new Error('Callback required as second argument');
    }

    tilelive.load(uri, function(err, handler) {
        if (err) return callback(err);
        handler.getInfo(function(err, data) {
            _(data).defaults(tilelive.defaults);
            if (err) return callback(err, data, handler);
            callback(null, data, handler);
        });
    });
};

// Load metadata for all tile source URIs.
// Ignore errors from loading individual models (e.g.
// don't let one bad apple spoil the collection).
tilelive.all = function(source, callback) {
    if (typeof callback !== 'function') {
        throw new Error('Callback required as second argument');
    }

    var handlers = [], models = [];
    tilelive.list(source, function(err, uris) {
        if (err) return callback(err);
        if (!uris) return callback(null, []);

        Step(function() {
            var group = this.group();
            for (var id in uris) {
                var next = group();
                tilelive.info(uris[id], function(err, data, handler) {
                    if (handler) handlers.push(handler);
                    if (!err && data) models.push(data);
                    next(err);
                });
            }
        }, function(err) {
            if (err) console.error(err.stack);
            var sorted = _.sortBy(_.zip(models, handlers), function(m) { return (m[0].name || m[0].id).toLowerCase(); });
            callback(null, _(sorted).pluck(0), _(sorted).pluck(1));
        });
    });
};

tilelive.verify = function(ts) {
    if (!ts.id) {
        return new Error("Tilesource has invalid ID: " + util.inspect(ts.id));
    }

    // validates the retrieved settings.
    // Returns an error object when invalid, otherwise undefined.
    if (!(/^\d+(\.\d+){0,2}$/).test(ts.version)) {
        return new Error("Tilesource has invalid version: " + util.inspect(ts.version));
    }

    if (ts.scheme !== 'xyz' && ts.scheme !== 'tms') {
        return new Error("Tilesource has invalid scheme: " + util.inspect(ts.scheme));
    }

    if (ts.minzoom < 0 || ts.minzoom > 22) {
        return new Error("Tilesource has invalid minzoom: " + util.inspect(ts.minzoom));
    }
    if (ts.maxzoom < 0 || ts.maxzoom > 22) {
        return new Error("Tilesource has invalid maxzoom: " + util.inspect(ts.maxzoom));
    }
    if (ts.minzoom > ts.maxzoom) {
        return new Error("Tilesource's minzoom > maxzoom");
    }

    if (!Array.isArray(ts.bounds) || ts.bounds.length !== 4) {
        return new Error("Tilesource has invalid bounds: " + util.inspect(ts.bounds));
    }
    if (ts.bounds[0] < -180 || ts.bounds[0] > 180) {
        return new Error("Tilesource's west bound is invalid: " + util.inspect(ts.bounds));
    }
    if (ts.bounds[2] < -180 || ts.bounds[2] > 180) {
        return new Error("Tilesource's east bound is invalid: " + util.inspect(ts.bounds));
    }
    // @TODO these should actually be checked against
    // +-85.05112877980659 but some mbtiles may be using +-90 which
    // may not be worth failure.
    if (ts.bounds[1] < -90 || ts.bounds[1] > 90) {
        return new Error("Tilesource's south bound is invalid: " + util.inspect(ts.bounds));
    }
    if (ts.bounds[3] < -90 || ts.bounds[3] > 90) {
        return new Error("Tilesource's north bound is invalid: " + util.inspect(ts.bounds));
    }

    if (Array.isArray(ts.center)) {
        if (ts.center.length !== 3) {
            return new Error("Tilesource has invalid center: " + util.inspect(ts.center));
        }
        if (ts.center[0] < ts.bounds[0] || ts.center[0] > ts.bounds[2]) {
            return new Error("Tilesource has invalid longitude as center: " + util.inspect(ts.center));
        }
        if (ts.center[1] < ts.bounds[1] || ts.center[1] > ts.bounds[3]) {
            return new Error("Tilesource has invalid latitude as center: " + util.inspect(ts.center));
        }
        if (ts.center[2] < ts.minzoom || ts.center[2] > ts.maxzoom) {
            return new Error("Tilesource has invalid zoom as center: " + util.inspect(ts.center));
        }
    } else if (ts.center !== null) {
        return new Error("Tilesource has invalid center: " + util.inspect(ts.center));
    }
};

function totalTiles(x, y, z) {
    for (var total = 0, i = 0; i <= z; i++, x *= 2, y *= 2) {
        total += x * y;
    }
    return total;
}

tilelive.copy = function(args, callback) {
    if (typeof args.source.getTile !== 'function') throw new Error('source must implement the tilesource interface');
    if (typeof args.sink.putTile !== 'function') throw new Error('sink must implement the tilesink interface');
    if (!Array.isArray(args.bbox) || args.bbox.length !== 4) throw new Error('bbox must have four lat/long coordinates');
    if (args.bbox[0] < -180) throw new Error('bbox has invalid west value');
    if (args.bbox[1] < -85.05112877980659) throw new Error('bbox has invalid south value');
    if (args.bbox[2] > 180) throw new Error('bbox has invalid east value');
    if (args.bbox[3] > 85.05112877980659) throw new Error('bbox has invalid north value');
    if (args.bbox[0] > args.bbox[2]) throw new Error('bbox is invalid');
    if (args.bbox[1] > args.bbox[3]) throw new Error('bbox is invalid');
    if (typeof args.minZoom !== 'number') throw new Error('minZoom must be a number');
    if (typeof args.maxZoom !== 'number') throw new Error('maxZoom must be a number');
    if (typeof args.concurrency !== 'number') args.concurrency = os.cpus().length * 25;
    if (typeof callback !== 'function') callback = function() {};

    if (!args.tiles && !args.grids) throw new Error('You must copy at least grids or tiles (or both)');

    if (args.minZoom < 0) throw new Error('minZoom must be >= 0');
    if (args.maxZoom > 22) throw new Error('maxZoom must be <= 22');
    if (args.minZoom > args.maxZoom) throw new Error('maxZoom must be >= minZoom');

    var action = new EventEmitter;

    action.copied = 0;
    action.failed = 0;
    action.total = 0;
    action.started = Date.now();
    action.on('finished', callback);

    // Precalculate the tile int bounds for each zoom level.
    var bounds = {};
    for (var z = args.minZoom; z <= args.maxZoom; z++) {
        bounds[z] = sm.xyz(args.bbox, z);
        action.total += (bounds[z].maxX - bounds[z].minX + 1) *
                        (bounds[z].maxY - bounds[z].minY + 1);
    }

    // We have twice the amount of things to copy when we copy both.
    if (args.grids && args.tiles) action.total *= 2;

    // Sets z, x and y to the next tile.
    var z = args.minZoom;

    // Start out one tile left to the starting position because we increment
    // as the first action in the id() function.
    var x = bounds[z].minX - 1;
    var y = bounds[z].minY;

    if (args.source.metatile > 1) {
        var boxSize = args.source.metatile, boxLeft, boxTop, boxLeft, boxBottom;
        var resetID = function() {
            boxLeft = x - x % boxSize;
            boxRight = boxLeft + boxSize;
            boxTop = y - y % boxSize;
            boxBottom = boxTop + boxSize;
        }
        resetID(); // Initialize.
        var id = function() {
            if (z > args.maxZoom) return false;

            ++x;
            if (x >= boxRight || x > bounds[z].maxX) { // Start the next row in this metatile.
                x = Math.max(boxLeft, bounds[z].minX);
                y++;
            }
            if (y >= boxBottom || y > bounds[z].maxY) { // Start the next metatile in this row.
                boxLeft += boxSize;
                boxRight = boxLeft + boxSize;
                x = boxLeft;
                y = Math.max(boxTop, bounds[z].minY);
            }
            if (x > bounds[z].maxX) { // Start the next metatile row.
                x = bounds[z].minX;
                y = boxBottom;
                resetID();
            }
            if (y > bounds[z].maxY) { // All rows in this zoom level are exhausted.
                if (++z > args.maxZoom) return false;
                x = bounds[z].minX;
                y = bounds[z].minY;
                resetID();
            }
            return true;
        };

    } else {
        var id = function() {
            if (z > args.maxZoom) return false;

            if (++x > bounds[z].maxX) {
                x = bounds[z].minX;
                y++;
            }
            if (y > bounds[z].maxY) {
                if (++z > args.maxZoom) return false;
                x = bounds[z].minX;
                y = bounds[z].minY;
            }
            return true;
        };
    }

    function copy(type, z, x, y, callback) {
        var get = type === 'grid' ? 'getGrid' : 'getTile';
        var put = type === 'grid' ? 'putGrid' : 'putTile';
        args.source[get](z, x, y, function(err, data) {
            if (err) {
                action.failed++;
                action.emit('warning', err);
                return callback();
            } else {
                args.sink[put](z, x, y, data, function(err) {
                    if (err) {
                        action.failed++;
                        action.emit('warning', err);
                    } else {
                        action.copied++;
                    }
                    callback();
                });
            }
        });
    }

    var q = new Queue(function(coords, next) {
        // Make sure there's something in the queue.
        if (id()) q.add([z, x, y]);

        Step(function() {
            if (args.tiles) copy('tile', coords[0], coords[1], coords[2], this.parallel());
            if (args.grids) copy('grid', coords[0], coords[1], coords[2], this.parallel());
        }, next);
    }, args.concurrency);

    args.sink.startWriting(function(err) {
        if (err) return action.emit('error', err);

        // Once the queue is empty, all tiles have been copied.
        q.on('empty', function() {
            args.sink.stopWriting(function(err) {
                if (err) {
                    action.emit('error', err);
                } else {
                    action.emit('finished');
                }
            });
        });

        // Fill the queue.
        for (var i = args.concurrency; i && id(); i--) {
            q.add([z, x, y]);
        }
    });

    return action;
};
<commit_msg>More robust error handling.<commit_after>var tilelive = exports;
var _ = require('underscore');
var path = require('path');
var url = require('url');
var util = require('util');
var os = require('os');
var Queue = require('./queue');
var EventEmitter = require('events').EventEmitter;
var Step = require('step');
var sm = new (require('sphericalmercator'));

tilelive.protocols = {
    // Add your protocol handlers here.
    // 'mbtiles:': require('mbtiles')
};

tilelive.defaults = {
    id: null,
    name: '',
    description: '',
    version: "1.0.0",
    legend: null,
    scheme: "xyz",
    minzoom: 0,
    maxzoom: 22,
    bounds: [-180, -85.05112877980659, 180, 85.05112877980659],
    center: null
};

// List all tile source URIs from all tile sources.
tilelive.list = function(source, callback) {
    if (typeof callback !== 'function') {
        throw new Error('Callback required as second argument');
    }

    if (!Object.keys(tilelive.protocols).length) {
        return callback(new Error('No tilesource protocols defined'));
    }

    Step(function() {
        var group = this.group();
        for (var name in tilelive.protocols) {
            tilelive.protocols[name].list(source, group());
        }
    }, function(err, uris) {
        if (err) return callback(err);

        // Combine lists from all protocol sources, eliminating duplicate IDs.
        var result = {};
        for (var i = 0; i < uris.length; i++) {
            _.defaults(result, uris[i]);
        }
        callback(null, result);
    });
};

// Obtains a tile source URI from an ID, checking all tile source protocols
// until one is found.
tilelive.findID = function(source, id, callback) {
    if (typeof callback !== 'function') {
        throw new Error('Callback required as third argument');
    }
    var protocols = Object.keys(tilelive.protocols);
    check();
    function check(err, uri) {
        if (!protocols.length) {
            return callback(new Error('Tileset does not exist'));
        }
        tilelive.protocols[protocols.shift()].findID(source, id, function(err, uri) {
            if (err) check();
            else callback(null, uri);
        });
    }
};

tilelive.load = function(uri, callback) {
    if (typeof callback !== 'function') {
        throw new Error('Callback required as second argument');
    }

    if (typeof uri === 'string') uri = url.parse(uri, true);

    // Handle uris in the format /path/to/dir?id=bar
    if (!uri.protocol && uri.pathname && uri.query.id) {
        tilelive.findID(uri.pathname, uri.query.id, function(err, uri) {
            if (err) callback(err);
            else tilelive.load(uri, callback);
        });
        return;
    }

    if (!tilelive.protocols[uri.protocol]) {
        return callback(new Error('Invalid tilesource protocol'));
    }

    var handler = new tilelive.protocols[uri.protocol](uri, callback);
};

// Load a tilesource and retrieve metadata
tilelive.info = function(uri, callback) {
    if (typeof callback !== 'function') {
        throw new Error('Callback required as second argument');
    }

    tilelive.load(uri, function(err, handler) {
        if (err) return callback(err);
        handler.getInfo(function(err, data) {
            if (data) {
                _(data).defaults(tilelive.defaults);
                callback(err, data, handler);
            } else {
                callback(err);
            }
        });
    });
};

// Load metadata for all tile source URIs.
// Ignore errors from loading individual models (e.g.
// don't let one bad apple spoil the collection).
tilelive.all = function(source, callback) {
    if (typeof callback !== 'function') {
        throw new Error('Callback required as second argument');
    }

    var handlers = [], models = [];
    tilelive.list(source, function(err, uris) {
        if (err) return callback(err);
        if (!uris) return callback(null, []);

        Step(function() {
            var group = this.group();
            for (var id in uris) {
                var next = group();
                tilelive.info(uris[id], function(err, data, handler) {
                    if (!err && data && handler) {
                        models.push(data);
                        handlers.push(handler);
                    }
                    next(err);
                });
            }
        }, function(err) {
            if (err) console.error(err.stack);
            var sorted = _.sortBy(_.zip(models, handlers), function(m) { return (m[0].name || m[0].id).toLowerCase(); });
            callback(null, _(sorted).pluck(0), _(sorted).pluck(1));
        });
    });
};

tilelive.verify = function(ts) {
    if (!ts.id) {
        return new Error("Tilesource has invalid ID: " + util.inspect(ts.id));
    }

    // validates the retrieved settings.
    // Returns an error object when invalid, otherwise undefined.
    if (!(/^\d+(\.\d+){0,2}$/).test(ts.version)) {
        return new Error("Tilesource has invalid version: " + util.inspect(ts.version));
    }

    if (ts.scheme !== 'xyz' && ts.scheme !== 'tms') {
        return new Error("Tilesource has invalid scheme: " + util.inspect(ts.scheme));
    }

    if (ts.minzoom < 0 || ts.minzoom > 22) {
        return new Error("Tilesource has invalid minzoom: " + util.inspect(ts.minzoom));
    }
    if (ts.maxzoom < 0 || ts.maxzoom > 22) {
        return new Error("Tilesource has invalid maxzoom: " + util.inspect(ts.maxzoom));
    }
    if (ts.minzoom > ts.maxzoom) {
        return new Error("Tilesource's minzoom > maxzoom");
    }

    if (!Array.isArray(ts.bounds) || ts.bounds.length !== 4) {
        return new Error("Tilesource has invalid bounds: " + util.inspect(ts.bounds));
    }
    if (ts.bounds[0] < -180 || ts.bounds[0] > 180) {
        return new Error("Tilesource's west bound is invalid: " + util.inspect(ts.bounds));
    }
    if (ts.bounds[2] < -180 || ts.bounds[2] > 180) {
        return new Error("Tilesource's east bound is invalid: " + util.inspect(ts.bounds));
    }
    // @TODO these should actually be checked against
    // +-85.05112877980659 but some mbtiles may be using +-90 which
    // may not be worth failure.
    if (ts.bounds[1] < -90 || ts.bounds[1] > 90) {
        return new Error("Tilesource's south bound is invalid: " + util.inspect(ts.bounds));
    }
    if (ts.bounds[3] < -90 || ts.bounds[3] > 90) {
        return new Error("Tilesource's north bound is invalid: " + util.inspect(ts.bounds));
    }

    if (Array.isArray(ts.center)) {
        if (ts.center.length !== 3) {
            return new Error("Tilesource has invalid center: " + util.inspect(ts.center));
        }
        if (ts.center[0] < ts.bounds[0] || ts.center[0] > ts.bounds[2]) {
            return new Error("Tilesource has invalid longitude as center: " + util.inspect(ts.center));
        }
        if (ts.center[1] < ts.bounds[1] || ts.center[1] > ts.bounds[3]) {
            return new Error("Tilesource has invalid latitude as center: " + util.inspect(ts.center));
        }
        if (ts.center[2] < ts.minzoom || ts.center[2] > ts.maxzoom) {
            return new Error("Tilesource has invalid zoom as center: " + util.inspect(ts.center));
        }
    } else if (ts.center !== null) {
        return new Error("Tilesource has invalid center: " + util.inspect(ts.center));
    }
};

function totalTiles(x, y, z) {
    for (var total = 0, i = 0; i <= z; i++, x *= 2, y *= 2) {
        total += x * y;
    }
    return total;
}

tilelive.copy = function(args, callback) {
    if (typeof args.source.getTile !== 'function') throw new Error('source must implement the tilesource interface');
    if (typeof args.sink.putTile !== 'function') throw new Error('sink must implement the tilesink interface');
    if (!Array.isArray(args.bbox) || args.bbox.length !== 4) throw new Error('bbox must have four lat/long coordinates');
    if (args.bbox[0] < -180) throw new Error('bbox has invalid west value');
    if (args.bbox[1] < -85.05112877980659) throw new Error('bbox has invalid south value');
    if (args.bbox[2] > 180) throw new Error('bbox has invalid east value');
    if (args.bbox[3] > 85.05112877980659) throw new Error('bbox has invalid north value');
    if (args.bbox[0] > args.bbox[2]) throw new Error('bbox is invalid');
    if (args.bbox[1] > args.bbox[3]) throw new Error('bbox is invalid');
    if (typeof args.minZoom !== 'number') throw new Error('minZoom must be a number');
    if (typeof args.maxZoom !== 'number') throw new Error('maxZoom must be a number');
    if (typeof args.concurrency !== 'number') args.concurrency = os.cpus().length * 25;
    if (typeof callback !== 'function') callback = function() {};

    if (!args.tiles && !args.grids) throw new Error('You must copy at least grids or tiles (or both)');

    if (args.minZoom < 0) throw new Error('minZoom must be >= 0');
    if (args.maxZoom > 22) throw new Error('maxZoom must be <= 22');
    if (args.minZoom > args.maxZoom) throw new Error('maxZoom must be >= minZoom');

    var action = new EventEmitter;

    action.copied = 0;
    action.failed = 0;
    action.total = 0;
    action.started = Date.now();
    action.on('finished', callback);

    // Precalculate the tile int bounds for each zoom level.
    var bounds = {};
    for (var z = args.minZoom; z <= args.maxZoom; z++) {
        bounds[z] = sm.xyz(args.bbox, z);
        action.total += (bounds[z].maxX - bounds[z].minX + 1) *
                        (bounds[z].maxY - bounds[z].minY + 1);
    }

    // We have twice the amount of things to copy when we copy both.
    if (args.grids && args.tiles) action.total *= 2;

    // Sets z, x and y to the next tile.
    var z = args.minZoom;

    // Start out one tile left to the starting position because we increment
    // as the first action in the id() function.
    var x = bounds[z].minX - 1;
    var y = bounds[z].minY;

    if (args.source.metatile > 1) {
        var boxSize = args.source.metatile, boxLeft, boxTop, boxLeft, boxBottom;
        var resetID = function() {
            boxLeft = x - x % boxSize;
            boxRight = boxLeft + boxSize;
            boxTop = y - y % boxSize;
            boxBottom = boxTop + boxSize;
        }
        resetID(); // Initialize.
        var id = function() {
            if (z > args.maxZoom) return false;

            ++x;
            if (x >= boxRight || x > bounds[z].maxX) { // Start the next row in this metatile.
                x = Math.max(boxLeft, bounds[z].minX);
                y++;
            }
            if (y >= boxBottom || y > bounds[z].maxY) { // Start the next metatile in this row.
                boxLeft += boxSize;
                boxRight = boxLeft + boxSize;
                x = boxLeft;
                y = Math.max(boxTop, bounds[z].minY);
            }
            if (x > bounds[z].maxX) { // Start the next metatile row.
                x = bounds[z].minX;
                y = boxBottom;
                resetID();
            }
            if (y > bounds[z].maxY) { // All rows in this zoom level are exhausted.
                if (++z > args.maxZoom) return false;
                x = bounds[z].minX;
                y = bounds[z].minY;
                resetID();
            }
            return true;
        };

    } else {
        var id = function() {
            if (z > args.maxZoom) return false;

            if (++x > bounds[z].maxX) {
                x = bounds[z].minX;
                y++;
            }
            if (y > bounds[z].maxY) {
                if (++z > args.maxZoom) return false;
                x = bounds[z].minX;
                y = bounds[z].minY;
            }
            return true;
        };
    }

    function copy(type, z, x, y, callback) {
        var get = type === 'grid' ? 'getGrid' : 'getTile';
        var put = type === 'grid' ? 'putGrid' : 'putTile';
        args.source[get](z, x, y, function(err, data) {
            if (err) {
                action.failed++;
                action.emit('warning', err);
                return callback();
            } else {
                args.sink[put](z, x, y, data, function(err) {
                    if (err) {
                        action.failed++;
                        action.emit('warning', err);
                    } else {
                        action.copied++;
                    }
                    callback();
                });
            }
        });
    }

    var q = new Queue(function(coords, next) {
        // Make sure there's something in the queue.
        if (id()) q.add([z, x, y]);

        Step(function() {
            if (args.tiles) copy('tile', coords[0], coords[1], coords[2], this.parallel());
            if (args.grids) copy('grid', coords[0], coords[1], coords[2], this.parallel());
        }, next);
    }, args.concurrency);

    args.sink.startWriting(function(err) {
        if (err) return action.emit('error', err);

        // Once the queue is empty, all tiles have been copied.
        q.on('empty', function() {
            args.sink.stopWriting(function(err) {
                if (err) {
                    action.emit('error', err);
                } else {
                    action.emit('finished');
                }
            });
        });

        // Fill the queue.
        for (var i = args.concurrency; i && id(); i--) {
            q.add([z, x, y]);
        }
    });

    return action;
};
<|endoftext|>


<commit_before>'use babel';

import TouchbarEditView from './touchbar-edit-view';
import {CompositeDisposable} from 'atom';

const {app, BrowserWindow, TouchBar, Point, Range} = require('remote')

const {
  TouchBarButton,
  TouchBarColorPicker,
  TouchBarGroup,
  TouchBarLabel,
  TouchBarPopover,
  TouchBarScrubber,
  TouchBarSegmentedControl,
  TouchBarSlider,
  TouchBarSpacer
} = TouchBar

const EditViewURI = 'atom://touchbar-edit-view'

const emojis = require('./emoji.json')

let touchBarEnabled = false;

let globalTouchBar = undefined;

const colorRegEx = new RegExp('#[A-Fa-f0-9]{6}')

export default {

  touchbarView : null,
  modalPanel : null,
  subscriptions : null,

  config : {
    buttons: {
      title: 'Elements',
      description: 'Configure your toutchbar elements here',
      type: 'string',
      default: '[{ "name": "my-new-label", "type": "label", "label": "my 🔥 label", "color": "#FF9300" }, { "name": "spacer", "type": "spacer", "size": "small" }, { "name": "comment-button", "type": "button", "label": "//", "command": "editor:toggle-line-comments", "color": "#5712d6" }, { "name": "beautify-button", "type": "button", "label": "💅", "command": "atom-beautify:beautify-editor", "color": "#83FF8F" }, { "name": "color-picker", "type": "color-picker" }, { "type": "popover", "label": "😄", "elements": [{ "name": "emoji-scrubber", "type": "scrubber", "label": "😄", "items": "emojis" } ] }, { "name": "toggle-command-palette", "type": "button", "label": "🎨", "command": "command-palette:toggle" }, {"name":"edit-touchbar","type":"button","label":"Edit Touchbar","command":"touchbar:edit","color":"#83FF8F"}, {"name":"toggle-github","type":"button","label":"GitHub","color":"#0033cc","command":"github:toggle-github-tab"}]'
    }
  },

  activate(state) {
    // Events subscribed to in atom's system can be easily cleaned up with a CompositeDisposable
    this.subscriptions = new CompositeDisposable();

    const touchBar = this.arrayToTouchBarElements(atom.config.get('touchbar.buttons'));

    globalTouchBar = touchBar

    atom.getCurrentWindow().setTouchBar(touchBar)

    // Register command that toggles this view
    this.subscriptions.add(atom.commands.add('atom-workspace', {
      'touchbar:toggle': () => this.toggle()
    }));

    atom.config.onDidChange('touchbar.buttons', ({newValue, oldValue}) => {
      const touchBar = this.arrayToTouchBarElements(newValue);
      globalTouchBar = touchBar
      atom.getCurrentWindow().setTouchBar(touchBar)
    })

    // Register command that opens the edit window
    this.subscriptions.add(atom.commands.add('atom-workspace', {
      'touchbar:edit': () => this.openEditWindow()
    }));
  },

  deactivate() {
    BrowserWindow.getFocusedWindow().setTouchBar(null)
    this.modalPanel.destroy();
    this.subscriptions.dispose();
    this.touchbarView.destroy();
  },

  serialize() {
    // return {touchbarViewState: this.touchbarView.serialize()};
  },

  toggle() {
    BrowserWindow.getFocusedWindow().setTouchBar(touchBarEnabled
      ? null
      : globalTouchBar)
    touchBarEnabled = !touchBarEnabled
  },

  openEditWindow() {
    let tbev = new TouchbarEditView(EditViewURI)
    atom.workspace.open(tbev)
  },

  arrayToTouchBarElements(input) {
    let elements = typeof input === 'string' ? JSON.parse(input) : input;
    let myTouchBarElements = []

    // map all touch bar elements from config and create equivalent objects
    elements.map((e, i) => {
      switch (e.type) {
        case "button":
          // creating new button
          myTouchBarElements.push(new TouchBarButton({
            label: e.label,
            click: () => {
              atom.commands.dispatch(atom.views.getView(atom.workspace.getActiveTextEditor()), e.command);
            },
            // apply selected color only when there is one specified in config, otherwise use default color
            backgroundColor: (e.color
              ? e.color
              : null)
          }))
          break;

        case "label":
          // creating new label
          myTouchBarElements.push(new TouchBarLabel({label: e.label, textColor: e.color}))
          break;

        case "color-picker":
          // breating new color picker
          myTouchBarElements.push(new TouchBarColorPicker({

            change: (color) => {
              // inserts the current selected color
              // get current cursor position
              var cbp = atom.workspace.getActiveTextEditor().getCursorScreenPosition()

              // check if cursor is at beginning of color hex string
              // otherwise insert 7 spaces that will be replaced with hex string
              var nextText = atom.workspace.getActiveTextEditor().getTextInBufferRange([[cbp.row-1, cbp.column], [cbp.row-1, cbp.column+7]])
              if(!colorRegEx.test(nextText)) {
                atom.workspace.getActiveTextEditor().insertText('       ')
              }

              // insert color string into next 7 range
              atom.workspace.getActiveTextEditor().setTextInBufferRange([[cbp.row-1, cbp.column], [cbp.row-1, cbp.column+7]], color)
              // set cursor position to begin of color hex code
              atom.workspace.getActiveTextEditor().setCursorScreenPosition(cbp)
            }
          }))
          break;

        case "spacer":
          // breating new color picker
          myTouchBarElements.push(new TouchBarSpacer({size: e.size}))
          break;

        case "group":
          myTouchBarElements.push(new TouchBarGroup({
            items: this.arrayToTouchBarElements(e.elements)
          }))
          break;

        case "popover":
          myTouchBarElements.push(new TouchBarPopover({
            label: e.label,
            items: this.arrayToTouchBarElements(e.elements)
          }))
          break;

        case "scrubber":
          myTouchBarElements.push(new TouchBarScrubber({
            items: emojis,
            highlight: (i) => {
              atom.workspace.getActiveTextEditor().insertText(emojis[i].label)
            },
            selectedStyle: "outline",
            overlayStyle: "outline",
            continuous: false
          }))
          break;

      }
    })

    return new TouchBar(myTouchBarElements)
  }
};
<commit_msg>fix color picker insert behaviour fixes #8 🐞<commit_after>'use babel';

import TouchbarEditView from './touchbar-edit-view';
import {CompositeDisposable} from 'atom';

const {app, BrowserWindow, TouchBar, Point, Range} = require('remote')

const {
  TouchBarButton,
  TouchBarColorPicker,
  TouchBarGroup,
  TouchBarLabel,
  TouchBarPopover,
  TouchBarScrubber,
  TouchBarSegmentedControl,
  TouchBarSlider,
  TouchBarSpacer
} = TouchBar

const EditViewURI = 'atom://touchbar-edit-view'

const emojis = require('./emoji.json')

let touchBarEnabled = false;

let globalTouchBar = undefined;

const colorRegEx = new RegExp('#[A-Fa-f0-9]{6}')

export default {

  touchbarView : null,
  modalPanel : null,
  subscriptions : null,

  config : {
    buttons: {
      title: 'Elements',
      description: 'Configure your toutchbar elements here',
      type: 'string',
      default: '[{ "name": "my-new-label", "type": "label", "label": "my 🔥 label", "color": "#FF9300" }, { "name": "spacer", "type": "spacer", "size": "small" }, { "name": "comment-button", "type": "button", "label": "//", "command": "editor:toggle-line-comments", "color": "#5712d6" }, { "name": "beautify-button", "type": "button", "label": "💅", "command": "atom-beautify:beautify-editor", "color": "#83FF8F" }, { "name": "color-picker", "type": "color-picker" }, { "type": "popover", "label": "😄", "elements": [{ "name": "emoji-scrubber", "type": "scrubber", "label": "😄", "items": "emojis" } ] }, { "name": "toggle-command-palette", "type": "button", "label": "🎨", "command": "command-palette:toggle" }, {"name":"edit-touchbar","type":"button","label":"Edit Touchbar","command":"touchbar:edit","color":"#83FF8F"}, {"name":"toggle-github","type":"button","label":"GitHub","color":"#0033cc","command":"github:toggle-github-tab"}]'
    }
  },

  activate(state) {
    // Events subscribed to in atom's system can be easily cleaned up with a CompositeDisposable
    this.subscriptions = new CompositeDisposable();

    const touchBar = this.arrayToTouchBarElements(atom.config.get('touchbar.buttons'));

    globalTouchBar = touchBar

    atom.getCurrentWindow().setTouchBar(touchBar)

    // Register command that toggles this view
    this.subscriptions.add(atom.commands.add('atom-workspace', {
      'touchbar:toggle': () => this.toggle()
    }));

    atom.config.onDidChange('touchbar.buttons', ({newValue, oldValue}) => {
      const touchBar = this.arrayToTouchBarElements(newValue);
      globalTouchBar = touchBar
      atom.getCurrentWindow().setTouchBar(touchBar)
    })

    // Register command that opens the edit window
    this.subscriptions.add(atom.commands.add('atom-workspace', {
      'touchbar:edit': () => this.openEditWindow()
    }));
  },

  deactivate() {
    BrowserWindow.getFocusedWindow().setTouchBar(null)
    this.modalPanel.destroy();
    this.subscriptions.dispose();
    this.touchbarView.destroy();
  },

  serialize() {
    // return {touchbarViewState: this.touchbarView.serialize()};
  },

  toggle() {
    BrowserWindow.getFocusedWindow().setTouchBar(touchBarEnabled
      ? null
      : globalTouchBar)
    touchBarEnabled = !touchBarEnabled
  },

  openEditWindow() {
    let tbev = new TouchbarEditView(EditViewURI)
    atom.workspace.open(tbev)
  },

  arrayToTouchBarElements(input) {
    let elements = typeof input === 'string' ? JSON.parse(input) : input;
    let myTouchBarElements = []

    // map all touch bar elements from config and create equivalent objects
    elements.map((e, i) => {
      switch (e.type) {
        case "button":
          // creating new button
          myTouchBarElements.push(new TouchBarButton({
            label: e.label,
            click: () => {
              atom.commands.dispatch(atom.views.getView(atom.workspace.getActiveTextEditor()), e.command);
            },
            // apply selected color only when there is one specified in config, otherwise use default color
            backgroundColor: (e.color
              ? e.color
              : null)
          }))
          break;

        case "label":
          // creating new label
          myTouchBarElements.push(new TouchBarLabel({label: e.label, textColor: e.color}))
          break;

        case "color-picker":
          // breating new color picker
          myTouchBarElements.push(new TouchBarColorPicker({

            change: (color) => {
              // inserts the current selected color
              // get current cursor position
              var cbp = atom.workspace.getActiveTextEditor().getCursorBufferPosition()
              //console.log(atom.workspace.getActiveTextEditor().getCurrentParagraphBufferRange()#25FF00)
              // check if cursor is at beginning of color hex string
              // otherwise insert 7 spaces that will be replaced with hex string
              var nextText = atom.workspace.getActiveTextEditor().getTextInBufferRange([[cbp.row, cbp.column], [cbp.row, cbp.column+7]])
              if(!colorRegEx.test(nextText)) {
                atom.workspace.getActiveTextEditor().insertText('       ')
              }

              // insert color string into next 7 range
              atom.workspace.getActiveTextEditor().setTextInBufferRange([[cbp.row, cbp.column], [cbp.row, cbp.column+7]], color)
              // set cursor position to begin of color hex code
              atom.workspace.getActiveTextEditor().setCursorBufferPosition(cbp)
            }
          }))
          break;

        case "spacer":
          // breating new color picker
          myTouchBarElements.push(new TouchBarSpacer({size: e.size}))
          break;

        case "group":
          myTouchBarElements.push(new TouchBarGroup({
            items: this.arrayToTouchBarElements(e.elements)
          }))
          break;

        case "popover":
          myTouchBarElements.push(new TouchBarPopover({
            label: e.label,
            items: this.arrayToTouchBarElements(e.elements)
          }))
          break;

        case "scrubber":
          myTouchBarElements.push(new TouchBarScrubber({
            items: emojis,
            highlight: (i) => {
              atom.workspace.getActiveTextEditor().insertText(emojis[i].label)
            },
            selectedStyle: "outline",
            overlayStyle: "outline",
            continuous: false
          }))
          break;

      }
    })

    return new TouchBar(myTouchBarElements)
  }
};
<|endoftext|>


<commit_before>'use strict';
module.exports = traverse;
module.exports.Traverser = Traverser;

function traverse(ast, options) {
  var traverser = new Traverser(ast, options);
  traverser.traverseTree();
  return JSON.parse(JSON.stringify(traverser.result));
}
function Traverser(ast, options){
  options = options || {};
  if (typeof ast !== 'object') {
    throw new Error('Expected ast to be an object but got "' + (typeof ast) + '"');
  }
  if (typeof options !== 'object') {
    throw new Error('Expected "options" to be an object but got "' + (typeof options) + '"');
  }
  this.ast = Object.assign({}, ast);
  this.filename = options.filename;
  this.src = options.src;
  this.plugins = options.plugins || [];
  this.currentNode = this.ast;
  this.parentNode = this.ast;
  this.parentNodePath = [];
  this.result = [];
  this.iterationProtect = options.iterationProtect || 20;
  this.index = 0;
}

Traverser.prototype = {
  constructor: Traverser,
  traverseTree: function(parentNode){
    if(this.currentNode.type === "Block"){
      var tmp = this.currentNode;
      for (var i = 0; i < tmp.nodes.length; i++) {
        this.currentNode = tmp.nodes[i];
        this.currentNode.path = Object.assign([], tmp.path);
        this.traverseTree(tmp);
      }
    }else {
      if(this.currentNode.block){
        var tmp = this.currentNode;
        if(!tmp.path) {
          tmp.path = [];
        }
        this.currentNode = this.currentNode.block;
        this.currentNode.path = tmp.path.concat(this.genPath(tmp));
        this.traverseTree(tmp);
      }else{
        this.addToResult(this.currentNode);//添加到结果列表
      }
    }
  },
  addToResult: function(node) {
    node.nodePath = node.path.join(' ');
    this.result.push(node);
  },
  genPath: function(node) {
    if(node.type === "Block"){
      return '';
    }

    this.checkAttr(node);
    var path = node.name + this.parseAttr(node.attrs);
    return path;
  },
  parseAttr: function(attrs) {
    var result = '';
    attrs.forEach(function(item){
      if (item.name == "id") {
        result += "#" + item.val.replace(/'/g, "").replace(/"/g, "");
      } else if (item.name == "class") {
        result += "." + item.val.replace(/'/g, "").replace(/"/g, "");
      } else {
        result += "[" + item.name + "=" + JSON.parse(JSON.stringify(item.val)) + "]";
      }
    });

    return result;
  },
  checkAttr: function(node){
    let attrs = node.attrs;
    for (let i = 0; i < attrs.length; i++) {
      let val = attrs[i].val;
      let name = attrs[i].name;

      if (!(val.indexOf('"') >= 0 || val.indexOf("'") >= 0)) {
        // 是变量, 添加到结果列表中
        this.addToResult({
          type: "Attr",
          val: val,
          line: node.line,
          attrName: name,
          filename: node.filename,
          path: node.path
        });
      }
    }
  }
}
<commit_msg>FIXED:BUG<commit_after>'use strict';
module.exports = traverse;
module.exports.Traverser = Traverser;

function traverse(ast, options) {
  var traverser = new Traverser(ast, options);
  traverser.traverseTree();
  return JSON.parse(JSON.stringify(traverser.result));
}
function Traverser(ast, options){
  options = options || {};
  if (typeof ast !== 'object') {
    throw new Error('Expected ast to be an object but got "' + (typeof ast) + '"');
  }
  if (typeof options !== 'object') {
    throw new Error('Expected "options" to be an object but got "' + (typeof options) + '"');
  }
  this.ast = Object.assign({}, ast);
  this.filename = options.filename;
  this.src = options.src;
  this.plugins = options.plugins || [];
  this.currentNode = this.ast;
  this.parentNode = this.ast;
  this.parentNodePath = [];
  this.result = [];
  this.iterationProtect = options.iterationProtect || 20;
  this.index = 0;
}

Traverser.prototype = {
  constructor: Traverser,
  traverseTree: function(parentNode){
    if(this.currentNode.type === "Block"){
      var tmp = this.currentNode;
      for (var i = 0; i < tmp.nodes.length; i++) {
        this.currentNode = tmp.nodes[i];
        this.currentNode.path = Object.assign([], tmp.path);
        this.traverseTree(tmp);
      }
    }else {
      if(this.currentNode.block){
        var tmp = this.currentNode;
        if(!tmp.path) {
          tmp.path = [];
        }
        this.currentNode = this.currentNode.block;
        this.currentNode.path = tmp.path.concat(this.genPath(tmp));
        this.traverseTree(tmp);
      }else{
        this.addToResult(this.currentNode);//添加到结果列表
      }
    }
  },
  addToResult: function(node) {
    node.nodePath = node.path.join(' ');
    this.result.push(node);
  },
  genPath: function(node) {
    if(node.type === "Block"){
      return '';
    }

    this.checkAttr(node);
    var path = node.name + this.parseAttr(node.attrs);
    return path;
  },
  parseAttr: function(attrs) {
    var result = '';
    if(attrs){
      attrs.forEach(function(item){
        if (item.name == "id") {
          result += "#" + item.val.replace(/'/g, "").replace(/"/g, "");
        } else if (item.name == "class") {
          result += "." + item.val.replace(/'/g, "").replace(/"/g, "");
        } else {
          result += "[" + item.name + "=" + JSON.parse(JSON.stringify(item.val)) + "]";
        }
      });
    }

    return result;
  },
  checkAttr: function(node){
    let attrs = node.attrs;
    if(attrs) {
      for (let i = 0; i < attrs.length; i++) {
        let val = attrs[i].val;
        let name = attrs[i].name;

        if (!(val.indexOf('"') >= 0 || val.indexOf("'") >= 0)) {
          // 是变量, 添加到结果列表中
          this.addToResult({
            type: "Attr",
            val: val,
            line: node.line,
            attrName: name,
            filename: node.filename,
            path: node.path
          });
        }
      }
    }
  }
}
<|endoftext|>


<commit_before>'use strict';

var daap = {};
var session = {};

/* Local functions */
function create_token(data, callback) {
    var token = {};

    token.name = data['dmcp.playstatus']['dacp.nowplayingname'];
    token.artist = data['dmcp.playstatus']['dacp.nowplayingartist'];
    token.album = data['dmcp.playstatus']['dacp.nowplayingalbum'];
    token.genre = data['dmcp.playstatus']['dacp.nowplayinggenre'];

    var meta = ['daap.songuserrating'];
    var nowplaying = data['dmcp.playstatus']['dacp.nowplayingids'];

    daap.item(session.id, nowplaying.databaseId, nowplaying.trackId, function (error, response) {
        if (error) {
            return callback(error);
        }

        if ('daap.databasesongs'in response &&
            'dmap.listing' in response['daap.databasesongs'] &&
            'dmap.listingitem'in response['daap.databasesongs']['dmap.listing'] &&
            'daap.songuserrating' in response['daap.databasesongs']['dmap.listing']['dmap.listingitem']) {
            var userRating = response['daap.databasesongs']['dmap.listing']['dmap.listingitem']['daap.songuserrating'];

            token.stars = Math.round(userRating / 20);
        } else {
            token.stars = 0;
        }

        callback(null, token);
    }, meta);
}

/* Trigger functions */
exports.process = function (data, prevdata) {
    if ('dmcp.playstatus'in data &&
        'dmcp.playstatus'in prevdata &&
        'dacp.nowplayingids' in data['dmcp.playstatus'] &&
        'dacp.nowplayingids' in prevdata['dmcp.playstatus']) {
        if (data['dmcp.playstatus']['dacp.nowplayingids'].trackId != prevdata['dmcp.playstatus']['dacp.nowplayingids'].trackId) {
            create_token(data, function (error, token) {
                if (error) {
                    Homey.log(error);
                    return;
                }

                Homey.manager('flow').trigger('song_changed', token);
            });
        }
    }

    if ('dmcp.playstatus'in data &&
        'dmcp.playstatus'in prevdata &&
        'dacp.playerstate' in data['dmcp.playstatus'] &&
        'dacp.playerstate' in prevdata['dmcp.playstatus']) {
        if (data['dmcp.playstatus']['dacp.playerstate'] != prevdata['dmcp.playstatus']['dacp.playerstate']) {
            if (data['dmcp.playstatus']['dacp.playerstate'] == 3) {
                create_token(data, function (error, token) {
                    if (error) {
                        Homey.log(error);
                        return;
                    }

                    Homey.manager('flow').trigger('song_paused', token);
                });
            } else if (data['dmcp.playstatus']['dacp.playerstate'] == 4) {
                create_token(data, function (error, token) {
                    if (error) {
                        Homey.log(error);
                        return;
                    }

                    Homey.manager('flow').trigger('song_resumed', token);
                });
            }
        }
    }
};

/* Init */
exports.init = function (_daap, _session) {
    daap = _daap;
    session = _session;
};
<commit_msg>triggers: check for databaseid and trackid<commit_after>'use strict';

var daap = {};
var session = {};

/* Local functions */
function create_token(data, callback) {
    var token = {};

    token.name = data['dmcp.playstatus']['dacp.nowplayingname'];
    token.artist = data['dmcp.playstatus']['dacp.nowplayingartist'];
    token.album = data['dmcp.playstatus']['dacp.nowplayingalbum'];
    token.genre = data['dmcp.playstatus']['dacp.nowplayinggenre'];

    var meta = ['daap.songuserrating'];
    var nowplaying = data['dmcp.playstatus']['dacp.nowplayingids'];

    if (nowplaying.databaseId && nowplaying.trackId) {
        daap.item(session.id, nowplaying.databaseId, nowplaying.trackId, function (error, response) {
            if (error) {
                return callback(error);
            }

            if ('daap.databasesongs'in response &&
                'dmap.listing' in response['daap.databasesongs'] &&
                'dmap.listingitem'in response['daap.databasesongs']['dmap.listing'] &&
                'daap.songuserrating' in response['daap.databasesongs']['dmap.listing']['dmap.listingitem']) {
                var userRating = response['daap.databasesongs']['dmap.listing']['dmap.listingitem']['daap.songuserrating'];

                token.stars = Math.round(userRating / 20);
            } else {
                token.stars = 0;
            }

            callback(null, token);
        }, meta);
    } else {
        return callback(null, token);
    }
}

/* Trigger functions */
exports.process = function (data, prevdata) {
    if ('dmcp.playstatus'in data &&
        'dmcp.playstatus'in prevdata &&
        'dacp.nowplayingids' in data['dmcp.playstatus'] &&
        'dacp.nowplayingids' in prevdata['dmcp.playstatus']) {
        if (data['dmcp.playstatus']['dacp.nowplayingids'].trackId != prevdata['dmcp.playstatus']['dacp.nowplayingids'].trackId) {
            create_token(data, function (error, token) {
                if (error) {
                    Homey.log(error);
                    return;
                }

                Homey.manager('flow').trigger('song_changed', token);
            });
        }
    }

    if ('dmcp.playstatus'in data &&
        'dmcp.playstatus'in prevdata &&
        'dacp.playerstate' in data['dmcp.playstatus'] &&
        'dacp.playerstate' in prevdata['dmcp.playstatus']) {
        if (data['dmcp.playstatus']['dacp.playerstate'] != prevdata['dmcp.playstatus']['dacp.playerstate']) {
            if (data['dmcp.playstatus']['dacp.playerstate'] == 3) {
                create_token(data, function (error, token) {
                    if (error) {
                        Homey.log(error);
                        return;
                    }

                    Homey.manager('flow').trigger('song_paused', token);
                });
            } else if (data['dmcp.playstatus']['dacp.playerstate'] == 4) {
                create_token(data, function (error, token) {
                    if (error) {
                        Homey.log(error);
                        return;
                    }

                    Homey.manager('flow').trigger('song_resumed', token);
                });
            }
        }
    }
};

/* Init */
exports.init = function (_daap, _session) {
    daap = _daap;
    session = _session;
};
<|endoftext|>


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

    exports.help = help;
    exports.execute = execute;

    var _ = require('lodash');
    var minimist = require('minimist');
    var fs = require('fs');
    var url = require('url');
    var request = require('request');
    var http = require('http');
    var pd = require('pretty-data').pd;
    var common = require('./common');

    /**
     * Display help for this task.
     */
    function help() {
	common.displayHelp('tsu-http');
    }

    /**
     * Execute this task.
     */
    function execute(cmdLine) {
	var args = parseCmdLine(cmdLine);

	if (errors.length > 0) {
	    common.displayErrors(errors);
	    process.exit(1);
	} else {
	    sendRequest(args);
	}
    }

    /**
     * Parse command line arguments.
     */
    function parseCmdLine(cmdLine) {
	var argsOptions = {
	    'boolean': ['v', 'follow-redirect'],
	    'alias': {
		'd': 'data',
		'f': 'file',
		'h': 'header',
		'm': 'method',
		'u': 'basic-auth',
		'v': 'verbose'
	    }
	};

	return minimist(cmdLine, argsOptions);
    }

    /**
     * Send request.
     */
    function sendRequest(args) {
	var options = createRequestOptions(args);

	request(options).on('response', function (response) {
	    var verbose = args.verbose || false;
	    var head = args.method && 'HEAD' === args.method.toUpperCase();

	    displayResponseInfos(response, verbose, head);
	    displayResponseBody(response);
	}).on('error', function (error) {
	    console.log(error);
	    process.exit(1);
	});
    }

    /**
     * create request options.
     */
    function createRequestOptions(args) {
	var options = {
	    url: args._[0] || 'http://localhost',
	    proxy: args.proxy,
	    method: args.method || 'GET',
	    followRedirect: args['follow-redirect'] || false,
	    headers: {}
	};

	addBasicAuth(options, args);
	addHeaders(options, args);
	addData(options, args);

	return options;
    }

    /**
     * Add basic-auth to request options.
     */
    function addBasicAuth(options, args) {
	var target = url.parse(options.url);
	var creds = target.auth || args['basic-auth'];

	if (creds) {
	    var auth = new Buffer(creds).toString('base64');
	    var header = 'Authorization:Basic ' + auth;
	    addHeader(options, header);
	}
    }

    /**
     * Add a single header to request options.
     */
    function addHeader(options, header) {
	if (header) {
	    var headerData = header.split(':');
	    options.headers[headerData[0]] = headerData[1];
	}
    }

    /**
     * Add data to send to request options.
     */
    function addData(options, args) {
	var data = args.data;
	if (data) {
	    options.body = data;
	}

	var file = args.file;
	if (file) {
	    try {
		options.body = fs.readFileSync(file);
	    } catch (e) {
		console.error('Error while reading file %s: %s',
			      file,
			      e.message);
		process.exit(1);
	    }
	}
    }

    /**
     * Add all headers to request options.
     */
    function addHeaders(options, args) {
	var header = args.header;
	if (header) {
	    if (_.isArray(header)) {
		_.each(header, function (element) {
		    addHeader(options, element);
		});
	    } else {
		addHeader(options, header);
	    }
	}
    }

    /**
     * Display response informations.
     * @param response response from server
     * @param verbose indicates if verbose mode is requested
     * @param head indicates if it is a head request
     */
    function displayResponseInfos(response, verbose, head) {
	if (verbose || head) {
	    var status = response.statusCode;
	    var statusLine = status + ' ' + http.STATUS_CODES[status];
	    console.log('Status: ' + statusLine);

	    console.log('Headers:\n' + pd.json(response.headers));

	    if (!head) {
		console.log('Body:');
	    }
	}
    }

    /**
     * Display response body.
     * @param response response from server
     */
    function displayResponseBody(response) {
	response.setEncoding('utf8');

	var contentType = response.headers['content-type'];
	var data = "";

	response.on('data', function (chunk) {
	    data += chunk;
	});

	response.on('end', function () {
	    if (isJsonContent(contentType)) {
		var json = pd.json(data);
		console.log(json);
	    } else if (isXmlContent(contentType)) {
		var xml = pd.xml(data);
		console.log(xml);
	    } else {
		console.log(data);
	    }
	});
    }

    /**
     * Test if content should be displayed as JSON according to contentType.
     */
    function isJsonContent(contentType) {
	var isJSON = false;

	if (contentType) {
	    isJSON = contentType.indexOf('application/json') >= 0;
	}

	return isJSON;
    }

    /**
     * Test if content should be displayed as XML according to contentType.
     */
    function isXmlContent(contentType) {
	var isXML = false;

	if (contentType) {
	    isXML = contentType.indexOf('xml') >= 0;
	}

	return isXML;
    }
})();
<commit_msg>Fix HTTP task execution<commit_after>(function () {
    'use strict';

    exports.help = help;
    exports.execute = execute;

    var _ = require('lodash');
    var minimist = require('minimist');
    var fs = require('fs');
    var url = require('url');
    var request = require('request');
    var http = require('http');
    var pd = require('pretty-data').pd;
    var common = require('./common');

    /**
     * Display help for this task.
     */
    function help() {
	common.displayHelp('tsu-http');
    }

    /**
     * Execute this task.
     */
    function execute(cmdLine) {
	var args = parseCmdLine(cmdLine);
	sendRequest(args);
    }

    /**
     * Parse command line arguments.
     */
    function parseCmdLine(cmdLine) {
	var argsOptions = {
	    'boolean': ['v', 'follow-redirect'],
	    'alias': {
		'd': 'data',
		'f': 'file',
		'h': 'header',
		'm': 'method',
		'u': 'basic-auth',
		'v': 'verbose'
	    }
	};

	return minimist(cmdLine, argsOptions);
    }

    /**
     * Send request.
     */
    function sendRequest(args) {
	var options = createRequestOptions(args);

	request(options).on('response', function (response) {
	    var verbose = args.verbose || false;
	    var head = args.method && 'HEAD' === args.method.toUpperCase();

	    displayResponseInfos(response, verbose, head);
	    displayResponseBody(response);
	}).on('error', function (error) {
	    console.log(error);
	    process.exit(1);
	});
    }

    /**
     * create request options.
     */
    function createRequestOptions(args) {
	var options = {
	    url: args._[0] || 'http://localhost',
	    proxy: args.proxy,
	    method: args.method || 'GET',
	    followRedirect: args['follow-redirect'] || false,
	    headers: {}
	};

	addBasicAuth(options, args);
	addHeaders(options, args);
	addData(options, args);

	return options;
    }

    /**
     * Add basic-auth to request options.
     */
    function addBasicAuth(options, args) {
	var target = url.parse(options.url);
	var creds = target.auth || args['basic-auth'];

	if (creds) {
	    var auth = new Buffer(creds).toString('base64');
	    var header = 'Authorization:Basic ' + auth;
	    addHeader(options, header);
	}
    }

    /**
     * Add a single header to request options.
     */
    function addHeader(options, header) {
	if (header) {
	    var headerData = header.split(':');
	    options.headers[headerData[0]] = headerData[1];
	}
    }

    /**
     * Add data to send to request options.
     */
    function addData(options, args) {
	var data = args.data;
	if (data) {
	    options.body = data;
	}

	var file = args.file;
	if (file) {
	    try {
		options.body = fs.readFileSync(file);
	    } catch (e) {
		console.error('Error while reading file %s: %s',
			      file,
			      e.message);
		process.exit(1);
	    }
	}
    }

    /**
     * Add all headers to request options.
     */
    function addHeaders(options, args) {
	var header = args.header;
	if (header) {
	    if (_.isArray(header)) {
		_.each(header, function (element) {
		    addHeader(options, element);
		});
	    } else {
		addHeader(options, header);
	    }
	}
    }

    /**
     * Display response informations.
     * @param response response from server
     * @param verbose indicates if verbose mode is requested
     * @param head indicates if it is a head request
     */
    function displayResponseInfos(response, verbose, head) {
	if (verbose || head) {
	    var status = response.statusCode;
	    var statusLine = status + ' ' + http.STATUS_CODES[status];
	    console.log('Status: ' + statusLine);

	    console.log('Headers:\n' + pd.json(response.headers));

	    if (!head) {
		console.log('Body:');
	    }
	}
    }

    /**
     * Display response body.
     * @param response response from server
     */
    function displayResponseBody(response) {
	response.setEncoding('utf8');

	var contentType = response.headers['content-type'];
	var data = "";

	response.on('data', function (chunk) {
	    data += chunk;
	});

	response.on('end', function () {
	    if (isJsonContent(contentType)) {
		var json = pd.json(data);
		console.log(json);
	    } else if (isXmlContent(contentType)) {
		var xml = pd.xml(data);
		console.log(xml);
	    } else {
		console.log(data);
	    }
	});
    }

    /**
     * Test if content should be displayed as JSON according to contentType.
     */
    function isJsonContent(contentType) {
	var isJSON = false;

	if (contentType) {
	    isJSON = contentType.indexOf('application/json') >= 0;
	}

	return isJSON;
    }

    /**
     * Test if content should be displayed as XML according to contentType.
     */
    function isXmlContent(contentType) {
	var isXML = false;

	if (contentType) {
	    isXML = contentType.indexOf('xml') >= 0;
	}

	return isXML;
    }
})();
<|endoftext|>