<commit_before>/**
 * Wrapper service for common sharing actions.
 */

var angular = require('angular');
var sprintf = require('sprintf');

var shareSettings = require('../../build/app_settings')['SHARING'];

var SharingService = function () {

  var DEFAULT_SHARE_APP_MSG = 'Download EFF\'s mobile app and get instant alerts when they need your help';

  return {

    shareApp: function (service) {

      var shareURL = shareSettings['URL'];
      var shareMessage = shareSettings['MESSAGES'][service] || DEFAULT_SHARE_APP_MSG;

      if (angular.isObject(shareMessage)) {
        angular.forEach(shareMessage, function (val, key) {
          shareMessage[key] = sprintf(val, shareURL);
        });
      } else {
        shareMessage = sprintf(shareMessage, shareURL);
      }

      if (service === 'EMAIL') {
        var title = shareMessage['SUBJECT'] || 'Download EFF\'s Android app';
        var body = shareMessage['BODY'] || DEFAULT_SHARE_APP_MSG;

        // On Android this opens up as either Drive or Gmail, and doesn't work for Drive.
        // This doesn't seem like a huge deal, so for now allow it so that it pops up a share intent
        // filtered for email clients.
        window.plugins.socialsharing.shareViaEmail(body, title, null, null, null, null);
      } else if (service === 'SMS') {
        window.plugins.socialsharing.shareViaSMS(shareMessage, undefined);
      } else if (service === 'FACEBOOK') {
        // The message isn't passed through correctly via FB on Android unfortunately, link is though
        window.plugins.socialsharing.shareViaFacebook(shareMessage, undefined, shareURL);
      } else if (service === 'TWITTER') {
        window.plugins.socialsharing.shareViaTwitter(shareMessage, undefined, shareURL);
      } else if (service === 'OTHER') {
        // Open up a standard share intent:
        window.plugins.socialsharing.share(shareMessage, null, null, shareURL);
      }
    },

    shareAppAsAction: function () {
      return {
        id: 'SHARE_ACTION',
        link: {
          _href: shareSettings['URL']
        },
        summary: {
          __text: shareSettings['TEXT']
        },
        title: shareSettings['TITLE']
      };
    },

    shareAction: function (action, service) {
      var shareURL = action.link._href;
      var shareMessage = action.summary.__text;

      if (service === 'EMAIL') {
        var title = action.title;
        var body = action.summary.__text;

        // On Android this opens up as either Drive or Gmail, and doesn't work for Drive.
        // This doesn't seem like a huge deal, so for now allow it so that it pops up a share intent
        // filtered for email clients.
        window.plugins.socialsharing.shareViaEmail(body, title, null, null, null, null);
      } else if (service === 'SMS') {
        window.plugins.socialsharing.shareViaSMS(shareMessage, undefined);
      } else if (service === 'FACEBOOK') {
        // The message isn't passed through correctly via FB on Android unfortunately, link is though
        window.plugins.socialsharing.shareViaFacebook(shareMessage, undefined, shareURL);
      } else if (service === 'TWITTER') {
        window.plugins.socialsharing.shareViaTwitter(shareMessage, undefined, shareURL);
      } else if (service === 'OTHER') {
        // Open up a standard share intent:
        window.plugins.socialsharing.share(shareMessage, null, null, shareURL);
      }
    },

  };

};

module.exports = SharingService;
<commit_msg>fixes #47 Sharing actions from actions tab is borked in various ways<commit_after>/**
 * Wrapper service for common sharing actions.
 */

var angular = require('angular');
var sprintf = require('sprintf');

var shareSettings = require('../../build/app_settings')['SHARING'];

var SharingService = function () {

  var DEFAULT_SHARE_APP_MSG = 'Download EFF\'s mobile app and get instant alerts when they need your help';

  return {

    shareApp: function (service) {

      var shareURL = shareSettings['URL'];
      var shareMessage = shareSettings['MESSAGES'][service] || DEFAULT_SHARE_APP_MSG;

      if (angular.isObject(shareMessage)) {
        angular.forEach(shareMessage, function (val, key) {
          shareMessage[key] = sprintf(val, shareURL);
        });
      } else {
        shareMessage = sprintf(shareMessage, shareURL);
      }

      if (service === 'EMAIL') {
        var title = shareMessage['SUBJECT'] || 'Download EFF\'s Android app';
        var body = shareMessage['BODY'] || DEFAULT_SHARE_APP_MSG;

        // On Android this opens up as either Drive or Gmail, and doesn't work for Drive.
        // This doesn't seem like a huge deal, so for now allow it so that it pops up a share intent
        // filtered for email clients.
        window.plugins.socialsharing.shareViaEmail(body, title, null, null, null, null);
      } else if (service === 'SMS') {
        window.plugins.socialsharing.shareViaSMS(shareMessage, undefined);
      } else if (service === 'FACEBOOK') {
        // The message isn't passed through correctly via FB on Android unfortunately, link is though
        window.plugins.socialsharing.shareViaFacebook(shareMessage, undefined, shareURL);
      } else if (service === 'TWITTER') {
        window.plugins.socialsharing.shareViaTwitter(shareMessage, undefined, shareURL);
      } else if (service === 'OTHER') {
        // Open up a standard share intent:
        window.plugins.socialsharing.share(shareMessage, null, null, shareURL);
      }
    },

    shareAppAsAction: function () {
      return {
        id: 'SHARE_ACTION',
        link: {
          _href: shareSettings['URL']
        },
        summary: {
          __text: shareSettings['TEXT']
        },
        title: shareSettings['TITLE']
      };
    },

    shareAction: function (action, service) {
      var shareURL = action.link._href;
      var shareMessage = angular.element(action.summary.__text).text();
      var title = action.title;

      if (service === 'EMAIL') {
        var body = shareMessage + "\n\n" + shareURL;

        // On Android this opens up as either Drive or Gmail, and doesn't work for Drive.
        // This doesn't seem like a huge deal, so for now allow it so that it pops up a share intent
        // filtered for email clients.
        window.plugins.socialsharing.shareViaEmail(body, title, null, null, null, null);
      } else if (service === 'SMS') {
        window.plugins.socialsharing.shareViaSMS(title + ' ' + shareURL, undefined);
      } else if (service === 'FACEBOOK') {
        // The message isn't passed through correctly via FB on Android unfortunately, link is though
        window.plugins.socialsharing.shareViaFacebook(shareMessage, undefined, shareURL);
      } else if (service === 'TWITTER') {
        window.plugins.socialsharing.shareViaTwitter(title, undefined, shareURL);
      } else if (service === 'OTHER') {
        // Open up a standard share intent:
        window.plugins.socialsharing.share(title, null, null, shareURL);
      }
    },

  };

};

module.exports = SharingService;
<|endoftext|>


<commit_before>// ==UserScript==
// @name        Highlight Short Answer
// @namespace   https://github.com/jamesjonesmath/canvancement
// @description Highlights fill in the blank questions that are not correct in the one-at-a-time navigation links
// @include     https://*.instructure.com/courses/*/quizzes/*/history?*
// @version     1
// @grant none
// ==/UserScript==
(function() {
  'use strict';
  if (/^\/courses\/[0-9]+\/quizzes\/[0-9]+\/history$/.test(window.location.pathname)) {
    // Change your colors here. You can also use names like 'white' and 'black'
    var backgroundColor = '#900';
    var foregroundColor = '#eee';
    highlightWrong(backgroundColor, foregroundColor);
  }
  function highlightWrong(bgColor, fgColor) {
    var checkQuestionTypes = [ 'short_answer_question', 'fill_in_multiple_blanks_question' ];
    try {
      var nList = document.querySelectorAll('div#quiz-nav-inner-wrapper ul li');
      if (nList.length === 0) {
        return;
      }
      var qList = document.querySelectorAll('div#questions div.question.display_question');
      var qRegex = new RegExp('^question_([0-9]+)$');
      for (var i = 0; i < qList.length; i++) {
        var match = qRegex.exec(qList[i].id);
        if (match) {
          if (qList[i].classList.contains('correct')) {
            continue;
          }
          var questionId = match[1];
          var navItem = document.getElementById('quiz_nav_' + questionId);
          if (navItem) {
            var process = false;
            for (var j = 0; j < checkQuestionTypes.length; j++) {
              if (qList[i].classList.contains(checkQuestionTypes[j])) {
                process = true;
                break;
              }
            }
            if (process) {
              var nItem = navItem.querySelector('a.question-nav-link');
              nItem.style.backgroundColor = (typeof bgColor === 'undefined') ? 'black' : bgColor;
              nItem.style.color = (typeof fgColor === 'undefined') ? 'white' : fgColor;
            }
          }
        }
      }
    } catch (e) {
      console.log(e);
    }
  }
})();
<commit_msg>Look for "essay" questions<commit_after>// ==UserScript==
// @name        Highlight Short Answer
// @namespace   https://github.com/jamesjonesmath/canvancement
// @description Highlights fill in the blank questions that are not correct in the one-at-a-time navigation links
// @include     https://*.instructure.com/courses/*/quizzes/*/history?*
// @version     2
// @grant none
// ==/UserScript==
(function() {
  'use strict';
  if (!/^\/courses\/[0-9]+\/quizzes\/[0-9]+\/history$/.test(window.location.pathname)) {
    return;
  }

  // Change your colors here. You can also use names like 'white' and 'black'
  var backgroundColor = '#900';
  var foregroundColor = '#eee';
  highlightWrong(backgroundColor, foregroundColor);

  function highlightWrong(bgColor, fgColor) {
    var checkQuestionTypes = [ 'essay_question', 'short_answer_question', 'fill_in_multiple_blanks_question' ];
    var nList = document.querySelectorAll('div#quiz-nav-inner-wrapper ul li');
    if (nList.length === 0) {
      return;
    }
    var qList = document.querySelectorAll('div#questions div.question.display_question');
    var qRegex = new RegExp('^question_([0-9]+)$');
    for (var i = 0; i < qList.length; i++) {
      var question = qList[i];
      var match = qRegex.exec(question.id);
      if (!match || question.classList.contains('correct')) {
        continue;
      }
      var questionId = match[1];
      var navItem = document.getElementById('quiz_nav_' + questionId);
      var qScore = question.querySelector('input.question_input_hidden[name="question_score_' + questionId + '"]');
      if (navItem) {
        var process = checkQuestionTypes.some(function(name) {
          return question.classList.contains(name);
        });
        if (process) {
          var nItem = navItem.querySelector('a.question-nav-link');
          nItem.style.backgroundColor = (typeof bgColor === 'undefined') ? 'black' : bgColor;
          nItem.style.color = (typeof fgColor === 'undefined') ? 'white' : fgColor;
          if (qScore.value !== '') {
            nItem.style.opacity = 0.5;
          }
        }
      }
    }
  }
})();
<|endoftext|>


<commit_before><commit_msg>Fix lib content<commit_after><|endoftext|>


<commit_before>// window._ = require('lodash');

// window.$ = window.jQuery = require('jquery');
// require('bootstrap-sass');

window.Vue = require('vue');
<commit_msg>require app.scss<commit_after>// window._ = require('lodash');
// window.$ = window.jQuery = require('jquery');
// require('bootstrap-sass');

require('assets/css/app');
window.Vue = require('vue');
<|endoftext|>


