<commit_before>// ==UserScript==
// @name         JIRA Tree
// @namespace    http://elnarion.ad.loc/
// @version      0.6
// @description  shows a tree widget with all issues linked as child to the selected issue
// @author       dev.lauer
// @match        *://*/*/secure/Dashboar*
// @match        *://*/secure/Dashboar*
// @grant        none
// @require https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.4/jquery.min.js
// @require https://cdnjs.cloudflare.com/ajax/libs/jquery-contextmenu/2.2.3/jquery.contextMenu.min.js
// @require https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.0/jquery-ui.min.js
// @require https://cdnjs.cloudflare.com/ajax/libs/jquery-contextmenu/2.2.3/jquery.ui.position.min.js
// @require https://cdnjs.cloudflare.com/ajax/libs/jstree/3.3.1/jstree.min.js
// @run-at document-end
// ==/UserScript==

(function() {
    'use strict';
    $(document).ready(function() {

        /////////////////////////////////////////////////////////////////////////////////////
        // custom css
        ////////////////////////////////////////////////////////////////////////////////////

        // add css for context menu
        var link = document.createElement('link');
        link.rel = 'stylesheet';
        link.type = 'text/css';
        link.href = 'https://cdnjs.cloudflare.com/ajax/libs/jquery-contextmenu/2.2.3/jquery.contextMenu.min.css';
        document.getElementsByTagName("head")[0].appendChild(link);
        link = document.createElement('link');
        link.rel = 'stylesheet';
        link.type = 'text/css';
        link.href = 'https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.0/jquery-ui.min.css';
        document.getElementsByTagName("head")[0].appendChild(link);
        link = document.createElement('link');
        link.rel = 'stylesheet';
        link.type = 'text/css';
        link.href = 'https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.0/jquery-ui.theme.min.css';
        document.getElementsByTagName("head")[0].appendChild(link);
        link = document.createElement('link');
        link.rel = 'stylesheet';
        link.type = 'text/css';
        link.href = 'https://cdnjs.cloudflare.com/ajax/libs/jstree/3.3.2/themes/default/style.min.css';
        document.getElementsByTagName("head")[0].appendChild(link);
        // small workaround for IE which does not like creating styles
        var css = ""+
            ".ui-dialog { overflow:hidden} "+
            ".jstree-contextmenu { z-index: 999}"
        ;
        var htmlDiv = document.createElement('div');
        htmlDiv.innerHTML = '
foo
';
        document.getElementsByTagName("head")[0].appendChild(htmlDiv.childNodes[1]);

        /////////////////////////////////////////////////////////////////////////////////////
        // dialog widget
        ////////////////////////////////////////////////////////////////////////////////////

        $('body').append("

 ");
        $( "#dialogBaum" ).dialog({autoOpen: false,
                                   modal: true});
        /////////////////////////////////////////////////////////////////////////////////////
        // tree widget
        ////////////////////////////////////////////////////////////////////////////////////

        $('#treetable').jstree(
            { 'core' :
             { 'data' :
              [
                  {id:'id1',text:'Simple root node',parent:'#'}
              ]
             },
             "plugins" : [ "contextmenu" ],
             "contextmenu": {
                 "items": function ($node) {
                     console.log('test');
                     return {
                         "ShowInWindow": {
                             "label": "show in new window",
                             "action": function (obj) {
                                 de.elnarion.jira.showIssue(obj.reference[0].parentElement.id,true);
                             }
                         },
                         "Show": {
                             "label": "show in same window",
                             "action": function (obj) {
                                 de.elnarion.jira.showIssue(obj.reference[0].parentElement.id,false);
                             }
                         }
                     };
                 }
             }
            }
        );
        /////////////////////////////////////////////////////////////////////////////////////
        // standard context-menu
        ////////////////////////////////////////////////////////////////////////////////////

        $(".dashboard-item-frame").contextMenu({
            selector: '.issuekey',
            callback: function(key, options) {
                var issueKey = options.$trigger.find(".issue-link").attr('data-issue-key');
                de.elnarion.jira.paintTree(issueKey);
            },
            items: {
                "Treeview": {name: "Treeview", icon: "tree"}
            }
        });

        /////////////////////////////////////////////////////////////////////////////////////
        // custom namespace de.elnarion
        // - used to separate all custom functions from the global namespace
        ////////////////////////////////////////////////////////////////////////////////////
        window.de = window.de || {};
        de.elnarion = function(){
            return {
            };
        }();
        de.elnarion.counter = 0;
        de.elnarion.jira = function(){
            /////////////////////////////////////
            // private defaultvalues
            ////////////////////////////////////
            var styleObject = {
                'Done':'background-color:lightgray;color:darkgray;text-decoration: line-through',
                'Geschlossen':'background-color:lightgray;color:darkgray;text-decoration: line-through',
                'Fertig':'background-color:lightgray;color:darkgray;text-decoration: line-through',
                'Erfolgreich':'background-color:lightgray;color:darkgray;text-decoration: line-through',
                'Open':'background-color:#ffcccc',
                'Offen':'background-color:#ffcccc',
                'Analyse':'background-color:#ffcc00',
                'Reopened':'background-color:#ffcccc',
                'Erneut geöffnet':'background-color:#ffcccc',
                'To Do':'background-color:#ffcccc',
                'Aufgaben':'background-color:#ffcccc',
                'In Bestätigung':'background-color:#ffcccc',
                'Vorlage zur Bestätigung':'background-color:#ffcccc',
                'Freigegeben':'background-color:#ffcccc',
                'Abgestimmt':'',
                'Bestätigt':'',
                'Resolved':'background-color:#ccffcc',
                'Erledigt':'background-color:#ccffcc',
                'In Progress':'background-color:#b3b3ff',
                'In Arbeit':'background-color:#b3b3ff',
                'Backlog':'background-color:#e6ccb3'
            };
            var debug = true;
            var baseContext = "/jira";
            var baseURL = '';
            var baseBrowseURL = '';
            var imageURL = '';
            var usedLinkTypes = [
                'is blocked by',
                'hängt ab von',
                'Umsetzung von',
                'verbunden mit',
                'references'
                ];
            var currentIssue = {};
            var promiseContext = $.ajax({
                url: baseContext+"/rest/api/latest/serverInfo",
                type: "GET",
                dataType: "json",
                contentType: "application/json",
            }).fail(function( jqXHR, textStatus, errorThrown  ) {
                console.log('Context not found! Error:'+errorThrown);
                baseContext = '';
            }).always(function(data, textStatus, jqXHR){
                baseURL = baseContext+"/rest/api/latest/";
                baseBrowseURL = baseContext+"/browse/";
                imageURL = baseContext+"/images/icons/issuetypes/";
            });
            /////////////////////////////////////
            // public getIssue()
            //    issuekey - the issuekey in jira
            //    options - currently ignored
            //
            // calls the jira REST-API and returns some data (timetracking, subtasks,
            // sub-tasks, issuelinks, issuetype, status) for the issue
            /////////////////////////////////////
            function getIssue(issuekey, options){
                var promise = $.ajax({
                    url: baseURL+"issue/"+ issuekey +"?fields=timetracking,subtasks,sub-tasks,issuelinks,issuetype,status",
                    type: "GET",
                    dataType: "json",
                    contentType: "application/json",
                });
                return promise;
            }
            /////////////////////////////////////
            // private paintTreeWidget()
            //    root - a json-data array for the widget
            //
            // refreshes the existing widget with the name "dialogBaum"
            // with the data contained in the root-object
            /////////////////////////////////////
            function paintTreeWidget(root)
            {
                $('#treetable').jstree(true).settings.core.data = root;
                $('#treetable').jstree(true).refresh();
                $( "#dialogBaum" ).dialog({autoOpen: false, modal: true});
                $( "#dialogBaum" ).dialog('open');
                if(debug)
                {
                    console.log("tree");
                    console.log(root);
                }
            }
            /////////////////////////////////////
            // private resolveData()
            //    childObject - an object referencing an issue
            //    root - a json-data array for the tree-widget
            //
            // resolves the data for the passed-in issue and
            // repaints the tree-widget if all recursive resolving
            // is done
            /////////////////////////////////////
            function resolveData(childObject,root)
            {
                de.elnarion.counter++;
                childObject.state = {  opened    : true  };
                $.when(getIssue(childObject.id).done(function(data){
                    var result = buildTree(data,root,childObject);
                    de.elnarion.counter--;
                    if(de.elnarion.counter===0)
                    {
                        paintTreeWidget(root);
                    }
                }));
            }
            /////////////////////////////////////
            // private fillIssueData()
            //    issue - an object referencing an issue
            //    parentID - the key of the parent issue
            //
            // sets the data for a tree-node-object and its style
            // and returns this object
            /////////////////////////////////////
            function fillIssueData(issue,parentID)
            {
                var result={};
                if(debug)
                {
                    console.log('issue');
                    console.log(issue);
                }
                result.id = issue.key;
                result.parent = parentID;
                result.text = issue.fields.summary;
                if(result.text=== undefined)
                    result.text=issue.key;
                var styleKey = issue.fields.status.name;
                if(debug)
                {
                    console.log('style for reference is');
                    console.log('::'+styleKey+'::');
                    console.log('styleObject'+styleObject[styleKey]);
                }
                if(!(styleObject[styleKey]===undefined))
                {
                    result.a_attr = { style:styleObject[styleKey]};
                }
                result.icon = issue.fields.issuetype.iconUrl;
                result.state = {  opened    : true  };
                return result;
            }
            /////////////////////////////////////
            // private handleTreeData()
            //    treeArray - an object array representing all nodes of a tree
            //    issue - the issuedata to be added to the tree
            //    parentID - the key of the parent issue
            //
            // sets the data for a tree-node and its style
            /////////////////////////////////////
            function handleTreeData(treeArray,issue,parentId)
            {
                var child={};
                child = fillIssueData(issue, parentId);
                treeArray.push(child);
                resolveData(child,treeArray);
            }
            /////////////////////////////////////
            // private buildTree()
            //    tree - an object array representing all nodes of a tree
            //    issue - the issue as root of the tree
            //    parent - the key of the parent issue
            //
            // builds an object array representing all nodes of a tree
            // by iterating through all referenced issues, subTasks etc.
            // after everything is finished a tree-widget is painted or refreshed.
            /////////////////////////////////////
            function buildTree(issue,tree,parent)
            {
                var children = false;
                var child={};
                var links = issue.fields.issuelinks;
                var linksLength = links.length;
                var i = 0;
                var jsontree;
                for(i=0;i<linksLength;i++)
                {

                    if(!(links[i].type===undefined))
                    {
                        jsontree = JSON.stringify(tree);
                        if(debug)
                        {
                            console.log('issuetype');
                            console.log(links[i].type.inward);
                            console.log(links[i]);
                        }
                        var linkedissue = {};
                        if(!(links[i].inwardIssue===undefined))
                        {
                            linkedissue=links[i].inwardIssue;
                        }
                        if(!(links[i].outwardIssue===undefined))
                        {
                            linkedissue=links[i].outwardIssue;
                        }
                        if(jsontree.indexOf(linkedissue.key)>-1)
                        {
                            console.log('ignored issue'+linkedissue.key+' already inside');
                        }
                        else if (usedLinkTypes.indexOf(links[i].type.inward)>-1)
                        {
                            handleTreeData(tree,linkedissue, parent.id);
                            children = true;
                        }
                    }
                }
                var subTasks = issue.fields.subtasks;
                if (!(subTasks === undefined ))
                {
                    var subTasksLength = subTasks.length;
                    for(i=0;i<subTasksLength;i++)
                    {
                        if(debug)
                        {
                            console.log('subtask');
                            console.log(subTasks[i]);
                        }
                        handleTreeData(tree,subTasks[i], parent.id);
                        children = true;
                    }
                }
                if((tree[0]==parent)&&(!children))
                {
                    paintTreeWidget(tree);
                }
                return tree;
            }
            /////////////////////////////////////
            // public paintTree()
            //    issuekey - the issuekey of the root-issue of the tree
            //
            // paints a tree of an issue and its descendants
            /////////////////////////////////////
            function paintTree(issuekey){
                var promise = getIssue(issuekey);
                var tree = {};
                $.when(promise.done(function(data){
                    tree = fillIssueData(data,'#');
                    buildTree(data,[tree],tree);
                }));
            }
            /////////////////////////////////////
            // public showIssue()
            //    issuekey - the issuekey of the root-issue of the tree
            //    newWindow - true for opening the issue in a new window,
            //                false for opening the issue in the same window
            //
            // open an issue in jira
            /////////////////////////////////////
            function showIssue(issuekey,newWindow)
            {
                if(debug)
                {
                    console.log(issuekey+"showIssue");
                }
                var windowname = '_blank';
                if(!newWindow)
                {
                    windowname = '_self';
                }
                window.open(baseBrowseURL+issuekey, windowname);
            }
            /////////////////////////////////////
            // public functions
            /////////////////////////////////////
            return {
                getIssue: getIssue,
                paintTree: paintTree,
                showIssue: showIssue
            };
        }();

        /////////////////////////////////////////////////////////////////////////////////////
        // end custom namespace de.elnarion
        ////////////////////////////////////////////////////////////////////////////////////
    });
})();<commit_msg>changed version<commit_after>// ==UserScript==
// @name         JIRA Tree
// @namespace    http://elnarion.ad.loc/
// @version      0.7
// @description  shows a tree widget with all issues linked as child to the selected issue
// @author       dev.lauer
// @match        *://*/*/secure/Dashboar*
// @match        *://*/secure/Dashboar*
// @grant        none
// @require https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.4/jquery.min.js
// @require https://cdnjs.cloudflare.com/ajax/libs/jquery-contextmenu/2.2.3/jquery.contextMenu.min.js
// @require https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.0/jquery-ui.min.js
// @require https://cdnjs.cloudflare.com/ajax/libs/jquery-contextmenu/2.2.3/jquery.ui.position.min.js
// @require https://cdnjs.cloudflare.com/ajax/libs/jstree/3.3.1/jstree.min.js
// @run-at document-end
// ==/UserScript==

(function() {
    'use strict';
    $(document).ready(function() {

        /////////////////////////////////////////////////////////////////////////////////////
        // custom css
        ////////////////////////////////////////////////////////////////////////////////////

        // add css for context menu
        var link = document.createElement('link');
        link.rel = 'stylesheet';
        link.type = 'text/css';
        link.href = 'https://cdnjs.cloudflare.com/ajax/libs/jquery-contextmenu/2.2.3/jquery.contextMenu.min.css';
        document.getElementsByTagName("head")[0].appendChild(link);
        link = document.createElement('link');
        link.rel = 'stylesheet';
        link.type = 'text/css';
        link.href = 'https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.0/jquery-ui.min.css';
        document.getElementsByTagName("head")[0].appendChild(link);
        link = document.createElement('link');
        link.rel = 'stylesheet';
        link.type = 'text/css';
        link.href = 'https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.0/jquery-ui.theme.min.css';
        document.getElementsByTagName("head")[0].appendChild(link);
        link = document.createElement('link');
        link.rel = 'stylesheet';
        link.type = 'text/css';
        link.href = 'https://cdnjs.cloudflare.com/ajax/libs/jstree/3.3.2/themes/default/style.min.css';
        document.getElementsByTagName("head")[0].appendChild(link);
        // small workaround for IE which does not like creating styles
        var css = ""+
            ".ui-dialog { overflow:hidden} "+
            ".jstree-contextmenu { z-index: 999}"
        ;
        var htmlDiv = document.createElement('div');
        htmlDiv.innerHTML = '
foo
';
        document.getElementsByTagName("head")[0].appendChild(htmlDiv.childNodes[1]);

        /////////////////////////////////////////////////////////////////////////////////////
        // dialog widget
        ////////////////////////////////////////////////////////////////////////////////////

        $('body').append("

 ");
        $( "#dialogBaum" ).dialog({autoOpen: false,
                                   modal: true});
        /////////////////////////////////////////////////////////////////////////////////////
        // tree widget
        ////////////////////////////////////////////////////////////////////////////////////

        $('#treetable').jstree(
            { 'core' :
             { 'data' :
              [
                  {id:'id1',text:'Simple root node',parent:'#'}
              ]
             },
             "plugins" : [ "contextmenu" ],
             "contextmenu": {
                 "items": function ($node) {
                     console.log('test');
                     return {
                         "ShowInWindow": {
                             "label": "show in new window",
                             "action": function (obj) {
                                 de.elnarion.jira.showIssue(obj.reference[0].parentElement.id,true);
                             }
                         },
                         "Show": {
                             "label": "show in same window",
                             "action": function (obj) {
                                 de.elnarion.jira.showIssue(obj.reference[0].parentElement.id,false);
                             }
                         }
                     };
                 }
             }
            }
        );
        /////////////////////////////////////////////////////////////////////////////////////
        // standard context-menu
        ////////////////////////////////////////////////////////////////////////////////////

        $(".dashboard-item-frame").contextMenu({
            selector: '.issuekey',
            callback: function(key, options) {
                var issueKey = options.$trigger.find(".issue-link").attr('data-issue-key');
                de.elnarion.jira.paintTree(issueKey);
            },
            items: {
                "Treeview": {name: "Treeview", icon: "tree"}
            }
        });

        /////////////////////////////////////////////////////////////////////////////////////
        // custom namespace de.elnarion
        // - used to separate all custom functions from the global namespace
        ////////////////////////////////////////////////////////////////////////////////////
        window.de = window.de || {};
        de.elnarion = function(){
            return {
            };
        }();
        de.elnarion.counter = 0;
        de.elnarion.jira = function(){
            /////////////////////////////////////
            // private defaultvalues
            ////////////////////////////////////
            var styleObject = {
                'Done':'background-color:lightgray;color:darkgray;text-decoration: line-through',
                'Geschlossen':'background-color:lightgray;color:darkgray;text-decoration: line-through',
                'Fertig':'background-color:lightgray;color:darkgray;text-decoration: line-through',
                'Erfolgreich':'background-color:lightgray;color:darkgray;text-decoration: line-through',
                'Open':'background-color:#ffcccc',
                'Offen':'background-color:#ffcccc',
                'Analyse':'background-color:#ffcc00',
                'Reopened':'background-color:#ffcccc',
                'Erneut geöffnet':'background-color:#ffcccc',
                'To Do':'background-color:#ffcccc',
                'Aufgaben':'background-color:#ffcccc',
                'In Bestätigung':'background-color:#ffcccc',
                'Vorlage zur Bestätigung':'background-color:#ffcccc',
                'Freigegeben':'background-color:#ffcccc',
                'Abgestimmt':'',
                'Bestätigt':'',
                'Resolved':'background-color:#ccffcc',
                'Erledigt':'background-color:#ccffcc',
                'In Progress':'background-color:#b3b3ff',
                'In Arbeit':'background-color:#b3b3ff',
                'Backlog':'background-color:#e6ccb3'
            };
            var debug = true;
            var baseContext = "/jira";
            var baseURL = '';
            var baseBrowseURL = '';
            var imageURL = '';
            var usedLinkTypes = [
                'is blocked by',
                'hängt ab von',
                'Umsetzung von',
                'verbunden mit',
                'references'
                ];
            var currentIssue = {};
            var promiseContext = $.ajax({
                url: baseContext+"/rest/api/latest/serverInfo",
                type: "GET",
                dataType: "json",
                contentType: "application/json",
            }).fail(function( jqXHR, textStatus, errorThrown  ) {
                console.log('Context not found! Error:'+errorThrown);
                baseContext = '';
            }).always(function(data, textStatus, jqXHR){
                baseURL = baseContext+"/rest/api/latest/";
                baseBrowseURL = baseContext+"/browse/";
                imageURL = baseContext+"/images/icons/issuetypes/";
            });
            /////////////////////////////////////
            // public getIssue()
            //    issuekey - the issuekey in jira
            //    options - currently ignored
            //
            // calls the jira REST-API and returns some data (timetracking, subtasks,
            // sub-tasks, issuelinks, issuetype, status) for the issue
            /////////////////////////////////////
            function getIssue(issuekey, options){
                var promise = $.ajax({
                    url: baseURL+"issue/"+ issuekey +"?fields=timetracking,subtasks,sub-tasks,issuelinks,issuetype,status",
                    type: "GET",
                    dataType: "json",
                    contentType: "application/json",
                });
                return promise;
            }
            /////////////////////////////////////
            // private paintTreeWidget()
            //    root - a json-data array for the widget
            //
            // refreshes the existing widget with the name "dialogBaum"
            // with the data contained in the root-object
            /////////////////////////////////////
            function paintTreeWidget(root)
            {
                $('#treetable').jstree(true).settings.core.data = root;
                $('#treetable').jstree(true).refresh();
                $( "#dialogBaum" ).dialog({autoOpen: false, modal: true});
                $( "#dialogBaum" ).dialog('open');
                if(debug)
                {
                    console.log("tree");
                    console.log(root);
                }
            }
            /////////////////////////////////////
            // private resolveData()
            //    childObject - an object referencing an issue
            //    root - a json-data array for the tree-widget
            //
            // resolves the data for the passed-in issue and
            // repaints the tree-widget if all recursive resolving
            // is done
            /////////////////////////////////////
            function resolveData(childObject,root)
            {
                de.elnarion.counter++;
                childObject.state = {  opened    : true  };
                $.when(getIssue(childObject.id).done(function(data){
                    var result = buildTree(data,root,childObject);
                    de.elnarion.counter--;
                    if(de.elnarion.counter===0)
                    {
                        paintTreeWidget(root);
                    }
                }));
            }
            /////////////////////////////////////
            // private fillIssueData()
            //    issue - an object referencing an issue
            //    parentID - the key of the parent issue
            //
            // sets the data for a tree-node-object and its style
            // and returns this object
            /////////////////////////////////////
            function fillIssueData(issue,parentID)
            {
                var result={};
                if(debug)
                {
                    console.log('issue');
                    console.log(issue);
                }
                result.id = issue.key;
                result.parent = parentID;
                result.text = issue.fields.summary;
                if(result.text=== undefined)
                    result.text=issue.key;
                var styleKey = issue.fields.status.name;
                if(debug)
                {
                    console.log('style for reference is');
                    console.log('::'+styleKey+'::');
                    console.log('styleObject'+styleObject[styleKey]);
                }
                if(!(styleObject[styleKey]===undefined))
                {
                    result.a_attr = { style:styleObject[styleKey]};
                }
                result.icon = issue.fields.issuetype.iconUrl;
                result.state = {  opened    : true  };
                return result;
            }
            /////////////////////////////////////
            // private handleTreeData()
            //    treeArray - an object array representing all nodes of a tree
            //    issue - the issuedata to be added to the tree
            //    parentID - the key of the parent issue
            //
            // sets the data for a tree-node and its style
            /////////////////////////////////////
            function handleTreeData(treeArray,issue,parentId)
            {
                var child={};
                child = fillIssueData(issue, parentId);
                treeArray.push(child);
                resolveData(child,treeArray);
            }
            /////////////////////////////////////
            // private buildTree()
            //    tree - an object array representing all nodes of a tree
            //    issue - the issue as root of the tree
            //    parent - the key of the parent issue
            //
            // builds an object array representing all nodes of a tree
            // by iterating through all referenced issues, subTasks etc.
            // after everything is finished a tree-widget is painted or refreshed.
            /////////////////////////////////////
            function buildTree(issue,tree,parent)
            {
                var children = false;
                var child={};
                var links = issue.fields.issuelinks;
                var linksLength = links.length;
                var i = 0;
                var jsontree;
                for(i=0;i<linksLength;i++)
                {

                    if(!(links[i].type===undefined))
                    {
                        jsontree = JSON.stringify(tree);
                        if(debug)
                        {
                            console.log('issuetype');
                            console.log(links[i].type.inward);
                            console.log(links[i]);
                        }
                        var linkedissue = {};
                        if(!(links[i].inwardIssue===undefined))
                        {
                            linkedissue=links[i].inwardIssue;
                        }
                        if(!(links[i].outwardIssue===undefined))
                        {
                            linkedissue=links[i].outwardIssue;
                        }
                        if(jsontree.indexOf(linkedissue.key)>-1)
                        {
                            console.log('ignored issue'+linkedissue.key+' already inside');
                        }
                        else if (usedLinkTypes.indexOf(links[i].type.inward)>-1)
                        {
                            handleTreeData(tree,linkedissue, parent.id);
                            children = true;
                        }
                    }
                }
                var subTasks = issue.fields.subtasks;
                if (!(subTasks === undefined ))
                {
                    var subTasksLength = subTasks.length;
                    for(i=0;i<subTasksLength;i++)
                    {
                        if(debug)
                        {
                            console.log('subtask');
                            console.log(subTasks[i]);
                        }
                        handleTreeData(tree,subTasks[i], parent.id);
                        children = true;
                    }
                }
                if((tree[0]==parent)&&(!children))
                {
                    paintTreeWidget(tree);
                }
                return tree;
            }
            /////////////////////////////////////
            // public paintTree()
            //    issuekey - the issuekey of the root-issue of the tree
            //
            // paints a tree of an issue and its descendants
            /////////////////////////////////////
            function paintTree(issuekey){
                var promise = getIssue(issuekey);
                var tree = {};
                $.when(promise.done(function(data){
                    tree = fillIssueData(data,'#');
                    buildTree(data,[tree],tree);
                }));
            }
            /////////////////////////////////////
            // public showIssue()
            //    issuekey - the issuekey of the root-issue of the tree
            //    newWindow - true for opening the issue in a new window,
            //                false for opening the issue in the same window
            //
            // open an issue in jira
            /////////////////////////////////////
            function showIssue(issuekey,newWindow)
            {
                if(debug)
                {
                    console.log(issuekey+"showIssue");
                }
                var windowname = '_blank';
                if(!newWindow)
                {
                    windowname = '_self';
                }
                window.open(baseBrowseURL+issuekey, windowname);
            }
            /////////////////////////////////////
            // public functions
            /////////////////////////////////////
            return {
                getIssue: getIssue,
                paintTree: paintTree,
                showIssue: showIssue
            };
        }();

        /////////////////////////////////////////////////////////////////////////////////////
        // end custom namespace de.elnarion
        ////////////////////////////////////////////////////////////////////////////////////
    });
})();<|endoftext|>


<commit_before>/**
 *
 * Example test script for Hello OpenFin App using Mocha, CHAI and WebdriverIO (http://webdriver.io)
 * ChromeDriver must be running before test.
 *
 */

"use strict";

var should = require('chai').should(),
    webdriver = require('webdriverio'),
    assert = require("assert"),
    config = require("../../config"),
    spawn = require('child_process').spawn;

describe('Hello OpenFin App testing with webdriver.io', function() {
    var client, notificationButton, cpuInfoButton, cpuInfoExitButton;

    this.timeout(config.testTimeout);

    before(function(done) {

        // configure webdriver
        var driverOptions = {
            desiredCapabilities: config.desiredCapabilities,
            host: config.remoteDriverHost,
            port: config.remoteDriverPort,
            waitforTimeout: config.testTimeout,
            logLevel: 'debug'
        };
        client = webdriver.remote(driverOptions);

        if (!config.remoteDriverPath) {
            client.requestHandler.startPath = "";  // webdriverio defaults it to '/wd/hub';
        }
        client.init().then(function () {
            client.timeouts("implicit", config.testTimeout).then(function (t) {
                client.timeouts("script", config.testTimeout).then(function (t2) {
                    client.timeouts("page load", config.testTimeout).then(function (t3) {
                        done();
                    })
                });

            });
        });
    });

    after(function() {
        return client.end();
    });

    /**
     * Select a Window
     * @param windowHandle handle of the window
     * @param callback callback with window title if selection is successful
     */
    function switchWindow(windowHandle, callback) {
        client.switchTab(windowHandle).then(function () {
            client.getTitle().then(function (title) {
                callback(title);
            });
        });
    }

    /**
     * Select the window with specified title
     * @param windowTitle window title
     * @param done done callback for Mocha
     */
    function switchWindowByTitle(windowTitle, done) {
        client.getTabIds().then(function (tabIds) {
            var handleIndex = 0;
            var checkTitle = function (title) {
                if (title === windowTitle) {
                    done();
                } else {
                    handleIndex++;
                    if (handleIndex < tabIds.length) {
                        switchWindow(tabIds[handleIndex], checkTitle);
                    } else {
                        // the window may not be loaded yet, so call itself again
                        switchWindowByTitle(windowTitle, done);
                    }
                }
            };
            switchWindow(tabIds[handleIndex], checkTitle);
        });
    }

    /**
     *  Check if OpenFin Javascript API fin.desktop.System.getVersion exits
     *
    **/
    function checkFinGetVersion(callback) {
        client.executeAsync(function (done) {
            if (fin && fin.desktop && fin.desktop.System && fin.desktop.System.getVersion) {
                done(true);
            } else {
                done(false);
            }
        }).then(function (result) {
            callback(result.value);
        });
    }

    /**
     *  Wait for OpenFin Javascript API to be injected
     *
    **/
    function waitForFinDesktop(readyCallback) {
        var callback = function(ready) {
            if (ready === true) {
                readyCallback();
            } else {
                client.pause(1000).then(function() {
                    waitForFinDesktop(readyCallback);
                });
            }
        };
        checkFinGetVersion(callback);
    }

    it('Switch to Hello OpenFin Main window', function(done) {
        should.exist(client);
        switchWindowByTitle("Hello OpenFin", done);
    });

    it('Wait for OpenFin API ready', function(done) {
        should.exist(client);
        waitForFinDesktop(done);
    });

    it('Verify OpenFin Runtime Version', function(done) {
        should.exist(client);
        client.executeAsync(function (done) {
            fin.desktop.System.getVersion(function(v) { console.log(v); done(v); } );
        }).then(function (result) {
            should.exist(result.value);
            result.value.should.equal(config.expectedRuntimeVersion);
            done();
        });
    });

    it("Find notification button", function(done) {
        should.exist(client);
        client.element("#desktop-notification").then(function(result) {
            should.exist(result.value);
            notificationButton = result.value;
            done();
        });
    });

    it("Click notification button", function(done) {
        should.exist(client);
        should.exist(notificationButton);
        client.elementIdClick(notificationButton.ELEMENT).then(function(result) {
            done();
        });
    });

    it("Find CPU Info button", function(done) {
        should.exist(client);
        client.element("#cpu-info").then(function(result) {
            should.exist(result.value);
            cpuInfoButton = result.value;
            done();
        });
    });

    it("Click CPU Info button", function(done) {
        should.exist(client);
        should.exist(cpuInfoButton);
        client.elementIdClick(cpuInfoButton.ELEMENT).then(function(result) {
            client.pause(3000).then(function() {  // pause just for demo purpose so we can see the window
                done();
            });
        })
    });

    it('Switch to CPU Info window', function(done) {
        should.exist(client);
        switchWindowByTitle("Hello OpenFin CPU Info", done);
    });

    it("Find Exit button for CPU Info window", function(done) {
        should.exist(client);
        client.element("#close-app").then(function(result) {
            should.exist(result.value);
            cpuInfoExitButton = result.value;
            done();
        });
    });

    it("Click CPU Info Exit button", function(done) {
        should.exist(client);
        should.exist(cpuInfoExitButton);
        client.elementIdClick(cpuInfoExitButton.ELEMENT).then(function(result) {
            done();
        })
    });

    it('Exit OpenFin Runtime', function (done) {
        should.exist(client);
        client.execute(function () {
            fin.desktop.System.exit();
        });
        client.pause(1000).then(function() {  // pause here to give Runtime time to exit
            done();
        });
    });

});
<commit_msg>RUN-4256: fixed logLevel for WDIO<commit_after>/**
 *
 * Example test script for Hello OpenFin App using Mocha, CHAI and WebdriverIO (http://webdriver.io)
 * ChromeDriver must be running before test.
 *
 */

"use strict";

var should = require('chai').should(),
    webdriver = require('webdriverio'),
    assert = require("assert"),
    config = require("../../config"),
    spawn = require('child_process').spawn;

describe('Hello OpenFin App testing with webdriver.io', function() {
    var client, notificationButton, cpuInfoButton, cpuInfoExitButton;

    this.timeout(config.testTimeout);

    before(function(done) {

        // configure webdriver
        var driverOptions = {
            desiredCapabilities: config.desiredCapabilities,
            host: config.remoteDriverHost,
            port: config.remoteDriverPort,
            waitforTimeout: config.testTimeout,
            logLevel: 'verbose'  // http://webdriver.io/guide/getstarted/configuration.html
        };
        client = webdriver.remote(driverOptions);

        if (!config.remoteDriverPath) {
            client.requestHandler.startPath = "";  // webdriverio defaults it to '/wd/hub';
        }
        client.init().then(function () {
            client.timeouts("implicit", config.testTimeout).then(function (t) {
                client.timeouts("script", config.testTimeout).then(function (t2) {
                    client.timeouts("page load", config.testTimeout).then(function (t3) {
                        done();
                    })
                });

            });
        });
    });

    after(function() {
        return client.end();
    });

    /**
     * Select a Window
     * @param windowHandle handle of the window
     * @param callback callback with window title if selection is successful
     */
    function switchWindow(windowHandle, callback) {
        client.switchTab(windowHandle).then(function () {
            client.getTitle().then(function (title) {
                callback(title);
            });
        });
    }

    /**
     * Select the window with specified title
     * @param windowTitle window title
     * @param done done callback for Mocha
     */
    function switchWindowByTitle(windowTitle, done) {
        client.getTabIds().then(function (tabIds) {
            var handleIndex = 0;
            var checkTitle = function (title) {
                if (title === windowTitle) {
                    done();
                } else {
                    handleIndex++;
                    if (handleIndex < tabIds.length) {
                        switchWindow(tabIds[handleIndex], checkTitle);
                    } else {
                        // the window may not be loaded yet, so call itself again
                        switchWindowByTitle(windowTitle, done);
                    }
                }
            };
            switchWindow(tabIds[handleIndex], checkTitle);
        });
    }

    /**
     *  Check if OpenFin Javascript API fin.desktop.System.getVersion exits
     *
    **/
    function checkFinGetVersion(callback) {
        client.executeAsync(function (done) {
            if (fin && fin.desktop && fin.desktop.System && fin.desktop.System.getVersion) {
                done(true);
            } else {
                done(false);
            }
        }).then(function (result) {
            callback(result.value);
        });
    }

    /**
     *  Wait for OpenFin Javascript API to be injected
     *
    **/
    function waitForFinDesktop(readyCallback) {
        var callback = function(ready) {
            if (ready === true) {
                readyCallback();
            } else {
                client.pause(1000).then(function() {
                    waitForFinDesktop(readyCallback);
                });
            }
        };
        checkFinGetVersion(callback);
    }

    it('Switch to Hello OpenFin Main window', function(done) {
        should.exist(client);
        switchWindowByTitle("Hello OpenFin", done);
    });

    it('Wait for OpenFin API ready', function(done) {
        should.exist(client);
        waitForFinDesktop(done);
    });

    it('Verify OpenFin Runtime Version', function(done) {
        should.exist(client);
        client.executeAsync(function (done) {
            fin.desktop.System.getVersion(function(v) { console.log(v); done(v); } );
        }).then(function (result) {
            should.exist(result.value);
            result.value.should.equal(config.expectedRuntimeVersion);
            done();
        });
    });

    it("Find notification button", function(done) {
        should.exist(client);
        client.element("#desktop-notification").then(function(result) {
            should.exist(result.value);
            notificationButton = result.value;
            done();
        });
    });

    it("Click notification button", function(done) {
        should.exist(client);
        should.exist(notificationButton);
        client.elementIdClick(notificationButton.ELEMENT).then(function(result) {
            done();
        });
    });

    it("Find CPU Info button", function(done) {
        should.exist(client);
        client.element("#cpu-info").then(function(result) {
            should.exist(result.value);
            cpuInfoButton = result.value;
            done();
        });
    });

    it("Click CPU Info button", function(done) {
        should.exist(client);
        should.exist(cpuInfoButton);
        client.elementIdClick(cpuInfoButton.ELEMENT).then(function(result) {
            client.pause(3000).then(function() {  // pause just for demo purpose so we can see the window
                done();
            });
        })
    });

    it('Switch to CPU Info window', function(done) {
        should.exist(client);
        switchWindowByTitle("Hello OpenFin CPU Info", done);
    });

    it("Find Exit button for CPU Info window", function(done) {
        should.exist(client);
        client.element("#close-app").then(function(result) {
            should.exist(result.value);
            cpuInfoExitButton = result.value;
            done();
        });
    });

    it("Click CPU Info Exit button", function(done) {
        should.exist(client);
        should.exist(cpuInfoExitButton);
        client.elementIdClick(cpuInfoExitButton.ELEMENT).then(function(result) {
            done();
        })
    });

    it('Exit OpenFin Runtime', function (done) {
        should.exist(client);
        client.execute(function () {
            fin.desktop.System.exit();
        });
        client.pause(1000).then(function() {  // pause here to give Runtime time to exit
            done();
        });
    });

});
<|endoftext|>


<commit_before>var protocol = require('./Inspector.json');
var util = require('util');
var events = require('events');
var http = require('http');
var WebSocket = require('ws');

var Chrome = function (options, notifier) {
    var self = this;
    addCommandShorthands.call(self);
    self.notifier = notifier;
    self.callbacks = {};
    self.nextCommandId = 1;
    connectToChrome.call(self, options.host, options.port, options.chooseTab);
};

util.inherits(Chrome, events.EventEmitter);

Chrome.prototype.send = function (method, params, callback) {
    var self = this;
    var id = self.nextCommandId++;
    if (typeof params === 'function') {
        callback = params;
        params = undefined;
    }
    var message = {'id': id, 'method': method, 'params': params};
    self.ws.send(JSON.stringify(message));
    // register command response callback
    if (typeof callback === 'function') {
        self.callbacks[id] = callback;
    }
};

Chrome.prototype.close = function () {
    var self = this;
    self.ws.removeAllListeners();
    self.ws.close();
};

function addCommand(domain, command, help) {
    var self = this;
    console.log(domain, command)
    Chrome.prototype[domain][command] = function (params, callback) {
        self.send(domain + '.' + command, params, callback);
    };
    Chrome.prototype[domain][command].help = help;
}

function addCommandShorthands() {
    var self = this;
    for (var domain in protocol.domains) {
        var domainName = protocol.domains[domain].domain;
        Chrome.prototype[domainName] = {};
        var commands = protocol.domains[domain].commands;
        for (var i = 0; i < commands.length; i++) {
            var command = commands[i].name;
            delete commands[i].name
            addCommand.call(self, domainName, command, commands[i]);
        }
    }
}

function connectToChrome(host, port, chooseTab) {
    var self = this;
    var options = {'host': host, 'port': port, 'path': '/json'};
    var request = http.get(options, function (response) {
        var data = '';
        response.on('data', function (chunk) {
            data += chunk;
        });
        response.on('end', function () {
            var error;
            var tabs = JSON.parse(data);
            var tab = tabs[chooseTab(tabs)];
            if (tab) {
                var tabDebuggerUrl = tab.webSocketDebuggerUrl;
                if (tabDebuggerUrl) {
                    connectToWebSocket.call(self, tabDebuggerUrl);
                } else {
                    // a WebSocket is already connected to this tab?
                    error = new Error('Unable to connect to the WebSocket');
                    self.notifier.emit('error', error);
                }
            } else {
                error = new Error('Invalid tab index');
                self.notifier.emit('error', error);
            }
        });
    });
    request.on('error', function (error) {
        self.notifier.emit('error', error);
    });
}

function connectToWebSocket(url) {
    var self = this;
    self.ws = new WebSocket(url);
    self.ws.on('open', function() {
        self.notifier.emit('connect', self);
    });
    self.ws.on('message', function (data) {
        var message = JSON.parse(data);
        // command response
        if (message.id) {
            var callback = self.callbacks[message.id];
            if (callback) {
                if (message.result) {
                    callback(false, message.result);
                } else if (message.error) {
                    callback(true, message.error);
                }
                // unregister command response callback
                delete self.callbacks[message.id];
            }
        }
        // event
        else if (message.method) {
            self.emit('event', message);
            self.emit(message.method, message.params);
        }
    });
    self.ws.on('error', function (error) {
        self.notifier.emit('error', error);
    });
}

module.exports = Chrome;
<commit_msg>add events shortcuts<commit_after>var protocol = require('./Inspector.json');
var util = require('util');
var events = require('events');
var http = require('http');
var WebSocket = require('ws');

var Chrome = function (options, notifier) {
    var self = this;
    addCommandShorthands.call(self);
    self.notifier = notifier;
    self.callbacks = {};
    self.nextCommandId = 1;
    connectToChrome.call(self, options.host, options.port, options.chooseTab);
};

util.inherits(Chrome, events.EventEmitter);

Chrome.prototype.send = function (method, params, callback) {
    var self = this;
    var id = self.nextCommandId++;
    if (typeof params === 'function') {
        callback = params;
        params = undefined;
    }
    var message = {'id': id, 'method': method, 'params': params};
    self.ws.send(JSON.stringify(message));
    // register command response callback
    if (typeof callback === 'function') {
        self.callbacks[id] = callback;
    }
};

Chrome.prototype.close = function () {
    var self = this;
    self.ws.removeAllListeners();
    self.ws.close();
};

function addCommand(domain, command, help) {
    var self = this;
    Chrome.prototype[domain][command] = function (params, callback) {
        self.send(domain + '.' + command, params, callback);
    };
    Chrome.prototype[domain][command].help = help;
}

function addEvent(domain, event, help) {
    var self = this;
    Chrome.prototype[domain][event] = function (handler) {
       self.on(domain + '.' + event, handler)
    }
    Chrome.prototype[domain][event].help = help;
}

function addCommandShorthands() {
    var self = this;
    for (var domain in protocol.domains) {
        var domainName = protocol.domains[domain].domain;
        Chrome.prototype[domainName] = {};
        var commands = protocol.domains[domain].commands;
        if (commands) {
          for (var i = 0; i < commands.length; i++) {
            var command = commands[i].name;
            delete commands[i].name
            addCommand.call(self, domainName, command, commands[i]);
          }
        }
        // TODO make dry
        var events = protocol.domains[domain].events;
        if (events) {
          for (var i = 0; i < events.length; i++) {
            var event = events[i].name;
            delete events[i].name
            addEvent.call(self, domainName, event, events[i]);
          }
        }
    }
}

function connectToChrome(host, port, chooseTab) {
    var self = this;
    var options = {'host': host, 'port': port, 'path': '/json'};
    var request = http.get(options, function (response) {
        var data = '';
        response.on('data', function (chunk) {
            data += chunk;
        });
        response.on('end', function () {
            var error;
            var tabs = JSON.parse(data);
            var tab = tabs[chooseTab(tabs)];
            if (tab) {
                var tabDebuggerUrl = tab.webSocketDebuggerUrl;
                if (tabDebuggerUrl) {
                    connectToWebSocket.call(self, tabDebuggerUrl);
                } else {
                    // a WebSocket is already connected to this tab?
                    error = new Error('Unable to connect to the WebSocket');
                    self.notifier.emit('error', error);
                }
            } else {
                error = new Error('Invalid tab index');
                self.notifier.emit('error', error);
            }
        });
    });
    request.on('error', function (error) {
        self.notifier.emit('error', error);
    });
}

function connectToWebSocket(url) {
    var self = this;
    self.ws = new WebSocket(url);
    self.ws.on('open', function() {
        self.notifier.emit('connect', self);
    });
    self.ws.on('message', function (data) {
        var message = JSON.parse(data);
        // command response
        if (message.id) {
            var callback = self.callbacks[message.id];
            if (callback) {
                if (message.result) {
                    callback(false, message.result);
                } else if (message.error) {
                    callback(true, message.error);
                }
                // unregister command response callback
                delete self.callbacks[message.id];
            }
        }
        // event
        else if (message.method) {
            self.emit('event', message);
            self.emit(message.method, message.params);
        }
    });
    self.ws.on('error', function (error) {
        self.notifier.emit('error', error);
    });
}

module.exports = Chrome;
<|endoftext|>


<commit_before>var _ = require('lodash');
var net = require('net');
var Promise = require('bluebird');
var templates = require('./templates');
_.mixin(require('congruence'));

var singular = [
  'VERSION',
  'SUMMARY',
  'CHECK'
];
var replyMapping = {
  check: 'CHECK'
};
function createDeviceMethod (device) {
  var deviceUC = device.toUpperCase();

  this['_' + device] = function (r) {
    return r['' + deviceUC][0];
  }.bind(this);

  this['_' + device + 'count'] = function (r) {
    return r['' + deviceUC + 'S'][0].Count;
  }.bind(this);
}

function createCommandMethod (command) {
  this[command.name] = function (args) {
    return this.request(command, args)
      .then(function (result) {
        if (_.isFunction(this['_' + command.name])) {
          return Promise.try(function () {
            return this['_' + name](result);
          }.bind(this));
        }
        else {
          return result;
        }
    }.bind(this));
  }.bind(this);
}

/**
 * @constructor
 */
function CGMinerClient(options) {
  if (!_.isObject(options)) {
    options = {};
  }
  this.host = options.host || '127.0.0.1';
  this.port = options.port || 4028;
  createCommandMethod.bind(this)({ name: 'version', reply: 'VERSION' });
}

/**
 * Load the commands for the API version of the miner we are connected to.
 */
CGMinerClient.prototype.connect = function () {
  if (this._loaded) return Promise.resolve(this);
  this._loaded = true;

  return this.version()
    .then(function (version) {
      return require('./apis/parser').parse(version.CGMiner);
    })
    .then(function (commands) {
      //console.log(commands);
      this._commands = commands;
      this._types = ['pga', 'gpu', 'asc'];

      _.each(this._commands, createCommandMethod.bind(this));
      _.each(this._types, createDeviceMethod.bind(this));
    }.bind(this))
    .return(this);

};

/**
 * Send a requret to cgminer
 * @param command.name
 * @param command.reply
 * @param args
 */
CGMinerClient.prototype.request = function (command, args) {
  if (_.has(replyMapping, command.name)) {
    command.reply = replyMapping[command.name];
  }
  var deferred = Promise.defer();
  var socket = net.connect({
    host: this.host,
    port: this.port
  });
  socket.on('error', function (err) {
    return deferred.reject(err);
  });
  socket.on('connect', function () {
    var buffer;
    buffer = '';
    socket.on('data', function (data) {
      return buffer += data.toString();
    });
    socket.on('end', function () {
      var json;
      // XXX workaround for https://bitcointalk.org/index.php?topic=28402.msg9170949#msg9170949
      var str = buffer.toString().replace(/\-nan/g, '0');
      try {
        if (/RESTART/.test(str)) {
          json = {
            STATUS: [{
              STATUS: 'S',
              Code: -1,
              Msg: 'Restart',
              Description: 'Restarting now',
              When: (new Date()).valueOf()
            }]
          };
        }
        else {
          console.log(str);
          json = JSON.parse(str.replace(/[^\}]+$/, ''));
        }
        //console.log(command);
        //console.log(command.reply);
        //console.log(json);
        if (command.reply === null) {
          if (_.similar(templates['null'], json.STATUS[0])) {
            return deferred.resolve(json.STATUS[0]);
          }
          else {
            return deferred.reject({ msg: 'response did not match template', response: json });
          }
        }
        if (json.STATUS[0].STATUS === 'E') {
          return deferred.reject(new Error(json.STATUS[0].Msg));
        }
        _.each(json[command.reply], function (reply) {
          if (!_.similar(templates[command.name], json[command.reply])) {
            return deferred.reject({ msg: 'response did not match template', response: json });
          }
        });
        if (_.contains(singular, command.reply)) {
          return deferred.resolve(json[command.reply][0]);
        }
        else {
         return deferred.resolve(json[command.reply]);
        }
      }
      catch (e) {
        return deferred.reject(e);
      }
    });
    return socket.write(JSON.stringify({
      command: command.name,
      parameter: _.isArray(args) ? args.join(',') : args
    }));
  });
  return deferred.promise;
};

module.exports = CGMinerClient;
<commit_msg>removed log statement<commit_after>var _ = require('lodash');
var net = require('net');
var Promise = require('bluebird');
var templates = require('./templates');
_.mixin(require('congruence'));

var singular = [
  'VERSION',
  'SUMMARY',
  'CHECK'
];
var replyMapping = {
  check: 'CHECK'
};
function createDeviceMethod (device) {
  var deviceUC = device.toUpperCase();

  this['_' + device] = function (r) {
    return r['' + deviceUC][0];
  }.bind(this);

  this['_' + device + 'count'] = function (r) {
    return r['' + deviceUC + 'S'][0].Count;
  }.bind(this);
}

function createCommandMethod (command) {
  this[command.name] = function (args) {
    return this.request(command, args)
      .then(function (result) {
        if (_.isFunction(this['_' + command.name])) {
          return Promise.try(function () {
            return this['_' + name](result);
          }.bind(this));
        }
        else {
          return result;
        }
    }.bind(this));
  }.bind(this);
}

/**
 * @constructor
 */
function CGMinerClient(options) {
  if (!_.isObject(options)) {
    options = {};
  }
  this.host = options.host || '127.0.0.1';
  this.port = options.port || 4028;
  createCommandMethod.bind(this)({ name: 'version', reply: 'VERSION' });
}

/**
 * Load the commands for the API version of the miner we are connected to.
 */
CGMinerClient.prototype.connect = function () {
  if (this._loaded) return Promise.resolve(this);
  this._loaded = true;

  return this.version()
    .then(function (version) {
      return require('./apis/parser').parse(version.CGMiner);
    })
    .then(function (commands) {
      //console.log(commands);
      this._commands = commands;
      this._types = ['pga', 'gpu', 'asc'];

      _.each(this._commands, createCommandMethod.bind(this));
      _.each(this._types, createDeviceMethod.bind(this));
    }.bind(this))
    .return(this);

};

/**
 * Send a requret to cgminer
 * @param command.name
 * @param command.reply
 * @param args
 */
CGMinerClient.prototype.request = function (command, args) {
  if (_.has(replyMapping, command.name)) {
    command.reply = replyMapping[command.name];
  }
  var deferred = Promise.defer();
  var socket = net.connect({
    host: this.host,
    port: this.port
  });
  socket.on('error', function (err) {
    return deferred.reject(err);
  });
  socket.on('connect', function () {
    var buffer;
    buffer = '';
    socket.on('data', function (data) {
      return buffer += data.toString();
    });
    socket.on('end', function () {
      var json;
      // XXX workaround for https://bitcointalk.org/index.php?topic=28402.msg9170949#msg9170949
      var str = buffer.toString().replace(/\-nan/g, '0');
      try {
        if (/RESTART/.test(str)) {
          json = {
            STATUS: [{
              STATUS: 'S',
              Code: -1,
              Msg: 'Restart',
              Description: 'Restarting now',
              When: (new Date()).valueOf()
            }]
          };
        }
        else {
          //console.log(str);
          json = JSON.parse(str.replace(/[^\}]+$/, ''));
        }
        //console.log(command);
        //console.log(command.reply);
        //console.log(json);
        if (command.reply === null) {
          if (_.similar(templates['null'], json.STATUS[0])) {
            return deferred.resolve(json.STATUS[0]);
          }
          else {
            return deferred.reject({ msg: 'response did not match template', response: json });
          }
        }
        if (json.STATUS[0].STATUS === 'E') {
          return deferred.reject(new Error(json.STATUS[0].Msg));
        }
        _.each(json[command.reply], function (reply) {
          if (!_.similar(templates[command.name], json[command.reply])) {
            return deferred.reject({ msg: 'response did not match template', response: json });
          }
        });
        if (_.contains(singular, command.reply)) {
          return deferred.resolve(json[command.reply][0]);
        }
        else {
         return deferred.resolve(json[command.reply]);
        }
      }
      catch (e) {
        return deferred.reject(e);
      }
    });
    return socket.write(JSON.stringify({
      command: command.name,
      parameter: _.isArray(args) ? args.join(',') : args
    }));
  });
  return deferred.promise;
};

