<commit_before>/**
 * Copyright (c) 2015-2017 Netflix, Inc.  All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 *
An abstract factory class for producing {@link MslTokener},
 * {@link MslObject}, and {@link MslArray} instances of various encoder
 * formats.

 *
 *
A concrete implementations must identify its supported and preferred
 * encoder formats and provide implementations for encoding and decoding those
 * formats.

 *
 * @author Wesley Miaw <wmiaw@netflix.com>
 */
(function(require, module) {
    "use strict";

    var Base64 = require('../util/Base64.js');
    var MslObject = require('../io/MslObject.js');
    var MslArray = require('../io/MslArray.js');
    var Class = require('../util/Class.js');
    var MslEncoderFormat = require('../io/MslEncoderFormat.js');
    var AsyncExecutor = require('../util/AsyncExecutor.js');
    var MslEncoderException = require('../io/MslEncoderException.js');
    var JsonMslTokenizer = require('../io/JsonMslTokenizer.js');
    var JsonMslObject = require('../io/JsonMslObject.js');
    var JsonMslArray = require('../io/JsonMslArray.js');
    var BufferedInputStream = require('../io/BufferedInputStream.js');

    /**
     * Escape a string to be output as a single line of text.
     *
     * @param {?string} s the string. May be {@code null}.
     * @returns {string} the escaped string.
     */
    var MslEncoderFactory$quote = function MslEncoderFactory$quote(s) {
        var json = JSON.stringify(s);
        return json
            .replace(/[\"]/g, '\\"')
            .replace(/[\\]/g, '\\\\')
            .replace(/[\/]/g, '\\/')
            .replace(/[\b]/g, '\\b')
            .replace(/[\f]/g, '\\f')
            .replace(/[\n]/g, '\\n')
            .replace(/[\r]/g, '\\r')
            .replace(/[\t]/g, '\\t');
    };

    /**
     * Convert a value to a string for print purposes.
     *
     * @param {?} value the value to convert to a string. May be {@code null}.
     * @return {string} the string.
     */
    var MslEncoderFactory$stringify = function MslEncoderFactory$stringify(v) {
        if (v instanceof MslObject || v instanceof MslArray) {
            return v.toString();
        } else if (v instanceof Uint8Array) {
            return Base64.encode(v);
        } else {
            var json = JSON.stringify(v);
            return json
                .replace(/[\"]/g, '\\"')
                .replace(/[\\]/g, '\\\\')
                .replace(/[\/]/g, '\\/')
                .replace(/[\b]/g, '\\b')
                .replace(/[\f]/g, '\\f')
                .replace(/[\n]/g, '\\n')
                .replace(/[\r]/g, '\\r')
                .replace(/[\t]/g, '\\t');
        }
    };

    var MslEncoderFactory = module.exports = Class.create({
        /**
         * Returns the most preferred encoder format from the provided set of
         * formats.
         *
         * @param {Array<MslEncoderFormat>} formats the set of formats to choose from. May be {@code null} or
         *        empty.
         * @return {MslEncoderFormat} the preferred format from the provided set or the default format
         *         if format set is {@code null} or empty.
         */
        getPreferredFormat: function(formats) {},

        /**
         *
Create a new {@link MslTokenizer}.

         *
         *
If the encoding format is not provided, it will be determined by
         * inspecting the byte stream identifier located in the first byte.

         *
         * @param {InputStream} source the binary data to tokenize.
         * @param {?MslEncoderFormat} format the encoding format. May be
         *        {@code null}.
         * @param {number} timeout read timeout used to determine the encoding
         *        format or -1 for no timeout.
         * @param {{result: function(MslTokenizer), timeout: function(), error: function(Error)}}
         *        callback the callback that will receive the
         *        {@link MslTokenizer}, be notified of timeouts, or any thrown
         *        exceptions.
         * @throws IOException if there is a problem reading the byte stream
         *         identifier.
         * @throws MslEncoderException if the encoder format is not recognized or
         *         is not supported.
         */
        createTokenizer: function createTokenizer(source, format, timeout, callback) {
            var self = this;

            AsyncExecutor(callback, function() {
                // If the format was provided, return the tokenizer directly.
                if (format)
                    return this.generateTokenizer(source, format);

                // Read the byte stream identifier.
                var bufferedSource = source.markSupported() ? source : new BufferedInputStream(source);
                bufferedSource.mark();
                bufferedSource.read(1, timeout, {
                    result: function(bytes) {
                        AsyncExecutor(callback, function() {
                            if (bytes == null || bytes.length < 1)
                                throw new MslEncoderException("End of stream reached when attempting to read the byte stream identifier.");
                            var id = bytes[0];
                            identify(bufferedSource, id);
                        }, self);
                    },
                    timeout: callback.timeout,
                    error: callback.error,
                });
            }, self);

            function identify(bufferedSource, id) {
                AsyncExecutor(callback, function() {
                    format = MslEncoderFormat.getFormat(id);
                    if (!format)
                        throw new MslEncoderException("Unidentified encoder format ID: (byte)" + id + ".");

                    // Reset the input stream and return the tokenizer.
                    bufferedSource.reset();
                    return this.generateTokenizer(bufferedSource, format);
                }, self);
            }
        },

        /**
         * Create a new {@link MslTokenizer} of the specified encoder format.
         *
         * @param source the binary data to tokenize.
         * @param format the encoder format.
         * @return the {@link MslTokenizer}.
         * @throws MslEncoderException if the encoder format is not supported.
         */
        generateTokenizer: function generateTokenizer(source, format) {},

        /**
         *
Create a new {@link MslObject}.

         *
         *
If a map is provided the object will be populated with the map
         * data.

         *
         * @param {Object=} map the map of name/value pairs. This must be a map of
         *        {@code string}s onto values. May be {@code null}.
         * @return {MslObject} the {@link MslObject}.
         * @throws TypeError if one of the values is of an
         *         unsupported type.
         */
        createObject: function createObject(map) {
            return new MslObject(map);
        },

        /**
         * Identify the encoder format of the {@link MslObject} of the encoded
         * data. The format will be identified by inspecting the byte stream
         * identifier located in the first byte.
         *
         * @param {Uint8Array} encoding the encoded data.
         * @return {MslEncoderFormat} the encoder format.
         * @throws MslEncoderException if the encoder format cannot be identified
         *         or there is an error parsing the encoder format ID.
         */
        parseFormat: function parseFormat(encoding) {
            // Fail if the encoding is too short.
            if (encoding.length < 1)
                throw new MslEncoderException("No encoding identifier found.");

            // Identify the encoder format.
            var id = encoding[0];
            var format = MslEncoderFormat.getFormat(id);
            if (!format)
                throw new MslEncoderException("Unidentified encoder format ID: (byte)" + id + ".");
            return format;
        },

        /**
         * Parse a {@link MslObject} from encoded data. The encoder format will be
         * determined by inspecting the byte stream identifier located in the first
         * byte.
         *
         * @param {Uint8Array} encoding the encoded data to parse.
         * @return {MslObject} the {@link MslObject}.
         * @throws MslEncoderException if the encoder format is not supported or
         *         there is an error parsing the encoded data.
         */
        parseObject: function(encoding) {},

        /**
         * Encode a {@link MslObject} into the specified encoder format.
         *
         * @param {MslObject} object the {@link MslObject} to encode.
         * @param {MslEncoderFormat} format the encoder format.
         * @param {{result: function(Uint8Array), error: function(Error)}}
         *        callback the callback that will receive the encoded data or
         *        any thrown exceptions.
         * @throws MslEncoderException if the encoder format is not supported or
         *         there is an error encoding the object.
         */
        encodeObject: function(object, format, callback) {},

        /**
         *
Create a new {@link MslArray}.

         *
         *
If a collection of values is provided the array will be populated
         * with the collection data.

         *
         * @param {Array<*>} collection the collection of values. May be {@code null}.
         * @return {MslArray} the {@link MslArray}.
         * @throws TypeError if one of the values is of an
         *         unsupported type.
         */
        createArray: function createArray(collection) {
            return new MslArray(collection);
        },
    });

    // Exports.
    module.exports.quote = MslEncoderFactory$quote;
    module.exports.stringify = MslEncoderFactory$stringify;
})(require, (typeof module !== 'undefined') ? module : mkmodule('MslEncoderFactory'));<commit_msg>Remove unused references to default JSON tokenizer, object, and array implementations.<commit_after>/**
 * Copyright (c) 2015-2017 Netflix, Inc.  All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 *
An abstract factory class for producing {@link MslTokener},
 * {@link MslObject}, and {@link MslArray} instances of various encoder
 * formats.

 *
 *
A concrete implementations must identify its supported and preferred
 * encoder formats and provide implementations for encoding and decoding those
 * formats.

 *
 * @author Wesley Miaw <wmiaw@netflix.com>
 */
(function(require, module) {
    "use strict";

    var Base64 = require('../util/Base64.js');
    var MslObject = require('../io/MslObject.js');
    var MslArray = require('../io/MslArray.js');
    var Class = require('../util/Class.js');
    var MslEncoderFormat = require('../io/MslEncoderFormat.js');
    var AsyncExecutor = require('../util/AsyncExecutor.js');
    var MslEncoderException = require('../io/MslEncoderException.js');
    var BufferedInputStream = require('../io/BufferedInputStream.js');

    /**
     * Escape a string to be output as a single line of text.
     *
     * @param {?string} s the string. May be {@code null}.
     * @returns {string} the escaped string.
     */
    var MslEncoderFactory$quote = function MslEncoderFactory$quote(s) {
        var json = JSON.stringify(s);
        return json
            .replace(/[\"]/g, '\\"')
            .replace(/[\\]/g, '\\\\')
            .replace(/[\/]/g, '\\/')
            .replace(/[\b]/g, '\\b')
            .replace(/[\f]/g, '\\f')
            .replace(/[\n]/g, '\\n')
            .replace(/[\r]/g, '\\r')
            .replace(/[\t]/g, '\\t');
    };

    /**
     * Convert a value to a string for print purposes.
     *
     * @param {?} value the value to convert to a string. May be {@code null}.
     * @return {string} the string.
     */
    var MslEncoderFactory$stringify = function MslEncoderFactory$stringify(v) {
        if (v instanceof MslObject || v instanceof MslArray) {
            return v.toString();
        } else if (v instanceof Uint8Array) {
            return Base64.encode(v);
        } else {
            var json = JSON.stringify(v);
            return json
                .replace(/[\"]/g, '\\"')
                .replace(/[\\]/g, '\\\\')
                .replace(/[\/]/g, '\\/')
                .replace(/[\b]/g, '\\b')
                .replace(/[\f]/g, '\\f')
                .replace(/[\n]/g, '\\n')
                .replace(/[\r]/g, '\\r')
                .replace(/[\t]/g, '\\t');
        }
    };

    var MslEncoderFactory = module.exports = Class.create({
        /**
         * Returns the most preferred encoder format from the provided set of
         * formats.
         *
         * @param {Array<MslEncoderFormat>} formats the set of formats to choose from. May be {@code null} or
         *        empty.
         * @return {MslEncoderFormat} the preferred format from the provided set or the default format
         *         if format set is {@code null} or empty.
         */
        getPreferredFormat: function(formats) {},

        /**
         *
Create a new {@link MslTokenizer}.

         *
         *
If the encoding format is not provided, it will be determined by
         * inspecting the byte stream identifier located in the first byte.

         *
         * @param {InputStream} source the binary data to tokenize.
         * @param {?MslEncoderFormat} format the encoding format. May be
         *        {@code null}.
         * @param {number} timeout read timeout used to determine the encoding
         *        format or -1 for no timeout.
         * @param {{result: function(MslTokenizer), timeout: function(), error: function(Error)}}
         *        callback the callback that will receive the
         *        {@link MslTokenizer}, be notified of timeouts, or any thrown
         *        exceptions.
         * @throws IOException if there is a problem reading the byte stream
         *         identifier.
         * @throws MslEncoderException if the encoder format is not recognized or
         *         is not supported.
         */
        createTokenizer: function createTokenizer(source, format, timeout, callback) {
            var self = this;

            AsyncExecutor(callback, function() {
                // If the format was provided, return the tokenizer directly.
                if (format)
                    return this.generateTokenizer(source, format);

                // Read the byte stream identifier.
                var bufferedSource = source.markSupported() ? source : new BufferedInputStream(source);
                bufferedSource.mark();
                bufferedSource.read(1, timeout, {
                    result: function(bytes) {
                        AsyncExecutor(callback, function() {
                            if (bytes == null || bytes.length < 1)
                                throw new MslEncoderException("End of stream reached when attempting to read the byte stream identifier.");
                            var id = bytes[0];
                            identify(bufferedSource, id);
                        }, self);
                    },
                    timeout: callback.timeout,
                    error: callback.error,
                });
            }, self);

            function identify(bufferedSource, id) {
                AsyncExecutor(callback, function() {
                    format = MslEncoderFormat.getFormat(id);
                    if (!format)
                        throw new MslEncoderException("Unidentified encoder format ID: (byte)" + id + ".");

                    // Reset the input stream and return the tokenizer.
                    bufferedSource.reset();
                    return this.generateTokenizer(bufferedSource, format);
                }, self);
            }
        },

        /**
         * Create a new {@link MslTokenizer} of the specified encoder format.
         *
         * @param source the binary data to tokenize.
         * @param format the encoder format.
         * @return the {@link MslTokenizer}.
         * @throws MslEncoderException if the encoder format is not supported.
         */
        generateTokenizer: function generateTokenizer(source, format) {},

        /**
         *
Create a new {@link MslObject}.

         *
         *
If a map is provided the object will be populated with the map
         * data.

         *
         * @param {Object=} map the map of name/value pairs. This must be a map of
         *        {@code string}s onto values. May be {@code null}.
         * @return {MslObject} the {@link MslObject}.
         * @throws TypeError if one of the values is of an
         *         unsupported type.
         */
        createObject: function createObject(map) {
            return new MslObject(map);
        },

        /**
         * Identify the encoder format of the {@link MslObject} of the encoded
         * data. The format will be identified by inspecting the byte stream
         * identifier located in the first byte.
         *
         * @param {Uint8Array} encoding the encoded data.
         * @return {MslEncoderFormat} the encoder format.
         * @throws MslEncoderException if the encoder format cannot be identified
         *         or there is an error parsing the encoder format ID.
         */
        parseFormat: function parseFormat(encoding) {
            // Fail if the encoding is too short.
            if (encoding.length < 1)
                throw new MslEncoderException("No encoding identifier found.");

            // Identify the encoder format.
            var id = encoding[0];
            var format = MslEncoderFormat.getFormat(id);
            if (!format)
                throw new MslEncoderException("Unidentified encoder format ID: (byte)" + id + ".");
            return format;
        },

        /**
         * Parse a {@link MslObject} from encoded data. The encoder format will be
         * determined by inspecting the byte stream identifier located in the first
         * byte.
         *
         * @param {Uint8Array} encoding the encoded data to parse.
         * @return {MslObject} the {@link MslObject}.
         * @throws MslEncoderException if the encoder format is not supported or
         *         there is an error parsing the encoded data.
         */
        parseObject: function(encoding) {},

        /**
         * Encode a {@link MslObject} into the specified encoder format.
         *
         * @param {MslObject} object the {@link MslObject} to encode.
         * @param {MslEncoderFormat} format the encoder format.
         * @param {{result: function(Uint8Array), error: function(Error)}}
         *        callback the callback that will receive the encoded data or
         *        any thrown exceptions.
         * @throws MslEncoderException if the encoder format is not supported or
         *         there is an error encoding the object.
         */
        encodeObject: function(object, format, callback) {},

        /**
         *
Create a new {@link MslArray}.

         *
         *
If a collection of values is provided the array will be populated
         * with the collection data.

         *
         * @param {Array<*>} collection the collection of values. May be {@code null}.
         * @return {MslArray} the {@link MslArray}.
         * @throws TypeError if one of the values is of an
         *         unsupported type.
         */
        createArray: function createArray(collection) {
            return new MslArray(collection);
        },
    });

    // Exports.
    module.exports.quote = MslEncoderFactory$quote;
    module.exports.stringify = MslEncoderFactory$stringify;
})(require, (typeof module !== 'undefined') ? module : mkmodule('MslEncoderFactory'));<|endoftext|>


<commit_before>// 全局变量
var duoshuoName = "";

// 移动设备侦测
var isMobile = {
    Android: function() {
        return navigator.userAgent.match(/Android/i);
    },
    BlackBerry: function() {
        return navigator.userAgent.match(/BlackBerry/i);
    },
    iOS: function() {
        return navigator.userAgent.match(/iPhone|iPad|iPod/i);
    },
    Opera: function() {
        return navigator.userAgent.match(/Opera Mini/i);
    },
    Windows: function() {
        return navigator.userAgent.match(/IEMobile/i);
    },
    any: function() {
        return (isMobile.Android() || isMobile.BlackBerry() || isMobile.iOS() || isMobile.Opera() || isMobile.Windows());
    }
};

var operation = {
    init: function() {
        if (title == '主页') {
            this.insertWeibo();
        }
        this.toTop();
        // 搜索自动补全
        this.autoComplete();
        // 文章导航
        this.menuIndex($('article'));
    },
    //实现搜索框自动提示(注意data属性不要配置)
    autoComplete: function() {
        var pr = {
            id: 'searchdiv',
            //ajax请求的参数
            paraName: 'keyword',
            // 显示的文字的属性名
            textFiled: 'title',
            // 获取value的属性名
            valueFiled: 'title',
            //ajax请求的url
            url: '/autocomplete',
            showdivId: 'autodiv',
            select: function() {
                document.getElementById('searchartical').submit();
            },
            style: {},
        };
        AutoComplete(pr);
    },
    insertWeibo: function() {
        var htmlStr = '';
        if (!isMobile.any() && ($(window).width() > 992) && !$(".share_self").size()) {
            $(".weibo").css("background", "#ccc").append(htmlStr);
        }
        if (isMobile.any()) {
            $(".weibo").remove();
        }
    },
    // 回到顶部
    toTop: function() {
        var $toTop = $(".gotop");
        $toTop.fadeOut();
        $(window).on("scroll", function() {
            // 加上滚动条的高度;
            var scrollHeight = document.body.scrollHeight && document.body.clientWidth && window.screen.width;
            var height = $(window).height();
            height = height / scrollHeight * height;
            if (($(window).scrollTop() + height) >= $(window).height()) {
                $toTop.css("display", "block").fadeIn();
            } else {
                $toTop.fadeOut();
            }
        });

        $toTop.on("click", function(evt) {
            var $obj = $("body");
            $obj.animate({
                scrollTop: 0
            }, 240);
            evt.preventDefault();
        });
    },
    alertMsg: function(msg) {
        if (!msg) return;
        var $msg = $(".alertInfo").size() ? $(".alertInfo") : $("

").appendTo($("body"));
        $msg = $($msg);
        $msg.html(msg).css("right", "-9999").animate({
            right: 20
        }, 800);
        // 3s后消失
        clearTimeout(window._alert_timer);
        window._alert_timer = setTimeout(function() {
            $msg.animate({ right: -9999 }, 800);
        }, 3000);
    },
    // 显示欢迎信息[多说]
    welcome: function() {
        var self = this,
            visitor;

        function getNamefailed() {
            var histories = {},
                userinfo = {};
            try { histories = JSON.parse($.cookie("visitor_history")); } catch (e) {}
            for (var key in histories) {
                userinfo = {
                    name: key,
                    avatar: histories[key]
                }
            }
            if (userinfo.name && userinfo.avatar) {
                var htmlStr = makeHtml(userinfo.name, userinfo.avatar);
                self.alertMsg(htmlStr);
            }
        }

        function makeHtml(name, avatar) {
            return "热烈欢迎:" + "" + name;
        }

        if (visitor = $.cookie("visitor")) {
            visitor = visitor.split("|");
            if (visitor && visitor[0] && visitor[1]) {
                // 避免每次刷新都显示欢迎信息
                var htmlStr = makeHtml(visitor[0], visitor[1]);
                self.alertMsg(htmlStr);
                return;
            }
        }

        $.removeCookie("visitor");
        duoshuoName && $.ajax({
            url: "https://" + duoshuoName + ".duoshuo.com/api/threads/listPosts.jsonp?thread_key=/&require=visitor",
            dataType: "jsonp",
            timeout: 5000,
            success: function(data) {
                if (!(data && data.visitor && data.visitor.name && data.visitor.avatar_url)) {
                    getNamefailed();
                    return;
                }
                var name = data.visitor.name;
                var avatar = data.visitor.avatar_url;

                var htmlStr = makeHtml(name, avatar);
                self.alertMsg(htmlStr);

                // 目前登录人缓存半天
                $.cookie("visitor", name + "|" + avatar, {
                    expires: 0.25,
                    path: "/"
                });

                // 缓存历史登录者
                var histories = $.cookie("visitor_history");
                try {
                    histories = JSON.parse(histories);
                } catch (e) {
                    histories = {};
                }
                histories[name] = avatar;
                try {
                    $.cookie("visitor_history", JSON.stringify(histories), {
                        expires: 100,
                        path: "/"
                    });
                } catch (e) {}
            },
            error: function() {
                getNamefailed();
            }
        });
    },
    cookieHelper: {
        SetCookie: function(name, value) {
            var argv = arguments;
            var argc = arguments.length;
            var expires = (2 < argc) ? argv[2] : null;
            var path = (3 < argc) ? argv[3] : null;
            var domain = (4 < argc) ? argv[4] : null;
            var secure = (5 < argc) ? argv[5] : false;
            document.cookie = name + "=" + escape(value) + ((expires == null) ? "" : ("; expires=" + expires.toGMTString())) + ((path == null) ? "" : ("; path=" + path)) + ((domain == null) ? "" : ("; domain=" + domain)) + ((secure == true) ? "; secure" : "");
        },
        GetCookie: function(Name) {
            var search = Name + "=";
            var returnvalue = "";
            if (document.cookie.length > 0) {
                offset = document.cookie.indexOf(search);
                if (offset != -1) {
                    offset += search.length;
                    end = document.cookie.indexOf(";", offset);
                    if (end == -1)
                        end = document.cookie.length;
                    returnvalue = unescape(document.cookie.substring(offset, end));
                }
            }
            return returnvalue;
        }
    },
    // 目录导航树
    menuIndex: function($obj) {
        if (!/\/p\//.test(window.location.href)) {
            return;
        }

        if ($('h3', $obj).length > 2 && !isMobile.any()) {
            var h3 = [],
                h4 = [],
                tmpl = '
',
                h3index = 0;

            $.each($('h3,h4', $obj), function(index, item) {
                if (item.tagName.toLowerCase() == 'h3') {
                    var h3item = {};
                    h3item.name = $(item).text();
                    h3item.id = 'menuIndex' + index;
                    h3.push(h3item);
                    h3index++;
                } else {
                    var h4item = {};
                    h4item.name = $(item).text();
                    h4item.id = 'menuIndex' + index;
                    if (!h4[h3index - 1]) {
                        h4[h3index - 1] = [];
                    }
                    h4[h3index - 1].push(h4item);
                }
                item.id = 'menuIndex' + index
            });

            //添加h1
            tmpl += '
' + $('h1').text() + '
';

            for (var i = 0; i < h3.length; i++) {
                tmpl += '
' + h3[i].name + '
';
                if (h4[i]) {
                    for (var j = 0; j < h4[i].length; j++) {
                        tmpl += '
' + h4[i][j].name + '
';
                    }
                }
            }
            tmpl += '
';

            $('body').append('

');
            $('#menuIndex').append($(tmpl)).delegate('a', 'click', function(e) {
                e.preventDefault();
                var scrollNum = $(this).attr('data-top') || $('#' + $(this).attr('data-id')).offset().top;
                $('body, html').animate({ scrollTop: scrollNum - 30 }, 400, 'swing');
            });
            $(window).load(function() {
                var scrollTop = [];
                $.each($('#menuIndex li a'), function(index, item) {
                    if (!$(item).attr('data-top')) {
                        var top = $('#' + $(item).attr('data-id')).offset().top;
                        scrollTop.push(top);
                        $(item).attr('data-top', top);
                    }
                });

                var waitForFinalEvent = (function() {
                    var timers = {};
                    return function(callback, ms, uniqueId) {
                        if (!uniqueId) {
                            uniqueId = "Don't call this twice without a uniqueId";
                        }
                        if (timers[uniqueId]) {
                            clearTimeout(timers[uniqueId]);
                        }
                        timers[uniqueId] = setTimeout(callback, ms);
                    };
                })();
                $(window).scroll(function() {
                    waitForFinalEvent(function() {
                        var nowTop = $(window).scrollTop(),
                            index, length = scrollTop.length;
                        if (nowTop + 60 > scrollTop[length - 1]) {
                            index = length
                        } else {
                            for (var i = 0; i < length; i++) {
                                if (nowTop + 60 <= scrollTop[i]) {
                                    index = i
                                    break;
                                }
                            }
                        }
                        $('#menuIndex li').removeClass('on')
                        $('#menuIndex li').eq(index).addClass('on')
                    })
                });
            });

            //用js计算屏幕的高度
            $('#menuIndex').css('max-height', $(window).height() - 80);
        }
    },
}

$(function() {
    operation.init();
    // 显示欢迎消息
    duoshuoName = $(".ds-thread").attr("data-name");
    window.duoshuoQuery = { short_name: duoshuoName };
    if (window.duoshuoQuery.short_name) {
        $.getScript((document.location.protocol == 'https:' ? 'https:' : 'http:') + '//static.duoshuo.com/embed.js', function() {
            operation.welcome();
        });
    } else {
        operation.welcome();
    }

    $(".close-weibo").on('click', function() {
        $(this).parent().hide();
    });
    // 设置皮肤
    var skin = operation.cookieHelper.GetCookie('skin');
    if (skin == "white") {
        document.body.style.background = "white";
    }
    $(".white").on('click', function() {
        document.body.style.background = "white";
        operation.cookieHelper.SetCookie('skin', 'white');
    });
    $(".yellow").on('click', function() {
        document.body.style.background = "#f5f5d5";
        operation.cookieHelper.SetCookie('skin', 'yellow');
    });
});
<commit_msg>fix: nav to top not work<commit_after>// 全局变量
var duoshuoName = "";

// 移动设备侦测
var isMobile = {
    Android: function() {
        return navigator.userAgent.match(/Android/i);
    },
    BlackBerry: function() {
        return navigator.userAgent.match(/BlackBerry/i);
    },
    iOS: function() {
        return navigator.userAgent.match(/iPhone|iPad|iPod/i);
    },
    Opera: function() {
        return navigator.userAgent.match(/Opera Mini/i);
    },
    Windows: function() {
        return navigator.userAgent.match(/IEMobile/i);
    },
    any: function() {
        return (isMobile.Android() || isMobile.BlackBerry() || isMobile.iOS() || isMobile.Opera() || isMobile.Windows());
    }
};

var operation = {
    init: function() {
        if (title == '主页') {
            this.insertWeibo();
        }
        this.toTop();
        // 搜索自动补全
        this.autoComplete();
        // 文章导航
        this.menuIndex($('article'));
    },
    //实现搜索框自动提示(注意data属性不要配置)
    autoComplete: function() {
        var pr = {
            id: 'searchdiv',
            //ajax请求的参数
            paraName: 'keyword',
            // 显示的文字的属性名
            textFiled: 'title',
            // 获取value的属性名
            valueFiled: 'title',
            //ajax请求的url
            url: '/autocomplete',
            showdivId: 'autodiv',
            select: function() {
                document.getElementById('searchartical').submit();
            },
            style: {},
        };
        AutoComplete(pr);
    },
    insertWeibo: function() {
        var htmlStr = '';
        if (!isMobile.any() && ($(window).width() > 992) && !$(".share_self").size()) {
            $(".weibo").css("background", "#ccc").append(htmlStr);
        }
        if (isMobile.any()) {
            $(".weibo").remove();
        }
    },
    // 回到顶部
    toTop: function() {
        var $toTop = $(".gotop");
        $toTop.fadeOut();
        $(window).on("scroll", function() {
            // 加上滚动条的高度;
            var scrollHeight = document.body.scrollHeight && document.body.clientWidth && window.screen.width;
            var height = $(window).height();
            height = height / scrollHeight * height;
            if (($(window).scrollTop() + height) >= $(window).height()) {
                $toTop.css("display", "block").fadeIn();
            } else {
                $toTop.fadeOut();
            }
        });

        $toTop.on("click", function(evt) {
            var $obj = $("body,html");
            $obj.animate({
                scrollTop: 0
            }, 240);
            evt.preventDefault();
        });
    },
    alertMsg: function(msg) {
        if (!msg) return;
        var $msg = $(".alertInfo").size() ? $(".alertInfo") : $("

").appendTo($("body"));
        $msg = $($msg);
        $msg.html(msg).css("right", "-9999").animate({
            right: 20
        }, 800);
        // 3s后消失
        clearTimeout(window._alert_timer);
        window._alert_timer = setTimeout(function() {
            $msg.animate({ right: -9999 }, 800);
        }, 3000);
    },
    // 显示欢迎信息[多说]
    welcome: function() {
        var self = this,
            visitor;

        function getNamefailed() {
            var histories = {},
                userinfo = {};
            try { histories = JSON.parse($.cookie("visitor_history")); } catch (e) {}
            for (var key in histories) {
                userinfo = {
                    name: key,
                    avatar: histories[key]
                }
            }
            if (userinfo.name && userinfo.avatar) {
                var htmlStr = makeHtml(userinfo.name, userinfo.avatar);
                self.alertMsg(htmlStr);
            }
        }

        function makeHtml(name, avatar) {
            return "热烈欢迎:" + "" + name;
        }

        if (visitor = $.cookie("visitor")) {
            visitor = visitor.split("|");
            if (visitor && visitor[0] && visitor[1]) {
                // 避免每次刷新都显示欢迎信息
                var htmlStr = makeHtml(visitor[0], visitor[1]);
                self.alertMsg(htmlStr);
                return;
            }
        }

        $.removeCookie("visitor");
        duoshuoName && $.ajax({
            url: "https://" + duoshuoName + ".duoshuo.com/api/threads/listPosts.jsonp?thread_key=/&require=visitor",
            dataType: "jsonp",
            timeout: 5000,
            success: function(data) {
                if (!(data && data.visitor && data.visitor.name && data.visitor.avatar_url)) {
                    getNamefailed();
                    return;
                }
                var name = data.visitor.name;
                var avatar = data.visitor.avatar_url;

                var htmlStr = makeHtml(name, avatar);
                self.alertMsg(htmlStr);

                // 目前登录人缓存半天
                $.cookie("visitor", name + "|" + avatar, {
                    expires: 0.25,
                    path: "/"
                });

                // 缓存历史登录者
                var histories = $.cookie("visitor_history");
                try {
                    histories = JSON.parse(histories);
                } catch (e) {
                    histories = {};
                }
                histories[name] = avatar;
                try {
                    $.cookie("visitor_history", JSON.stringify(histories), {
                        expires: 100,
                        path: "/"
                    });
                } catch (e) {}
            },
            error: function() {
                getNamefailed();
            }
        });
    },
    cookieHelper: {
        SetCookie: function(name, value) {
            var argv = arguments;
            var argc = arguments.length;
            var expires = (2 < argc) ? argv[2] : null;
            var path = (3 < argc) ? argv[3] : null;
            var domain = (4 < argc) ? argv[4] : null;
            var secure = (5 < argc) ? argv[5] : false;
            document.cookie = name + "=" + escape(value) + ((expires == null) ? "" : ("; expires=" + expires.toGMTString())) + ((path == null) ? "" : ("; path=" + path)) + ((domain == null) ? "" : ("; domain=" + domain)) + ((secure == true) ? "; secure" : "");
        },
        GetCookie: function(Name) {
            var search = Name + "=";
            var returnvalue = "";
            if (document.cookie.length > 0) {
                offset = document.cookie.indexOf(search);
                if (offset != -1) {
                    offset += search.length;
                    end = document.cookie.indexOf(";", offset);
                    if (end == -1)
                        end = document.cookie.length;
                    returnvalue = unescape(document.cookie.substring(offset, end));
                }
            }
            return returnvalue;
        }
    },
    // 目录导航树
    menuIndex: function($obj) {
        if (!/\/p\//.test(window.location.href)) {
            return;
        }

        if ($('h3', $obj).length > 2 && !isMobile.any()) {
            var h3 = [],
                h4 = [],
                tmpl = '
',
                h3index = 0;

            $.each($('h3,h4', $obj), function(index, item) {
                if (item.tagName.toLowerCase() == 'h3') {
                    var h3item = {};
                    h3item.name = $(item).text();
                    h3item.id = 'menuIndex' + index;
                    h3.push(h3item);
                    h3index++;
                } else {
                    var h4item = {};
                    h4item.name = $(item).text();
                    h4item.id = 'menuIndex' + index;
                    if (!h4[h3index - 1]) {
                        h4[h3index - 1] = [];
                    }
                    h4[h3index - 1].push(h4item);
                }
                item.id = 'menuIndex' + index
            });

            //添加h1
            tmpl += '
' + $('h1').text() + '
';

            for (var i = 0; i < h3.length; i++) {
                tmpl += '
' + h3[i].name + '
';
                if (h4[i]) {
                    for (var j = 0; j < h4[i].length; j++) {
                        tmpl += '
' + h4[i][j].name + '
';
                    }
                }
            }
            tmpl += '
';

            $('body').append('

');
            $('#menuIndex').append($(tmpl)).delegate('a', 'click', function(e) {
                e.preventDefault();
                var scrollNum = $(this).attr('data-top') || $('#' + $(this).attr('data-id')).offset().top;
                $('body, html').animate({ scrollTop: scrollNum - 30 }, 400, 'swing');
            });
            $(window).load(function() {
                var scrollTop = [];
                $.each($('#menuIndex li a'), function(index, item) {
                    if (!$(item).attr('data-top')) {
                        var top = $('#' + $(item).attr('data-id')).offset().top;
                        scrollTop.push(top);
                        $(item).attr('data-top', top);
                    }
                });

                var waitForFinalEvent = (function() {
                    var timers = {};
                    return function(callback, ms, uniqueId) {
                        if (!uniqueId) {
                            uniqueId = "Don't call this twice without a uniqueId";
                        }
                        if (timers[uniqueId]) {
                            clearTimeout(timers[uniqueId]);
                        }
                        timers[uniqueId] = setTimeout(callback, ms);
                    };
                })();
                $(window).scroll(function() {
                    waitForFinalEvent(function() {
                        var nowTop = $(window).scrollTop(),
                            index, length = scrollTop.length;
                        if (nowTop + 60 > scrollTop[length - 1]) {
                            index = length
                        } else {
                            for (var i = 0; i < length; i++) {
                                if (nowTop + 60 <= scrollTop[i]) {
                                    index = i
                                    break;
                                }
                            }
                        }
                        $('#menuIndex li').removeClass('on')
                        $('#menuIndex li').eq(index).addClass('on')
                    })
                });
            });

            //用js计算屏幕的高度
            $('#menuIndex').css('max-height', $(window).height() - 80);
        }
    },
}

$(function() {
    operation.init();
    // 显示欢迎消息
    duoshuoName = $(".ds-thread").attr("data-name");
    window.duoshuoQuery = { short_name: duoshuoName };
    if (window.duoshuoQuery.short_name) {
        $.getScript((document.location.protocol == 'https:' ? 'https:' : 'http:') + '//static.duoshuo.com/embed.js', function() {
            operation.welcome();
        });
    } else {
        operation.welcome();
    }

    $(".close-weibo").on('click', function() {
        $(this).parent().hide();
    });
    // 设置皮肤
    var skin = operation.cookieHelper.GetCookie('skin');
    if (skin == "white") {
        document.body.style.background = "white";
    }
    $(".white").on('click', function() {
        document.body.style.background = "white";
        operation.cookieHelper.SetCookie('skin', 'white');
    });
    $(".yellow").on('click', function() {
        document.body.style.background = "#f5f5d5";
        operation.cookieHelper.SetCookie('skin', 'yellow');
    });
});
<|endoftext|>


<commit_before>var _ = require('underscore');
var str = require('underscore.string');
var teoria = require('teoria');
var music = require('../knowledge/music');
var utils = require('../utils');

/**
 * @constructor
 */
function FrettedBuildModel(tuning) {
    this.tuning = _.map(this.stringListParser(tuning, '-'), music.extractRoot);
    this.priority_ = -1;
}

/**
 * @param {Array.<string>} row
 * @param {FrettedBuildModel} prev
 * @returns {FrettedBuildModel}
 */
FrettedBuildModel.prototype.deserialize = function(row, prev) {

    this.name = '';
    this.displayName = row[0] || prev.displayName;

    this.aliases_ = this.stringListParser(row[1], ',') || prev.aliases_;
    this.bass_ = row[2] || prev.bass_;
    this.chord_ = row[3] || prev.chord_;

    this.own_ = {
        frets: this.fretListParser(row[4]),
        fingering: this.fretListParser(row[5])
    };

    this.derivatePointerNotes_ = this.stringListParser(row[6], ' ');

    this.priority_ = row[8] ? parseInt(row[8], 10) : 0;

    this.alternateDefinitions_ = {
        frets: this.fretListParser(row[9]) || this.own_.frets,
        fingering: this.fretListParser(row[10]) || this.own_.fingering
    };

    // validation
    try {
        teoria.note(this.bass_);
    } catch(error) {
        console.warn('invalid chord', chord);
    }

    var invalidAliases = [];
    this.verifiedAliases_ = _.filter(this.aliases_, function(alias) {
        try {
            return !!teoria.chord(alias);
        } catch(error) {
            invalidAliases.push({
                alias: alias,
                error:  error.message
            });
        }
        return false;
    }, this);

    if (_.isEmpty(this.verifiedAliases_)) {
        console.log('invalid aliases', invalidAliases);
        console.log('----');
    }
    return this;
};

/**
 * @return {number}
 */
FrettedBuildModel.prototype.getPriority = function() {
    return this.priority_;
};

FrettedBuildModel.prototype.serialize = function() {
    return {
        displayName: this.displayName,
        name: this.name,
        frets: this.own_.frets,
        fingering: this.own_.fingering// ,
        // fretsFingering: _.zip(this.own_.frets, this.own_.fingering)
    }
};

/**
 * @returns {FrettedBuildModel}
 */
FrettedBuildModel.prototype.clone = function() {
    return _.extend(new FrettedBuildModel(), utils.clone(this));
};

/**
 * @param {boolean} value
 * @returns {FrettedBuildModel}
 */
FrettedBuildModel.prototype.setDerived = function(value) {
    this.derived_ = false;
    return this;
};

/**
 * @returns {boolean}
 */
FrettedBuildModel.prototype.isDerived = function() {
    return this.derived_;
};

/**
 * @param {string} value
 * @returns {FrettedBuildModel}
 */
FrettedBuildModel.prototype.setBass = function(value) {
    this.bass_ = value;
    return this;
};

/**
 * @param {string} value
 * @returns {FrettedBuildModel}
 */
FrettedBuildModel.prototype.setName = function(value) {
    this.name = value;
    return this;
};

/**
 * @param {string} value
 * @returns {FrettedBuildModel}
 */
FrettedBuildModel.prototype.setDisplayName = function(value) {
    this.displayName = value;
    return this;
};

/**
 * @param {Object.<string, FrettedBuildModel>} map
 * @returns {FrettedBuildModel}
 */
FrettedBuildModel.prototype.alternates = function(map) {
    return this;
};

FrettedBuildModel.prototype.buildUsingPointers = function(map, results, listItem, insertionIndex) {
    if (!_.isEmpty(this.derivatePointerNotes_)) {
        // console.log(this.name, 'has', this.derivatePointerNotes_.length, 'alternate sources');
        // var distance = notes.distanceInSemitones()
    }

    _.forEach(this.derivatePointerNotes_, function(pointerBassNote, pointerBassIndex) {
        var sourceName = music.transformChord(this.name, pointerBassNote);
        var sourceList = map[sourceName];
        if (_.isEmpty(sourceList)) {
            console.log('failed finding source', this.name, sourceName);
            return;
        }
        var createdCount = 0;
        _.forEach(sourceList, function(chord) {
            var firstStringNote = chord.getFirstNote();
            if (_.isNull(firstStringNote)) {
                console.log('mapped to chord with no frets', this.name, sourceName);
                return null;
            }
            var distance = music.positiveDistanceInSemitones(firstStringNote, this.bass_ || this.name);
            results.splice(insertionIndex + pointerBassIndex + createdCount++, 0,
              chord.clone()
                .setName(this.name)
                .setDisplayName(this.displayName)
                .deriveFrets(distance)
                .deriveFingering()
            );
        }, this);
    }, this);
    return this;
};

/**
 * @returns {boolean}
 */
FrettedBuildModel.prototype.isDefined = function() {
    return !!this.getFirstNote();
};

FrettedBuildModel.prototype.getFirstNote = function() {
    var note = null;
    _.find(this.own_.frets, function(fret, index) {
        if (fret > -1) {
            note = music.transposeNote(this.tuning[index], fret);
            return true;
        }
        return false;
    }, this);
    return note;
};

/**
 * @param {number} offset
 * @return {FrettedBuildModel}
 */
FrettedBuildModel.prototype.deriveFrets = function(offset) {
    this.own_.frets = _.map(this.alternateDefinitions_.frets || this.own_.frets, function(fret) {
        if (fret === -1) {
            return fret;
        }
        return fret + offset;
    });
    return this;
};

/**
 * @return {FrettedBuildModel}
 */
FrettedBuildModel.prototype.deriveFingering = function() {
    var offset = 0;
    if (_.isEqual(this.alternateDefinitions_.fingering, this.own_.fingering)) {
        offset = 1;
    }
    this.own_.fingering = _.map(this.alternateDefinitions_.fingering, function(finger) {
        if (finger === -1) {
            return finger;
        }
        return finger + offset;
    });
    return this;
};

/**
 * @param {Object.<string, Array.<FrettedBuildModel>>} map
 * @returns {FrettedBuildModel}
 */
FrettedBuildModel.prototype.register = function(map) {
    _.forEach(this.verifiedAliases_, function(alias) {
        var root = music.extractRoot(alias);
        var roots = [root, music.reverseAccidental(root)];
        var basses = [null, null];
        if (root !== this.bass_) {
            roots = roots.concat(roots);
            basses = [this.bass_, this.bass_, music.reverseAccidental(this.bass_), music.reverseAccidental(this.bass_)];
        }

        _.forEach(_.zip(roots, basses), function(rootBass) {
            this.registerAlias_(rootBass[0] + alias.substring(rootBass[0].length), rootBass[1], map);
        }, this);

        //if (music.extractRoot(alias) === this.bass_) {
        //    // not a slash chord
        //    this.registerAlias_(alias, null, map);
        //    var root = music.extractRoot(alias);
        //    if (music.isAccidental(root)) {
        //        // register accidental
        //        this.registerAlias_(music.reverseAccidental(root) + alias.substring(2), null, map);
        //    }
        //} else {
        //    // slash chord
        //    this.registerAlias_(alias, this.bass_, map);
        //    // if slash chord is an accidental register the respective opposite
        //    if (music.isAccidental(this.bass_)) {
        //        this.registerAlias_(alias, music.reverseAccidental(this.bass_), map);
        //    }
        //}
    }, this);
    return this;
};

FrettedBuildModel.prototype.registerAlias_ = function(alias, bass, map) {
    var name = alias + (bass ? ('/' + bass) : '');
    map[name] = [this.clone()
      .setBass(bass)
      .setName(name)
    ];
};

/**
 * @param {string} value
 * @param {string=} delimiter
 * @returns {Array.<number> | null}
 */
FrettedBuildModel.prototype.fretListParser = function(value, delimiter) {
    if (!value) {
        return null;
    }
    delimiter = delimiter || ' ';
    var ret = _.chain(value.split(delimiter))
      .map(function(string) {
          var num = parseInt(utils.removeAllWhiteSpace(string), 10);
          return isNaN(num) ? -1 : num;
      })
      .value();
    return _.isEmpty(ret) ? null : ret;
};

/**
 * @param {string} value
 * @param {string} delimiter
 * @returns {Array.<string> | null}
 */
FrettedBuildModel.prototype.stringListParser = function(value, delimiter) {
    if (!value) {
        return null;
    }

    var ret = _.chain(value.split(delimiter))
      .map(utils.removeAllWhiteSpace)
      .compact()
      .value();

    return _.isEmpty(ret) ? null : ret;
};

FrettedBuildModel.prototype.valueOf = function() {
    var frets = this.own_.frets ? this.own_.frets.join(',') : 'undefined';
    var fingering = this.own_.fingering ? this.own_.fingering.join(',') : 'undefined';
    return frets + ':' + fingering;
};

module.exports = exports = FrettedBuildModel;

<commit_msg>- removed dead code<commit_after>var _ = require('underscore');
var str = require('underscore.string');
var teoria = require('teoria');
var music = require('../knowledge/music');
var utils = require('../utils');

/**
 * @constructor
 */
function FrettedBuildModel(tuning) {
    this.tuning = _.map(this.stringListParser(tuning, '-'), music.extractRoot);
    this.priority_ = -1;
}

/**
 * @param {Array.<string>} row
 * @param {FrettedBuildModel} prev
 * @returns {FrettedBuildModel}
 */
FrettedBuildModel.prototype.deserialize = function(row, prev) {

    this.name = '';
    this.displayName = row[0] || prev.displayName;

    this.aliases_ = this.stringListParser(row[1], ',') || prev.aliases_;
    this.bass_ = row[2] || prev.bass_;
    this.chord_ = row[3] || prev.chord_;

    this.own_ = {
        frets: this.fretListParser(row[4]),
        fingering: this.fretListParser(row[5])
    };

    this.derivatePointerNotes_ = this.stringListParser(row[6], ' ');

    this.priority_ = row[8] ? parseInt(row[8], 10) : 0;

    this.alternateDefinitions_ = {
        frets: this.fretListParser(row[9]) || this.own_.frets,
        fingering: this.fretListParser(row[10]) || this.own_.fingering
    };

    // validation
    try {
        teoria.note(this.bass_);
    } catch(error) {
        console.warn('invalid chord', chord);
    }

    var invalidAliases = [];
    this.verifiedAliases_ = _.filter(this.aliases_, function(alias) {
        try {
            return !!teoria.chord(alias);
        } catch(error) {
            invalidAliases.push({
                alias: alias,
                error:  error.message
            });
        }
        return false;
    }, this);

    if (_.isEmpty(this.verifiedAliases_)) {
        console.log('invalid aliases', invalidAliases);
        console.log('----');
    }
    return this;
};

/**
 * @return {number}
 */
FrettedBuildModel.prototype.getPriority = function() {
    return this.priority_;
};

FrettedBuildModel.prototype.serialize = function() {
    return {
        displayName: this.displayName,
        name: this.name,
        frets: this.own_.frets,
        fingering: this.own_.fingering// ,
        // fretsFingering: _.zip(this.own_.frets, this.own_.fingering)
    }
};

/**
 * @returns {FrettedBuildModel}
 */
FrettedBuildModel.prototype.clone = function() {
    return _.extend(new FrettedBuildModel(), utils.clone(this));
};

/**
 * @param {boolean} value
 * @returns {FrettedBuildModel}
 */
FrettedBuildModel.prototype.setDerived = function(value) {
    this.derived_ = false;
    return this;
};

/**
 * @returns {boolean}
 */
FrettedBuildModel.prototype.isDerived = function() {
    return this.derived_;
};

/**
 * @param {string} value
 * @returns {FrettedBuildModel}
 */
FrettedBuildModel.prototype.setBass = function(value) {
    this.bass_ = value;
    return this;
};

/**
 * @param {string} value
 * @returns {FrettedBuildModel}
 */
FrettedBuildModel.prototype.setName = function(value) {
    this.name = value;
    return this;
};

/**
 * @param {string} value
 * @returns {FrettedBuildModel}
 */
FrettedBuildModel.prototype.setDisplayName = function(value) {
    this.displayName = value;
    return this;
};

/**
 * @param {Object.<string, FrettedBuildModel>} map
 * @returns {FrettedBuildModel}
 */
FrettedBuildModel.prototype.alternates = function(map) {
    return this;
};

FrettedBuildModel.prototype.buildUsingPointers = function(map, results, listItem, insertionIndex) {
    if (!_.isEmpty(this.derivatePointerNotes_)) {
        // console.log(this.name, 'has', this.derivatePointerNotes_.length, 'alternate sources');
        // var distance = notes.distanceInSemitones()
    }

    _.forEach(this.derivatePointerNotes_, function(pointerBassNote, pointerBassIndex) {
        var sourceName = music.transformChord(this.name, pointerBassNote);
        var sourceList = map[sourceName];
        if (_.isEmpty(sourceList)) {
            console.log('failed finding source', this.name, sourceName);
            return;
        }
        var createdCount = 0;
        _.forEach(sourceList, function(chord) {
            var firstStringNote = chord.getFirstNote();
            if (_.isNull(firstStringNote)) {
                console.log('mapped to chord with no frets', this.name, sourceName);
                return null;
            }
            var distance = music.positiveDistanceInSemitones(firstStringNote, this.bass_ || this.name);
            results.splice(insertionIndex + pointerBassIndex + createdCount++, 0,
              chord.clone()
                .setName(this.name)
                .setDisplayName(this.displayName)
                .deriveFrets(distance)
                .deriveFingering()
            );
        }, this);
    }, this);
    return this;
};

/**
 * @returns {boolean}
 */
FrettedBuildModel.prototype.isDefined = function() {
    return !!this.getFirstNote();
};

FrettedBuildModel.prototype.getFirstNote = function() {
    var note = null;
    _.find(this.own_.frets, function(fret, index) {
        if (fret > -1) {
            note = music.transposeNote(this.tuning[index], fret);
            return true;
        }
        return false;
    }, this);
    return note;
};

/**
 * @param {number} offset
 * @return {FrettedBuildModel}
 */
FrettedBuildModel.prototype.deriveFrets = function(offset) {
    this.own_.frets = _.map(this.alternateDefinitions_.frets || this.own_.frets, function(fret) {
        if (fret === -1) {
            return fret;
        }
        return fret + offset;
    });
    return this;
};

/**
 * @return {FrettedBuildModel}
 */
FrettedBuildModel.prototype.deriveFingering = function() {
    var offset = 0;
    if (_.isEqual(this.alternateDefinitions_.fingering, this.own_.fingering)) {
        offset = 1;
    }
    this.own_.fingering = _.map(this.alternateDefinitions_.fingering, function(finger) {
        if (finger === -1) {
            return finger;
        }
        return finger + offset;
    });
    return this;
};

/**
 * @param {Object.<string, Array.<FrettedBuildModel>>} map
 * @returns {FrettedBuildModel}
 */
FrettedBuildModel.prototype.register = function(map) {
    _.forEach(this.verifiedAliases_, function(alias) {
        var root = music.extractRoot(alias);
        var roots = [root, music.reverseAccidental(root)];
        var basses = [null, null];
        if (root !== this.bass_) {
            roots = roots.concat(roots);
            basses = [this.bass_, this.bass_, music.reverseAccidental(this.bass_), music.reverseAccidental(this.bass_)];
        }

        _.forEach(_.zip(roots, basses), function(rootBass) {
            this.registerAlias_(rootBass[0] + alias.substring(rootBass[0].length), rootBass[1], map);
        }, this);

    }, this);
    return this;
};

FrettedBuildModel.prototype.registerAlias_ = function(alias, bass, map) {
    var name = alias + (bass ? ('/' + bass) : '');
    map[name] = [this.clone()
      .setBass(bass)
      .setName(name)
    ];
};

/**
 * @param {string} value
 * @param {string=} delimiter
 * @returns {Array.<number> | null}
 */
FrettedBuildModel.prototype.fretListParser = function(value, delimiter) {
    if (!value) {
        return null;
    }
    delimiter = delimiter || ' ';
    var ret = _.chain(value.split(delimiter))
      .map(function(string) {
          var num = parseInt(utils.removeAllWhiteSpace(string), 10);
          return isNaN(num) ? -1 : num;
      })
      .value();
    return _.isEmpty(ret) ? null : ret;
};

/**
 * @param {string} value
 * @param {string} delimiter
 * @returns {Array.<string> | null}
 */
FrettedBuildModel.prototype.stringListParser = function(value, delimiter) {
    if (!value) {
        return null;
    }

    var ret = _.chain(value.split(delimiter))
      .map(utils.removeAllWhiteSpace)
      .compact()
      .value();

    return _.isEmpty(ret) ? null : ret;
};

FrettedBuildModel.prototype.valueOf = function() {
    var frets = this.own_.frets ? this.own_.frets.join(',') : 'undefined';
    var fingering = this.own_.fingering ? this.own_.fingering.join(',') : 'undefined';
    return frets + ':' + fingering;
};

module.exports = exports = FrettedBuildModel;

<|endoftext|>


<commit_before>const crypto = require('crypto');

module.exports = class DigestClient {

  constructor(res) {
    this.res = res ;
    this.req = res.req ;
    this.agent = res.agent ;

    this.nc = 0;
  }

  authenticate(callback) {
    var options = this.req._originalParams.options ;

    // get the username and password - either provided directly or via a callback
    var fn ;
    if (typeof options.auth === 'function') {
      fn = options.auth ;
    }
    else if (typeof options.auth === 'object') {
      fn = (req, res, callback) => { return callback(null, options.auth.username, options.auth.password); };
    }
    else {
      callback(new Error('no credentials were supplied to reply to server authentication challenge')) ;
    }

    // note: we pass the original request and the 401 (or whatever) response in case the caller wants to see it
    fn(this.req, this.res, (err, username, password) => {
      if (err) {
        return callback(err);
      }

      var header = this.res.statusCode === 407 ? 'proxy-authenticate' : 'www-authenticate' ;
      var challenge = this._parseChallenge(this.res.get(header));

      var ha1 = crypto.createHash('md5');
      ha1.update([username, challenge.realm, password].join(':'));
      var ha2 = crypto.createHash('md5');
      ha2.update([options.method, options.uri].join(':'));

      // bump CSeq and preserve Call-Id
      var headers = options.headers || {};
      var seq = this.req.getParsedHeader('cseq').seq ;
      seq++ ;
      headers['CSeq'] = '' + seq + ' ' + this.req.method ;
      headers['call-id'] = this.req.get('call-id') ;

      // preserve tag on From header as well
      delete headers.from;
      headers['From'] = this.req.get('from') ;

      // Generate cnonce
      var cnonce = false;
      var nc = false;
      if (typeof challenge.qop === 'string') {
        var cnonceHash = crypto.createHash('md5');
        cnonceHash.update(Math.random().toString(36));
        cnonce = cnonceHash.digest('hex').substr(0, 8);
        nc = this._updateNC();
      }

      // Generate response hash
      var response = crypto.createHash('md5');
      var responseParams = [
        ha1.digest('hex'),
        challenge.nonce
      ];

      if (cnonce) {
        responseParams.push(nc);
        responseParams.push(cnonce);
      }

      if (challenge.qop) {
        responseParams.push(challenge.qop);
      }
      responseParams.push(ha2.digest('hex'));
      response.update(responseParams.join(':'));

      // Setup response parameters
      var authParams = {
        username: username,
        realm: challenge.realm,
        nonce: challenge.nonce,
        uri: options.uri,
        response: response.digest('hex'),
        algorithm: 'MD5'
      };
      if (challenge.qop) { authParams.qop = challenge.qop; }
      if (challenge.opaque) { authParams.opaque = challenge.opaque; }

      if (cnonce) {
        authParams.nc = nc;
        authParams.cnonce = cnonce;
      }

      headers[407 === this.res.statusCode ? 'Proxy-Authorization' : 'Authorization'] = this._compileParams(authParams);

      options.headers = headers;

      // if the original request was sent to a request-uri that looks to have a DNS name,
      // let's avoid another DNS lookup on the second request -- i.e. when challenged,
      // we want to send our credentialled request to the same server that challenged us
      const originalUri = options.uri;
      if (!originalUri.match(/sips?:[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/)) {
        const proxy = originalUri.replace(/(sips*):[^;]*/, `$1:${this.res.source_address}:${this.res.source_port}`);
        Object.assign(options, {proxy});
      }
      this.agent.request(options, callback) ;
    }) ;
  }

  _updateNC() {
    const max = 99999999;
    this.nc++;
    if (this.nc > max) {
      this.nc = 1;
    }
    const padding = new Array(8).join('0') + '';
    const nc = this.nc + '';
    return padding.substr(0, 8 - nc.length) + nc;
  }

  _compileParams(params) {
    const parts = [];
    for (const i in params) {
      if (['nc', 'algorithm'].includes(i)) parts.push(`${i}=${params[i]}`);
      else parts.push(`${i}="${params[i]}"`);
    }
    return `Digest ${parts.join(',')}`;
  }

  _parseChallenge(digest) {
    const prefix = 'Digest ';
    const challenge = digest.substr(digest.indexOf(prefix) + prefix.length);
    const parts = challenge.split(',');
    const length = parts.length;
    const params = {};
    for (let i = 0; i < length; i++) {
      const part = parts[i].match(/^\s*?([a-zA-Z0-0]+)="?(.*?)"?\s*?$/);
      if (part && part.length > 2) {
        params[part[1]] = part[2];
      }
    }
    return params;
  }
} ;
<commit_msg>on response to Challenge, QOP should be without quotes.<commit_after>const crypto = require('crypto');

module.exports = class DigestClient {

  constructor(res) {
    this.res = res ;
    this.req = res.req ;
    this.agent = res.agent ;

    this.nc = 0;
  }

  authenticate(callback) {
    var options = this.req._originalParams.options ;

    // get the username and password - either provided directly or via a callback
    var fn ;
    if (typeof options.auth === 'function') {
      fn = options.auth ;
    }
    else if (typeof options.auth === 'object') {
      fn = (req, res, callback) => { return callback(null, options.auth.username, options.auth.password); };
    }
    else {
      callback(new Error('no credentials were supplied to reply to server authentication challenge')) ;
    }

    // note: we pass the original request and the 401 (or whatever) response in case the caller wants to see it
    fn(this.req, this.res, (err, username, password) => {
      if (err) {
        return callback(err);
      }

      var header = this.res.statusCode === 407 ? 'proxy-authenticate' : 'www-authenticate' ;
      var challenge = this._parseChallenge(this.res.get(header));

      var ha1 = crypto.createHash('md5');
      ha1.update([username, challenge.realm, password].join(':'));
      var ha2 = crypto.createHash('md5');
      ha2.update([options.method, options.uri].join(':'));

      // bump CSeq and preserve Call-Id
      var headers = options.headers || {};
      var seq = this.req.getParsedHeader('cseq').seq ;
      seq++ ;
      headers['CSeq'] = '' + seq + ' ' + this.req.method ;
      headers['call-id'] = this.req.get('call-id') ;

      // preserve tag on From header as well
      delete headers.from;
      headers['From'] = this.req.get('from') ;

      // Generate cnonce
      var cnonce = false;
      var nc = false;
      if (typeof challenge.qop === 'string') {
        var cnonceHash = crypto.createHash('md5');
        cnonceHash.update(Math.random().toString(36));
        cnonce = cnonceHash.digest('hex').substr(0, 8);
        nc = this._updateNC();
      }

      // Generate response hash
      var response = crypto.createHash('md5');
      var responseParams = [
        ha1.digest('hex'),
        challenge.nonce
      ];

      if (cnonce) {
        responseParams.push(nc);
        responseParams.push(cnonce);
      }

      if (challenge.qop) {
        responseParams.push(challenge.qop);
      }
      responseParams.push(ha2.digest('hex'));
      response.update(responseParams.join(':'));

      // Setup response parameters
      var authParams = {
        username: username,
        realm: challenge.realm,
        nonce: challenge.nonce,
        uri: options.uri,
        response: response.digest('hex'),
        algorithm: 'MD5'
      };
      if (challenge.qop) { authParams.qop = challenge.qop; }
      if (challenge.opaque) { authParams.opaque = challenge.opaque; }

      if (cnonce) {
        authParams.nc = nc;
        authParams.cnonce = cnonce;
      }

      headers[407 === this.res.statusCode ? 'Proxy-Authorization' : 'Authorization'] = this._compileParams(authParams);

      options.headers = headers;

      // if the original request was sent to a request-uri that looks to have a DNS name,
      // let's avoid another DNS lookup on the second request -- i.e. when challenged,
      // we want to send our credentialled request to the same server that challenged us
      const originalUri = options.uri;
      if (!originalUri.match(/sips?:[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/)) {
        const proxy = originalUri.replace(/(sips*):[^;]*/, `$1:${this.res.source_address}:${this.res.source_port}`);
        Object.assign(options, {proxy});
      }
      this.agent.request(options, callback) ;
    }) ;
  }

  _updateNC() {
    const max = 99999999;
    this.nc++;
    if (this.nc > max) {
      this.nc = 1;
    }
    const padding = new Array(8).join('0') + '';
    const nc = this.nc + '';
    return padding.substr(0, 8 - nc.length) + nc;
  }

  _compileParams(params) {
    const parts = [];
    for (const i in params) {
      if (['nc', 'algorithm', 'qop'].includes(i)) parts.push(`${i}=${params[i]}`);
      else parts.push(`${i}="${params[i]}"`);
    }
    return `Digest ${parts.join(',')}`;
  }

  _parseChallenge(digest) {
    const prefix = 'Digest ';
    const challenge = digest.substr(digest.indexOf(prefix) + prefix.length);
    const parts = challenge.split(',');
    const length = parts.length;
    const params = {};
    for (let i = 0; i < length; i++) {
      const part = parts[i].match(/^\s*?([a-zA-Z0-0]+)="?(.*?)"?\s*?$/);
      if (part && part.length > 2) {
        params[part[1]] = part[2];
      }
    }
    return params;
  }
} ;
<|endoftext|>


<commit_before>
const _ = require('busyman')
const debug = require('debug')('mqtt-node:msgHdlr')
const warn = require('debug')('mqtt-node:msgHdlr:warn')
const mutils = require('lwmqn-util')

const CNST = require('./constants')
const reporter = require('./reporter')

const { TTYPE } = CNST
const { CMDNAMES } = CNST
// const { ERR } = CNST
const { CMD } = CNST
const { RSP } = CNST

const handlers = {}

handlers._rawHdlr = function (qn, topic, message, packet) {
  if (!_.isFunction(qn.decrypt)) {
    qn.decrypt = function (msgStr, callback) {
      callback(null, msgStr)
    }
  }

  qn.decrypt(message, (err, decrypted) => {
    const intf = mutils.slashPath(topic)
    const isGeneric = !_.includes(qn._subics, intf)
    let strmsg
    let jmsg
    let tid
    let _evt

    if (err) {
      warn('decrytion fails') // log 'decrytion fails'
      return
    }

    message = decrypted

    if (isGeneric) {
      setImmediate(() => {
        qn.emit('message', topic, message, packet)
      })
      return
    }

    strmsg = (message instanceof Buffer) ? message.toString() : message

    if (strmsg[0] === '{' && strmsg[strmsg.length - 1] === '}') {
      try {
        jmsg = JSON.parse(strmsg)
        tid = jmsg.transId
      } catch (e) {
        warn(`Parse json fails. Interface: ${intf}. Message: ${strmsg}`) // log 'parse json fails'
        return
      }
    }
    switch (intf) {
      case qn._subics.register:
        if (_.isObject(jmsg)) {
          _evt = `register:rsp:${tid}`
          if (jmsg.status === RSP.ok || jmsg.status === RSP.created) qn._lifeUpdate(true)
          else qn._lifeUpdate(false)
        }
        break
      case qn._subics.deregister:
        _evt = `deregister:rsp:${tid}`
        break
      case qn._subics.schedule:
        _evt = `schedule:rsp:${tid}`
        break
      case qn._subics.notify:
        _evt = `notify:rsp:${tid}`
        break
      case qn._subics.update:
        _evt = `update:rsp:${tid}`
        break
      case qn._subics.ping:
        _evt = `ping:rsp:${tid}`
        break
      case qn._subics.request:
        _evt = '_request' //  No callbacks
        break
      case qn._subics.announce:
        _evt = 'announce' //  No callbacks
        jmsg = strmsg
        break
      default:
        break
    }

    if (!_.isUndefined(_evt)) {
      if (_evt === 'announce') {
        setImmediate(() => {
          qn.emit(_evt, jmsg)
        })
      } else {
        setImmediate(() => {
          qn.emit(_evt, null, jmsg) // err-first
        })
      }

      if (!_.isUndefined(qn._tobjs[_evt])) {
        clearTimeout(qn._tobjs[_evt])
        delete qn._tobjs[_evt]
      }
    }
  })
}

handlers._reqHdlr = function (qn, msg) {
  let reqMsgHdlr
  let trg
  let rtn = true
  const rsp = {
    transId: null,
    cmdId: 'unknown',
    status: RSP.ok,
    data: null
  }

  if (_.isObject(msg)) {
    rsp.transId = msg.transId
    rsp.cmdId = msg.cmdId

    debug('REQ <-- %s, transId: %d', CMDNAMES[msg.cmdId], msg.transId)
  }

  if (rsp.cmdId !== CMD.ping && rsp.cmdId !== CMD.identify) {
    try {
      trg = qn._target(msg.oid, msg.iid, msg.rid) // { type, exist }

      if (trg.type === TTYPE.root || _.isNil(msg.oid)) rsp.status = RSP.badreq // Request Root is not allowed
      else if (!trg.exist) rsp.status = RSP.notfound
      else rtn = false
    } catch (err) {
      warn(err)
    }
  } else {
    rtn = false
  }

  if (rtn) {
    if (_.isObject(msg)) debug('RSP --> %s, transId: %d', CMDNAMES[msg.cmdId] || msg.cmdId, msg.transId)
    else debug('RSP --> %s, transId: %d', msg)
    qn.respond(rsp)
    return
  }

  switch (rsp.cmdId) {
    case CMD.read:
      reqMsgHdlr = handlers._readReqHandler
      break
    case CMD.write:
      reqMsgHdlr = handlers._writeReqHandler
      break
    case CMD.writeAttrs:
      reqMsgHdlr = handlers._writeAttrsReqHandler
      break
    case CMD.discover:
      reqMsgHdlr = handlers._discoverReqHandler
      break
    case CMD.execute:
      reqMsgHdlr = handlers._executeReqHandler
      break
    case CMD.observe:
      reqMsgHdlr = handlers._observeReqHandler
      break
    case CMD.ping:
      reqMsgHdlr = handlers._pingReqHandler
      break
    case CMD.identify:
      reqMsgHdlr = handlers._identifyReqHandler
      break
    default:
      reqMsgHdlr = handlers._unknownReqHandler
  }

  setImmediate(() => {
    reqMsgHdlr(qn, trg, msg, (status, data) => {
      rsp.status = status
      rsp.data = data

      debug('RSP --> %s, transId: %d, status: %d', CMDNAMES[rsp.cmdId], rsp.transId, rsp.status)

      qn.respond(rsp)
    })
  })
}

/** ***************************************** */
/** * MqttNode Request Handlers            ** */
/** ***************************************** */
handlers._readReqHandler = function (qn, trg, msg, cb) {
  // trg: { type, exist }
  const so = qn.getSmartObject()
  let status = RSP.content

  if (!trg.exist) {
    invokeCb(qn, msg, RSP.notfound, null, cb) // (status, data, cb)
  } else if (trg.type === TTYPE.obj) {
    so.dump(msg.oid, { restrict: true }, (err, data) => {
      if (err) status = RSP.inerr

      invokeCb(qn, msg, status, data, cb)
    })
  } else if (trg.type === TTYPE.inst) {
    so.dump(msg.oid, msg.iid, { restrict: true }, (err, data) => {
      if (err) status = RSP.inerr

      invokeCb(qn, msg, status, data, cb)
    })
  } else if (trg.type === TTYPE.rsc) {
    so.read(msg.oid, msg.iid, msg.rid, { restrict: true }, (err, data) => {
      if (err) {
        if (data === '_unreadable_' || data === '_exec_') status = RSP.notallowed
        else if (data === '_notfound_') status = RSP.notfound
        else status = RSP.inerr
      } else {
        status = RSP.content
      }
      invokeCb(qn, msg, status, data, cb)
    })
  } else {
    invokeCb(qn, msg, RSP.badreq, null, cb)
  }
}

handlers._writeReqHandler = function (qn, trg, msg, cb) {
  // trg: { type, exist }
  const so = qn.getSmartObject()
  let status = RSP.changed

  if (trg.type === TTYPE.obj || trg.type === TTYPE.inst) { // [TODO] will support in the future
    invokeCb(qn, msg, RSP.notallowed, null, cb)
  } else if (trg.type === TTYPE.rsc) {
    so.write(msg.oid, msg.iid, msg.rid, msg.data, { restrict: true }, (err, data) => {
      if (err) {
        if (data === '_unwritable_' || data === '_exec_') status = RSP.notallowed
        else if (data === '_notfound_') status = RSP.notfound
        else status = RSP.inerr
      } else {
        status = RSP.changed
      }

      invokeCb(qn, msg, status, data, cb)
    })
  } else {
    invokeCb(qn, msg, RSP.badreq, null, cb)
  }
}

handlers._executeReqHandler = function (qn, trg, msg, cb) {
  const so = qn.getSmartObject()
  let status

  if (trg.type !== TTYPE.rsc) {
    invokeCb(qn, msg, RSP.notallowed, null, cb)
  } else {
    so.exec(msg.oid, msg.iid, msg.rid, msg.data, (err, data) => {
      if (err) {
        if (data === '_unexecutable_') status = RSP.notallowed
        else if (data === '_notfound_') status = RSP.notfound
        else if (data === '_badarg_') status = RSP.badreq
        else status = RSP.inerr
      } else {
        status = RSP.changed
      }

      invokeCb(qn, msg, status, data, cb)
    })
  }
}

handlers._writeAttrsReqHandler = function (qn, trg, msg, cb) {
  let attrs
  let badAttr = false
  const allowedAttrs = ['pmin', 'pmax', 'gt', 'lt', 'stp', 'cancel', 'pintvl']

  if (!_.isPlainObject(msg.data)) {
    cb(RSP.badreq, null)
    return
  }

  _.forEach(msg.data, (n, k) => {
    badAttr = badAttr || !_.includes(allowedAttrs, k)
  })

  if (badAttr) {
    cb(RSP.badreq, null)
    return
  }

  // The availability has been checked in _reqHdlr
  if (trg.type === TTYPE.obj) {
    if (msg.data.cancel) qn.disableReport(msg.oid)

    reporter.setAttrs(qn, msg.oid, msg.data)
  } else if (trg.type === TTYPE.inst) {
    if (msg.data.cancel) qn.disableReport(msg.oid, msg.iid)

    reporter.setAttrs(qn, msg.oid, msg.iid, msg.data)
  } else if (trg.type === TTYPE.rsc) {
    if (msg.data.cancel) qn.disableReport(msg.oid, msg.iid, msg.rid)

    reporter.setAttrs(qn, msg.oid, msg.iid, msg.rid, msg.data)
  }

  attrs = _.cloneDeep(reporter.getAttrs(qn, msg.oid, msg.iid, msg.rid))

  cb(RSP.changed, _.omit(attrs, ['mute', 'lastRpVal']))
}

handlers._discoverReqHandler = function (qn, trg, msg, cb) {
  const so = qn.getSmartObject()
  let target
  let status = RSP.content
  let attrs
  const resrcList = {}

  if (trg.type === TTYPE.obj) {
    attrs = _.cloneDeep(reporter.getAttrs(qn, msg.oid))
    target = so.dumpSync(msg.oid)

    if (_.isObject(target)) {
      _.forEach(target, (inst, iid) => {
        resrcList[iid] = []
        _.forEach(inst, (r, rid) => {
          resrcList[iid].push(mutils.ridNum(msg.oid, rid))
        })
      })
      attrs.resrcList = resrcList
    } else {
      status = RSP.notfound
    }
  } else if (trg.type === TTYPE.inst) {
    attrs = _.cloneDeep(reporter.getAttrs(qn, msg.oid, msg.iid))
  } else if (trg.type === TTYPE.rsc) {
    attrs = _.cloneDeep(reporter.getAttrs(qn, msg.oid, msg.iid, msg.rid))
  }

  if (status === RSP.notfound) cb(status, null)
  else cb(status, _.omit(attrs, ['mute', 'lastRpVal']))
}

handlers._observeReqHandler = function (qn, trg, msg, cb) {
  // msg.data = { option: 1 } to cancel reporting

  if (trg.type === TTYPE.obj) {
    cb(RSP.notallowed)
  } else if (trg.type === TTYPE.inst) {
    if (msg.data && msg.data.option) qn.disableReport(msg.oid, msg.iid)
    else qn.enableReport(msg.oid, msg.iid)

    cb(RSP.content)
  } else if (trg.type === TTYPE.rsc) {
    if (msg.data && msg.data.option) qn.disableReport(msg.oid, msg.iid, msg.rid)
    else qn.enableReport(msg.oid, msg.iid, msg.rid)

    cb(RSP.content)
  }
}

handlers._pingReqHandler = function (qn, trg, msg, cb) {
  cb(RSP.ok)
}

handlers._identifyReqHandler = function (qn, trg, msg, cb) {
  if (!qn) cb(RSP.notfound)
  else if (!_.isFunction(qn.identify)) cb(RSP.inerr)
  else {
    qn.identify((err) => {
      if (err) cb(RSP.inerr)
      else cb(RSP.ok)
    })
  }
}

handlers._unknownReqHandler = function (qn, trg, msg, cb) {
  cb(RSP.badreq)
}

function invokeCb (qn, msg, status, data, cb) {
  if (_.isFunction(cb)) {
    process.nextTick(() => {
      cb(status, data)
    })
  }

  if (!_.isNil(data)) reporter.checkAndReportResource(qn, msg.oid, msg.iid, msg.rid, data)
}

module.exports = handlers
<commit_msg>Refactor msghandlers<commit_after>const _ = require('busyman')
const debug = require('debug')('mqtt-node:msgHdlr')
const warn = require('debug')('mqtt-node:msgHdlr:warn')
const mutils = require('lwmqn-util')

const CNST = require('./constants')
const reporter = require('./reporter')

const { TTYPE } = CNST
const { CMDNAMES } = CNST
const { CMD } = CNST
const { RSP } = CNST

function invokeCb (qn, msg, status, data, cb) {
  if (_.isFunction(cb)) {
    process.nextTick(() => {
      cb(status, data)
    })
  }

  if (!_.isNil(data)) reporter.checkAndReportResource(qn, msg.oid, msg.iid, msg.rid, data)
}

module.exports = {
  _rawHdlr (qn, topic, message, packet) {
    if (!_.isFunction(qn.decrypt)) {
      qn.decrypt = function (msgStr, callback) {
        callback(null, msgStr)
      }
    }

    qn.decrypt(message, (err, decrypted) => {
      const intf = mutils.slashPath(topic)
      const isGeneric = !_.includes(qn._subics, intf)
      let jmsg
      let tid
      let _evt

      if (err) {
        warn('decrytion fails') // log 'decrytion fails'
        return
      }

      message = decrypted

      if (isGeneric) {
        setImmediate(() => {
          qn.emit('message', topic, message, packet)
        })
        return
      }

      let strmsg = (message instanceof Buffer) ? message.toString() : message

      if (strmsg[0] === '{' && strmsg[strmsg.length - 1] === '}') {
        try {
          jmsg = JSON.parse(strmsg)
          tid = jmsg.transId
        } catch (e) {
          warn(`Parse json fails. Interface: ${intf}. Message: ${strmsg}`) // log 'parse json fails'
          return
        }
      }
      switch (intf) {
        case qn._subics.register:
          if (_.isObject(jmsg)) {
            _evt = `register:rsp:${tid}`
            if (jmsg.status === RSP.ok || jmsg.status === RSP.created) qn._lifeUpdate(true)
            else qn._lifeUpdate(false)
          }
          break
        case qn._subics.deregister:
          _evt = `deregister:rsp:${tid}`
          break
        case qn._subics.schedule:
          _evt = `schedule:rsp:${tid}`
          break
        case qn._subics.notify:
          _evt = `notify:rsp:${tid}`
          break
        case qn._subics.update:
          _evt = `update:rsp:${tid}`
          break
        case qn._subics.ping:
          _evt = `ping:rsp:${tid}`
          break
        case qn._subics.request:
          _evt = '_request' //  No callbacks
          break
        case qn._subics.announce:
          _evt = 'announce' //  No callbacks
          jmsg = strmsg
          break
        default:
          break
      }

      if (!_.isUndefined(_evt)) {
        if (_evt === 'announce') {
          setImmediate(() => {
            qn.emit(_evt, jmsg)
          })
        } else {
          setImmediate(() => {
            qn.emit(_evt, null, jmsg) // err-first
          })
        }

        if (!_.isUndefined(qn._tobjs[_evt])) {
          clearTimeout(qn._tobjs[_evt])
          delete qn._tobjs[_evt]
        }
      }
    })
  },
  _reqHdlr (qn, msg) {
    let reqMsgHdlr
    let trg
    let rtn = true
    const rsp = { transId: null, cmdId: 'unknown', status: RSP.ok, data: null }

    if (_.isObject(msg)) {
      rsp.transId = msg.transId
      rsp.cmdId = msg.cmdId

      debug('REQ <-- %s, transId: %d', CMDNAMES[msg.cmdId], msg.transId)
    }

    if (rsp.cmdId !== CMD.ping && rsp.cmdId !== CMD.identify) {
      try {
        trg = qn._target(msg.oid, msg.iid, msg.rid) // { type, exist }

        if (trg.type === TTYPE.root || _.isNil(msg.oid)) rsp.status = RSP.badreq // Request Root is not allowed
        else if (!trg.exist) rsp.status = RSP.notfound
        else rtn = false
      } catch (err) {
        warn(err)
      }
    } else {
      rtn = false
    }

    if (rtn) {
      if (_.isObject(msg)) debug('RSP --> %s, transId: %d', CMDNAMES[msg.cmdId] || msg.cmdId, msg.transId)
      else debug('RSP --> %s, transId: %d', msg)
      qn.respond(rsp)
      return
    }

    switch (rsp.cmdId) {
      case CMD.read:
        reqMsgHdlr = handlers._readReqHandler
        break
      case CMD.write:
        reqMsgHdlr = handlers._writeReqHandler
        break
      case CMD.writeAttrs:
        reqMsgHdlr = handlers._writeAttrsReqHandler
        break
      case CMD.discover:
        reqMsgHdlr = handlers._discoverReqHandler
        break
      case CMD.execute:
        reqMsgHdlr = handlers._executeReqHandler
        break
      case CMD.observe:
        reqMsgHdlr = handlers._observeReqHandler
        break
      case CMD.ping:
        reqMsgHdlr = handlers._pingReqHandler
        break
      case CMD.identify:
        reqMsgHdlr = handlers._identifyReqHandler
        break
      default:
        reqMsgHdlr = handlers._unknownReqHandler
    }

    setImmediate(() => {
      reqMsgHdlr(qn, trg, msg, (status, data) => {
        rsp.status = status
        rsp.data = data

        debug('RSP --> %s, transId: %d, status: %d', CMDNAMES[rsp.cmdId], rsp.transId, rsp.status)

        qn.respond(rsp)
      })
    })
  },
  _readReqHandler (qn, trg, msg, cb) {
    // trg: { type, exist }
    const so = qn.getSmartObject()
    let status = RSP.content

    if (!trg.exist) {
      invokeCb(qn, msg, RSP.notfound, null, cb) // (status, data, cb)
    } else if (trg.type === TTYPE.obj) {
      so.dump(msg.oid, { restrict: true }, (err, data) => {
        if (err) status = RSP.inerr

        invokeCb(qn, msg, status, data, cb)
      })
    } else if (trg.type === TTYPE.inst) {
      so.dump(msg.oid, msg.iid, { restrict: true }, (err, data) => {
        if (err) status = RSP.inerr

        invokeCb(qn, msg, status, data, cb)
      })
    } else if (trg.type === TTYPE.rsc) {
      so.read(msg.oid, msg.iid, msg.rid, { restrict: true }, (err, data) => {
        if (err) {
          if (data === '_unreadable_' || data === '_exec_') status = RSP.notallowed
          else if (data === '_notfound_') status = RSP.notfound
          else status = RSP.inerr
        } else {
          status = RSP.content
        }
        invokeCb(qn, msg, status, data, cb)
      })
    } else {
      invokeCb(qn, msg, RSP.badreq, null, cb)
    }
  },
  _writeReqHandler (qn, trg, msg, cb) {
    const so = qn.getSmartObject()
    let status = RSP.changed

    if (trg.type === TTYPE.obj || trg.type === TTYPE.inst) { // [TODO] will support in the future
      invokeCb(qn, msg, RSP.notallowed, null, cb)
    } else if (trg.type === TTYPE.rsc) {
      so.write(msg.oid, msg.iid, msg.rid, msg.data, { restrict: true }, (err, data) => {
        if (err) {
          if (data === '_unwritable_' || data === '_exec_') status = RSP.notallowed
          else if (data === '_notfound_') status = RSP.notfound
          else status = RSP.inerr
        } else {
          status = RSP.changed
        }

        invokeCb(qn, msg, status, data, cb)
      })
    } else {
      invokeCb(qn, msg, RSP.badreq, null, cb)
    }
  },
  _executeReqHandler (qn, trg, msg, cb) {
    const so = qn.getSmartObject()
    let status

    if (trg.type !== TTYPE.rsc) {
      invokeCb(qn, msg, RSP.notallowed, null, cb)
    } else {
      so.exec(msg.oid, msg.iid, msg.rid, msg.data, (err, data) => {
        if (err) {
          if (data === '_unexecutable_') status = RSP.notallowed
          else if (data === '_notfound_') status = RSP.notfound
          else if (data === '_badarg_') status = RSP.badreq
          else status = RSP.inerr
        } else {
          status = RSP.changed
        }

        invokeCb(qn, msg, status, data, cb)
      })
    }
  },
  _writeAttrsReqHandler (qn, trg, msg, cb) {
    let attrs
    let badAttr = false
    const allowedAttrs = ['pmin', 'pmax', 'gt', 'lt', 'stp', 'cancel', 'pintvl']

    if (!_.isPlainObject(msg.data)) return cb(RSP.badreq, null)

    _.forEach(msg.data, (n, k) => {
      badAttr = badAttr || !_.includes(allowedAttrs, k)
    })

    if (badAttr) return cb(RSP.badreq, null)

    // The availability has been checked in _reqHdlr
    if (trg.type === TTYPE.obj) {
      if (msg.data.cancel) qn.disableReport(msg.oid)

      reporter.setAttrs(qn, msg.oid, msg.data)
    } else if (trg.type === TTYPE.inst) {
      if (msg.data.cancel) qn.disableReport(msg.oid, msg.iid)

      reporter.setAttrs(qn, msg.oid, msg.iid, msg.data)
    } else if (trg.type === TTYPE.rsc) {
      if (msg.data.cancel) qn.disableReport(msg.oid, msg.iid, msg.rid)

      reporter.setAttrs(qn, msg.oid, msg.iid, msg.rid, msg.data)
    }

    attrs = _.cloneDeep(reporter.getAttrs(qn, msg.oid, msg.iid, msg.rid))

    cb(RSP.changed, _.omit(attrs, ['mute', 'lastRpVal']))
  },
  _discoverReqHandler (qn, trg, msg, cb) {
    const so = qn.getSmartObject()
    let target
    let status = RSP.content
    let attrs
    const resrcList = {}

    if (trg.type === TTYPE.obj) {
      attrs = _.cloneDeep(reporter.getAttrs(qn, msg.oid))
      target = so.dumpSync(msg.oid)

      if (_.isObject(target)) {
        _.forEach(target, (inst, iid) => {
          resrcList[iid] = []
          _.forEach(inst, (r, rid) => {
            resrcList[iid].push(mutils.ridNum(msg.oid, rid))
          })
        })
        attrs.resrcList = resrcList
      } else {
        status = RSP.notfound
      }
    } else if (trg.type === TTYPE.inst) {
      attrs = _.cloneDeep(reporter.getAttrs(qn, msg.oid, msg.iid))
    } else if (trg.type === TTYPE.rsc) {
      attrs = _.cloneDeep(reporter.getAttrs(qn, msg.oid, msg.iid, msg.rid))
    }

    if (status === RSP.notfound) cb(status, null)
    else cb(status, _.omit(attrs, ['mute', 'lastRpVal']))
  },
  _observeReqHandler (qn, trg, msg, cb) {
    // msg.data = { option: 1 } to cancel reporting
    if (trg.type === TTYPE.obj) {
      cb(RSP.notallowed)
    } else if (trg.type === TTYPE.inst) {
      if (msg.data && msg.data.option) qn.disableReport(msg.oid, msg.iid)
      else qn.enableReport(msg.oid, msg.iid)

      cb(RSP.content)
    } else if (trg.type === TTYPE.rsc) {
      if (msg.data && msg.data.option) qn.disableReport(msg.oid, msg.iid, msg.rid)
      else qn.enableReport(msg.oid, msg.iid, msg.rid)

      cb(RSP.content)
    }
  },
  _pingReqHandler: (qn, trg, msg, cb) => cb(RSP.ok),
  _identifyReqHandler (qn, trg, msg, cb) {
    if (!qn) cb(RSP.notfound)
    else if (!_.isFunction(qn.identify)) cb(RSP.inerr)
    else {
      qn.identify((err) => {
        if (err) cb(RSP.inerr)
        else cb(RSP.ok)
      })
    }
  },
  _unknownReqHandler: (qn, trg, msg, cb) => cb(RSP.badreq)
}
<|endoftext|>