<commit_before>$.fn.showChartDataEntryTanggal = function(data){
    var chart_data = data;
    Highcharts.setOptions({
        lang: {
            decimalPoint: ',',
            thousandsSep: '.'
        }
    });

    $.each(chart_data,function(user,form){
        var x = [];
        var y = [];
        $.each(form,function(index,value){
            x.push(index);
            y.push(value);
        });
        user = user.replace(" ","_");
        $('#'+user).highcharts({
            chart: {
                zoomType: 'xy',
                height: 400
            },
            title: {
                text: ''
            },
            xAxis: [{
                    categories: x
                }],
            yAxis: [{min:0,startOnTick: false},{min:0,startOnTick: false,opposite: true},{ // Primary yAxis
                labels: {
                    format: '{value}',
                    style: {
                        color: Highcharts.getOptions().colors[1]
                        }
                    },
                    title: {
                        text: 'Jumlah Entry',
                        style: {
                            color: Highcharts.getOptions().colors[1]
                        }
                    }
                }],
            tooltip: {
                shared: true
            },
            legend: {
                enabled : false
            },
            plotOptions: {
                series: {
                    borderWidth: 0,
                    dataLabels: {
                        enabled: true,
                        format: '{point.y}'
                    }
                }
            },
            series: [{
                    name: 'Jumlah Entry',
                    type: 'column',
                    data: y,
                    color: '#73c1f7',
                    tooltip: {
                        valueSuffix: ''
                    }
                }]
        });
    });

};

$.fn.showChartDataEntryMinggu = function(data){
    var chart_data = data;
    Highcharts.setOptions({
        lang: {
            decimalPoint: ',',
            thousandsSep: '.'
        }
    });

    $.each(chart_data,function(user,weeks){
        var x = ['Senin','Selasa','Rabu','Kamis','Jumat','Sabtu'];
        var y_now = [];
        var y_last = [];
        $.each(weeks,function(week,form){
            if(week=='thisweek'){
                $.each(form,function(index,value){
                    y_now.push(value);
                });
            }else{
                $.each(form,function(index,value){
                    y_last.push(value);
                });
            }

        });
        user = user.replace(" ","_");
        $('#'+user).highcharts({
            chart: {
                zoomType: 'xy',
                height: 400
            },
            title: {
                text: ''
            },
            xAxis: [{
                    categories: x
                }],
            yAxis: [{min:0,startOnTick: false},{min:0,startOnTick: false,opposite: true},{ // Primary yAxis
                labels: {
                    format: '{value}',
                    style: {
                        color: Highcharts.getOptions().colors[1]
                        }
                    },
                    title: {
                        text: 'Jumlah Entry',
                        style: {
                            color: Highcharts.getOptions().colors[1]
                        }
                    }
                }],
            tooltip: {
                shared: true
            },
            legend: {
                enabled : false
            },
            plotOptions: {
                series: {
                    borderWidth: 0,
                    dataLabels: {
                        enabled: true,
                        format: '{point.y}'
                    }
                }
            },
            series: [{
                    name: 'Minggu Ini',
                    type: 'column',
                    data: y_now,
                    color: '#0000ff',
                    tooltip: {
                        valueSuffix: ''
                    }
                },{
                    name: 'Minggu Lalu',
                    type: 'column',
                    data: y_last,
                    color: '#ff0000',
                    tooltip: {
                        valueSuffix: ''
                    }
                }]
        });
    });

};

$.fn.showChartDataEntryBulan = function(data){
    var chart_data = data;
    Highcharts.setOptions({
        lang: {
            decimalPoint: ',',
            thousandsSep: '.'
        }
    });

    $.each(chart_data,function(user,months){
        var x = ['Januari','Februari','Maret','April','Mei','Juni','Juli','Agustus','September','Oktober','November','Desember'];
        var y_now = [];
        var y_last = [];
        var thisyear = 0;
        var lastyear = 0;
        $.each(months,function(month,form){
            if(month=='thisyear'){
                $.each(form,function(index,value){
                    thisyear = index.split("-");
                    y_now.push(value);
                });
            }else{
                $.each(form,function(index,value){
                    lastyear = index.split("-");
                    y_last.push(value);
                });
            }

        });
        user = user.replace(" ","_");
        $('#'+user).highcharts({
            chart: {
                zoomType: 'xy',
                height: 400
            },
            title: {
                text: ''
            },
            xAxis: [{
                    categories: x
                }],
            yAxis: [{min:0,startOnTick: false},{min:0,startOnTick: false,opposite: true},{ // Primary yAxis
                labels: {
                    format: '{value}',
                    style: {
                        color: Highcharts.getOptions().colors[1]
                        }
                    },
                    title: {
                        text: 'Jumlah Entry',
                        style: {
                            color: Highcharts.getOptions().colors[1]
                        }
                    }
                }],
            tooltip: {
                shared: true
            },
            legend: {
                enabled : false
            },
            plotOptions: {
                series: {
                    borderWidth: 0,
                    dataLabels: {
                        enabled: true,
                        format: '{point.y}'
                    }
                }
            },
            series: [{
                    name: 'Tahun '+thisyear[0],
                    type: 'column',
                    data: y_now,
                    color: '#0000ff',
                    tooltip: {
                        valueSuffix: ''
                    }
                },{
                    name: 'Tahun '+lastyear[0],
                    type: 'column',
                    data: y_last,
                    color: '#ff0000',
                    tooltip: {
                        valueSuffix: ''
                    }
                }]
        });
    });

};

$.fn.showChartDataEntryForm = function(data){
    var chart_data = data;
    Highcharts.setOptions({
        lang: {
            decimalPoint: ',',
            thousandsSep: '.'
        }
    });

    $.each(chart_data,function(user,form){
        var x = [];
        var y = [];
        $.each(form,function(index,value){
            x.push(index);
            y.push(value);
        });
        user = user.replace(" ","_");
        $('#'+user).highcharts({
            chart: {
                zoomType: 'xy',
                height: 400
            },
            title: {
                text: ''
            },
            xAxis: [{
                    categories: x
                }],
            yAxis: [{min:0,startOnTick: false},{min:0,startOnTick: false,opposite: true},{ // Primary yAxis
                labels: {
                    format: '{value}',
                    style: {
                        color: Highcharts.getOptions().colors[1]
                        }
                    },
                    title: {
                        text: 'Jumlah Entry',
                        style: {
                            color: Highcharts.getOptions().colors[1]
                        }
                    }
                }],
            tooltip: {
                shared: true
            },
            legend: {
                enabled : false
            },
            plotOptions: {
                series: {
                    borderWidth: 0,
                    dataLabels: {
                        enabled: true,
                        format: '{point.y}'
                    }
                }
            },
            series: [{
                    name: 'Jumlah Entry',
                    type: 'column',
                    data: y,
                    color: '#73c1f7',
                    tooltip: {
                        valueSuffix: ''
                    }
                }]
        });
    });

};

$.fn.showChart = function(data,id,title_text,series_name){
    title_text = title_text || "";
    series_name = series_name || "";
    var x = [];
    var y = [];
    $.each(data,function(index,value){
        x.push(index);
        y.push(value);
    });
    Highcharts.setOptions({
        lang: {
            decimalPoint: ',',
            thousandsSep: '.'
        }
    });

    $('#'+id).highcharts({
        chart: {
            zoomType: 'xy',
            height: 400
        },
        title: {
            text: ''
        },
        xAxis: [{
                categories: x
            }],
        yAxis: [{min:0,startOnTick: false},{min:0,startOnTick: false,opposite: true},{ // Primary yAxis
            labels: {
                format: '{value}',
                style: {
                    color: Highcharts.getOptions().colors[1]
                    }
                },
                title: {
                    text: title_text,
                    style: {
                        color: Highcharts.getOptions().colors[1]
                    }
                }
            }],
        tooltip: {
            shared: true
        },
        legend: {
            enabled : false
        },
        plotOptions: {
            series: {
                borderWidth: 0,
                dataLabels: {
                    enabled: true,
                    format: '{point.y}'
                }
            }
        },
        series: [{
                name: series_name,
                type: 'column',
                data: y,
                color: '#73c1f7',
                tooltip: {
                    valueSuffix: ''
                }
            }]
    });

};<commit_msg>AH | edit general function for showing chart<commit_after>$.fn.showChartDataEntryTanggal = function(data){
    var chart_data = data;
    Highcharts.setOptions({
        lang: {
            decimalPoint: ',',
            thousandsSep: '.'
        }
    });

    $.each(chart_data,function(user,form){
        var x = [];
        var y = [];
        $.each(form,function(index,value){
            x.push(index);
            y.push(value);
        });
        user = user.replace(" ","_");
        $('#'+user).highcharts({
            chart: {
                zoomType: 'xy',
                height: 400
            },
            title: {
                text: ''
            },
            xAxis: [{
                    categories: x
                }],
            yAxis: [{min:0,startOnTick: false},{min:0,startOnTick: false,opposite: true},{ // Primary yAxis
                labels: {
                    format: '{value}',
                    style: {
                        color: Highcharts.getOptions().colors[1]
                        }
                    },
                    title: {
                        text: 'Jumlah Entry',
                        style: {
                            color: Highcharts.getOptions().colors[1]
                        }
                    }
                }],
            tooltip: {
                shared: true
            },
            legend: {
                enabled : false
            },
            plotOptions: {
                series: {
                    borderWidth: 0,
                    dataLabels: {
                        enabled: true,
                        format: '{point.y}'
                    }
                }
            },
            series: [{
                    name: 'Jumlah Entry',
                    type: 'column',
                    data: y,
                    color: '#73c1f7',
                    tooltip: {
                        valueSuffix: ''
                    }
                }]
        });
    });

};

$.fn.showChartDataEntryMinggu = function(data){
    var chart_data = data;
    Highcharts.setOptions({
        lang: {
            decimalPoint: ',',
            thousandsSep: '.'
        }
    });

    $.each(chart_data,function(user,weeks){
        var x = ['Senin','Selasa','Rabu','Kamis','Jumat','Sabtu'];
        var y_now = [];
        var y_last = [];
        $.each(weeks,function(week,form){
            if(week=='thisweek'){
                $.each(form,function(index,value){
                    y_now.push(value);
                });
            }else{
                $.each(form,function(index,value){
                    y_last.push(value);
                });
            }

        });
        user = user.replace(" ","_");
        $('#'+user).highcharts({
            chart: {
                zoomType: 'xy',
                height: 400
            },
            title: {
                text: ''
            },
            xAxis: [{
                    categories: x
                }],
            yAxis: [{min:0,startOnTick: false},{min:0,startOnTick: false,opposite: true},{ // Primary yAxis
                labels: {
                    format: '{value}',
                    style: {
                        color: Highcharts.getOptions().colors[1]
                        }
                    },
                    title: {
                        text: 'Jumlah Entry',
                        style: {
                            color: Highcharts.getOptions().colors[1]
                        }
                    }
                }],
            tooltip: {
                shared: true
            },
            legend: {
                enabled : false
            },
            plotOptions: {
                series: {
                    borderWidth: 0,
                    dataLabels: {
                        enabled: true,
                        format: '{point.y}'
                    }
                }
            },
            series: [{
                    name: 'Minggu Ini',
                    type: 'column',
                    data: y_now,
                    color: '#0000ff',
                    tooltip: {
                        valueSuffix: ''
                    }
                },{
                    name: 'Minggu Lalu',
                    type: 'column',
                    data: y_last,
                    color: '#ff0000',
                    tooltip: {
                        valueSuffix: ''
                    }
                }]
        });
    });

};