module.exports = CGMinerClient;
<|endoftext|>


<commit_before>var util    = require('util')
  , req     = require('request')
  , crypto  = require('crypto')
  , fs      = require('fs')
  , ImboUrl = require('./url')
  , info    = require(__dirname + '/../package.json')
  , request = req.defaults({
    headers: {
        'Accept'    : 'application/json,image/*',
        'User-Agent': info.name + ' ' + info.version + (typeof process !== 'undefined' ? (' (node ' + process.version + ')') : '')
    }
  })
  , undef;

function ImboClient(serverUrls, publicKey, privateKey) {
    this.options = {
        hosts:      this.parseUrls(serverUrls),
        publicKey:  publicKey,
        privateKey: privateKey
    };
}

/**
 * Base/core methods
 */
ImboClient.prototype.getImageIdentifier = function(imgPath, callback) {
    return md5file(imgPath, callback);
};

ImboClient.prototype.getImageUrl = function(imageIdentifier) {
    var host = this.getHostForImageIdentifier(imageIdentifier);
    return new ImboUrl(host, this.options.publicKey, this.options.privateKey, imageIdentifier);
};

ImboClient.prototype.getResourceUrl = function(resourceIdentifier, path) {
    return new ImboUrl(this.options.hosts[0], this.options.publicKey, this.options.privateKey, resourceIdentifier, path);
};

