<commit_before>//Mobile navigation toggle
$(document).ready(function(){
    $('#nav-icon-menu').click(function(){
        $(this).toggleClass('open');
        $('body').toggleClass('navigation-opened');
        return false;
    });
});

//Mobile navigation — doc-list toggle
$('.doc-list-toggle').on('click', function(){
    $('.doc-list-inside').toggleClass('active');
    $(this).toggleClass('active');
});

//Mobile footer navitation
$(document).ready(function() {
    var $window = $(window);
    $('.toggle').on('click',function(){
        $(this).toggleClass('active');
    });
});

// Youtube Player API
// create script tag and add to DOM
var tag = document.createElement('script');
tag.src = "https://www.youtube.com/iframe_api";
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);

// Array of videoIds
// The key corresponds to the data attributes in about/index.html
var playerInfoList = [
    {type: 'yt', key: 'UOIJNygDNlE'},
    {type: 'yt', key: 'nz-LcdoMYWA'},
    {type: 'yt', key: 'sZx3oZt7LVg'},
    {type: 'yt', key: 'RvUP7vX2P4s'},
    {type: 'slideshare', key: 'https://www.slideshare.net/sujatatibre/g-rpc-talk-with-intel-3'},
    {type: 'slideshare', key: 'https://www.slideshare.net/VarunTalwar4/grpc-design-and-implementation'},
    {type: 'slideshare', key: 'https://www.slideshare.net/VarunTalwar4/grpc-overview'},
    {type: 'slideshare', key: 'https://www.ustream.tv/recorded/86187859'}
];

function createPlayer(key) {
  $('#player').append('<iframe id="ytplayer" type="text/html" width="640" height="390" src="https://www.youtube.com/embed/'+key+'" frameborder="0" allowfullscreen>');
}

// click event for presentations/talks in about
$('.pt').on('click', function() {
  var self = this,
      video = playerInfoList.filter(function(obj) {
        return obj.key == $(self).data('key');
      })[0];

  if (video.type == 'yt') {
    createPlayer(video.key);
  } else {
    window.open(video.key);
  }

  resizePlayer();
  $('#player iframe').on('load', function() {
    $('.pt-lightbox').addClass('active');
  });
});

// Close lightbox when clicking anywhere on overlay
$('.pt-lightbox').on('click', function() {
  if ($(this).hasClass('active')) {
    $(this).removeClass('active');
    $(this).find('iframe').remove();
    $('body, html').removeClass('noscroll');
  }
});

// Resize Player
function resizePlayer() {
  var $inner = $('.pt-player'),
      defaultHeight = window.innerHeight || document.documentElement.clientHeight,
      defaultWidth = window.innerWidth || document.documentElement.clientWidth,
      maxHeight = defaultHeight*.75,
      maxWidth = defaultWidth*.75,
      newWidth = maxWidth,
      newHeight = 16 * maxWidth / 9;

  if (defaultWidth > defaultHeight){
      if (newHeight > maxHeight){
        newWidth = 16 * maxHeight / 9;
        newHeight = maxHeight;
      }
  } else {
      newWidth = 16 * maxHeight / 9;
      newHeight = maxHeight;
      if (newWidth > maxWidth){
          newHeight = 9 * maxWidth / 16;
          newWidth = maxWidth;
      }
  }

  $inner.css({"width": newWidth, "height": newHeight});
}

// Jquery UI for tabbed panes
$.getScript("https://ajax.googleapis.com/ajax/libs/jqueryui/1.10.4/jquery-ui.min.js", function(){
  setupTabs();
});

// Add the 'external' class to every outbound link on the site.
// The css will add a small right arrow after the link.
$('a').filter(function() {
   return this.hostname && this.hostname !== location.hostname;
}).addClass("external");

// Remove external mark on Octocat icons, that already look external enough.
$("i.fa-github-alt").parent().removeClass("external");

//Set up tabs
function setupTabs(rootElement) {
      rootElement = rootElement || document;
      var tabs = $(rootElement).find('div.tabs');
      if(tabs.length > 0) {
        tabs.tabs();
      }
};

// Prettyprint
$('pre').addClass("prettyprint");
$.getScript("https://cdn.rawgit.com/google/code-prettify/master/loader/run_prettify.js", function(){
});

// Collapsible navbar menu, using https://github.com/jordnkr/collapsible
$.getScript("/js/jquery.collapsible.js", function(){
  highlightActive();
  $('.submenu').collapsible();
});

// Remove class from the paren element when the child is active
$(function() {
    if ($('#doc-side-nav-inside a').hasClass('current')) {
        var element = document.getElementById('side-nav-parent-item');
        element.classList.remove('current');
    }
});

var tocNav = $('#toc');
$(function() {
//Calls the tocify method on your HTML nav.
    tocNav.tocify({selectors:"h2, h3, h4", showAndHide: false, scrollTo: 64, extendPage: false});
});

window.onscroll = function() {
    FixToc();
    FixHead();
};

//Fix TOC navigation on page while scrolling
function FixToc() {
    if (tocNav.length > 0) {
        var tocNavFixedPosition = 120; //Sticky TOC offset
        if (window.pageYOffset > tocNavFixedPosition) {
            tocNav.addClass("sticky");
        }
        else {
            tocNav.removeClass("sticky");
        }
    }
}

//Animation header on scroll
function FixHead() {
    var header = $('#header');
    if (header.length > 0) {
        var headerFixPosition = $(".nav-hero-container").innerHeight();
        if (window.pageYOffset > headerFixPosition) {
            header.addClass("not-top"); //When navigation below offset
            header.addClass("pinned"); //When navigation below hero section
            header.removeClass("unpinned");
        }
        else {
            header.removeClass("pinned");
            header.addClass("unpinned");
        }

        //Return classes to the initial state when the navigation at the top of the page
        if (window.pageYOffset < 64) {
            header.removeClass("not-top");
            header.removeClass("unpinned");
        }
    }
}
<commit_msg>Add variable for the header height in the common.js<commit_after>//Mobile navigation toggle
$(document).ready(function(){
    $('#nav-icon-menu').click(function(){
        $(this).toggleClass('open');
        $('body').toggleClass('navigation-opened');
        return false;
    });
});

//Mobile navigation — doc-list toggle
$('.doc-list-toggle').on('click', function(){
    $('.doc-list-inside').toggleClass('active');
    $(this).toggleClass('active');
});

//Mobile footer navitation
$(document).ready(function() {
    var $window = $(window);
    $('.toggle').on('click',function(){
        $(this).toggleClass('active');
    });
});

// Youtube Player API
// create script tag and add to DOM
var tag = document.createElement('script');
tag.src = "https://www.youtube.com/iframe_api";
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);

// Array of videoIds
// The key corresponds to the data attributes in about/index.html
var playerInfoList = [
    {type: 'yt', key: 'UOIJNygDNlE'},
    {type: 'yt', key: 'nz-LcdoMYWA'},
    {type: 'yt', key: 'sZx3oZt7LVg'},
    {type: 'yt', key: 'RvUP7vX2P4s'},
    {type: 'slideshare', key: 'https://www.slideshare.net/sujatatibre/g-rpc-talk-with-intel-3'},
    {type: 'slideshare', key: 'https://www.slideshare.net/VarunTalwar4/grpc-design-and-implementation'},
    {type: 'slideshare', key: 'https://www.slideshare.net/VarunTalwar4/grpc-overview'},
    {type: 'slideshare', key: 'https://www.ustream.tv/recorded/86187859'}
];

function createPlayer(key) {
  $('#player').append('<iframe id="ytplayer" type="text/html" width="640" height="390" src="https://www.youtube.com/embed/'+key+'" frameborder="0" allowfullscreen>');
}

// click event for presentations/talks in about
$('.pt').on('click', function() {
  var self = this,
      video = playerInfoList.filter(function(obj) {
        return obj.key == $(self).data('key');
      })[0];

  if (video.type == 'yt') {
    createPlayer(video.key);
  } else {
    window.open(video.key);
  }

  resizePlayer();
  $('#player iframe').on('load', function() {
    $('.pt-lightbox').addClass('active');
  });
});

// Close lightbox when clicking anywhere on overlay
$('.pt-lightbox').on('click', function() {
  if ($(this).hasClass('active')) {
    $(this).removeClass('active');
    $(this).find('iframe').remove();
    $('body, html').removeClass('noscroll');
  }
});

// Resize Player
function resizePlayer() {
  var $inner = $('.pt-player'),
      defaultHeight = window.innerHeight || document.documentElement.clientHeight,
      defaultWidth = window.innerWidth || document.documentElement.clientWidth,
      maxHeight = defaultHeight*.75,
      maxWidth = defaultWidth*.75,
      newWidth = maxWidth,
      newHeight = 16 * maxWidth / 9;

  if (defaultWidth > defaultHeight){
      if (newHeight > maxHeight){
        newWidth = 16 * maxHeight / 9;
        newHeight = maxHeight;
      }
  } else {
      newWidth = 16 * maxHeight / 9;
      newHeight = maxHeight;
      if (newWidth > maxWidth){
          newHeight = 9 * maxWidth / 16;
          newWidth = maxWidth;
      }
  }

  $inner.css({"width": newWidth, "height": newHeight});
}

// Jquery UI for tabbed panes
$.getScript("https://ajax.googleapis.com/ajax/libs/jqueryui/1.10.4/jquery-ui.min.js", function(){
  setupTabs();
});

// Add the 'external' class to every outbound link on the site.
// The css will add a small right arrow after the link.
$('a').filter(function() {
   return this.hostname && this.hostname !== location.hostname;
}).addClass("external");

// Remove external mark on Octocat icons, that already look external enough.
$("i.fa-github-alt").parent().removeClass("external");

//Set up tabs
function setupTabs(rootElement) {
      rootElement = rootElement || document;
      var tabs = $(rootElement).find('div.tabs');
      if(tabs.length > 0) {
        tabs.tabs();
      }
};

// Prettyprint
$('pre').addClass("prettyprint");
$.getScript("https://cdn.rawgit.com/google/code-prettify/master/loader/run_prettify.js", function(){
});

// Collapsible navbar menu, using https://github.com/jordnkr/collapsible
$.getScript("/js/jquery.collapsible.js", function(){
  highlightActive();
  $('.submenu').collapsible();
});

// Remove class from the paren element when the child is active
$(function() {
    if ($('#doc-side-nav-inside a').hasClass('current')) {
        var element = document.getElementById('side-nav-parent-item');
        element.classList.remove('current');
    }
});