$.fn.showChartDataEntryBulan = function(data){
    var chart_data = data;
    Highcharts.setOptions({
        lang: {
            decimalPoint: ',',
            thousandsSep: '.'
        }
    });

    $.each(chart_data,function(user,months){
        var x = ['Januari','Februari','Maret','April','Mei','Juni','Juli','Agustus','September','Oktober','November','Desember'];
        var y_now = [];
        var y_last = [];
        var thisyear = 0;
        var lastyear = 0;
        $.each(months,function(month,form){
            if(month=='thisyear'){
                $.each(form,function(index,value){
                    thisyear = index.split("-");
                    y_now.push(value);
                });
            }else{
                $.each(form,function(index,value){
                    lastyear = index.split("-");
                    y_last.push(value);
                });
            }

        });
        user = user.replace(" ","_");
        $('#'+user).highcharts({
            chart: {
                zoomType: 'xy',
                height: 400
            },
            title: {
                text: ''
            },
            xAxis: [{
                    categories: x
                }],
            yAxis: [{min:0,startOnTick: false},{min:0,startOnTick: false,opposite: true},{ // Primary yAxis
                labels: {
                    format: '{value}',
                    style: {
                        color: Highcharts.getOptions().colors[1]
                        }
                    },
                    title: {
                        text: 'Jumlah Entry',
                        style: {
                            color: Highcharts.getOptions().colors[1]
                        }
                    }
                }],
            tooltip: {
                shared: true
            },
            legend: {
                enabled : false
            },
            plotOptions: {
                series: {
                    borderWidth: 0,
                    dataLabels: {
                        enabled: true,
                        format: '{point.y}'
                    }
                }
            },
            series: [{
                    name: 'Tahun '+thisyear[0],
                    type: 'column',
                    data: y_now,
                    color: '#0000ff',
                    tooltip: {
                        valueSuffix: ''
                    }
                },{
                    name: 'Tahun '+lastyear[0],
                    type: 'column',
                    data: y_last,
                    color: '#ff0000',
                    tooltip: {
                        valueSuffix: ''
                    }
                }]
        });
    });

};

$.fn.showChartDataEntryForm = function(data){
    var chart_data = data;
    Highcharts.setOptions({
        lang: {
            decimalPoint: ',',
            thousandsSep: '.'
        }
    });

    $.each(chart_data,function(user,form){
        var x = [];
        var y = [];
        $.each(form,function(index,value){
            x.push(index);
            y.push(value);
        });
        user = user.replace(" ","_");
        $('#'+user).highcharts({
            chart: {
                zoomType: 'xy',
                height: 400
            },
            title: {
                text: ''
            },
            xAxis: [{
                    categories: x
                }],
            yAxis: [{min:0,startOnTick: false},{min:0,startOnTick: false,opposite: true},{ // Primary yAxis
                labels: {
                    format: '{value}',
                    style: {
                        color: Highcharts.getOptions().colors[1]
                        }
                    },
                    title: {
                        text: 'Jumlah Entry',
                        style: {
                            color: Highcharts.getOptions().colors[1]
                        }
                    }
                }],
            tooltip: {
                shared: true
            },
            legend: {
                enabled : false
            },
            plotOptions: {
                series: {
                    borderWidth: 0,
                    dataLabels: {
                        enabled: true,
                        format: '{point.y}'
                    }
                }
            },
            series: [{
                    name: 'Jumlah Entry',
                    type: 'column',
                    data: y,
                    color: '#73c1f7',
                    tooltip: {
                        valueSuffix: ''
                    }
                }]
        });
    });

};

$.fn.showChart = function(data){
    $.each(data,function(index1,data1){
        var id = data1['page'];
        var x = [];
        var y = [];
        $.each(data1['form'],function(index2,data2){
            x.push(index2);
            y.push(data2);
        });
        Highcharts.setOptions({
            lang: {
                decimalPoint: ',',
                thousandsSep: '.'
            }
        });

        $('#'+id).highcharts({
            chart: {
                zoomType: 'xy',
                height: 400
            },
            title: {
                text: ''
            },
            xAxis: [{
                    categories: x
                }],
            yAxis: [{min:0,startOnTick: false},{min:0,startOnTick: false,opposite: true},{ // Primary yAxis
                labels: {
                    format: '{value}',
                    style: {
                        color: Highcharts.getOptions().colors[1]
                        }
                    },
                    title: {
                        text: data1['y_label'],
                        style: {
                            color: Highcharts.getOptions().colors[1]
                        }
                    }
                }],
            tooltip: {
                shared: true
            },
            legend: {
                enabled : false
            },
            plotOptions: {
                series: {
                    borderWidth: 0,
                    dataLabels: {
                        enabled: true,
                        format: '{point.y}'
                    }
                }
            },
            series: [{
                    name: data1['series_name'],
                    type: 'column',
                    data: y,
                    color: '#73c1f7',
                    tooltip: {
                        valueSuffix: ''
                    }
                }]
        });
    });
};<|endoftext|>


<commit_before>/*
 * utils.js 洢ĿгõĹķ
 */
var utils = {
    //listToArray:תΪ(е)
    listToArray: function (likeAry) {
        var ary = [];
        try {
            ary = Array.prototype.slice.call(likeAry, 0);
        } catch (e) {
            for (var i = 0; i < likeAry.length; i++) {
                ary[ary.length] = likeAry[i];
            }
        }
        return ary;
    },
    //toJSON:jsonʽַתΪjsonʽĶ
    toJSON: function (str) {
        var obj = null;
        try {
            obj = JSON.parse(str);
        } catch (e) {
            obj = eval("(" + str + ")");
        }
        return obj;
    }
};
<commit_msg>2016-01-14晚<commit_after>var utils = {
    listToArray: function (likeAry) {
        var ary = [];
        try {
            ary = Array.prototype.slice.call(likeAry, 0);
        } catch (e) {
            for (var i = 0; i < likeAry.length; i++) {
                ary[ary.length] = likeAry[i];
            }
        }
        return ary;
    },
    toJSON: function (str) {
        var obj = null;
        try {
            obj = JSON.parse(str);
        } catch (e) {
            obj = eval("(" + str + ")");
        }
        return obj;
    }
};
<|endoftext|>


<commit_before><commit_msg>10 - clean up<commit_after><|endoftext|>


<commit_before>
App.Address = DS.Model.extend(
  Ember.Validations.Mixin,
{
  firstName: DS.attr('string'),
  lastName: DS.attr('string'),
  addressLine_1: DS.attr('string'),
  addressLine_2: DS.attr('string'),
  city: DS.attr('string'),
  state: DS.attr('string'),
  zipCode: DS.attr('string'),
  userId: DS.attr('string'),
  countryId: DS.attr('string'),
  smartyPayload: DS.attr(),
  isVerified: true,

  name: function () {
    return this.get('firstName') + ' ' + this.get('lastName');
  }.property('firstName', 'lastName'),

  addressString: function () {
    return "%@ %@ %@, %@ %@".fmt(
        this.get('addressLine_1'),
        this.get('addressLine_2'),
        this.get('city'),
        this.get('state'),
        this.get('zipCode')
      );
  }.property('addressLine_1', 'addressLine_2', 'city', 'state', 'zipCode'),

  isVerifiable: function () {
    var zipAndLine1 = this.get('errors.addressLine_1.length') < 1 &&
                      this.get('errors.zipCode.length') < 1;

    var cityStateAndLine1 = this.get('errors.addressLine_1.length') < 1 &&
                            this.get('errors.city.length') < 1 &&
                            this.get('errors.state.length')  < 1;

    return zipAndLine1 || cityStateAndLine1;
  }.property('errors.addressLine_1', 'errors.zipCode'),

  verify: function () {
    var self = this;

    if (!this.get('isVerifiable')) return;

    Ember.run.cancel(this.verificationTimer);
    this.verificationTimer = Ember.run.later(this, function (){

      // console.info('[SmartyStreets] Verifying address with ID:', this.get('id'));

      $.ajax({
        url: "https://api.smartystreets.com/street-address",
        data: {
          "auth-token": DSC.CONF.smartystreets["auth-token"],
          "street": this.get('addressString')
        },
        success: function (response) {
          self.set('isVerified', response.length > 0);
          self.set('smartyPayload', response);
        },
        error: function () {
          self.set('isVerified', false);
        }
      });

    }, 500);

  }.observes('addressLine_1', 'addressLine_2', 'city', 'state', 'zipCode', 'isVerifiable'),

  validations: {
    firstName: {
      length: { maximum: 25 },
      presence: true
    },
    lastName: {
      length: { maximum: 25 },
      presence: true
    },
    addressLine_1: {
      length: { maximum: 35 },
      presence: true
    },
    addressLine_2: {
      length: { maximum: 35 }
    },
    city: {
      length: { maximum: 40 },
      presence: true
    },
    state: {
      presence: true
    },
    zipCode: {
      length: { is: 5 },
      numericality: true,
      presence: true
    }
  }

});
<commit_msg>SmartyStreets: wait for 5 digits in zip and increase timer to 1000ms<commit_after>
App.Address = DS.Model.extend(
  Ember.Validations.Mixin,
{
  firstName: DS.attr('string'),
  lastName: DS.attr('string'),
  addressLine_1: DS.attr('string'),
  addressLine_2: DS.attr('string'),
  city: DS.attr('string'),
  state: DS.attr('string'),
  zipCode: DS.attr('string'),
  userId: DS.attr('string'),
  countryId: DS.attr('string'),
  smartyPayload: DS.attr(),
  isVerified: true,

  name: function () {
    return this.get('firstName') + ' ' + this.get('lastName');
  }.property('firstName', 'lastName'),

  addressString: function () {
    return "%@ %@ %@, %@ %@".fmt(
        this.get('addressLine_1'),
        this.get('addressLine_2'),
        this.get('city'),
        this.get('state'),
        this.get('zipCode')
      );
  }.property('addressLine_1', 'addressLine_2', 'city', 'state', 'zipCode'),

  isVerifiable: function () {
    var zipAndLine1 = this.get('errors.addressLine_1.length') < 1 &&
                      this.get('errors.zipCode.length') < 4;

    var cityStateAndLine1 = this.get('errors.addressLine_1.length') < 1 &&
                            this.get('errors.city.length') < 1 &&
                            this.get('errors.state.length') < 1;

    return zipAndLine1 || cityStateAndLine1;
  }.property('errors.addressLine_1', 'errors.zipCode'),

  verify: function () {
    var self = this;

    if (!this.get('isVerifiable')) return;

    Ember.run.cancel(this.verificationTimer);
    this.verificationTimer = Ember.run.later(this, function () {

      // console.info('[SmartyStreets] Verifying address with ID:', this.get('id'));

      $.ajax({
        url: "https://api.smartystreets.com/street-address",
        data: {
          "auth-token": DSC.CONF.smartystreets["auth-token"],
          "street": this.get('addressString')
        },
        success: function (response) {
          self.set('isVerified', response.length > 0);
          self.set('smartyPayload', response);
        },
        error: function () {
          self.set('isVerified', false);
        }
      });

    }, 1000);

  }.observes('addressLine_1', 'addressLine_2', 'city', 'state', 'zipCode', 'isVerifiable'),

  validations: {
    firstName: {
      length: { maximum: 25 },
      presence: true
    },
    lastName: {
      length: { maximum: 25 },
      presence: true
    },
    addressLine_1: {
      length: { maximum: 35 },
      presence: true
    },
    addressLine_2: {
      length: { maximum: 35 }
    },
    city: {
      length: { maximum: 40 },
      presence: true
    },
    state: {
      presence: true
    },
    zipCode: {
      length: { is: 5 },
      numericality: true,
      presence: true
    }
  }

});
<|endoftext|>