ImboClient.prototype.getSignedResourceUrl = function(method, url, date) {
    var timestamp = (date || new Date()).toISOString().replace(/\.\d+Z$/, 'Z');
    var signature = this.generateSignature(method, url.toString(), timestamp);

    var qs = url.toString().indexOf('?') > -1 ? '&' : '?';
    qs += 'signature='  + encodeURIComponent(signature);
    qs += '&timestamp=' + encodeURIComponent(timestamp);

    return url + qs;
};

ImboClient.prototype.generateSignature = function(method, url, timestamp) {
    var data = [method, url, this.options.publicKey, timestamp].join('|');
    var signature = crypto.createHmac('sha256', this.options.privateKey).update(data).digest('hex');
    return signature;
};

ImboClient.prototype.getHostForImageIdentifier = function(imageIdentifier) {
    var dec = parseInt(imageIdentifier.slice(0, 2), 16);
    return this.options.hosts[dec % this.options.hosts.length];
};

/**
 * Parse an array of URLs, stripping excessive parts
 *
 * @param  array|string urls
 * @return array
 */
ImboClient.prototype.parseUrls = function(urls) {
    // Accept string for host, if user only specifies one
    if (typeof urls === 'string') {
        urls = [urls];
    }

    // Strip out any unnecessary parts
    var serverUrls = [];
    for (var i = 0; i < urls.length; i++) {
        serverUrls.push(urls[i].replace(/:80(\/|$)/, '$1').replace(/\/$/, ''));
    }

    return serverUrls;
};