var InitialHeadHeight = $("#header").innerHeight();
var tocNav = $('#toc');
$(function() {
//Calls the tocify method on your HTML nav.
//InitialHeadHeight + 12 (12px — small offset from the header navigation)
    tocNav.tocify({selectors:"h2, h3, h4", showAndHide: false, scrollTo: InitialHeadHeight+12, extendPage: false});
});

window.onscroll = function() {
    FixToc();
    FixHead();
};

//Fix TOC navigation on page while scrolling
function FixToc() {
    if (tocNav.length > 0) {
        var tocNavFixedPosition = 120; //Sticky TOC offset
        if (window.pageYOffset > tocNavFixedPosition) {
            tocNav.addClass("sticky");
        }
        else {
            tocNav.removeClass("sticky");
        }
    }
}

//Animation header on scroll
function FixHead() {
    var header = $('#header');
    if (header.length > 0) {
        var headerFixPosition = $(".nav-hero-container").innerHeight();
        if (window.pageYOffset > headerFixPosition) {
            header.addClass("not-top"); //When navigation below offset
            header.addClass("pinned"); //When navigation below hero section
            header.removeClass("unpinned");
        }
        else {
            header.removeClass("pinned");
            header.addClass("unpinned");
        }

        //Return classes to the initial state when the navigation at the top of the page
        if (window.pageYOffset < InitialHeadHeight) {
            header.removeClass("not-top");
            header.removeClass("unpinned");
        }
    }
}
<|endoftext|>


<commit_before>/**
 * Created by buchholb on 3/31/15.
 */
$(document).ready(function () {
    handleStatsDisplay();
    var ind = window.location.href.indexOf("?query=");
    if (ind > 0) {
        ind += 7;
        var ccInd = window.location.href.indexOf("&cmd=clearcache");
        if (ccInd > 0) {
            $("#clear").prop("checked", true);
        }
        var sInd = window.location.href.indexOf("&send=");
        if (sInd > 0) {
            if (ccInd <= 0 || ccInd > sInd) {
                ccInd = sInd;
            }
        }
        if (ccInd > 0) {
            $("#query").val(decodeURIComponent(
                window.location.href.substr(ind, ccInd - ind)));
        } else {
            $("#query").val(decodeURIComponent(
                window.location.href.substr(ind)));
        }
        processQuery(window.location.href.substr(ind - 7));
    }
    $("#runbtn").click(function () {
        var q = encodeURIComponent($("#query").val());
        console.log(q);
        var queryString = "?query=" + q;
        if ($("#clear").prop('checked')) {
            console.log("With clearcache");
            queryString += "&cmd=clearcache";
        } else {
            console.log("Without clearcache");
        }
        queryString += "&send=100"
        var loc = window.location.href.substr(0, window.location.href.indexOf("?"));
        if (loc.length == 0) {
            loc = "index.html"
        }
        window.history.pushState("html:index.html", "QLever",
            loc + queryString);
        processQuery(queryString)
    });
    $("#csvbtn").click(function () {
        var q = encodeURIComponent($("#query").val());
        console.log(q);
        var queryString = "?query=" + q;
        if ($("#clear").prop('checked')) {
            console.log("With clearcache");
            queryString += "&cmd=clearcache";
        } else {
            console.log("Without clearcache");
        }
        processCsvQuery(queryString)
    });
    $("#tsvbtn").click(function () {
        var q = encodeURIComponent($("#query").val());
        console.log(q);
        var queryString = "?query=" + q;
        if ($("#clear").prop('checked')) {
            console.log("With clearcache");
            queryString += "&cmd=clearcache";
        } else {
            console.log("Without clearcache");
        }
        processTsvQuery(queryString)
    });
});

// Copy&paste from:
// http://stackoverflow.com/questions/901115/how-can-i-get-query-string-values-in-javascript/
// TODO: use consistently, so far it has been added later to comfortably get stats.
function getParameterByName(name, url) {
    if (!url) {
        url = window.location.href;
    }
    name = name.replace(/[\[\]]/g, "\\$&");
    var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"),
        results = regex.exec(url);
    if (!results) return null;
    if (!results[2]) return '';
    return decodeURIComponent(results[2].replace(/\+/g, " "));
}