<commit_before>#!/usr/bin/env node

// assigned when options are parsed by the main function
let force

// -----------------------------------------------------------------------------

const assert = require('assert')
const flatten = require('lodash/flatten')
const getopts = require('getopts')
const lockfile = require('proper-lockfile')
const pipe = require('promise-toolbox/pipe')
const { default: Vhd } = require('vhd-lib')
const { dirname, resolve } = require('path')
const { DISK_TYPE_DIFFERENCING } = require('vhd-lib/dist/_constants')
const { isValidXva } = require('@xen-orchestra/backups/isValidXva')

const asyncMap = require('../_asyncMap')
const fs = require('../_fs')

const handler = require('@xen-orchestra/fs').getHandler({ url: 'file://' })

// -----------------------------------------------------------------------------

// chain is an array of VHDs from child to parent
//
// the whole chain will be merged into parent, parent will be renamed to child
// and all the others will deleted
async function mergeVhdChain(chain) {
  assert(chain.length >= 2)

  const child = chain[0]
  const parent = chain[chain.length - 1]
  const children = chain.slice(0, -1).reverse()

  console.warn('Unused parents of VHD', child)
  chain
    .slice(1)
    .reverse()
    .forEach(parent => {
      console.warn('  ', parent)
    })
  force && console.warn('  merging…')
  console.warn('')
  if (force) {
    // `mergeVhd` does not work with a stream, either
    // - make it accept a stream
    // - or create synthetic VHD which is not a stream
    return console.warn('TODO: implement merge')
    // await mergeVhd(
    //   handler,
    //   parent,
    //   handler,
    //   children.length === 1
    //     ? child
    //     : await createSyntheticStream(handler, children)
    // )
  }

  await Promise.all([
    force && fs.rename(parent, child),
    asyncMap(children.slice(0, -1), child => {
      console.warn('Unused VHD', child)
      force && console.warn('  deleting…')
      console.warn('')
      return force && handler.unlink(child)
    }),
  ])
}

const listVhds = pipe([
  vmDir => vmDir + '/vdis',
  fs.readdir2,
  asyncMap(fs.readdir2),
  flatten,
  asyncMap(fs.readdir2),
  flatten,
  _ => _.filter(_ => _.endsWith('.vhd')),
])

async function handleVm(vmDir) {
  const vhds = new Set()
  const vhdParents = { __proto__: null }
  const vhdChildren = { __proto__: null }

  // remove broken VHDs
  await asyncMap(await listVhds(vmDir), async path => {
    try {
      const vhd = new Vhd(handler, path)
      await vhd.readHeaderAndFooter()
      vhds.add(path)
      if (vhd.footer.diskType === DISK_TYPE_DIFFERENCING) {
        const parent = resolve(dirname(path), vhd.header.parentUnicodeName)
        vhdParents[path] = parent
        if (parent in vhdChildren) {
          const error = new Error(
            'this script does not support multiple VHD children'
          )
          error.parent = parent
          error.child1 = vhdChildren[parent]
          error.child2 = path
          throw error // should we throw?
        }
        vhdChildren[parent] = path
      }
    } catch (error) {
      console.warn('Error while checking VHD', path)
      console.warn('  ', error)
      if (error != null && error.code === 'ERR_ASSERTION') {
        force && console.warn('  deleting…')
        console.warn('')
        force && (await handler.unlink(path))
      }
    }
  })

  // remove VHDs with missing ancestors
  {
    const deletions = []

    // return true if the VHD has been deleted or is missing
    const deleteIfOrphan = vhd => {
      const parent = vhdParents[vhd]
      if (parent === undefined) {
        return
      }

      // no longer needs to be checked
      delete vhdParents[vhd]

      deleteIfOrphan(parent)

      if (!vhds.has(parent)) {
        vhds.delete(vhd)

        console.warn('Error while checking VHD', vhd)
        console.warn('  missing parent', parent)
        force && console.warn('  deleting…')
        console.warn('')
        force && deletions.push(handler.unlink(vhd))
      }
    }

    // > A property that is deleted before it has been visited will not be
    // > visited later.
    // >
    // > -- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...in#Deleted_added_or_modified_properties
    for (const child in vhdParents) {
      deleteIfOrphan(child)
    }

    await Promise.all(deletions)
  }

  const [jsons, xvas] = await fs
    .readdir2(vmDir)
    .then(entries => [
      entries.filter(_ => _.endsWith('.json')),
      new Set(entries.filter(_ => _.endsWith('.xva'))),
    ])

  await asyncMap(xvas, async path => {
    // check is not good enough to delete the file, the best we can do is report
    // it
    if (!(await isValidXva(path))) {
      console.warn('Potential broken XVA', path)
      console.warn('')
    }
  })

  const unusedVhds = new Set(vhds)
  const unusedXvas = new Set(xvas)

  // compile the list of unused XVAs and VHDs, and remove backup metadata which
  // reference a missing XVA/VHD
  await asyncMap(jsons, async json => {
    const metadata = JSON.parse(await fs.readFile(json))
    const { mode } = metadata
    if (mode === 'full') {
      const linkedXva = resolve(vmDir, metadata.xva)

      if (xvas.has(linkedXva)) {
        unusedXvas.delete(linkedXva)
      } else {
        console.warn('Error while checking backup', json)
        console.warn('  missing file', linkedXva)
        force && console.warn('  deleting…')
        console.warn('')
        force && (await handler.unlink(json))
      }
    } else if (mode === 'delta') {
      const linkedVhds = (() => {
        const { vhds } = metadata
        return Object.keys(vhds).map(key => resolve(vmDir, vhds[key]))
      })()

      // FIXME: find better approach by keeping as much of the backup as
      // possible (existing disks) even if one disk is missing
      if (linkedVhds.every(_ => vhds.has(_))) {
        linkedVhds.forEach(_ => unusedVhds.delete(_))
      } else {
        console.warn('Error while checking backup', json)
        const missingVhds = linkedVhds.filter(_ => !vhds.has(_))
        console.warn(
          '  %i/%i missing VHDs',
          missingVhds.length,
          linkedVhds.length
        )
        missingVhds.forEach(vhd => {
          console.warn('  ', vhd)
        })
        force && console.warn('  deleting…')
        console.warn('')
        force && (await handler.unlink(json))
      }
    }
  })

  // TODO: parallelize by vm/job/vdi
  const unusedVhdsDeletion = []
  {
    // VHD chains (as list from child to ancestor) to merge indexed by last
    // ancestor
    const vhdChainsToMerge = { __proto__: null }

    const toCheck = new Set(unusedVhds)

    const getUsedChildChainOrDelete = vhd => {
      if (vhd in vhdChainsToMerge) {
        const chain = vhdChainsToMerge[vhd]
        delete vhdChainsToMerge[vhd]
        return chain
      }

      if (!unusedVhds.has(vhd)) {
        return [vhd]
      }

      // no longer needs to be checked
      toCheck.delete(vhd)

      const child = vhdChildren[vhd]
      if (child !== undefined) {
        const chain = getUsedChildChainOrDelete(child)
        if (chain !== undefined) {
          chain.push(vhd)
          return chain
        }
      }

      console.warn('Unused VHD', vhd)
      force && console.warn('  deleting…')
      console.warn('')
      force && unusedVhdsDeletion.push(handler.unlink(vhd))
    }

    toCheck.forEach(vhd => {
      vhdChainsToMerge[vhd] = getUsedChildChainOrDelete(vhd)
    })

    Object.keys(vhdChainsToMerge).forEach(key => {
      const chain = vhdChainsToMerge[key]
      if (chain !== undefined) {
        unusedVhdsDeletion.push(mergeVhdChain(chain))
      }
    })
  }

  await Promise.all([
    unusedVhdsDeletion,
    asyncMap(unusedXvas, path => {
      console.warn('Unused XVA', path)
      force && console.warn('  deleting…')
      console.warn('')
      return force && handler.unlink(path)
    }),
  ])
}

// -----------------------------------------------------------------------------

module.exports = async function main(args) {
  const opts = getopts(args, {
    alias: {
      force: 'f',
    },
    boolean: ['force'],
    default: {
      force: false,
    },
  })

  ;({ force } = opts)
  await asyncMap(opts._, async vmDir => {
    vmDir = resolve(vmDir)

    // TODO: implement this in `xo-server`, not easy because not compatible with
    // `@xen-orchestra/fs`.
    const release = await lockfile.lock(vmDir)
    try {
      await handleVm(vmDir)
    } catch (error) {
      console.error('handleVm', vmDir, error)
    } finally {
      await release()
    }
  })
}
<commit_msg>feat(backups-cli): clean checksums for missing XVAs<commit_after>#!/usr/bin/env node

// assigned when options are parsed by the main function
let force

// -----------------------------------------------------------------------------

const assert = require('assert')
const flatten = require('lodash/flatten')
const getopts = require('getopts')
const lockfile = require('proper-lockfile')
const pipe = require('promise-toolbox/pipe')
const { default: Vhd } = require('vhd-lib')
const { dirname, resolve } = require('path')
const { DISK_TYPE_DIFFERENCING } = require('vhd-lib/dist/_constants')
const { isValidXva } = require('@xen-orchestra/backups/isValidXva')

const asyncMap = require('../_asyncMap')
const fs = require('../_fs')

const handler = require('@xen-orchestra/fs').getHandler({ url: 'file://' })

// -----------------------------------------------------------------------------

// chain is an array of VHDs from child to parent
//
// the whole chain will be merged into parent, parent will be renamed to child
// and all the others will deleted
async function mergeVhdChain(chain) {
  assert(chain.length >= 2)

  const child = chain[0]
  const parent = chain[chain.length - 1]
  const children = chain.slice(0, -1).reverse()

  console.warn('Unused parents of VHD', child)
  chain
    .slice(1)
    .reverse()
    .forEach(parent => {
      console.warn('  ', parent)
    })
  force && console.warn('  merging…')
  console.warn('')
  if (force) {
    // `mergeVhd` does not work with a stream, either
    // - make it accept a stream
    // - or create synthetic VHD which is not a stream
    return console.warn('TODO: implement merge')
    // await mergeVhd(
    //   handler,
    //   parent,
    //   handler,
    //   children.length === 1
    //     ? child
    //     : await createSyntheticStream(handler, children)
    // )
  }

  await Promise.all([
    force && fs.rename(parent, child),
    asyncMap(children.slice(0, -1), child => {
      console.warn('Unused VHD', child)
      force && console.warn('  deleting…')
      console.warn('')
      return force && handler.unlink(child)
    }),
  ])
}