/**
 * Image operations
 */
ImboClient.prototype.headImage = function(imageIdentifier, callback) {
    var url = this.getResourceUrl(imageIdentifier), undef;

    request.head({
        uri: url.toString()
    }, function(err, res) {
        if (err) {
            return callback(err, res);
        } else if (res.statusCode !== 200) {
            return callback(getErrorMessage(res), res);
        }
        return callback(undef, res);
    });
};

ImboClient.prototype.deleteImage = function(imgPath, callback) {
    var self = this;
    self.getImageIdentifier(imgPath, function(err, imageIdentifier) {
        if (err) {
            return callback(err);
        }

        self.deleteImageByIdentifier(imageIdentifier, callback);
    });
};

ImboClient.prototype.deleteImageByIdentifier = function(imageIdentifier, callback) {
    var url = this.getSignedResourceUrl('DELETE', this.getResourceUrl(imageIdentifier));

    request.del({
        uri: url,
        json: true
    }, function(err, res) {
        if (err || (res && res.statusCode != 200)) {
            return callback(err || getErrorMessage(res), res);
        }

        callback(undef, res);
    });
};

ImboClient.prototype.imageIdentifierExists = function(identifier, callback) {
    this.headImage(identifier, function(err, res) {
        if (err && (err == 404 || (res && res.statusCode == 404))) {
            return callback(undef, false);
        } else if (err) {
            return callback(err);
        } else if (res && res.statusCode != 404 && res.statusCode != 200) {
            return callback(getErrorMessage(res));
        }

        callback(undef, res.statusCode == 200);
    });
};