function htmlEscape(str) {
//    return str.replace(/&/g, "&")
//        .replace(/</g, "<")
//        .replace(/>/g, ">");
    return $("
").text(str).html();
}

function getShortStr(str, maxLength) {
    var pos;
    var cpy = str;
    if (cpy.charAt(0) == '<') {
        pos = cpy.lastIndexOf('/');
        var paraClose = cpy.lastIndexOf(')');
        if (paraClose > 0 && paraClose > pos) {
            var paraOpen = cpy.lastIndexOf('(', paraClose);
            if (paraOpen > 0 && paraOpen < pos) {
                pos = cpy.lastIndexOf('/', paraOpen);
            }
        }

        if (pos < 0) {
            pos += 1;
        }
        cpy = cpy.substring(pos + 1, cpy.length - 1);
        if (cpy.length > maxLength) {
            return cpy.substring(0, maxLength - 1) + "[...]"
        } else {
            return cpy;
        }
    }
    if (cpy.charAt(0) == '\"') {
        pos = cpy.lastIndexOf('\"');
        if (pos !== 0) {
            cpy = cpy.substring(0, pos + 1);
        }
        if (cpy.length > maxLength) {
            return cpy.substring(0, maxLength - 1) + "[...]\""
        } else {
            return cpy;
        }
    }
    return str;
}

function displayError(result) {
    var disp = "
";
    disp += "Query: " + "
" + result.query  + "
" + "
" + "
";
    disp += "Error Msg: " + "
" + result.exception + "
" + "
";
    $("#answer").html(disp);
}

function processCsvQuery(query) {
        window.location.href = "/" + query + "&action=csv_export";
        return false;
}

function processTsvQuery(query) {
    window.location.href = "/" + query + "&action=tsv_export";
    return false;
}

function processQuery(query) {
    maxSend = 0;
    var sInd = window.location.href.indexOf("&send=");
    if (sInd > 0) {
        var nextAmp = window.location.href.indexOf("&", sInd + 1);
        if (nextAmp > 0) {
            maxSend = parseInt(window.location.href.substr(sInd + 6, nextAmp - (sInd + 6)))
        } else {
            maxSend = parseInt(window.location.href.substr(sInd + 6))
        }
    }
    $.getJSON("/" + query, function (result) {
        if (result.status == "ERROR") {
            displayError(result);
            return;
        }
        var res = "
";
        // Time
        res += "
";
        res += "Number of rows: " + result.resultsize + "

";
        res += "Time elapsed:
";
        res += "Total: " + result.time.total + "
";
        res += " - Computation: " + result.time.computeResult + "
";
        res += " - Resolving entity names + excerpts and creating JSON: "
            + (parseInt(result.time.total.replace(/ms/, ""))
            - parseInt(result.time.computeResult.replace(/ms/, ""))).toString()
            + "ms";
        res += "
";
        if (maxSend > 0 && maxSend < parseInt(result.resultsize)) {
            res += "
Only transmitted " + maxSend.toString()
                + " rows out of the " + result.resultsize
                + " that were computed server-side. " +
                "[show all]";
        }
        res += "
";
        for (var i = 0; i < result.res.length; i++) {
            res += "
"
            for (var j = 0; j < result.res[i].length; ++j) {
                res += ""
                    + htmlEscape(getShortStr(result.res[i][j], 26))
                    + "	";
            }
            res += "
";
        }
        res += "
";
        res += "
";
        $("#answer").html(res);
    });
}

function handleStatsDisplay() {
    $.getJSON("/?cmd=stats", function (result) {
        $("#kbname").html("KB index: " + result.kbindex + " ");
        $("#textname").html("Text index: " + result.textindex + " ");
        $("#ntriples").html("number of triples: " + result.noftriples + " ");
        $("#nrecords").html("number of text records: " + result.nofrecords + " ");
    });
}<commit_msg>removed unnecessary js snippet<commit_after>/**
 * Created by buchholb on 3/31/15.
 */
$(document).ready(function () {
    handleStatsDisplay();
    var ind = window.location.href.indexOf("?query=");
    if (ind > 0) {
        ind += 7;
        var ccInd = window.location.href.indexOf("&cmd=clearcache");
        if (ccInd > 0) {
            $("#clear").prop("checked", true);
        }
        var sInd = window.location.href.indexOf("&send=");
        if (sInd > 0) {
            if (ccInd <= 0 || ccInd > sInd) {
                ccInd = sInd;
            }
        }
        if (ccInd > 0) {
            $("#query").val(decodeURIComponent(
                window.location.href.substr(ind, ccInd - ind)));
        } else {
            $("#query").val(decodeURIComponent(
                window.location.href.substr(ind)));
        }
        processQuery(window.location.href.substr(ind - 7));
    }
    $("#runbtn").click(function () {
        var q = encodeURIComponent($("#query").val());
        console.log(q);
        var queryString = "?query=" + q;
        if ($("#clear").prop('checked')) {
            console.log("With clearcache");
            queryString += "&cmd=clearcache";
        } else {
            console.log("Without clearcache");
        }
        queryString += "&send=100"
        var loc = window.location.href.substr(0, window.location.href.indexOf("?"));
        if (loc.length == 0) {
            loc = "index.html"
        }
        window.history.pushState("html:index.html", "QLever",
            loc + queryString);
        processQuery(queryString)
    });
    $("#csvbtn").click(function () {
        var q = encodeURIComponent($("#query").val());
        console.log(q);
        var queryString = "?query=" + q;
        if ($("#clear").prop('checked')) {
            console.log("With clearcache");
            queryString += "&cmd=clearcache";
        } else {
            console.log("Without clearcache");
        }
        processCsvQuery(queryString)
    });
    $("#tsvbtn").click(function () {
        var q = encodeURIComponent($("#query").val());
        console.log(q);
        var queryString = "?query=" + q;
        if ($("#clear").prop('checked')) {
            console.log("With clearcache");
            queryString += "&cmd=clearcache";
        } else {
            console.log("Without clearcache");
        }
        processTsvQuery(queryString)
    });
});

function htmlEscape(str) {
//    return str.replace(/&/g, "&")
//        .replace(/</g, "<")
//        .replace(/>/g, ">");
    return $("
").text(str).html();
}

function getShortStr(str, maxLength) {
    var pos;
    var cpy = str;
    if (cpy.charAt(0) == '<') {
        pos = cpy.lastIndexOf('/');
        var paraClose = cpy.lastIndexOf(')');
        if (paraClose > 0 && paraClose > pos) {
            var paraOpen = cpy.lastIndexOf('(', paraClose);
            if (paraOpen > 0 && paraOpen < pos) {
                pos = cpy.lastIndexOf('/', paraOpen);
            }
        }

        if (pos < 0) {
            pos += 1;
        }
        cpy = cpy.substring(pos + 1, cpy.length - 1);
        if (cpy.length > maxLength) {
            return cpy.substring(0, maxLength - 1) + "[...]"
        } else {
            return cpy;
        }
    }
    if (cpy.charAt(0) == '\"') {
        pos = cpy.lastIndexOf('\"');
        if (pos !== 0) {
            cpy = cpy.substring(0, pos + 1);
        }
        if (cpy.length > maxLength) {
            return cpy.substring(0, maxLength - 1) + "[...]\""
        } else {
            return cpy;
        }
    }
    return str;
}

function displayError(result) {
    var disp = "
";
    disp += "Query: " + "
" + result.query  + "
" + "
" + "
";
    disp += "Error Msg: " + "
" + result.exception + "
" + "
";
    $("#answer").html(disp);
}

function processCsvQuery(query) {
        window.location.href = "/" + query + "&action=csv_export";
        return false;
}

function processTsvQuery(query) {
    window.location.href = "/" + query + "&action=tsv_export";
    return false;
}

function processQuery(query) {
    maxSend = 0;
    var sInd = window.location.href.indexOf("&send=");
    if (sInd > 0) {
        var nextAmp = window.location.href.indexOf("&", sInd + 1);
        if (nextAmp > 0) {
            maxSend = parseInt(window.location.href.substr(sInd + 6, nextAmp - (sInd + 6)))
        } else {
            maxSend = parseInt(window.location.href.substr(sInd + 6))
        }
    }
    $.getJSON("/" + query, function (result) {
        if (result.status == "ERROR") {
            displayError(result);
            return;
        }
        var res = "
";
        // Time
        res += "
";
        res += "Number of rows: " + result.resultsize + "

";
        res += "Time elapsed:
";
        res += "Total: " + result.time.total + "
";
        res += " - Computation: " + result.time.computeResult + "
";
        res += " - Resolving entity names + excerpts and creating JSON: "
            + (parseInt(result.time.total.replace(/ms/, ""))
            - parseInt(result.time.computeResult.replace(/ms/, ""))).toString()
            + "ms";
        res += "
";
        if (maxSend > 0 && maxSend < parseInt(result.resultsize)) {
            res += "
Only transmitted " + maxSend.toString()
                + " rows out of the " + result.resultsize
                + " that were computed server-side. " +
                "[show all]";
        }
        res += "
";
        for (var i = 0; i < result.res.length; i++) {
            res += "
"
            for (var j = 0; j < result.res[i].length; ++j) {
                res += ""
                    + htmlEscape(getShortStr(result.res[i][j], 26))
                    + "	";
            }
            res += "
";
        }
        res += "
";
        res += "
";
        $("#answer").html(res);
    });
}

function handleStatsDisplay() {
    $.getJSON("/?cmd=stats", function (result) {
        $("#kbname").html("KB index: " + result.kbindex + " ");
        $("#textname").html("Text index: " + result.textindex + " ");
        $("#ntriples").html("number of triples: " + result.noftriples + " ");
        $("#nrecords").html("number of text records: " + result.nofrecords + " ");
    });
}<|endoftext|>


<commit_before>/* global TrelloPowerUp */

var MAPROSOFT_ICON_WITH_TEXT_COLOR = './images/Maprosoft-logo-with-text-color.svg';
var MAPROSOFT_ICON_GRAY = './images/Maprosoft-logo-no-text-gray.svg';
var MAPROSOFT_ICON_COLOR = './images/Maprosoft-logo-no-text-color.svg';
var WHITE_ICON = './images/icon-white.svg';
var GRAY_ICON = './images/icon-gray.svg';

var CACHED_SHARED_MAP_INFO_KEY = 'cached-shared-map-info';
var TEAM_NAME_KEY = 'maprosoft-team-name';
var TEAM_TOKEN_KEY = 'maprosoft-team-token';

//var Promise = TrelloPowerUp.Promise;
//var t = TrelloPowerUp.iframe();

//var retrieveSharedMapsUrl = 'https://www.maprosoft.com/app/shared?team=demo&getSharedMapNames=yes';
//var cachedMapInfo = {};

var teamNameToKey = function(teamNameOrKey) {
  var key = teamNameOrKey;
  key = key.toLowerCase();
  key = key.replace(' ', '-');
  key = key.replace('\'', '');
  key = key.replace('"', '');
  key = key.replace('~', '');
  key = key.replace('`', '');
  key = key.replace('!', '');
  key = key.replace('@', '');
  key = key.replace('#', '');
  key = key.replace('$', '');
  key = key.replace('%', '');
  key = key.replace('^', '');
  key = key.replace('&', '');
  key = key.replace('*', '');
  key = key.replace('(', '');
  key = key.replace(')', '');
  key = key.replace('{', '');
  key = key.replace('}', '');
  key = key.replace('[', '');
  key = key.replace(']', '');
  key = key.replace(':', '');
  key = key.replace(';', '');
  key = key.replace('.', '');
  key = key.replace('<', '');
  key = key.replace('>', '');
  key = key.replace('?', '');
  key = key.replace('/', '');
  key = key.replace('\\', '');
  key = key.replace('|', '');
  key = key.replace('+', '');
  key = key.replace('=', '');
  key = key.replace('_', '');
  return key;
};

var extractSharedMapNameFromUrl = function(url) {
  var sharedMapName = 'Maprosoft shared map';
  if (url) {
    var urlParts = url.split('/');
    if (urlParts && urlParts.length) {
      sharedMapName = urlParts[urlParts.length - 1];
    }
  }
  return sharedMapName;
};

//var extractSharedMapNameFromUrl = function(url) {
//  var sharedMapName = 'Maprosoft shared map';
//  if (url) {
//    //var sharedMapUrl = 'https://www.maprosoft.com/app/shared/' + encodeTeamName + '/' + encodedSharedMapName;
//    //var queryIndex = url.indexOf('?');
//    var parser = document.createElement('a');
//    //parser.href = "https://www.maprosoft.com:3000/app/shared/team-name-or-key/shared-map-name";
//    //parser.protocol; // => "https:"
//    //parser.hostname; // => "www.maprosoft.com"
//    //parser.port;     // => "3000"
//    //parser.pathname; // => "/app/shared/team-name-or-key/shared-map-name"
//    //parser.search;   // => ""
//    //parser.hash;     // => "#hash"
//    //parser.host; // => "www.maprosoft.com:3000"
//    if (parser && parser.pathname) {
//      var pathParts = parser.pathname.split('/');
//      if (pathParts && pathParts.length) {
//        sharedMapName = pathParts[pathParts.length - 1];
//      }
//    }
//  }
//  return sharedMapName;
//};

var buildGeneralMapUrl = function(teamNameOrKey) {
  var teamKey = teamNameToKey(teamNameOrKey);
  return 'https://www.maprosoft.com/app/map?team=' + teamKey;
};

var buildGeocodeAddressUrl = function(token, address) {
  var encodedAddress = encodeURIComponent(address);
  return 'https://www.maprosoft.com/app/geocode?token=' + token + '&address=' + encodedAddress;
};

var buildUrlWithDropPin = function(teamNameOrKey, address, latitude, longitude) {
  var teamKey = teamNameToKey(teamNameOrKey);
  var encodedAddress = encodeURIComponent(address);
  return 'https://www.maprosoft.com/app/map?team=' + teamKey +
      '&dropPinTitle=' + encodedAddress +
      '&dropPinLatitude=' + latitude +
      '&dropPinLongitude=' + longitude +
      '&customLatitude=' + latitude +
      '&customLongitude=' + longitude +
      '&customZoom=16';
};

var buildRetrieveSharedMapsUrl = function(teamNameOrKey, token) {
  var teamKey = teamNameToKey(teamNameOrKey);
  return 'https://www.maprosoft.com/app/shared?team=' + teamKey + '&getSharedMapNames=yes';
};

var doGet = function(url) {
  var getPromise = new Promise(function(resolve, reject) {
    var request = new XMLHttpRequest();
    request.open('GET', url, true);
    request.onload = function() {
      try {
        if (request.status === 200) {
          var responseJson = JSON.parse(request.responseText);
          return resolve(responseJson);
        } else if (request.status === 404) {
          return reject(new Error("resource not found."));
        } else {
          return reject(new Error("Unable to load locale, status: " + request.status));
        }
      } catch(ex) {
        return reject(new Error(ex.message));
      }
    };
    request.send();
  });
  return getPromise;
};

var geocodeAddress = function(token, address) {
  var retrieveSharedMapsUrl = buildGeocodeAddressUrl(token, address);
  return doGet(retrieveSharedMapsUrl);
};

var getFreshMapInfo = function(teamName) {
  var token = null;
  var retrieveSharedMapsUrl = buildRetrieveSharedMapsUrl(teamName, token);
  return doGet(retrieveSharedMapsUrl);
};

var formatNPSUrl = function(t, url) {
  if(!/^https?:\/\/www\.nps\.gov\/[a-z]{4}\//.test(url)){
    return null;
  }
  var parkShort = /^https?:\/\/www\.nps\.gov\/([a-z]{4})\//.exec(url)[1];
  if (parkShort && parkMap[parkShort]){
    return parkMap[parkShort];
  } else{
    return null;
  }
};

//var getSharedMapInfo = function(t) {
//  return t.get('board', 'shared', CACHED_SHARED_MAP_INFO_KEY, null);
//};

var isMapLinkAttachment = function(attachment) {
  return attachment.url.indexOf('https://www.maprosoft.com/app/map') === 0 || attachment.url.indexOf('https://www.maprosoft.com/app/shared') === 0;
};

// This example is from https://developer.mozilla.org/en-US/docs/Web/Events/resize:
var optimizedResize = (function() {

  var callbacks = [],
      running = false;

  // fired on resize event
  function resize() {

    if (!running) {
      running = true;

      if (window.requestAnimationFrame) {
        window.requestAnimationFrame(runCallbacks);
      } else {
        setTimeout(runCallbacks, 66);
      }
    }

  }

  // run the actual callbacks
  function runCallbacks() {

    callbacks.forEach(function(callback) {
      callback();
    });

    running = false;
  }

  // adds callback to loop
  function addCallback(callback) {

    if (callback) {
      callbacks.push(callback);
    }

  }

  return {
    // public method to add additional callback
    addWindowResizeListener: function(callback) {
      if (!callbacks.length) {
        window.addEventListener('resize', resize);
      }
      addCallback(callback);
    }
  }
}());

<commit_msg>update<commit_after>/* global TrelloPowerUp */

var MAPROSOFT_ICON_WITH_TEXT_COLOR = './images/Maprosoft-logo-with-text-color.svg';
var MAPROSOFT_ICON_GRAY = './images/Maprosoft-logo-no-text-gray.svg';
var MAPROSOFT_ICON_COLOR = './images/Maprosoft-logo-no-text-color.svg';
var WHITE_ICON = './images/icon-white.svg';
var GRAY_ICON = './images/icon-gray.svg';

var CACHED_SHARED_MAP_INFO_KEY = 'cached-shared-map-info';
var TEAM_NAME_KEY = 'maprosoft-team-name';
var TEAM_TOKEN_KEY = 'maprosoft-team-token';

//var Promise = TrelloPowerUp.Promise;
//var t = TrelloPowerUp.iframe();

//var retrieveSharedMapsUrl = 'https://www.maprosoft.com/app/shared?team=demo&getSharedMapNames=yes';
//var cachedMapInfo = {};

var teamNameToKey = function(teamNameOrKey) {
  var key = teamNameOrKey;
  key = key.toLowerCase();
  key = key.replace(' ', '-');
  key = key.replace('\'', '');
  key = key.replace('"', '');
  key = key.replace('~', '');
  key = key.replace('`', '');
  key = key.replace('!', '');
  key = key.replace('@', '');
  key = key.replace('#', '');
  key = key.replace('$', '');
  key = key.replace('%', '');
  key = key.replace('^', '');
  key = key.replace('&', '');
  key = key.replace('*', '');
  key = key.replace('(', '');
  key = key.replace(')', '');
  key = key.replace('{', '');
  key = key.replace('}', '');
  key = key.replace('[', '');
  key = key.replace(']', '');
  key = key.replace(':', '');
  key = key.replace(';', '');
  key = key.replace('.', '');
  key = key.replace('<', '');
  key = key.replace('>', '');
  key = key.replace('?', '');
  key = key.replace('/', '');
  key = key.replace('\\', '');
  key = key.replace('|', '');
  key = key.replace('+', '');
  key = key.replace('=', '');
  key = key.replace('_', '');
  return key;
};

var extractSharedMapNameFromUrl = function(url) {
  var sharedMapName = 'Maprosoft shared map';
  if (url) {
    var decodedUrl = decodeURI(url);
    var urlParts = decodedUrl.split('/');
    if (urlParts && urlParts.length) {
      sharedMapName = urlParts[urlParts.length - 1];
    }
  }
  return sharedMapName;
};

//var extractSharedMapNameFromUrl = function(url) {
//  var sharedMapName = 'Maprosoft shared map';
//  if (url) {
//    //var sharedMapUrl = 'https://www.maprosoft.com/app/shared/' + encodeTeamName + '/' + encodedSharedMapName;
//    //var queryIndex = url.indexOf('?');
//    var parser = document.createElement('a');
//    //parser.href = "https://www.maprosoft.com:3000/app/shared/team-name-or-key/shared-map-name";
//    //parser.protocol; // => "https:"
//    //parser.hostname; // => "www.maprosoft.com"
//    //parser.port;     // => "3000"
//    //parser.pathname; // => "/app/shared/team-name-or-key/shared-map-name"
//    //parser.search;   // => ""
//    //parser.hash;     // => "#hash"
//    //parser.host; // => "www.maprosoft.com:3000"
//    if (parser && parser.pathname) {
//      var pathParts = parser.pathname.split('/');
//      if (pathParts && pathParts.length) {
//        sharedMapName = pathParts[pathParts.length - 1];
//      }
//    }
//  }
//  return sharedMapName;
//};

var buildGeneralMapUrl = function(teamNameOrKey) {
  var teamKey = teamNameToKey(teamNameOrKey);
  return 'https://www.maprosoft.com/app/map?team=' + teamKey;
};

var buildGeocodeAddressUrl = function(token, address) {
  var encodedAddress = encodeURIComponent(address);
  return 'https://www.maprosoft.com/app/geocode?token=' + token + '&address=' + encodedAddress;
};

var buildUrlWithDropPin = function(teamNameOrKey, address, latitude, longitude) {
  var teamKey = teamNameToKey(teamNameOrKey);
  var encodedAddress = encodeURIComponent(address);
  return 'https://www.maprosoft.com/app/map?team=' + teamKey +
      '&dropPinTitle=' + encodedAddress +
      '&dropPinLatitude=' + latitude +
      '&dropPinLongitude=' + longitude +
      '&customLatitude=' + latitude +
      '&customLongitude=' + longitude +
      '&customZoom=16';
};

var buildRetrieveSharedMapsUrl = function(teamNameOrKey, token) {
  var teamKey = teamNameToKey(teamNameOrKey);
  return 'https://www.maprosoft.com/app/shared?team=' + teamKey + '&getSharedMapNames=yes';
};

var doGet = function(url) {
  var getPromise = new Promise(function(resolve, reject) {
    var request = new XMLHttpRequest();
    request.open('GET', url, true);
    request.onload = function() {
      try {
        if (request.status === 200) {
          var responseJson = JSON.parse(request.responseText);
          return resolve(responseJson);
        } else if (request.status === 404) {
          return reject(new Error("resource not found."));
        } else {
          return reject(new Error("Unable to load locale, status: " + request.status));
        }
      } catch(ex) {
        return reject(new Error(ex.message));
      }
    };
    request.send();
  });
  return getPromise;
};

var geocodeAddress = function(token, address) {
  var retrieveSharedMapsUrl = buildGeocodeAddressUrl(token, address);
  return doGet(retrieveSharedMapsUrl);
};

var getFreshMapInfo = function(teamName) {
  var token = null;
  var retrieveSharedMapsUrl = buildRetrieveSharedMapsUrl(teamName, token);
  return doGet(retrieveSharedMapsUrl);
};

var formatNPSUrl = function(t, url) {
  if(!/^https?:\/\/www\.nps\.gov\/[a-z]{4}\//.test(url)){
    return null;
  }
  var parkShort = /^https?:\/\/www\.nps\.gov\/([a-z]{4})\//.exec(url)[1];
  if (parkShort && parkMap[parkShort]){
    return parkMap[parkShort];
  } else{
    return null;
  }
};

//var getSharedMapInfo = function(t) {
//  return t.get('board', 'shared', CACHED_SHARED_MAP_INFO_KEY, null);
//};

var isMapLinkAttachment = function(attachment) {
  return attachment.url.indexOf('https://www.maprosoft.com/app/map') === 0 || attachment.url.indexOf('https://www.maprosoft.com/app/shared') === 0;
};

// This example is from https://developer.mozilla.org/en-US/docs/Web/Events/resize:
var optimizedResize = (function() {

  var callbacks = [],
      running = false;

  // fired on resize event
  function resize() {

    if (!running) {
      running = true;

      if (window.requestAnimationFrame) {
        window.requestAnimationFrame(runCallbacks);
      } else {
        setTimeout(runCallbacks, 66);
      }
    }

  }

  // run the actual callbacks
  function runCallbacks() {

    callbacks.forEach(function(callback) {
      callback();
    });

    running = false;
  }

  // adds callback to loop
  function addCallback(callback) {

    if (callback) {
      callbacks.push(callback);
    }

  }

  return {
    // public method to add additional callback
    addWindowResizeListener: function(callback) {
      if (!callbacks.length) {
        window.addEventListener('resize', resize);
      }
      addCallback(callback);
    }
  }
}());

<|endoftext|>


<commit_before>'use strict';

const _ = require('lodash');
const Connector = require('./connector');
const { SERVER_MODE_TYPES } = require('../src/common/constants');

/**
 * 전체 포트를 검색한다.
 * 검색 조건은 hwModule 의 config 에서 설정한다.
 * pnpId, VendorName, select port 등등 이다.
 *
 * 결과의 송수신은 router 에 만들어진 함수로 보낸다.
 */
class Scanner {
    static get SCAN_INTERVAL_MILLS() {
        return 1500;
    }

    constructor(router) {
        this.router = router;
        this.serialport = require('@serialport/stream');
        this.serialport.Binding = require('@entrylabs/bindings');
    }

    async startScan(hwModule, config) {
        this.stopScan();

        this.config = config;
        this.hwModule = hwModule;
        this.slaveTimers = {};
        this.connectors = {};
        this.scanCount = 0;

        const intervalScan = () => new Promise((resolve, reject) => {
            console.log('scanning...');
            this.scan()
                .then((connector) => {
                    if (connector) {
                        resolve(connector);
                    } else {
                        if (this.scanTimer) {
                            setTimeout(() => {
                                intervalScan().then(resolve);
                            }, Scanner.SCAN_INTERVAL_MILLS);
                        }
                    }
                })
                .catch(reject);
        });

        this.scanTimer = true;
        return await intervalScan();
    };

    scan() {
        return new Promise(async (resolve, reject) => {
            if (!this.config || !this.scanTimer) {
                return;
            }

            //TODO this_com_port 가 config 에서 설정될 수도 있고,
            // renderer 에서 COM 선택한것도 여기로 들어온다.
            const { serverMode, hardware, this_com_port: selectedComPortName } = this.config;
            let { select_com_port: needCOMPortSelect } = this.config;
            const {
                scanType,
                comName: verificatedComPortNames,
                pnpId,
                type,
            } = hardware;
            let { vendor } = hardware;

            // win, mac 플랫폼에 맞는 벤더명 설정
            if (vendor && _.isPlainObject(vendor)) {
                vendor = vendor[process.platform];
            }

            // win, mac 플랫폼에 맞춰 COMPort 확인창 필요한지 설정
            if (needCOMPortSelect && _.isPlainObject(needCOMPortSelect)) {
                needCOMPortSelect = needCOMPortSelect[process.platform];
            }

            // comPort 선택지가 필요한지 체크한다. 블루투스나 클라우드 모드인경우 무조건 검사한다.
            const isComPortSelected =
                needCOMPortSelect ||
                type === 'bluetooth' ||
                serverMode === SERVER_MODE_TYPES.multi;

            try {
                const comPorts = await this.getComPortList();
                // COMPort 필요하면서, 선택이 되지 않은 경우는 선택되기 전까지 진행하지 않는다.
                if (isComPortSelected && !selectedComPortName) {
                    this.router.sendState('select_port', comPorts);
                    return;
                }

                // TODO 스캔타입 삭제했습니다. 무조건 벤더검사 검사합니다. 190322 테스트 후 코멘트삭제요망

                comPorts.forEach(async (port) => {
                    const comName = port.comName || hardware.name;

                    // config 에 입력한 특정 벤더와 겹치는지 여부
                    const isVendor = this._indexOfStringOrArray(vendor, port.manufacturer);

                    // config 에 입력한 특정 COMPortName과 겹치는지 여부
                    const isComName = this._indexOfStringOrArray(verificatedComPortNames, comName);

                    // config 에 입력한 특정 pnpId와 겹치는지 여부
                    const isPnpId = this._indexOfStringOrArray(pnpId, port.pnpId);

                    /*
                    연결동작 성사 여부 [아래
                    - 제조사, 플러그앤플레이, COMPort네임, ComPort 선택해야하고 현재 ComPort가 선택된 경우
                     */
                    if (
                        isVendor || isPnpId || isComName ||
                        (needCOMPortSelect && comName === selectedComPortName)
                    ) {
                        let connector = this.connectors[comName];
                        if (connector === undefined) {
                            connector = await this.prefareConnector(comName);
                            this.connectors[comName] = connector;
                        }
                        resolve(connector);
                    } else {
                        resolve();
                    }
                });
            } catch (e) {
                reject(e);
            }
        });
    };

    /**
     *
     * @param {string} connectedComName 연결을 성사하고자 하는 COMPort
     * @returns {Promise<Connector>}
     */
    prefareConnector(connectedComName) {
        return new Promise(async (resolve, reject) => {
            // 통신개시후 완료확인 받아 낸 후 넘기기
            // 통신개시후 펌웨어 플래싱 필요한 경우 플래그 새기고 넘기기
            const hwModule = this.hwModule;
            const { hardware } = this.config;
            const connector = new Connector(hwModule, hardware);

            try {
                await connector.open(connectedComName);
                await connector.initialize();
                this.setConnector(connector);
                this.finalizeScan(connectedComName);
                resolve(connector);
            } catch (e) {
                delete this.connectors[connectedComName];
                reject(e);
            }
        });
    }

    /**
     * arrayOrString 내에 target 이 포함되어있는지 검사한다.
     * @param {?String|?Array<String>} arrayOrString
     * @param {?String} target
     * @returns {boolean}
     */
    _indexOfStringOrArray(arrayOrString, target) {
        if (!target || !arrayOrString) {
            return false;
        }

        if (Array.isArray(arrayOrString)) {
            // arrayOrString.some((item)=>target.includes(item))
            // noinspection JSUnresolvedFunction
            return arrayOrString.some((item) => target.indexOf(item) >= 0);
        } else {
            // noinspection JSValidateTypes
            return target.indexOf(arrayOrString) >= 0;
        }
    }

    getComPortList() {
        return new Promise((resolve, reject) => {
            // noinspection JSIgnoredPromiseFromCall
            this.serialport.list((error, list) => {
                if (error) {
                    reject(error);
                } else {
                    resolve(list);
                }
            });
        });
    }

    stopScan() {
        this.config = undefined;
        this.clearTimers();
        this.closeConnectors();
    };

    clearTimers() {
        if (this.scanTimer) {
            this.scanTimer = undefined;
        }

        const slaveTimers = this.slaveTimers;
        if (slaveTimers) {
            let slaveTimer;
            for (const key in slaveTimers) {
                slaveTimer = slaveTimers[key];
                if (slaveTimer) {
                    clearInterval(slaveTimer);
                }
            }
        }
        this.slaveTimers = {};
    };

    setConnector(connector) {
        this.router.connector = connector;
        this.router.sendState('before_connect');
    };

    finalizeScan(comName) {
        if (this.connectors && comName) {
            this.connectors[comName] = undefined;
        }
        this.stopScan();
    };

    closeConnectors() {
        const connectors = this.connectors;
        if (connectors) {
            let connector;
            for (const key in connectors) {
                connector = connectors[key];
                if (connector) {
                    connector.close();
                }
            }
        }
        this.connectors = {};
    };
}

module.exports = Scanner;
<commit_msg>Scanner port 검색이 비동기로 이슈 수정<commit_after>'use strict';

const _ = require('lodash');
const Connector = require('./connector');
const { SERVER_MODE_TYPES } = require('../src/common/constants');

/**
 * 전체 포트를 검색한다.
 * 검색 조건은 hwModule 의 config 에서 설정한다.
 * pnpId, VendorName, select port 등등 이다.
 *
 * 결과의 송수신은 router 에 만들어진 함수로 보낸다.
 */
class Scanner {
    static get SCAN_INTERVAL_MILLS() {
        return 1500;
    }

    constructor(router) {
        this.router = router;
        this.serialport = require('@serialport/stream');
        this.serialport.Binding = require('@entrylabs/bindings');
    }

    async startScan(hwModule, config) {
        this.stopScan();

        this.config = config;
        this.hwModule = hwModule;
        this.slaveTimers = {};
        this.connectors = {};
        this.scanCount = 0;

        const intervalScan = () => new Promise((resolve) => {
            console.log('scanning...');
            this.scan()
                .then((connector) => {
                    if (connector) {
                        resolve(connector);
                    } else {
                        if (this.scanTimer) {
                            setTimeout(() => {
                                intervalScan().then(resolve);
                            }, Scanner.SCAN_INTERVAL_MILLS);
                        }
                    }
                })
                .catch(() => {
                    if (this.scanTimer) {
                        setTimeout(() => {
                            intervalScan().then(resolve);
                        }, Scanner.SCAN_INTERVAL_MILLS);
                    }
                });
        });

        this.scanTimer = true;
        return await intervalScan();
    };

    scan() {
        return new Promise(async (resolve, reject) => {
            if (!this.config || !this.scanTimer) {
                return;
            }

            //TODO this_com_port 가 config 에서 설정될 수도 있고,
            // renderer 에서 COM 선택한것도 여기로 들어온다.
            const { serverMode, hardware, this_com_port: selectedComPortName } = this.config;
            let { select_com_port: needCOMPortSelect } = this.config;
            const {
                scanType,
                comName: verificatedComPortNames,
                pnpId,
                type,
            } = hardware;
            let { vendor } = hardware;

            // win, mac 플랫폼에 맞는 벤더명 설정
            if (vendor && _.isPlainObject(vendor)) {
                vendor = vendor[process.platform];
            }

            // win, mac 플랫폼에 맞춰 COMPort 확인창 필요한지 설정
            if (needCOMPortSelect && _.isPlainObject(needCOMPortSelect)) {
                needCOMPortSelect = needCOMPortSelect[process.platform];
            }

            // comPort 선택지가 필요한지 체크한다. 블루투스나 클라우드 모드인경우 무조건 검사한다.
            const isComPortSelected =
                needCOMPortSelect ||
                type === 'bluetooth' ||
                serverMode === SERVER_MODE_TYPES.multi;

            try {
                const comPorts = await this.getComPortList();
                // COMPort 필요하면서, 선택이 되지 않은 경우는 선택되기 전까지 진행하지 않는다.
                if (isComPortSelected && !selectedComPortName) {
                    this.router.sendState('select_port', comPorts);
                    return;
                }

                // TODO 스캔타입 삭제했습니다. 무조건 벤더검사 검사합니다. 190322 테스트 후 코멘트삭제요망

                let selectedComName = undefined;
                comPorts.some((port) => {
                    const comName = port.comName || hardware.name;

                    // config 에 입력한 특정 벤더와 겹치는지 여부
                    const isVendor = this._indexOfStringOrArray(vendor, port.manufacturer);

                    // config 에 입력한 특정 COMPortName과 겹치는지 여부
                    const isComName = this._indexOfStringOrArray(verificatedComPortNames, comName);

                    // config 에 입력한 특정 pnpId와 겹치는지 여부
                    const isPnpId = this._indexOfStringOrArray(pnpId, port.pnpId);

                    /*
                    연결동작 성사 여부 [아래
                    - 제조사, 플러그앤플레이, COMPort네임, ComPort 선택해야하고 현재 ComPort가 선택된 경우
                     */
                    if (
                        isVendor || isPnpId || isComName ||
                        (needCOMPortSelect && comName === selectedComPortName)
                    ) {
                        selectedComName = comName;
                        return true;
                    }
                });

                if (!selectedComName) {
                    resolve();
                } else {
                    let connector = this.connectors[selectedComName];
                    if (connector === undefined) {
                        connector = await this.prefareConnector(selectedComName);
                        this.connectors[selectedComName] = connector;
                    }
                    resolve(connector);
                }
            } catch (e) {
                reject(e);
            }
        });
    };

    /**
     *
     * @param {string} connectedComName 연결을 성사하고자 하는 COMPort
     * @returns {Promise<Connector>}
     */
    prefareConnector(connectedComName) {
        return new Promise(async (resolve, reject) => {
            // 통신개시후 완료확인 받아 낸 후 넘기기
            // 통신개시후 펌웨어 플래싱 필요한 경우 플래그 새기고 넘기기
            const hwModule = this.hwModule;
            const { hardware } = this.config;
            const connector = new Connector(hwModule, hardware);

            try {
                await connector.open(connectedComName);
                await connector.initialize();
                this.setConnector(connector);
                this.finalizeScan(connectedComName);
                resolve(connector);
            } catch (e) {
                delete this.connectors[connectedComName];
                reject(e);
            }
        });
    }

    /**
     * arrayOrString 내에 target 이 포함되어있는지 검사한다.
     * @param {?String|?Array<String>} arrayOrString
     * @param {?String} target
     * @returns {boolean}
     */
    _indexOfStringOrArray(arrayOrString, target) {
        if (!target || !arrayOrString) {
            return false;
        }

        if (Array.isArray(arrayOrString)) {
            // arrayOrString.some((item)=>target.includes(item))
            // noinspection JSUnresolvedFunction
            return arrayOrString.some((item) => target.indexOf(item) >= 0);
        } else {
            // noinspection JSValidateTypes
            return target.indexOf(arrayOrString) >= 0;
        }
    }

    getComPortList() {
        return new Promise((resolve, reject) => {
            // noinspection JSIgnoredPromiseFromCall
            this.serialport.list((error, list) => {
                if (error) {
                    reject(error);
                } else {
                    resolve(list);
                }
            });
        });
    }

    stopScan() {
        this.config = undefined;
        this.clearTimers();
        this.closeConnectors();
    };

    clearTimers() {
        if (this.scanTimer) {
            this.scanTimer = undefined;
        }

        const slaveTimers = this.slaveTimers;
        if (slaveTimers) {
            let slaveTimer;
            for (const key in slaveTimers) {
                slaveTimer = slaveTimers[key];
                if (slaveTimer) {
                    clearInterval(slaveTimer);
                }
            }
        }
        this.slaveTimers = {};
    };

    setConnector(connector) {
        this.router.connector = connector;
        this.router.sendState('before_connect');
    };

    finalizeScan(comName) {
        if (this.connectors && comName) {
            this.connectors[comName] = undefined;
        }
        this.stopScan();
    };

    closeConnectors() {
        const connectors = this.connectors;
        if (connectors) {
            let connector;
            for (const key in connectors) {
                connector = connectors[key];
                if (connector) {
                    connector.close();
                }
            }
        }
        this.connectors = {};
    };
}

module.exports = Scanner;
<|endoftext|>


<commit_before>/*
全域函數庫 - DOM函式庫(片段) by a0000778
*/
function $(select){
	return arguments.length? document.getElementById(select):document.body;
}
$.tag=function(tagname, conf){
	var ele=document.createElement(tagname);
	(function(obj,conf){
		for(var k in conf){
			if('function'===typeof obj[k]) obj[k].apply(obj,conf[k]);
			else if('object'===typeof conf[k]) arguments.callee(obj[k],conf[k]);
			else obj[k]=conf[k];
		}
	})(ele,conf);
	return ele;
}
HTMLElement.prototype.$add=function(){
	var at=0;
	var ele=('string'===typeof arguments[at])? $.tag(arguments[at++],arguments[at++]):arguments[at++];
	if(arguments[at]) this.insertBefore(ele,arguments[at]);
	else this.appendChild(ele);
	return (arguments[++at]? this:ele);
}
HTMLElement.prototype.$del=function (returnParent){
	this.parentNode.removeChild(this);
	return (returnParent? this.parentNode:this);
}
HTMLElement.prototype.$attr=function(key, value){
	if(key.charAt(0)!='-'){
		if(arguments.length==1) return this.getAttribute(key);
		else this.setAttribute(key, value);
	}else this.removeAttribute(key.substring(1,key.length));
	return this;
}

if(!HTMLCollection.prototype.forEach) HTMLCollection.prototype.forEach=Array.prototype.forEach;
if(!NamedNodeMap.prototype.forEach) NamedNodeMap.prototype.forEach=Array.prototype.forEach;
if(!NodeList.prototype.forEach) NodeList.prototype.forEach=Array.prototype.forEach;

function Ajax(method, url, data, option){
	this.xhr=new XMLHttpRequest();
	this.method=method.toUpperCase();
	this.url=url;
	this.data=data? data:null;
	var option=option? option:{};
	this.option={
		'async': option.hasOwnProperty('async')? option.async:true,
		'auth': option.hasOwnProperty('auth')? {
			'user': option.auth.hasOwnProperty('user')? option.auth.user:null,
			'pass': option.auth.hasOwnProperty('pass')? option.auth.pass:null
		}:{},
		'formData': option.hasOwnProperty('formData')? formData:true
	};
}
Ajax.prototype.setHeader=function(header,value){
	this.xhr.setRequestHeader(header,value);
	return this;
}
Ajax.prototype.on=function(eventName,func){
	this.xhr.addEventListener(eventName,func.bind(this));
	return this;
}
Ajax.prototype.send=function(){
	if(this.method=='GET' || !this.option.formData){
		var data='';
		var s='';
		for(k in this.data){
			if('object'==typeof this.data[k]){
				data+=s+this.data[k].reduce(function(r,v){
					r.r+=r.s+r.k+'='+encodeURIComponent(v);
					r.s='&';
				},{'r':'','s':'','k':k+'[]'}).r;
			}else{
				data+=s+k+'='+encodeURIComponent(this.data[k]);
			}
			s='&';
		}
	}else{
		var data=new FormData();
		for(k in this.data){
			if('object'==typeof this.data[k]){
				this.data[k].forEach(function(k,v){
					this.append(k+'[]',v);
				}.bind(data,k));
			}else{
				data.append(k,this.data[k]);
			}
		}
	}
	this.xhr.open(
		this.method,
		this.url+(this.method=='GET'? (this.url.indexOf('?')>=0? '&':'?')+data:''),
		this.option.async,
		this.option.auth.user,
		this.option.auth.pass
	);
	this.xhr.send(this.method=='GET'? null:data);
	return this;
}
Ajax.prototype.abort=function(){
	this.xhr.abort();
	return this;
}
Ajax.prototype.result=function(forceText){
	if(this.xhr.readyState!==4) return null;
	return forceText? this.xhr.responseText:this.xhr.response;
}
Ajax.prototype.resultHeader=function(select){
	if(this.xhr.readyState!==4) return null;
	if(select) return this.xhr.getResponseHeader(select);
	else return this.xhr.getAllResponseHeaders().split('\n').reduce(function(r,v){
		if(!v.length) return r;
		var s=v.indexOf(':');
		r[v.substring(0,s)]=v.substring(s+2,v.length);
		return r;
	},{});
}
function ajaxget(url,data){
	return new Ajax('GET',url,data,{'async': false}).send().result();
}
function ajaxpost(url,data){
	return new Ajax('POST',url,data,{'async': false}).send().result();
}<commit_msg>fix: Ajax函式庫的BUG<commit_after>/*
全域函數庫 - DOM函式庫(片段) by a0000778
*/
function $(select){
	return arguments.length? document.getElementById(select):document.body;
}
$.tag=function(tagname, conf){
	var ele=document.createElement(tagname);
	(function(obj,conf){
		for(var k in conf){
			if('function'===typeof obj[k]) obj[k].apply(obj,conf[k]);
			else if('object'===typeof conf[k]) arguments.callee(obj[k],conf[k]);
			else obj[k]=conf[k];
		}
	})(ele,conf);
	return ele;
}
HTMLElement.prototype.$add=function(){
	var at=0;
	var ele=('string'===typeof arguments[at])? $.tag(arguments[at++],arguments[at++]):arguments[at++];
	if(arguments[at]) this.insertBefore(ele,arguments[at]);
	else this.appendChild(ele);
	return (arguments[++at]? this:ele);
}
HTMLElement.prototype.$del=function (returnParent){
	this.parentNode.removeChild(this);
	return (returnParent? this.parentNode:this);
}
HTMLElement.prototype.$attr=function(key, value){
	if(key.charAt(0)!='-'){
		if(arguments.length==1) return this.getAttribute(key);
		else this.setAttribute(key, value);
	}else this.removeAttribute(key.substring(1,key.length));
	return this;
}

if(!HTMLCollection.prototype.forEach) HTMLCollection.prototype.forEach=Array.prototype.forEach;
if(!NamedNodeMap.prototype.forEach) NamedNodeMap.prototype.forEach=Array.prototype.forEach;
if(!NodeList.prototype.forEach) NodeList.prototype.forEach=Array.prototype.forEach;

function Ajax(method, url, data, option){
	this.xhr=new XMLHttpRequest();
	this.method=method.toUpperCase();
	this.url=url;
	this.data=data? data:null;
	var option=option? option:{};
	this.option={
		'async': option.hasOwnProperty('async')? option.async:true,
		'auth': option.hasOwnProperty('auth')? {
			'user': option.auth.hasOwnProperty('user')? option.auth.user:null,
			'pass': option.auth.hasOwnProperty('pass')? option.auth.pass:null
		}:{},
		'formData': option.hasOwnProperty('formData')? option.formData:true
	};
}
Ajax.prototype.setHeader=function(header,value){
	this.xhr.setRequestHeader(header,value);
	return this;
}
Ajax.prototype.on=function(eventName,func){
	this.xhr.addEventListener(eventName,func.bind(this));
	return this;
}
Ajax.prototype.send=function(){
	if(this.method=='GET' || !this.option.formData){
		var data='';
		var s='';
		for(k in this.data){
			if('object'==typeof this.data[k]){
				data+=s+this.data[k].reduce(function(r,v){
					r.r+=r.s+r.k+'='+encodeURIComponent(v);
					r.s='&';
				},{'r':'','s':'','k':k+'[]'}).r;
			}else{
				data+=s+k+'='+encodeURIComponent(this.data[k]);
			}
			s='&';
		}
	}else{
		var data=new FormData();
		for(k in this.data){
			if('object'==typeof this.data[k]){
				this.data[k].forEach(function(k,v){
					this.append(k+'[]',v);
				}.bind(data,k));
			}else{
				data.append(k,this.data[k]);
			}
		}
	}
	this.xhr.open(
		this.method,
		this.url+(this.method=='GET'? (this.url.indexOf('?')>=0? '&':'?')+data:''),
		this.option.async,
		this.option.auth.user,
		this.option.auth.pass
	);
	this.xhr.send(this.method=='GET'? null:data);
	return this;
}
Ajax.prototype.abort=function(){
	this.xhr.abort();
	return this;
}
Ajax.prototype.result=function(forceText){
	if(this.xhr.readyState!==4) return null;
	return forceText? this.xhr.responseText:this.xhr.response;
}
Ajax.prototype.resultHeader=function(select){
	if(this.xhr.readyState!==4) return null;
	if(select) return this.xhr.getResponseHeader(select);
	else return this.xhr.getAllResponseHeaders().split('\n').reduce(function(r,v){
		if(!v.length) return r;
		var s=v.indexOf(':');
		r[v.substring(0,s)]=v.substring(s+2,v.length);
		return r;
	},{});
}
function ajaxget(url,data){
	return new Ajax('GET',url,data,{'async': false}).send().result();
}
function ajaxpost(url,data){
	return new Ajax('POST',url,data,{'async': false}).send().result();
}<|endoftext|>


<commit_before>/* globals Promise:true */

var _ = require('lodash')
var bitcore = require('bitcore')
var inherits = require('util').inherits
var Promise = require('bluebird')
var timers = require('timers')

var Address = bitcore.Address
var Hash = bitcore.crypto.Hash

var config = require('../../lib/config')
var logger = require('../../lib/logger').logger
var messages = require('../../lib/messages').default()
var storage = require('../../lib/storage').default()
var bitcoind = require('./bitcoind').default()
var db = require('./db').default()
var slaves = require('./slaves').default()

/**
 * @class Master
 */
function Master () {
  /* @todo save to db */
  /* @todo not notify if syncing */
  this.isSyncing = true
}

/**
 * @return {Promise}
 */
Master.prototype.init = function () {
  var self = this

  slaves.on('sendTx', function (id, rawtx) {
    bitcoind.sendTx(rawtx)
      .then(function () { return })
      .catch(function (err) {
        if (err instanceof Error) {
          return {code: null, message: err.message}
        }

        return err
      })
      .then(function (ret) {
        return storage.execute(function (client) {
          return slaves.sendTxResponse(client, id, ret)
        })
      })
  })

  function once () {
    var st = Date.now()
    return Promise.all([db.getLatest(), bitcoind.getLatest()])
      .spread(function (latest, bitcoindLatest) {
        if (latest.blockid !== bitcoindLatest.blockid) {
          return self.catchUp().then(function () { self.isSyncing = false })
        }

        return self.updateMempool()
      })
      .finally(function () {
        var et = Date.now() - st
        var delay = config.get('chromanode.updateInterval') - et
        setTimeout(once, Math.max(0, delay))
      })
  }

  return bitcoind.init()
    .then(function () { return storage.init() })
    .then(function () { return messages.init() })
    .then(function () { return slaves.init() })
    .then(function () { return db.getLatest() })
    .then(function (latest) {
      logger.info('Start from %d (blockId: %s)', latest.height, latest.blockid)
      timers.setImmediate(once)
    })
}

/**
 * @param {pg.Client} client
 * @param {bitcore.Transaction[]} transactions
 * @param {number} [height] `undefined` for transactions in mempool
 * @return {Promise}
 */
Master.prototype.storeTransactions = function (client, transactions, height) {
  var queries = {
    blockchain: {
      storeTx: 'INSERT INTO transactions (txid, tx, height) VALUES ($1, $2, $3)',
      storeIn: 'INSERT INTO history' +
               '  (address, txid, index, prevtxid, outputindex, value, height)' +
               '  VALUES ($1, $2, $3, $4, $5, NULL, $6)',
      storeOut: 'INSERT INTO history' +
                '  (address, txid, index, prevtxid, outputindex, value, height)' +
                '  VALUES ($1, $2, $3, NULL, NULL, $4, $5)'
    },
    mempool: {
      storeTx: 'INSERT INTO transactions_mempool (txid, tx) VALUES ($1, $2)',
      storeIn: 'INSERT INTO history_mempool' +
               '  (address, txid, index, prevtxid, outputindex, value)' +
               '  VALUES ($1, $2, $3, $4, $5, NULL)',
      storeOut: 'INSERT INTO history_mempool' +
                '  (address, txid, index, prevtxid, outputindex, value)' +
                '  VALUES ($1, $2, $3, NULL, NULL, $4)'
    }
  }

  var selectAddresses = [
    'SELECT address FROM history WHERE txid = $1 AND index = $2',
    'SELECT address FROM history_mempool WHERE txid = $1 AND index = $2'
  ]

  var network = bitcore.Networks.get(config.get('chromanode.network'))
  var indexedTransactions = _.indexBy(transactions, 'id')
  var isMempool = height === undefined
  queries = isMempool ? queries.mempool : queries.blockchain

  function saveTx (tx) {
    var params = ['\\x' + tx.id, '\\x' + tx.toString()]
    if (!isMempool) { params.push(height) }
    return client.queryAsync(queries.storeTx, params)
  }

  function getInScriptAddresses (script, txid, outindex) {
    if (script.isPublicKeyHashIn()) {
      var hash = Hash.sha256ripemd160(script.chunks[1].buf)
      return Promise.resolve([new Address(hash, network, Address.PayToPublicKeyHash).toString()])
    }

    // first check current block
    var tx = indexedTransactions[txid]
    if (tx !== undefined) {
      var addresses = getOutScriptAddresses(tx.outputs[outindex].script)
      return Promise.resolve(_.invoke(addresses, 'toString'))
    }

    /* @todo If sync is finished check mempool first! */
    // load from storage
    var params = ['\\x' + txid, outindex]
    return client.queryAsync(selectAddresses[0], params)
      .then(function (res) {
        if (res.rows.length > 0) {
          return _.pluck(res.rows, 'address')
        }

        return client.queryAsync(selectAddresses[1], params)
          .then(function (res) {
            return _.pluck(res.rows, 'address')
          })
      })
  }

  function saveInputs (tx) {
    var txid = tx.id
    var promise = Promise.resolve()
    tx.inputs.forEach(function (input, index) {
      var prevTxId = input.prevTxId.toString('hex')
      if (prevTxId === '0000000000000000000000000000000000000000000000000000000000000000' &&
          input.outputIndex === 0xffffffff) {
        return
      }

      var params = ['\\x' + txid, index, '\\x' + prevTxId, input.outputIndex]
      if (!isMempool) { params.push(height) }

      promise = promise.then(function () {
        return getInScriptAddresses(input.script, prevTxId, input.outputIndex)
          .then(function (addresses) {
            return Promise.all(addresses.map(function (address) {
                return [
                  slaves.addressTouched(client, address, txid),
                  client.queryAsync(queries.storeIn, [address].concat(params))
                ]
            }))
          })
        })
    })
    return promise
  }

  function getOutScriptAddresses (script) {
    if (script.isPublicKeyHashOut() && script.chunks[2].len === 20) {
      return [new Address(script.chunks[2].buf, network, Address.PayToPublicKeyHash)]
    }

    if (script.isScriptHashOut()) {
      return [new Address(script.chunks[1].buf, network, Address.PayToScriptHash)]
    }

    if (script.isMultisigOut()) {
      return script.chunks.slice(1, -2).map(function (chunk) {
        var hash = Hash.sha256ripemd160(chunk.buf)
        return new Address(hash, network, Address.PayToPublicKeyHash)
      })
    }

    if (script.isPublicKeyOut()) {
      var hash = Hash.sha256ripemd160(script.chunks[0].buf)
      return [new Address(hash, network, Address.PayToPublicKeyHash)]
    }

    return []
  }

  function saveOutputs (tx) {
    var txid = tx.id
    var promise = Promise.resolve()
    tx.outputs.forEach(function (output, index) {
      // script validation
      try { output.script } catch (e) { return }

      var params = ['\\x' + txid, index, output.satoshis]
      if (!isMempool) { params.push(height) }

      promise = promise.then(function () {
        return Promise.all(_.flattenDeep(
          getOutScriptAddresses(output.script).map(function (address) {
            address = address.toString()
            return [
              slaves.addressTouched(client, address, txid),
              client.queryAsync(queries.storeOut, [address].concat(params))
            ]
          })))
        })
    })
    return promise
  }

  return Promise.all(transactions.map(function (tx) {
    return saveTx(tx),then(function () {
      return saveInputs(tx)
    }).then(function () {
      return saveOutputs(tx)
    })
  }))
}

function SyncComplete () {}
inherits(SyncComplete, Error)

function ReorgFound () {}
inherits(ReorgFound, Error)

/**
 * @return {Promise}
 */
Master.prototype.catchUp = function () {
  var self = this
  var deferred = Promise.defer()
  var mempoolTruncated = false

  function tryTruncateMempool (client) {
    if (mempoolTruncated) {
      return Promise.resolve()
    }

    mempoolTruncated = true
    return client.queryAsync('TRUNCATE transactions_mempool, history_mempool')
  }

  function runReorg (height) {
    logger.warn('Reorg to height: %d', height)
    return storage.executeTransaction(function (client) {
      return Promise.all([
        client.queryAsync('DELETE FROM blocks WHERE height > $1', [height]),
        client.queryAsync('DELETE FROM transactions WHERE height > $1', [height]),
        client.queryAsync('DELETE FROM history WHERE height > $1', [height]),
        tryTruncateMempool(client)
      ])
    })
    .then(function () { throw new ReorgFound() })
  }

  function once () {
    return Promise.all([db.getLatest(), bitcoind.getLatest()])
      .spread(function (latest, bitcoindLatest) {
        if (latest.blockid === bitcoindLatest.blockid) {
          throw new SyncComplete()
        }

        // get block from bitcoind
        return Promise.all([latest, bitcoind.getBlock(latest.height + 1)])
      })
      .spread(function (latest, block) {
        // reorg check
        var prevHash = new Buffer(block.header.prevHash)
        var prevBlockid = Array.prototype.reverse.call(prevHash).toString('hex')
        if (latest.blockid !== prevBlockid) {
          return runReorg(latest.height - 1)
        }

        var height = latest.height + 1
        return storage.executeTransaction(function (client) {
          var blockQuery = 'INSERT INTO blocks (height, blockid, header, txids) VALUES ($1, $2, $3, $4)'
          var blockValues = [
            height,
            '\\x' + block.id,
            '\\x' + block.header.toString(),
            '\\x' + _.pluck(block.transactions, 'id').join('')
          ]

          return Promise.all([
            tryTruncateMempool(client),
            client.queryAsync(blockQuery, blockValues),
            slaves.newBlock(client, block.id, height),
            self.storeTransactions(client, block.transactions, height)
          ])
        })
        .then(function () {
          /* @todo show progress when syncing and this message when finished */
          logger.verbose('Import #%d (blockId: %s)', height, block.id)
        })
      })
      .catch(ReorgFound, function () {})
      .then(function () { timers.setImmediate(once) })
      .catch(SyncComplete, function () { deferred.resolve() })
      .catch(function (err) { deferred.reject(err) })
  }

  once()

  return deferred.promise
}

/**
 * @return {Promise}
 */
Master.prototype.updateMempool = function () {
  var self = this
  return storage.executeTransaction(function (client) {
    return Promise.all([
      client.queryAsync('SELECT txid FROM transactions_mempool'),
      bitcoind.getRawMemPool()
    ])
    .spread(function (sres, bres) {
      sres = _.pluck(sres.rows, 'txid').map(function (buf) {
        return buf.toString('hex')
      })
      var oldtxs = _.difference(sres, bres)

      return Promise.map(oldtxs, function (txid) {
        txid = '\\x' + txid
        return Promise.all([
          client.queryAsync('DELETE FROM transactions_mempool WHERE txid = $1', [txid]),
          client.queryAsync('DELETE FROM history_mempool WHERE txid = $1', [txid])
        ])
      })
      .then(function () {
        return bitcoind.getTransactions(_.difference(bres, sres))
      })
      .then(function (newtxs) {
        return self.storeTransactions(client, newtxs)
      })
      .then(function () {
        var diff = _.difference(bres, sres).length - oldtxs.length
        if (diff >= 0) { diff = '+' + diff.toString() }
        logger.verbose('Mempool updated... %s transactions', diff.toString())
      })
    })
  })
}

/**
 * @return {Promise}
 */
module.exports.run = function () {
  var master = new Master()
  return master.init()
}
<commit_msg>fixed typo in previous commit<commit_after>/* globals Promise:true */

var _ = require('lodash')
var bitcore = require('bitcore')
var inherits = require('util').inherits
var Promise = require('bluebird')
var timers = require('timers')

var Address = bitcore.Address
var Hash = bitcore.crypto.Hash

var config = require('../../lib/config')
var logger = require('../../lib/logger').logger
var messages = require('../../lib/messages').default()
var storage = require('../../lib/storage').default()
var bitcoind = require('./bitcoind').default()
var db = require('./db').default()
var slaves = require('./slaves').default()

/**
 * @class Master
 */
function Master () {
  /* @todo save to db */
  /* @todo not notify if syncing */
  this.isSyncing = true
}

/**
 * @return {Promise}
 */
Master.prototype.init = function () {
  var self = this

  slaves.on('sendTx', function (id, rawtx) {
    bitcoind.sendTx(rawtx)
      .then(function () { return })
      .catch(function (err) {
        if (err instanceof Error) {
          return {code: null, message: err.message}
        }

        return err
      })
      .then(function (ret) {
        return storage.execute(function (client) {
          return slaves.sendTxResponse(client, id, ret)
        })
      })
  })

  function once () {
    var st = Date.now()
    return Promise.all([db.getLatest(), bitcoind.getLatest()])
      .spread(function (latest, bitcoindLatest) {
        if (latest.blockid !== bitcoindLatest.blockid) {
          return self.catchUp().then(function () { self.isSyncing = false })
        }

        return self.updateMempool()
      })
      .finally(function () {
        var et = Date.now() - st
        var delay = config.get('chromanode.updateInterval') - et
        setTimeout(once, Math.max(0, delay))
      })
  }

  return bitcoind.init()
    .then(function () { return storage.init() })
    .then(function () { return messages.init() })
    .then(function () { return slaves.init() })
    .then(function () { return db.getLatest() })
    .then(function (latest) {
      logger.info('Start from %d (blockId: %s)', latest.height, latest.blockid)
      timers.setImmediate(once)
    })
}

/**
 * @param {pg.Client} client
 * @param {bitcore.Transaction[]} transactions
 * @param {number} [height] `undefined` for transactions in mempool
 * @return {Promise}
 */
Master.prototype.storeTransactions = function (client, transactions, height) {
  var queries = {
    blockchain: {
      storeTx: 'INSERT INTO transactions (txid, tx, height) VALUES ($1, $2, $3)',
      storeIn: 'INSERT INTO history' +
               '  (address, txid, index, prevtxid, outputindex, value, height)' +
               '  VALUES ($1, $2, $3, $4, $5, NULL, $6)',
      storeOut: 'INSERT INTO history' +
                '  (address, txid, index, prevtxid, outputindex, value, height)' +
                '  VALUES ($1, $2, $3, NULL, NULL, $4, $5)'
    },
    mempool: {
      storeTx: 'INSERT INTO transactions_mempool (txid, tx) VALUES ($1, $2)',
      storeIn: 'INSERT INTO history_mempool' +
               '  (address, txid, index, prevtxid, outputindex, value)' +
               '  VALUES ($1, $2, $3, $4, $5, NULL)',
      storeOut: 'INSERT INTO history_mempool' +
                '  (address, txid, index, prevtxid, outputindex, value)' +
                '  VALUES ($1, $2, $3, NULL, NULL, $4)'
    }
  }

  var selectAddresses = [
    'SELECT address FROM history WHERE txid = $1 AND index = $2',
    'SELECT address FROM history_mempool WHERE txid = $1 AND index = $2'
  ]

  var network = bitcore.Networks.get(config.get('chromanode.network'))
  var indexedTransactions = _.indexBy(transactions, 'id')
  var isMempool = height === undefined
  queries = isMempool ? queries.mempool : queries.blockchain

  function saveTx (tx) {
    var params = ['\\x' + tx.id, '\\x' + tx.toString()]
    if (!isMempool) { params.push(height) }
    return client.queryAsync(queries.storeTx, params)
  }

  function getInScriptAddresses (script, txid, outindex) {
    if (script.isPublicKeyHashIn()) {
      var hash = Hash.sha256ripemd160(script.chunks[1].buf)
      return Promise.resolve([new Address(hash, network, Address.PayToPublicKeyHash).toString()])
    }

    // first check current block
    var tx = indexedTransactions[txid]
    if (tx !== undefined) {
      var addresses = getOutScriptAddresses(tx.outputs[outindex].script)
      return Promise.resolve(_.invoke(addresses, 'toString'))
    }

    /* @todo If sync is finished check mempool first! */
    // load from storage
    var params = ['\\x' + txid, outindex]
    return client.queryAsync(selectAddresses[0], params)
      .then(function (res) {
        if (res.rows.length > 0) {
          return _.pluck(res.rows, 'address')
        }

        return client.queryAsync(selectAddresses[1], params)
          .then(function (res) {
            return _.pluck(res.rows, 'address')
          })
      })
  }

  function saveInputs (tx) {
    var txid = tx.id
    var promise = Promise.resolve()
    tx.inputs.forEach(function (input, index) {
      var prevTxId = input.prevTxId.toString('hex')
      if (prevTxId === '0000000000000000000000000000000000000000000000000000000000000000' &&
          input.outputIndex === 0xffffffff) {
        return
      }

      var params = ['\\x' + txid, index, '\\x' + prevTxId, input.outputIndex]
      if (!isMempool) { params.push(height) }

      promise = promise.then(function () {
        return getInScriptAddresses(input.script, prevTxId, input.outputIndex)
          .then(function (addresses) {
            return Promise.all(addresses.map(function (address) {
                return [
                  slaves.addressTouched(client, address, txid),
                  client.queryAsync(queries.storeIn, [address].concat(params))
                ]
            }))
          })
        })
    })
    return promise
  }

  function getOutScriptAddresses (script) {
    if (script.isPublicKeyHashOut() && script.chunks[2].len === 20) {
      return [new Address(script.chunks[2].buf, network, Address.PayToPublicKeyHash)]
    }

    if (script.isScriptHashOut()) {
      return [new Address(script.chunks[1].buf, network, Address.PayToScriptHash)]
    }

    if (script.isMultisigOut()) {
      return script.chunks.slice(1, -2).map(function (chunk) {
        var hash = Hash.sha256ripemd160(chunk.buf)
        return new Address(hash, network, Address.PayToPublicKeyHash)
      })
    }

    if (script.isPublicKeyOut()) {
      var hash = Hash.sha256ripemd160(script.chunks[0].buf)
      return [new Address(hash, network, Address.PayToPublicKeyHash)]
    }

    return []
  }

  function saveOutputs (tx) {
    var txid = tx.id
    var promise = Promise.resolve()
    tx.outputs.forEach(function (output, index) {
      // script validation
      try { output.script } catch (e) { return }

      var params = ['\\x' + txid, index, output.satoshis]
      if (!isMempool) { params.push(height) }

      promise = promise.then(function () {
        return Promise.all(_.flattenDeep(
          getOutScriptAddresses(output.script).map(function (address) {
            address = address.toString()
            return [
              slaves.addressTouched(client, address, txid),
              client.queryAsync(queries.storeOut, [address].concat(params))
            ]
          })))
        })
    })
    return promise
  }

  return Promise.all(transactions.map(function (tx) {
    return saveTx(tx).then(function () {
      return saveInputs(tx)
    }).then(function () {
      return saveOutputs(tx)
    })
  }))
}

function SyncComplete () {}
inherits(SyncComplete, Error)

function ReorgFound () {}
inherits(ReorgFound, Error)

/**
 * @return {Promise}
 */
Master.prototype.catchUp = function () {
  var self = this
  var deferred = Promise.defer()
  var mempoolTruncated = false

  function tryTruncateMempool (client) {
    if (mempoolTruncated) {
      return Promise.resolve()
    }

    mempoolTruncated = true
    return client.queryAsync('TRUNCATE transactions_mempool, history_mempool')
  }

  function runReorg (height) {
    logger.warn('Reorg to height: %d', height)
    return storage.executeTransaction(function (client) {
      return Promise.all([
        client.queryAsync('DELETE FROM blocks WHERE height > $1', [height]),
        client.queryAsync('DELETE FROM transactions WHERE height > $1', [height]),
        client.queryAsync('DELETE FROM history WHERE height > $1', [height]),
        tryTruncateMempool(client)
      ])
    })
    .then(function () { throw new ReorgFound() })
  }

  function once () {
    return Promise.all([db.getLatest(), bitcoind.getLatest()])
      .spread(function (latest, bitcoindLatest) {
        if (latest.blockid === bitcoindLatest.blockid) {
          throw new SyncComplete()
        }

        // get block from bitcoind
        return Promise.all([latest, bitcoind.getBlock(latest.height + 1)])
      })
      .spread(function (latest, block) {
        // reorg check
        var prevHash = new Buffer(block.header.prevHash)
        var prevBlockid = Array.prototype.reverse.call(prevHash).toString('hex')
        if (latest.blockid !== prevBlockid) {
          return runReorg(latest.height - 1)
        }

        var height = latest.height + 1
        return storage.executeTransaction(function (client) {
          var blockQuery = 'INSERT INTO blocks (height, blockid, header, txids) VALUES ($1, $2, $3, $4)'
          var blockValues = [
            height,
            '\\x' + block.id,
            '\\x' + block.header.toString(),
            '\\x' + _.pluck(block.transactions, 'id').join('')
          ]

          return Promise.all([
            tryTruncateMempool(client),
            client.queryAsync(blockQuery, blockValues),
            slaves.newBlock(client, block.id, height),
            self.storeTransactions(client, block.transactions, height)
          ])
        })
        .then(function () {
          /* @todo show progress when syncing and this message when finished */
          logger.verbose('Import #%d (blockId: %s)', height, block.id)
        })
      })
      .catch(ReorgFound, function () {})
      .then(function () { timers.setImmediate(once) })
      .catch(SyncComplete, function () { deferred.resolve() })
      .catch(function (err) { deferred.reject(err) })
  }

  once()

  return deferred.promise
}

/**
 * @return {Promise}
 */
Master.prototype.updateMempool = function () {
  var self = this
  return storage.executeTransaction(function (client) {
    return Promise.all([
      client.queryAsync('SELECT txid FROM transactions_mempool'),
      bitcoind.getRawMemPool()
    ])
    .spread(function (sres, bres) {
      sres = _.pluck(sres.rows, 'txid').map(function (buf) {
        return buf.toString('hex')
      })
      var oldtxs = _.difference(sres, bres)

      return Promise.map(oldtxs, function (txid) {
        txid = '\\x' + txid
        return Promise.all([
          client.queryAsync('DELETE FROM transactions_mempool WHERE txid = $1', [txid]),
          client.queryAsync('DELETE FROM history_mempool WHERE txid = $1', [txid])
        ])
      })
      .then(function () {
        return bitcoind.getTransactions(_.difference(bres, sres))
      })
      .then(function (newtxs) {
        return self.storeTransactions(client, newtxs)
      })
      .then(function () {
        var diff = _.difference(bres, sres).length - oldtxs.length
        if (diff >= 0) { diff = '+' + diff.toString() }
        logger.verbose('Mempool updated... %s transactions', diff.toString())
      })
    })
  })
}

/**
 * @return {Promise}
 */
module.exports.run = function () {
  var master = new Master()
  return master.init()
}
<|endoftext|>