const listVhds = pipe([
  vmDir => vmDir + '/vdis',
  fs.readdir2,
  asyncMap(fs.readdir2),
  flatten,
  asyncMap(fs.readdir2),
  flatten,
  _ => _.filter(_ => _.endsWith('.vhd')),
])

async function handleVm(vmDir) {
  const vhds = new Set()
  const vhdParents = { __proto__: null }
  const vhdChildren = { __proto__: null }

  // remove broken VHDs
  await asyncMap(await listVhds(vmDir), async path => {
    try {
      const vhd = new Vhd(handler, path)
      await vhd.readHeaderAndFooter()
      vhds.add(path)
      if (vhd.footer.diskType === DISK_TYPE_DIFFERENCING) {
        const parent = resolve(dirname(path), vhd.header.parentUnicodeName)
        vhdParents[path] = parent
        if (parent in vhdChildren) {
          const error = new Error(
            'this script does not support multiple VHD children'
          )
          error.parent = parent
          error.child1 = vhdChildren[parent]
          error.child2 = path
          throw error // should we throw?
        }
        vhdChildren[parent] = path
      }
    } catch (error) {
      console.warn('Error while checking VHD', path)
      console.warn('  ', error)
      if (error != null && error.code === 'ERR_ASSERTION') {
        force && console.warn('  deleting…')
        console.warn('')
        force && (await handler.unlink(path))
      }
    }
  })

  // remove VHDs with missing ancestors
  {
    const deletions = []

    // return true if the VHD has been deleted or is missing
    const deleteIfOrphan = vhd => {
      const parent = vhdParents[vhd]
      if (parent === undefined) {
        return
      }

      // no longer needs to be checked
      delete vhdParents[vhd]

      deleteIfOrphan(parent)

      if (!vhds.has(parent)) {
        vhds.delete(vhd)

        console.warn('Error while checking VHD', vhd)
        console.warn('  missing parent', parent)
        force && console.warn('  deleting…')
        console.warn('')
        force && deletions.push(handler.unlink(vhd))
      }
    }

    // > A property that is deleted before it has been visited will not be
    // > visited later.
    // >
    // > -- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...in#Deleted_added_or_modified_properties
    for (const child in vhdParents) {
      deleteIfOrphan(child)
    }

    await Promise.all(deletions)
  }

  const [jsons, xvas, xvaSums] = await fs
    .readdir2(vmDir)
    .then(entries => [
      entries.filter(_ => _.endsWith('.json')),
      new Set(entries.filter(_ => _.endsWith('.xva'))),
      entries.filter(_ => _.endsWith('.xva.cheksum')),
    ])

  await asyncMap(xvas, async path => {
    // check is not good enough to delete the file, the best we can do is report
    // it
    if (!(await isValidXva(path))) {
      console.warn('Potential broken XVA', path)
      console.warn('')
    }
  })

  const unusedVhds = new Set(vhds)
  const unusedXvas = new Set(xvas)

  // compile the list of unused XVAs and VHDs, and remove backup metadata which
  // reference a missing XVA/VHD
  await asyncMap(jsons, async json => {
    const metadata = JSON.parse(await fs.readFile(json))
    const { mode } = metadata
    if (mode === 'full') {
      const linkedXva = resolve(vmDir, metadata.xva)

      if (xvas.has(linkedXva)) {
        unusedXvas.delete(linkedXva)
      } else {
        console.warn('Error while checking backup', json)
        console.warn('  missing file', linkedXva)
        force && console.warn('  deleting…')
        console.warn('')
        force && (await handler.unlink(json))
      }
    } else if (mode === 'delta') {
      const linkedVhds = (() => {
        const { vhds } = metadata
        return Object.keys(vhds).map(key => resolve(vmDir, vhds[key]))
      })()

      // FIXME: find better approach by keeping as much of the backup as
      // possible (existing disks) even if one disk is missing
      if (linkedVhds.every(_ => vhds.has(_))) {
        linkedVhds.forEach(_ => unusedVhds.delete(_))
      } else {
        console.warn('Error while checking backup', json)
        const missingVhds = linkedVhds.filter(_ => !vhds.has(_))
        console.warn(
          '  %i/%i missing VHDs',
          missingVhds.length,
          linkedVhds.length
        )
        missingVhds.forEach(vhd => {
          console.warn('  ', vhd)
        })
        force && console.warn('  deleting…')
        console.warn('')
        force && (await handler.unlink(json))
      }
    }
  })

  // TODO: parallelize by vm/job/vdi
  const unusedVhdsDeletion = []
  {
    // VHD chains (as list from child to ancestor) to merge indexed by last
    // ancestor
    const vhdChainsToMerge = { __proto__: null }

    const toCheck = new Set(unusedVhds)

    const getUsedChildChainOrDelete = vhd => {
      if (vhd in vhdChainsToMerge) {
        const chain = vhdChainsToMerge[vhd]
        delete vhdChainsToMerge[vhd]
        return chain
      }

      if (!unusedVhds.has(vhd)) {
        return [vhd]
      }

      // no longer needs to be checked
      toCheck.delete(vhd)

      const child = vhdChildren[vhd]
      if (child !== undefined) {
        const chain = getUsedChildChainOrDelete(child)
        if (chain !== undefined) {
          chain.push(vhd)
          return chain
        }
      }

      console.warn('Unused VHD', vhd)
      force && console.warn('  deleting…')
      console.warn('')
      force && unusedVhdsDeletion.push(handler.unlink(vhd))
    }

    toCheck.forEach(vhd => {
      vhdChainsToMerge[vhd] = getUsedChildChainOrDelete(vhd)
    })

    Object.keys(vhdChainsToMerge).forEach(key => {
      const chain = vhdChainsToMerge[key]
      if (chain !== undefined) {
        unusedVhdsDeletion.push(mergeVhdChain(chain))
      }
    })
  }

  await Promise.all([
    unusedVhdsDeletion,
    asyncMap(unusedXvas, path => {
      console.warn('Unused XVA', path)
      force && console.warn('  deleting…')
      console.warn('')
      return force && handler.unlink(path)
    }),
    asyncMap(xvaSums, path => {
      // no need to handle checksums for XVAs deleted by the script, they will be handled by `unlink()`
      if (!xvas.has(path.slice(0, -'.checksum'.length))) {
        console.warn('Unused XVA checksum', path)
        force && console.warn('  deleting…')
        console.warn('')
        return force && handler.unlink(path)
      }
    }),
  ])
}

// -----------------------------------------------------------------------------

module.exports = async function main(args) {
  const opts = getopts(args, {
    alias: {
      force: 'f',
    },
    boolean: ['force'],
    default: {
      force: false,
    },
  })

  ;({ force } = opts)
  await asyncMap(opts._, async vmDir => {
    vmDir = resolve(vmDir)

    // TODO: implement this in `xo-server`, not easy because not compatible with
    // `@xen-orchestra/fs`.
    const release = await lockfile.lock(vmDir)
    try {
      await handleVm(vmDir)
    } catch (error) {
      console.error('handleVm', vmDir, error)
    } finally {
      await release()
    }
  })
}
<|endoftext|>


<commit_before>import Disposable from 'promise-toolbox/Disposable'
import fromCallback from 'promise-toolbox/fromCallback'
import fromEvent from 'promise-toolbox/fromEvent'
import JsonRpcWebsocketClient from 'jsonrpc-websocket-client'
import parsePairs from 'parse-pairs'
import { createLogger } from '@xen-orchestra/log/dist'
import { deduped } from '@vates/disposable/deduped'
import { execFile, spawn } from 'child_process'
import { readFile } from 'fs-extra'

const TUNNEL_SERVICE = 'xoa-support-tunnel.service'

const { debug, warn } = createLogger('xo:proxy:appliance')

const getUpdater = deduped(async function () {
  const updater = new JsonRpcWebsocketClient('ws://localhost:9001')
  await updater.open()
  return new Disposable(() => updater.close(), updater)
})

const callUpdate = params =>
  Disposable.use(
    getUpdater(),
    updater =>
      new Promise((resolve, reject) => {
        updater
          .on('error', reject)
          .on('notification', ({ method, params }) => {
            if (method === 'print') {
              debug('updater.update: ' + params.content)
            } else if (method === 'end') {
              resolve(params)
            } else if (method === 'server-error') {
              reject(new Error(params.message))
            } else if (method !== 'connected') {
              warn('update.update, unhandled message', {
                method,
                params,
              })
            }
          })
          .notify('update', params)
      })
  )

async function checkAppliance() {
  const child = spawn('xoa', ['check'], {
    all: true,
    env: {
      ...process.env,

      // dont inherit this var from xo-server or the output will be polluted
      DEBUG: '',

      FORCE_COLOR: '1',
    },
  })

  const chunks = []
  let length = 0
  const onData = chunk => {
    chunks.push(chunk)
    length += chunk.length
  }
  child.stdout.on('data', onData)
  child.stderr.on('data', onData)

  await fromEvent(child, 'exit')

  return Buffer.concat(chunks, length).toString()
}

async function closeSupportTunnel() {
  await fromCallback(execFile, 'systemctl', ['stop', TUNNEL_SERVICE])
}

async function getApplianceInfo() {
  const pairs = parsePairs(await readFile('/etc/os-release', 'utf8'))
  return {
    build: pairs.XOA_BUILD,
    os: pairs.ID,
    osVersion: pairs.VERSION_ID,
  }
}

async function getStateSupportTunnel() {
  const isActive =
    (await fromEvent(
      spawn('systemctl', ['is-active', '--quiet', TUNNEL_SERVICE], {
        stdio: 'ignore',
      }),
      'exit'
    )) === 0

  const isActiveOrFailed =
    isActive ||
    (await fromEvent(
      spawn('systemctl', ['is-failed', '--quiet', TUNNEL_SERVICE], {
        stdio: 'ignore',
      }),
      'exit'
    )) === 0

  return {
    open: isActive,
    stdout: isActiveOrFailed ? await fromCallback(readFile, '/tmp/xoa-support-tunnel.out', 'utf8') : '',
  }
}

async function openSupportTunnel() {
  await fromCallback(execFile, 'systemctl', ['start', TUNNEL_SERVICE])
}

export default class Appliance {
  constructor(app) {
    app.api.addMethods({
      appliance: {
        check: checkAppliance,
        getInfo: [
          getApplianceInfo,
          {
            description: 'returns various information about the appliance itself',
          },
        ],
        supportTunnel: {
          close: [
            closeSupportTunnel,
            {
              description: 'close the support tunnel',
            },
          ],
          getState: [
            getStateSupportTunnel,
            {
              description: 'getState the support tunnel',
            },
          ],
          open: [
            openSupportTunnel,
            {
              description: 'open the support tunnel',
            },
          ],
        },
        updater: {
          getLocalManifest: () => Disposable.use(getUpdater(), _ => _.call('getLocalManifest')),
          getState: () => callUpdate(),
          upgrade: () => callUpdate({ upgrade: true }),
        },
      },
    })
  }

  // A proxy can be bound to a unique license
  getSelfLicense() {
    return Disposable.use(getUpdater(), _ => _.call('getSelfLicenses').then(licenses => licenses[0]))
  }
}
<commit_msg>chore(proxy/appliance): dont import log from dist/<commit_after>import Disposable from 'promise-toolbox/Disposable'
import fromCallback from 'promise-toolbox/fromCallback'
import fromEvent from 'promise-toolbox/fromEvent'
import JsonRpcWebsocketClient from 'jsonrpc-websocket-client'
import parsePairs from 'parse-pairs'
import { createLogger } from '@xen-orchestra/log'
import { deduped } from '@vates/disposable/deduped'
import { execFile, spawn } from 'child_process'
import { readFile } from 'fs-extra'