ImboClient.prototype.imageExists = function(imgPath, callback) {
    var self = this;
    self.getImageIdentifier(imgPath, function(err, imageIdentifier) {
        if (err) {
            return callback(err);
        }

        self.imageIdentifierExists(imageIdentifier, callback);
    });
};

ImboClient.prototype.addImage = function(imgPath, callback) {
    var self = this;

    self.getImageIdentifier(imgPath, function(err, imageIdentifier) {
        if (err) {
            return callback(err);
        }

        fs.stat(imgPath, function (err, stat) {
            if (err) {
                return callback(err);
            }

            var url = self.getSignedResourceUrl('PUT', self.getResourceUrl(imageIdentifier));
            fs.createReadStream(imgPath).pipe(request.put({url: url, headers: getHeadersForPutRequest(stat.size)}, function(err, res) {
                if (err) {
                    return callback(err);
                } else if (res.statusCode != 201) {
                    return callback(getErrorMessage(res), null, res);
                }
                return callback(undef, res.headers['x-imbo-imageidentifier'], res);
            }));
        });
    });
};

/**
 * Metadata methods
 */
ImboClient.prototype.getMetadata = function(imageIdentifier, callback) {
    var url = this.getResourceUrl(imageIdentifier, '/meta');
    request.get({
        uri: url.toString(),
        json: true
    }, function(err, res) {
        if (err || (res && res.statusCode != 200)) {
            return callback(err || getErrorMessage(res), null, res);
        }

        callback(undef, res.body, res);
    });
};

ImboClient.prototype.deleteMetadata = function(imageIdentifier, callback) {
    var url = this.getSignedResourceUrl('DELETE', this.getResourceUrl(imageIdentifier, '/meta'));
    request.del({
        uri: url,
        json: true
    }, function(err, res) {
        if (err || (res && res.statusCode != 200)) {
            return callback(err || getErrorMessage(res), res);
        }

        callback(undef, res);
    });
};

ImboClient.prototype.editMetadata = function(imageIdentifier, data, callback) {
    var url = this.getSignedResourceUrl('POST', this.getResourceUrl(imageIdentifier, '/meta'));
    request.post({
        uri: url,
        json: data
    }, function(err, res) {
        if (err || (res && res.statusCode != 200 && res.statusCode != 201)) {
            return callback(err || getErrorMessage(res), res);
        }

        callback(undef, res);
    });
};

/**
 * Utility methods
 */

function getHeadersForPutRequest(contentLength) {
    return {
                'Accept'    : 'application/json,image/*',
                'Content-Length': contentLength,
                'User-Agent': info.name + ' ' + info.version + (typeof process !== 'undefined' ? (' (node ' + process.version + ')') : '')
            }
}

function md5file(filename, callback) {
    fs.stat(filename, function(err, stats) {
        if (err || !stats.isFile()) {
            return callback('File does not exist (' + filename + ')');
        }

        var md5 = crypto.createHash('md5');
        var stream = fs.createReadStream(filename);
        stream.on('data', function(data) {
            md5.update(data);
        });

        stream.on('end', function() {
            callback(undef, md5.digest('hex'));
        });
    });
}