const TUNNEL_SERVICE = 'xoa-support-tunnel.service'

const { debug, warn } = createLogger('xo:proxy:appliance')

const getUpdater = deduped(async function () {
  const updater = new JsonRpcWebsocketClient('ws://localhost:9001')
  await updater.open()
  return new Disposable(() => updater.close(), updater)
})

const callUpdate = params =>
  Disposable.use(
    getUpdater(),
    updater =>
      new Promise((resolve, reject) => {
        updater
          .on('error', reject)
          .on('notification', ({ method, params }) => {
            if (method === 'print') {
              debug('updater.update: ' + params.content)
            } else if (method === 'end') {
              resolve(params)
            } else if (method === 'server-error') {
              reject(new Error(params.message))
            } else if (method !== 'connected') {
              warn('update.update, unhandled message', {
                method,
                params,
              })
            }
          })
          .notify('update', params)
      })
  )

async function checkAppliance() {
  const child = spawn('xoa', ['check'], {
    all: true,
    env: {
      ...process.env,

      // dont inherit this var from xo-server or the output will be polluted
      DEBUG: '',

      FORCE_COLOR: '1',
    },
  })

  const chunks = []
  let length = 0
  const onData = chunk => {
    chunks.push(chunk)
    length += chunk.length
  }
  child.stdout.on('data', onData)
  child.stderr.on('data', onData)

  await fromEvent(child, 'exit')

  return Buffer.concat(chunks, length).toString()
}

async function closeSupportTunnel() {
  await fromCallback(execFile, 'systemctl', ['stop', TUNNEL_SERVICE])
}

async function getApplianceInfo() {
  const pairs = parsePairs(await readFile('/etc/os-release', 'utf8'))
  return {
    build: pairs.XOA_BUILD,
    os: pairs.ID,
    osVersion: pairs.VERSION_ID,
  }
}

async function getStateSupportTunnel() {
  const isActive =
    (await fromEvent(
      spawn('systemctl', ['is-active', '--quiet', TUNNEL_SERVICE], {
        stdio: 'ignore',
      }),
      'exit'
    )) === 0

  const isActiveOrFailed =
    isActive ||
    (await fromEvent(
      spawn('systemctl', ['is-failed', '--quiet', TUNNEL_SERVICE], {
        stdio: 'ignore',
      }),
      'exit'
    )) === 0

  return {
    open: isActive,
    stdout: isActiveOrFailed ? await fromCallback(readFile, '/tmp/xoa-support-tunnel.out', 'utf8') : '',
  }
}

async function openSupportTunnel() {
  await fromCallback(execFile, 'systemctl', ['start', TUNNEL_SERVICE])
}

export default class Appliance {
  constructor(app) {
    app.api.addMethods({
      appliance: {
        check: checkAppliance,
        getInfo: [
          getApplianceInfo,
          {
            description: 'returns various information about the appliance itself',
          },
        ],
        supportTunnel: {
          close: [
            closeSupportTunnel,
            {
              description: 'close the support tunnel',
            },
          ],
          getState: [
            getStateSupportTunnel,
            {
              description: 'getState the support tunnel',
            },
          ],
          open: [
            openSupportTunnel,
            {
              description: 'open the support tunnel',
            },
          ],
        },
        updater: {
          getLocalManifest: () => Disposable.use(getUpdater(), _ => _.call('getLocalManifest')),
          getState: () => callUpdate(),
          upgrade: () => callUpdate({ upgrade: true }),
        },
      },
    })
  }

  // A proxy can be bound to a unique license
  getSelfLicense() {
    return Disposable.use(getUpdater(), _ => _.call('getSelfLicenses').then(licenses => licenses[0]))
  }
}
<|endoftext|>


<commit_before>var helper = require('helper');
var forEach = helper.forEach;
var ensure = helper.ensure;
var _ = require('lodash');
var urlNormalizer = helper.urlNormalizer;
var UID = helper.makeUid;
var makeSlug = helper.makeSlug;
var withoutExtension = helper.withoutExtension;
var redis = require('../client');
var Permalink = require('./build/prepare/permalink');
var Key = require('./key').url;
var model = require('./model');
var Blog = require('blog');
var get = require('./get');

//'/style.css', '/script.js', '/feed.rss', '/robots.txt', '/sitemap.xml'
// are not possible because . is replaced with. ideally check for
// all template views here...
var banned = [
  '/archives',
  '/archive',
  '/search',
  '/tagged',
  '/public',
  ''
];

var MIN_SUMMARY_SLUG_WORDS = 3;
var MAX_SUMMARY_SLUG_WORDS = 10;

var PERMALINK_PERMUTATIONS = 5;

// The random permalinks are
// this long. I figure 3 gives
// us (26 * 2 + 10)^3 (= 238,328)
// possible permalinks which is more
// than enough for one blog, espcially
// since the circumstances under
// which Blot needs to generate
// a random url are rare enough.
// It also avoids the issue of rude
// 4 letter words being generated.
var UID_LENGTH = 3;

// We generate this number
// of random permalinks in
// case all else fails.
var UID_PERMUTATIONS = 500;

// console.log(Candidates({
//   permalink: '',
//   slug: '',
//   summary: '',
//   url: '',
//   name: 'a.jpg',
//   path: '/a.jpg'
// }));

function Candidates (blog, entry) {

  var candidates = [];

  // Don't use the permalink format for pages
  if (!entry.permalink && !entry.page) {
    entry.permalink = Permalink(blog.timeZone, blog.permalink.format, entry);
  }

  // The user has specified a permalink in the
  // entry's metadata. We should use this if we can.
  if (entry.permalink) {

    candidates.push(entry.permalink);

    // If the permalink is unavailable, try appending a number
    // e.g. if 'apple', try 'apple-2', 'apple-3' ... 'apple-99'
    for (var i = 2; i < PERMALINK_PERMUTATIONS; i++)
      candidates.push(entry.permalink + '-' + i);
  }

  // This is generated from the entry's title
  if (entry.slug) {
    candidates.push(entry.slug);
  }

  if (entry.name)
    candidates.push(makeSlug(withoutExtension(entry.name)));

  if (entry.path)
    candidates.push(makeSlug(withoutExtension(entry.path.split('/').join('-'))));

  if (entry.summary) {

    var words = entry.summary.split(' ');

    for (var y = MIN_SUMMARY_SLUG_WORDS; y < words.length && y < MAX_SUMMARY_SLUG_WORDS; y++) {
      candidates.push(makeSlug(words.slice(0, y).join('-')));
    }
  }

  // If we make it path the permalink chosen by the
  // user, it's possible that we fell through to the
  // randomly generated (UID()...) permalinks below.
  // If so, we should insert the entry's previous URL
  // if it exists to ensure that the entry retains
  // a randomly generated URL consistently.
  if (entry.url)
    candidates.push(entry.url);

  for (var j = 0; j < UID_PERMUTATIONS; j++)
    candidates.push(UID(UID_LENGTH));

  // Trim, lowercase, strip trailing /, add leading /
  // ensure valid url pathname. Always returns a string
  // can be empty if invalid or just '/'
  candidates = candidates.map(urlNormalizer);

  // We store the decoded version. Not sure if this is a good idea
  // or not but it works for now. Pandoc *en*codes by default.
  // Perhaps I could store both?
  candidates = candidates.map(function(url){

    try {
      url = decodeURI(url);
    } catch (e) {
      console.log('Error: Could not decodeURIComponent for:', url, e.message);
    }

    return url;
  });

  candidates = candidates.filter(function(candidate){

    if (!candidate) return false;

    // WE DONT EVER ADD ENTRY.PATH so images are always accessible
    // It's possible that entry.name when normalized === entry.path
    if (entry.path && candidate === entry.path) return false;

    if (banned.indexOf(candidate) > -1) return false;

    return true;
  });

  candidates = _.uniq(candidates);

  return candidates;
}

function check (blogID, candidate, entryID, callback) {

  var key = Key(blogID, candidate);

  redis.get(key, function(err, existingID){

    if (err) return callback(err);

    // This url is available and unused
    if (!existingID) return callback();

    // This url points to this entry already
    if (existingID === entryID) return callback();

    // This url points to a different entry
    get(blogID, existingID, function(existingEntry){

      // For some reason (bug) the url key was
      // set but the entry does not exist. Claim the url.
      if (!existingEntry) return callback();

      // The existing entry has since moved to a different url
      // (perhaps the author modified its permalink etc...)
      // so this entry can claim this url.
      if (existingEntry.url !== candidate) return callback();

      // The existing entry was deleted after claiming this
      // url, so this entry can claim it.
      if (existingEntry.deleted) return callback();

      // If we reach this far down, it means the entry
      // which has claimed this url is still visible and
      // still uses this url, so we can't claim it.
      return callback(null, true);
    });
  });
}

// this needs to return an error if something went wrong
// and the finalized, stored url to the entry...
module.exports = function (blogID, entry, callback) {

  ensure(blogID, 'string')
    .and(entry, model)
    .and(callback, 'function');

  if (entry.draft || entry.deleted)
    return callback(null, '');

  Blog.get({id: blogID}, function(err, blog){

    if (err) return callback(err);

    // does This cause a memory leak? we sometimes
    // exist before calling all the next functions
    // if we find a successful candidate.
    forEach(Candidates(blog, entry), function(candidate, next){

      check(blogID, candidate, entry.id, function(err, taken){

        if (err) return callback(err);

        if (taken) return next();

        var key = Key(blogID, candidate);

        redis.set(key, entry.id, function(err){

          if (err) return callback(err);

          return callback(null, candidate);
        });
      });
    }, function(){

      // if we exhaust the list of candidates, what should happen?
      // just return an error for now... TODO in future, just keep
      // generating UIDS... but whatever for now.
      callback(new Error('Could not find a permalink for ' + entry.path));
    });
  });
};<commit_msg>override permalink unless specified<commit_after>var helper = require('helper');
var forEach = helper.forEach;
var ensure = helper.ensure;
var _ = require('lodash');
var urlNormalizer = helper.urlNormalizer;
var UID = helper.makeUid;
var makeSlug = helper.makeSlug;
var withoutExtension = helper.withoutExtension;
var redis = require('../client');
var Permalink = require('./build/prepare/permalink');
var Key = require('./key').url;
var model = require('./model');
var Blog = require('blog');
var get = require('./get');

//'/style.css', '/script.js', '/feed.rss', '/robots.txt', '/sitemap.xml'
// are not possible because . is replaced with. ideally check for
// all template views here...
var banned = [
  '/archives',
  '/archive',
  '/search',
  '/tagged',
  '/public',
  ''
];

var MIN_SUMMARY_SLUG_WORDS = 3;
var MAX_SUMMARY_SLUG_WORDS = 10;

var PERMALINK_PERMUTATIONS = 5;

// The random permalinks are
// this long. I figure 3 gives
// us (26 * 2 + 10)^3 (= 238,328)
// possible permalinks which is more
// than enough for one blog, espcially
// since the circumstances under
// which Blot needs to generate
// a random url are rare enough.
// It also avoids the issue of rude
// 4 letter words being generated.
var UID_LENGTH = 3;