function getErrorMessage(res) {
    if (!res) {
        return 'Unknown error';
    }

    return res.headers['x-imbo-error-message'] || res.statusCode;
}

module.exports = ImboClient;
<commit_msg>Reduced the number of calls to fs.stats.<commit_after>var util    = require('util')
  , req     = require('request')
  , crypto  = require('crypto')
  , fs      = require('fs')
  , ImboUrl = require('./url')
  , info    = require(__dirname + '/../package.json')
  , headers = {
      'Accept'    : 'application/json,image/*',
      'User-Agent': info.name + ' ' + info.version + (typeof process !== 'undefined' ? (' (node ' + process.version + ')') : '')
    }
  , request = req.defaults({ 'headers': headers })
  , undef;

function ImboClient(serverUrls, publicKey, privateKey) {
    this.options = {
        hosts:      this.parseUrls(serverUrls),
        publicKey:  publicKey,
        privateKey: privateKey
    };
}

/**
 * Base/core methods
 */
ImboClient.prototype.getImageIdentifier = function(imgPath, callback) {
    return md5file(imgPath, callback);
};

ImboClient.prototype.getImageUrl = function(imageIdentifier) {
    var host = this.getHostForImageIdentifier(imageIdentifier);
    return new ImboUrl(host, this.options.publicKey, this.options.privateKey, imageIdentifier);
};

ImboClient.prototype.getResourceUrl = function(resourceIdentifier, path) {
    return new ImboUrl(this.options.hosts[0], this.options.publicKey, this.options.privateKey, resourceIdentifier, path);
};

ImboClient.prototype.getSignedResourceUrl = function(method, url, date) {
    var timestamp = (date || new Date()).toISOString().replace(/\.\d+Z$/, 'Z');
    var signature = this.generateSignature(method, url.toString(), timestamp);

    var qs = url.toString().indexOf('?') > -1 ? '&' : '?';
    qs += 'signature='  + encodeURIComponent(signature);
    qs += '&timestamp=' + encodeURIComponent(timestamp);

    return url + qs;
};

ImboClient.prototype.generateSignature = function(method, url, timestamp) {
    var data = [method, url, this.options.publicKey, timestamp].join('|');
    var signature = crypto.createHmac('sha256', this.options.privateKey).update(data).digest('hex');
    return signature;
};

ImboClient.prototype.getHostForImageIdentifier = function(imageIdentifier) {
    var dec = parseInt(imageIdentifier.slice(0, 2), 16);
    return this.options.hosts[dec % this.options.hosts.length];
};

/**
 * Parse an array of URLs, stripping excessive parts
 *
 * @param  array|string urls
 * @return array
 */
ImboClient.prototype.parseUrls = function(urls) {
    // Accept string for host, if user only specifies one
    if (typeof urls === 'string') {
        urls = [urls];
    }

    // Strip out any unnecessary parts
    var serverUrls = [];
    for (var i = 0; i < urls.length; i++) {
        serverUrls.push(urls[i].replace(/:80(\/|$)/, '$1').replace(/\/$/, ''));
    }

    return serverUrls;
};

/**
 * Image operations
 */
ImboClient.prototype.headImage = function(imageIdentifier, callback) {
    var url = this.getResourceUrl(imageIdentifier), undef;

    request.head({
        uri: url.toString()
    }, function(err, res) {
        if (err) {
            return callback(err, res);
        } else if (res.statusCode !== 200) {
            return callback(getErrorMessage(res), res);
        }
        return callback(undef, res);
    });
};

ImboClient.prototype.deleteImage = function(imgPath, callback) {
    var self = this;
    self.getImageIdentifier(imgPath, function(err, imageIdentifier) {
        if (err) {
            return callback(err);
        }

        self.deleteImageByIdentifier(imageIdentifier, callback);
    });
};

ImboClient.prototype.deleteImageByIdentifier = function(imageIdentifier, callback) {
    var url = this.getSignedResourceUrl('DELETE', this.getResourceUrl(imageIdentifier));

    request.del({
        uri: url,
        json: true
    }, function(err, res) {
        if (err || (res && res.statusCode != 200)) {
            return callback(err || getErrorMessage(res), res);
        }

        callback(undef, res);
    });
};

ImboClient.prototype.imageIdentifierExists = function(identifier, callback) {
    this.headImage(identifier, function(err, res) {
        if (err && (err == 404 || (res && res.statusCode == 404))) {
            return callback(undef, false);
        } else if (err) {
            return callback(err);
        } else if (res && res.statusCode != 404 && res.statusCode != 200) {
            return callback(getErrorMessage(res));
        }

        callback(undef, res.statusCode == 200);
    });
};

ImboClient.prototype.imageExists = function(imgPath, callback) {
    var self = this;
    self.getImageIdentifier(imgPath, function(err, imageIdentifier) {
        if (err) {
            return callback(err);
        }

        self.imageIdentifierExists(imageIdentifier, callback);
    });
};

ImboClient.prototype.addImage = function(imgPath, callback) {
    var self = this;

    self.getImageIdentifier(imgPath, function(err, imageIdentifier, fileStats) {
        if (err) {
            return callback(err);
        }

        var url = self.getSignedResourceUrl('PUT', self.getResourceUrl(imageIdentifier));

        // Set up PUT-request
        var put = request.put({
            url: url,
            headers: getHeadersForPutRequest(fileStats.size)
        }, function(err, res) {
            if (err) {
                return callback(err);
            } else if (res.statusCode != 201) {
                return callback(getErrorMessage(res), null, res);
            }
            return callback(undef, res.headers['x-imbo-imageidentifier'], res);
        });

        fs.createReadStream(imgPath).pipe(put);
    });
};

/**
 * Metadata methods
 */
ImboClient.prototype.getMetadata = function(imageIdentifier, callback) {
    var url = this.getResourceUrl(imageIdentifier, '/meta');
    request.get({
        uri: url.toString(),
        json: true
    }, function(err, res) {
        if (err || (res && res.statusCode != 200)) {
            return callback(err || getErrorMessage(res), null, res);
        }

        callback(undef, res.body, res);
    });
};

ImboClient.prototype.deleteMetadata = function(imageIdentifier, callback) {
    var url = this.getSignedResourceUrl('DELETE', this.getResourceUrl(imageIdentifier, '/meta'));
    request.del({
        uri: url,
        json: true
    }, function(err, res) {
        if (err || (res && res.statusCode != 200)) {
            return callback(err || getErrorMessage(res), res);
        }

        callback(undef, res);
    });
};

ImboClient.prototype.editMetadata = function(imageIdentifier, data, callback) {
    var url = this.getSignedResourceUrl('POST', this.getResourceUrl(imageIdentifier, '/meta'));
    request.post({
        uri: url,
        json: data
    }, function(err, res) {
        if (err || (res && res.statusCode != 200 && res.statusCode != 201)) {
            return callback(err || getErrorMessage(res), res);
        }

        callback(undef, res);
    });
};

/**
 * Utility methods
 */

function getHeadersForPutRequest(contentLength) {
    var reqHeaders = {
        'Content-Length': contentLength || 0
    };

    for (var key in headers) {
        reqHeaders[key] = headers[key];
    }

    return reqHeaders;
}

function md5file(filename, callback) {
    fs.stat(filename, function(err, stats) {
        if (err || !stats.isFile()) {
            return callback('File does not exist (' + filename + ')');
        }

        var md5 = crypto.createHash('md5');
        var stream = fs.createReadStream(filename);
        stream.on('data', function(data) {
            md5.update(data);
        });

        stream.on('end', function() {
            callback(undef, md5.digest('hex'), stats);
        });
    });
}

function getErrorMessage(res) {
    if (!res) {
        return 'Unknown error';
    }

    return res.headers['x-imbo-error-message'] || res.statusCode;
}

module.exports = ImboClient;
<|endoftext|>