// We generate this number
// of random permalinks in
// case all else fails.
var UID_PERMUTATIONS = 500;

// console.log(Candidates({
//   permalink: '',
//   slug: '',
//   summary: '',
//   url: '',
//   name: 'a.jpg',
//   path: '/a.jpg'
// }));

function Candidates (blog, entry) {

  var candidates = [];

  // Don't use the permalink format for pages
  // or posts with user specified permalinks.
  if (!entry.metadata.permalink && !entry.metadata.slug && !entry.metadata.url && !entry.page) {
    entry.permalink = Permalink(blog.timeZone, blog.permalink.format, entry);
  }

  // The user has specified a permalink in the
  // entry's metadata. We should use this if we can.
  if (entry.permalink) {

    candidates.push(entry.permalink);

    // If the permalink is unavailable, try appending a number
    // e.g. if 'apple', try 'apple-2', 'apple-3' ... 'apple-99'
    for (var i = 2; i < PERMALINK_PERMUTATIONS; i++)
      candidates.push(entry.permalink + '-' + i);
  }

  // This is generated from the entry's title
  if (entry.slug) {
    candidates.push(entry.slug);
  }

  if (entry.name)
    candidates.push(makeSlug(withoutExtension(entry.name)));

  if (entry.path)
    candidates.push(makeSlug(withoutExtension(entry.path.split('/').join('-'))));

  if (entry.summary) {

    var words = entry.summary.split(' ');

    for (var y = MIN_SUMMARY_SLUG_WORDS; y < words.length && y < MAX_SUMMARY_SLUG_WORDS; y++) {
      candidates.push(makeSlug(words.slice(0, y).join('-')));
    }
  }

  // If we make it path the permalink chosen by the
  // user, it's possible that we fell through to the
  // randomly generated (UID()...) permalinks below.
  // If so, we should insert the entry's previous URL
  // if it exists to ensure that the entry retains
  // a randomly generated URL consistently.
  if (entry.url)
    candidates.push(entry.url);

  for (var j = 0; j < UID_PERMUTATIONS; j++)
    candidates.push(UID(UID_LENGTH));

  // Trim, lowercase, strip trailing /, add leading /
  // ensure valid url pathname. Always returns a string
  // can be empty if invalid or just '/'
  candidates = candidates.map(urlNormalizer);

  // We store the decoded version. Not sure if this is a good idea
  // or not but it works for now. Pandoc *en*codes by default.
  // Perhaps I could store both?
  candidates = candidates.map(function(url){

    try {
      url = decodeURI(url);
    } catch (e) {
      console.log('Error: Could not decodeURIComponent for:', url, e.message);
    }

    return url;
  });

  candidates = candidates.filter(function(candidate){

    if (!candidate) return false;

    // WE DONT EVER ADD ENTRY.PATH so images are always accessible
    // It's possible that entry.name when normalized === entry.path
    if (entry.path && candidate === entry.path) return false;

    if (banned.indexOf(candidate) > -1) return false;

    return true;
  });

  candidates = _.uniq(candidates);

  return candidates;
}

function check (blogID, candidate, entryID, callback) {

  var key = Key(blogID, candidate);

  redis.get(key, function(err, existingID){

    if (err) return callback(err);

    // This url is available and unused
    if (!existingID) return callback();

    // This url points to this entry already
    if (existingID === entryID) return callback();

    // This url points to a different entry
    get(blogID, existingID, function(existingEntry){

      // For some reason (bug) the url key was
      // set but the entry does not exist. Claim the url.
      if (!existingEntry) return callback();

      // The existing entry has since moved to a different url
      // (perhaps the author modified its permalink etc...)
      // so this entry can claim this url.
      if (existingEntry.url !== candidate) return callback();

      // The existing entry was deleted after claiming this
      // url, so this entry can claim it.
      if (existingEntry.deleted) return callback();

      // If we reach this far down, it means the entry
      // which has claimed this url is still visible and
      // still uses this url, so we can't claim it.
      return callback(null, true);
    });
  });
}

// this needs to return an error if something went wrong
// and the finalized, stored url to the entry...
module.exports = function (blogID, entry, callback) {

  ensure(blogID, 'string')
    .and(entry, model)
    .and(callback, 'function');

  if (entry.draft || entry.deleted)
    return callback(null, '');

  Blog.get({id: blogID}, function(err, blog){

    if (err) return callback(err);

    // does This cause a memory leak? we sometimes
    // exist before calling all the next functions
    // if we find a successful candidate.
    forEach(Candidates(blog, entry), function(candidate, next){

      check(blogID, candidate, entry.id, function(err, taken){

        if (err) return callback(err);

        if (taken) return next();

        var key = Key(blogID, candidate);

        redis.set(key, entry.id, function(err){

          if (err) return callback(err);

          return callback(null, candidate);
        });
      });
    }, function(){

      // if we exhaust the list of candidates, what should happen?
      // just return an error for now... TODO in future, just keep
      // generating UIDS... but whatever for now.
      callback(new Error('Could not find a permalink for ' + entry.path));
    });
  });
};<|endoftext|>


<commit_before>'use strict';

let AppTask = cb_require('tasks/app-task'),
	fileUtils = cb_require('utils/file'),
	pathUtils = cb_require('utils/paths'),
	path = require('path'),
	Q = require('q'),
	AppServer = require('totvs-platform-helper/appserver'),
	DevStudio = require('totvs-platform-helper/tdscli');

let ADVPL_SRC,
	ADVPL_INCLUDES,
	ADVPL_SRC_RELATIVE;

class AdvplCompileTask extends AppTask {

	constructor(options) {
		super(options);

		ADVPL_SRC_RELATIVE = pathUtils.get('ADVPL_SRC');
		ADVPL_SRC = pathUtils.get('ADVPL_SRC', this.projectDir);
		ADVPL_INCLUDES = pathUtils.get('ADVPL_INCLUDES', this.projectDir);

		this.tdsOptions = {
			serverType: "Logix",
			server: "127.0.0.1",
			build: "7.00.150715P",
			port: 5056,
			environment: "ENVIRONMENT"
		};
	}

	run(cloudbridge, argv) {
		let currentFiles = fileUtils.readModifiedTime(ADVPL_SRC),
			previousFiles = fileUtils.loadModifiedTime(this.projectDir, ADVPL_SRC_RELATIVE),
			result = fileUtils.diff(previousFiles, currentFiles),
			compileFiles = result.modified.concat(result.added),
			removeFiles = result.removed;

		if ((compileFiles.length === 0) &&
			(removeFiles.length === 0)) {
			//console.log("Nothing to compile.");
			return Q();
		}

		var tds = new DevStudio(),
			appserver = new AppServer({
				target: pathUtils.get("APPSERVER", this.projectDir)
			});

		return appserver.start()
			.then(() => {
				this.tdsOptions.port = appserver.tcpPort;
				this.tdsOptions.build = appserver.build;
			})
			.then(() => {
				if (removeFiles.length > 0) {
					let options = Object.assign({}, this.tdsOptions, {
						program: removeFiles.map(f => path.join(ADVPL_SRC, f)),
						authorization: ''
					});

					//console.log("REMOVING FILES: " + JSON.stringify(removeFiles, null, 2));

					return tds.remove(options);
				}
			})
			.then(() => {
				if (compileFiles.length > 0) {
					let options = Object.assign({}, this.tdsOptions, {
						recompile: true,
						program: compileFiles.map(f => path.join(ADVPL_SRC, f)),
						includes: [
							ADVPL_SRC,
							ADVPL_INCLUDES
						]
					});

					//console.log("COMPILING FILES: " + JSON.stringify(compileFiles, null, 2));

					return tds.compile(options);
				}
			})
			.then(() => {
				this.saveBuildInfo(currentFiles);

				return appserver.stop();
			});
	}

	saveBuildInfo(files) {
		fileUtils.saveModifiedTime(this.projectDir, ADVPL_SRC_RELATIVE, files);
	}

}

module.exports = AdvplCompileTask;
<commit_msg>AdvPL Clean Build<commit_after>'use strict';

let AppTask = cb_require('tasks/app-task'),
	fileUtils = cb_require('utils/file'),
	pathUtils = cb_require('utils/paths'),
	path = require('path'),
	Q = require('q'),
	AppServer = require('totvs-platform-helper/appserver'),
	DevStudio = require('totvs-platform-helper/tdscli');

let ADVPL_SRC,
	ADVPL_INCLUDES,
	ADVPL_SRC_RELATIVE;

class AdvplCompileTask extends AppTask {

	constructor(options) {
		super(options);

		ADVPL_SRC_RELATIVE = pathUtils.get('ADVPL_SRC');
		ADVPL_SRC = pathUtils.get('ADVPL_SRC', this.projectDir);
		ADVPL_INCLUDES = pathUtils.get('ADVPL_INCLUDES', this.projectDir);

		this.tdsOptions = {
			serverType: "Logix",
			server: "127.0.0.1",
			build: "7.00.150715P",
			port: 5056,
			environment: "ENVIRONMENT"
		};

		this.tds = new DevStudio();

		this.appserver = new AppServer({
			target: pathUtils.get("APPSERVER", this.projectDir)
		});
	}

	run(cloudbridge, argv) {
		let cleanFiles = [];

		if (argv.clean || argv.c) {
			cleanFiles = Object.keys(fileUtils.loadModifiedTime(this.projectDir, ADVPL_SRC_RELATIVE));
			this.saveBuildInfo({});
		}

		let currentFiles = fileUtils.readModifiedTime(ADVPL_SRC),
			previousFiles = fileUtils.loadModifiedTime(this.projectDir, ADVPL_SRC_RELATIVE),
			result = fileUtils.diff(previousFiles, currentFiles),
			compileFiles = result.modified.concat(result.added),
			removeFiles = result.removed;

		if (argv.clean || argv.c) {
			removeFiles = Array.from(new Set(compileFiles.concat(removeFiles).concat(cleanFiles)));
		}

		if ((compileFiles.length === 0) &&
			(removeFiles.length === 0)) {
			//console.log("Nothing to compile.");
			return Q();
		}

		return this.appserver.start()
			.then(() => {
				this.tdsOptions.port = this.appserver.tcpPort;
				this.tdsOptions.build = this.appserver.build;
			})
			.then(() => {
				if (removeFiles.length > 0)
					return this.remove(removeFiles);
			})
			.then(() => {
				if (compileFiles.length > 0)
					return this.compile(compileFiles);
			})
			.then(() => {
				this.saveBuildInfo(currentFiles);

				return this.appserver.stop();
			});
	}

	remove(files) {
		let options = Object.assign({}, this.tdsOptions, {
			program: files.map(f => path.join(ADVPL_SRC, f)),
			authorization: ''
		});

		//console.log("REMOVING FILES: " + JSON.stringify(removeFiles, null, 2));

		return this.tds.remove(options);
	}

	compile(files) {
		if (files.length > 0) {
			let options = Object.assign({}, this.tdsOptions, {
				recompile: true,
				program: files.map(f => path.join(ADVPL_SRC, f)),
				includes: [
					ADVPL_SRC,
					ADVPL_INCLUDES
				]
			});

			//console.log("COMPILING FILES: " + JSON.stringify(compileFiles, null, 2));

			return this.tds.compile(options);
		}
	}

	saveBuildInfo(files) {
		fileUtils.saveModifiedTime(this.projectDir, ADVPL_SRC_RELATIVE, files);
	}

}

module.exports = AdvplCompileTask;
<|endoftext|>


<commit_before>import { IdentityActions } from '../../../../../app/js/profiles/store/identity'

const DEFAULT_PROFILE = {
  '@type': 'Person',
  '@context': 'http://schema.org'
}

describe('Identity Store: Sync Actions', () => {
  describe('updateCurrentIdentity', () => {
    it('should return an action of type UPDATE_CURRENT', () => {
      const profile = {
        key: 'value'
      }
      const verifications = [{
        something: true
      }]
      const expectedResult = {
        type: 'UPDATE_CURRENT',
        domainName: 'satoshi.id',
        profile: {
          key: 'value'
        },
        verifications: [{
          something: true
        }],
        zoneFile: 'test'
      }
      const actualResult = IdentityActions.updateCurrentIdentity('satoshi.id',
      profile, verifications, 'test')
      assert.deepEqual(actualResult, expectedResult)
    })
  })

  describe('createNewIdentity', () => {
    it('should return an action of type CREATE_NEW', () => {
      const expectedResult = {
        type: 'CREATE_NEW',
        domainName: 'satoshi.id',
        ownerAddress: '17jxDTPDx51CTga1Sw3ezGQKYcJysPNeQC'
      }
      const actualResult = IdentityActions.createNewIdentity('satoshi.id', '17jxDTPDx51CTga1Sw3ezGQKYcJysPNeQC')
      assert.deepEqual(actualResult, expectedResult)
    })
  })

  describe('updateOwnedIdentities', () => {
    it('should return an action of type UPDATE_IDENTITIES', () => {
      const namesOwned = ['satoshi.id']
      const localIdentities = {
        'satoshi.id': {
          domainName: 'satoshi.id',
          profile: DEFAULT_PROFILE,
          verifications: [],
          registered: false
        }
      }
      const expectedResult = {
        type: 'UPDATE_IDENTITIES',
        localIdentities: {
          'satoshi.id': {
            domainName: 'satoshi.id',
            profile: DEFAULT_PROFILE,
            verifications: [],
            registered: false
          }
        },
        namesOwned: ['satoshi.id']
      }
      const actualResult = IdentityActions.updateOwnedIdentities(localIdentities, namesOwned)
      assert.deepEqual(actualResult, expectedResult)
    })
  })

  describe('updateProfile', () => {
    it('should return an action of type UPDATE_PROFILE', () => {
      const profile = {
        key: 'value'
      }
      const expectedResult = {
        type: 'UPDATE_PROFILE',
        domainName: 'satoshi.id',
        profile: {
          key: 'value'
        },
        zoneFile: 'test'
      }
      const actualResult = IdentityActions.updateProfile('satoshi.id', profile, 'test')
      assert.deepEqual(actualResult, expectedResult)
    })
  })

  describe('calculateLocalIdentities', () => {
    it('should add newly owned name with default profile to localIdentities collection', () => {
      const localIdentities = {
        'satoshi.id': {
          domainName: 'satoshi.id',
          profile: DEFAULT_PROFILE,
          verifications: [],
          registered: true
        }
      }
      const namesOwned = ['muneeb.id', 'satoshi.id']
      const expectedResult = {
        'satoshi.id': {
          domainName: 'satoshi.id',
          profile: DEFAULT_PROFILE,
          verifications: [],
          registered: true
        },
        'muneeb.id': {
          domainName: 'muneeb.id',
          profile: DEFAULT_PROFILE,
          verifications: [],
          registered: true
        }
      }
      const actualResult = IdentityActions.calculateLocalIdentities(localIdentities, namesOwned)
      assert.deepEqual(actualResult, expectedResult)
    })

    it('should add mark owned names with existing profiles as registered', () => {
      const localIdentities = {
        'satoshi.id': {
          domainName: 'satoshi.id',
          profile: { key: 'value' },
          verifications: [],
          registered: false
        },
        'muneeb.id': {
          domainName: 'muneeb.id',
          profile: { key: 'value' },
          verifications: [],
          registered: false
        },
        'blockstack.id': {
          domainName: 'blockstack.id',
          profile: DEFAULT_PROFILE,
          verifications: [],
          registered: false
        }
      }
      const namesOwned = ['muneeb.id', 'ryan.id', 'satoshi.id']
      const expectedResult = {
        'satoshi.id': {
          domainName: 'satoshi.id',
          profile: { key: 'value' },
          verifications: [],
          registered: true
        },
        'muneeb.id': {
          domainName: 'muneeb.id',
          profile: { key: 'value' },
          verifications: [],
          registered: true
        },
        'ryan.id': {
          domainName: 'ryan.id',
          profile: DEFAULT_PROFILE,
          verifications: [],
          registered: true
        },
        'blockstack.id': {
          domainName: 'blockstack.id',
          profile: DEFAULT_PROFILE,
          verifications: [],
          registered: false
        }
      }
      const actualResult = IdentityActions.calculateLocalIdentities(localIdentities, namesOwned)
      assert.deepEqual(actualResult, expectedResult)
    })
  })

  describe('addUsername', () => {
    it('should return an action of type ADD_USERNAME', () => {
      const expectedResult = {
        type: 'ADD_USERNAME',
        domainName: 'satoshi.id',
        ownerAddress: '17jxDTPDx51CTga1Sw3ezGQKYcJysPNeQC',
        zoneFile: 'test'
      }
      const actualResult = IdentityActions.addUsername('satoshi.id',
      '17jxDTPDx51CTga1Sw3ezGQKYcJysPNeQC', 'test')
      assert.deepEqual(actualResult, expectedResult)
    })
  })
})
<commit_msg>refactor test to verify verifications key is persisted<commit_after>import { IdentityActions } from '../../../../../app/js/profiles/store/identity'

const DEFAULT_PROFILE = {
  '@type': 'Person',
  '@context': 'http://schema.org'
}

describe('Identity Store: Sync Actions', () => {
  describe('updateCurrentIdentity', () => {
    it('should return an action of type UPDATE_CURRENT', () => {
      const profile = {
        key: 'value'
      }
      const verifications = [{
        something: true
      }]
      const expectedResult = {
        type: 'UPDATE_CURRENT',
        domainName: 'satoshi.id',
        profile: {
          key: 'value'
        },
        verifications: [{
          something: true
        }],
        zoneFile: 'test'
      }
      const actualResult = IdentityActions.updateCurrentIdentity('satoshi.id',
      profile, verifications, 'test')
      assert.deepEqual(actualResult, expectedResult)
    })
  })

  describe('createNewIdentity', () => {
    it('should return an action of type CREATE_NEW', () => {
      const expectedResult = {
        type: 'CREATE_NEW',
        domainName: 'satoshi.id',
        ownerAddress: '17jxDTPDx51CTga1Sw3ezGQKYcJysPNeQC'
      }
      const actualResult = IdentityActions.createNewIdentity('satoshi.id', '17jxDTPDx51CTga1Sw3ezGQKYcJysPNeQC')
      assert.deepEqual(actualResult, expectedResult)
    })
  })

  describe('updateOwnedIdentities', () => {
    it('should return an action of type UPDATE_IDENTITIES', () => {
      const namesOwned = ['satoshi.id']
      const localIdentities = {
        'satoshi.id': {
          domainName: 'satoshi.id',
          profile: DEFAULT_PROFILE,
          verifications: [],
          registered: false
        }
      }
      const expectedResult = {
        type: 'UPDATE_IDENTITIES',
        localIdentities: {
          'satoshi.id': {
            domainName: 'satoshi.id',
            profile: DEFAULT_PROFILE,
            verifications: [],
            registered: false
          }
        },
        namesOwned: ['satoshi.id']
      }
      const actualResult = IdentityActions.updateOwnedIdentities(localIdentities, namesOwned)
      assert.deepEqual(actualResult, expectedResult)
    })
  })

  describe('updateProfile', () => {
    it('should return an action of type UPDATE_PROFILE', () => {
      const profile = {
        key: 'value'
      }
      const expectedResult = {
        type: 'UPDATE_PROFILE',
        domainName: 'satoshi.id',
        profile: {
          key: 'value'
        },
        zoneFile: 'test',
        verifications: []
      }
      const actualResult = IdentityActions.updateProfile('satoshi.id', profile, 'test', [])
      assert.deepEqual(actualResult, expectedResult)
    })
  })

  describe('calculateLocalIdentities', () => {
    it('should add newly owned name with default profile to localIdentities collection', () => {
      const localIdentities = {
        'satoshi.id': {
          domainName: 'satoshi.id',
          profile: DEFAULT_PROFILE,
          verifications: [],
          registered: true
        }
      }
      const namesOwned = ['muneeb.id', 'satoshi.id']
      const expectedResult = {
        'satoshi.id': {
          domainName: 'satoshi.id',
          profile: DEFAULT_PROFILE,
          verifications: [],
          registered: true
        },
        'muneeb.id': {
          domainName: 'muneeb.id',
          profile: DEFAULT_PROFILE,
          verifications: [],
          registered: true
        }
      }
      const actualResult = IdentityActions.calculateLocalIdentities(localIdentities, namesOwned)
      assert.deepEqual(actualResult, expectedResult)
    })

    it('should add mark owned names with existing profiles as registered', () => {
      const localIdentities = {
        'satoshi.id': {
          domainName: 'satoshi.id',
          profile: { key: 'value' },
          verifications: [],
          registered: false
        },
        'muneeb.id': {
          domainName: 'muneeb.id',
          profile: { key: 'value' },
          verifications: [],
          registered: false
        },
        'blockstack.id': {
          domainName: 'blockstack.id',
          profile: DEFAULT_PROFILE,
          verifications: [],
          registered: false
        }
      }
      const namesOwned = ['muneeb.id', 'ryan.id', 'satoshi.id']
      const expectedResult = {
        'satoshi.id': {
          domainName: 'satoshi.id',
          profile: { key: 'value' },
          verifications: [],
          registered: true
        },
        'muneeb.id': {
          domainName: 'muneeb.id',
          profile: { key: 'value' },
          verifications: [],
          registered: true
        },
        'ryan.id': {
          domainName: 'ryan.id',
          profile: DEFAULT_PROFILE,
          verifications: [],
          registered: true
        },
        'blockstack.id': {
          domainName: 'blockstack.id',
          profile: DEFAULT_PROFILE,
          verifications: [],
          registered: false
        }
      }
      const actualResult = IdentityActions.calculateLocalIdentities(localIdentities, namesOwned)
      assert.deepEqual(actualResult, expectedResult)
    })
  })

  describe('addUsername', () => {
    it('should return an action of type ADD_USERNAME', () => {
      const expectedResult = {
        type: 'ADD_USERNAME',
        domainName: 'satoshi.id',
        ownerAddress: '17jxDTPDx51CTga1Sw3ezGQKYcJysPNeQC',
        zoneFile: 'test'
      }
      const actualResult = IdentityActions.addUsername('satoshi.id',
      '17jxDTPDx51CTga1Sw3ezGQKYcJysPNeQC', 'test')
      assert.deepEqual(actualResult, expectedResult)
    })
  })
})
<|endoftext|>