<commit_before>var FF_MAX_SUGGESTIONS = 5;
var FF_MAX_MATCHLENGTH = 1000;
var FF_DEBUGGING = false;
var FF_INCLUDE_HISTORY = true;
var ffHistory = [];

function ffEscapeRegExp(str) {
  return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
}

function ffActivateTag(tab) {
  if(tab.url) {
    chrome.tabs.update({
      url: tab.url
    });
  }
  if(tab.tabId) {
    chrome.tabs.update(tab.tabId, {active: true});
  }
  if(tab.windowId) {
    chrome.windows.update(tab.windowId, {focused: true});
  }
}

function ffEscapeHtml(unsafe) {
  return unsafe.replace(/&/g, "&")
               .replace(/</g, "<")
               .replace(/>/g, ">")
               .replace(/"/g, """)
               .replace(/'/g, "'");
}

function ffGetHostname(url) {
  var a  = document.createElement('a');
  a.href = url;
  return a.hostname;
}

function ffHighlightText(text, words) {
  words
  .map(function(word) { return ffRegexpExactHl(word); })
  .forEach(function(word) {
    if(text.match(word)) {
      text = text.replace(word, "\0$1\1")
    }
  });

  words
  .map(function(word) { return ffRegexpFuzzy(word); })
  .forEach(function(word) {
    if(text.match(word)) {
      text = text.replace(word, function(m) {
        return "\0" + m + "\1";
      });
    }
  });
  return ffEscapeHtml(text).replace(new RegExp("\0", "g"), "<match>")
                           .replace(new RegExp("\1", "g"), "</match>");
}

function ffCalculateScoreWords(tab, words) {
  var score = 0;
  var hostname = ffGetHostname(tab.url);

  var found_words = words.map(function() { return false; })

  words
  .map(function(word) { return ffRegexpExact(word); })
  .forEach(function(word, i) {
    if(tab.title.match(word)) { score += 20; found_words[i] = true; }
    if(tab.url.match(word)) { score += 20; found_words[i] = true; }
    if(hostname.match(word)) { score += 20; found_words[i] = true; }
  });

  words
  .map(function(word) { return ffRegexpFuzzy(word); })
  .forEach(function(word, i) {
    if(found_words[i]) { return; }
    if(tab.title.match(word)) { score += 100; found_words[i] = true; }
    if(tab.url.match(word)) { score += 100; found_words[i] = true; }
    if(hostname.match(word)) { score += 100; found_words[i] = true; }
  });

  if(found_words.filter(function(x) { return x; }).length !== words.length) { score = 0; }
  if(score > 0 && tab.pinned) { score += 1000; }

  if(FF_DEBUGGING && score > 0) { console.debug("tab", tab.title); }
  return score;
}

function ffRegexpExact(word) {
  return new RegExp(ffEscapeRegExp(word), 'i');
}

function ffRegexpExactHl(word) {
  return new RegExp("(" + ffEscapeRegExp(word) + ")", 'ig');
}

function ffRegexpFuzzy(word) {
  return new RegExp(word.split('').map(function(ch) { return ffEscapeRegExp(ch); }).join('.{0,10}?'), 'i');
}

function ffPrepareTabForOmnibox(tab, words) {
  var content = JSON.stringify({tabId: tab.id, windowId: tab.windowId});
  var desc = ffHighlightText(tab.title, words) + " <url>" +  ffHighlightText(ffGetHostname(tab.url), words) + "</url>";

  if(FF_DEBUGGING) {
    desc = "score:" + tab.score + " - " + desc;
  }

  if(tab.status && tab.status !== "complete") {
    desc = "[" + tab.status + "] " + desc;
  }

  if(tab.incognito) {
    desc = "<url>[Incognito]</url> " + desc;
  }

  if(tab.pinned) {
    desc = "<url>[Pinned]</url> " + desc;
  }

  if(tab.audible) {
    desc = "<url>[Audible]</url> " + desc;
  }

  return {content: content, description: desc};
}

function ffPrepareHistoryTabForOmnibox(tab, words) {
  var result = ffPrepareTabForOmnibox(tab, words);
  result.content = JSON.stringify({url: tab.url});
  result.description = "<url>[History]</url> " + result.description;
  return result;
}

function ffPrepareOpenTabForOmnibox(tab, words) {
  return ffPrepareTabForOmnibox(tab, words);
}

function ffSearchFor(text, callback) {
  text = text.trim();
  var words = text.split(/\s+/);

  var words_exact_hl = text.split(/\s+/).map(function(word) {
    return ffRegexpExactHl(word);
  });

  chrome.tabs.query({}, function(array_of_tabs) {
    var matching_tabs = array_of_tabs.
                        map(function(tab) { tab.score = ffCalculateScoreWords(tab, words); return tab; }).
                        filter(function(tab) { return tab.score >= 10; }).
                        sort(function(tab1, tab2) {
                          if(tab1.score < tab2.score) return 1;
                          if(tab1.score > tab2.score) return -1;
                          return 0;
                        });

    if(FF_INCLUDE_HISTORY && matching_tabs.length === 0) {
      chrome.history.search({text: "", maxResults: 1000}, function(array_of_history_items) {
        callback(
          array_of_history_items.
          map(function(tab) { tab.score = ffCalculateScoreWords(tab, words); return tab; }).
          filter(function(tab) { return tab.score >= 10; }).
          sort(function(tab1, tab2) {
            if(tab1.score < tab2.score) return 1;
            if(tab1.score > tab2.score) return -1;
            return 0;
          }).
          slice(0, FF_MAX_SUGGESTIONS).
          map(function(tab) {
            return ffPrepareHistoryTabForOmnibox(tab, words);
          })
        );
      });
      return;
    }

    callback(matching_tabs.slice(0, FF_MAX_SUGGESTIONS).map(function(tab) { return ffPrepareOpenTabForOmnibox(tab, words) }));
  });
}

chrome.omnibox.onInputChanged.addListener(
  function(text, suggest) {
    ffSearchFor(text, suggest);
  }
);

chrome.omnibox.onInputEntered.addListener(
  function(text) {
    if(FF_DEBUGGING) {
      console.debug("entered:", text);
      console.debug("history:", ffHistory);
    }

    var selected = {};

    if(text.length === 0) {
      if(ffHistory.length >= 2) {
        selected = ffHistory[ffHistory.length - 2];
      } else {
        return;
      }
    } else {
      try {
        selected = JSON.parse(text);
      } catch(e) {
        // User probably typed something but selected the first default option,
        // i.e., "Run ff command: query"
        ffSearchFor(text, function(suggestions) {
          if(suggestions.length === 0) { return; }
          var selected = JSON.parse(suggestions[0].content);
          ffHistory.push(selected);
          ffActivateTag(selected);
        });
        return;
      }
    }

    ffHistory.push(selected);
    ffActivateTag(selected);
  }
);
<commit_msg>Include history items if matching tabs are less than 6.<commit_after>var FF_MAX_SUGGESTIONS = 5;
var FF_MAX_MATCHLENGTH = 1000;
var FF_DEBUGGING = false;
var FF_INCLUDE_HISTORY = true;
var ffHistory = [];

function ffEscapeRegExp(str) {
  return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
}

function ffActivateTag(tab) {
  if(tab.url) {
    chrome.tabs.update({
      url: tab.url
    });
  }
  if(tab.tabId) {
    chrome.tabs.update(tab.tabId, {active: true});
  }
  if(tab.windowId) {
    chrome.windows.update(tab.windowId, {focused: true});
  }
}

function ffEscapeHtml(unsafe) {
  return unsafe.replace(/&/g, "&")
               .replace(/</g, "<")
               .replace(/>/g, ">")
               .replace(/"/g, """)
               .replace(/'/g, "'");
}

function ffGetHostname(url) {
  var a  = document.createElement('a');
  a.href = url;
  return a.hostname;
}

function ffHighlightText(text, words) {
  words
  .map(function(word) { return ffRegexpExactHl(word); })
  .forEach(function(word) {
    if(text.match(word)) {
      text = text.replace(word, "\0$1\1")
    }
  });

  words
  .map(function(word) { return ffRegexpFuzzy(word); })
  .forEach(function(word) {
    if(text.match(word)) {
      text = text.replace(word, function(m) {
        return "\0" + m + "\1";
      });
    }
  });
  return ffEscapeHtml(text).replace(new RegExp("\0", "g"), "<match>")
                           .replace(new RegExp("\1", "g"), "</match>");
}

function ffCalculateScoreWords(tab, words) {
  var score = 0;
  var hostname = ffGetHostname(tab.url);

  var found_words = words.map(function() { return false; })

  words
  .map(function(word) { return ffRegexpExact(word); })
  .forEach(function(word, i) {
    if(tab.title.match(word)) { score += 100; found_words[i] = true; }
    if(tab.url.match(word)) { score += 100; found_words[i] = true; }
    if(hostname.match(word)) { score += 100; found_words[i] = true; }
  });

  words
  .map(function(word) { return ffRegexpFuzzy(word); })
  .forEach(function(word, i) {
    if(found_words[i]) { return; }
    if(tab.title.match(word)) { score += 20; found_words[i] = true; }
    if(tab.url.match(word)) { score += 20; found_words[i] = true; }
    if(hostname.match(word)) { score += 20; found_words[i] = true; }
  });

  if(found_words.filter(function(x) { return x; }).length !== words.length) { score = 0; }
  if(score > 0 && tab.pinned) { score += 1000; }

  if(FF_DEBUGGING && score > 0) { console.debug("tab", tab.title); }
  return score;
}

function ffRegexpExact(word) {
  return new RegExp(ffEscapeRegExp(word), 'i');
}

function ffRegexpExactHl(word) {
  return new RegExp("(" + ffEscapeRegExp(word) + ")", 'ig');
}

function ffRegexpFuzzy(word) {
  return new RegExp(word.split('').map(function(ch) { return ffEscapeRegExp(ch); }).join('.{0,10}?'), 'i');
}

function ffPrepareTabForOmnibox(tab, words) {
  var content = JSON.stringify({tabId: tab.id, windowId: tab.windowId});
  var desc = ffHighlightText(tab.title, words) + " <url>" +  ffHighlightText(ffGetHostname(tab.url), words) + "</url>";

  if(FF_DEBUGGING) {
    desc = "score:" + tab.score + " - " + desc;
  }

  if(tab.status && tab.status !== "complete") {
    desc = "[" + tab.status + "] " + desc;
  }

  if(tab.incognito) {
    desc = "<url>[Incognito]</url> " + desc;
  }

  if(tab.pinned) {
    desc = "<url>[Pinned]</url> " + desc;
  }

  if(tab.audible) {
    desc = "<url>[Audible]</url> " + desc;
  }

  return {content: content, description: desc};
}

function ffPrepareHistoryTabForOmnibox(tab, words) {
  var result = ffPrepareTabForOmnibox(tab, words);
  result.content = JSON.stringify({url: tab.url});
  result.description = "<url>[History]</url> " + result.description;
  return result;
}

function ffPrepareOpenTabForOmnibox(tab, words) {
  return ffPrepareTabForOmnibox(tab, words);
}

function ffSearchFor(text, callback) {
  text = text.trim();
  var words = text.split(/\s+/);

  var words_exact_hl = text.split(/\s+/).map(function(word) {
    return ffRegexpExactHl(word);
  });

  chrome.tabs.query({}, function(array_of_tabs) {
    var matching_tabs = array_of_tabs.
                        map(function(tab) { tab.score = ffCalculateScoreWords(tab, words); return tab; }).
                        filter(function(tab) { return tab.score >= 10; }).
                        sort(function(tab1, tab2) {
                          if(tab1.score < tab2.score) return 1;
                          if(tab1.score > tab2.score) return -1;
                          return 0;
                        });

    if(FF_INCLUDE_HISTORY && matching_tabs.length < FF_MAX_SUGGESTIONS) {
      chrome.history.search({text: "", maxResults: 1000}, function(array_of_history_items) {
        callback(
          array_of_history_items.
          map(function(tab) { tab.score = ffCalculateScoreWords(tab, words); return tab; }).
          filter(function(tab) { return tab.score >= 10; }).
          sort(function(tab1, tab2) {
            if(tab1.score < tab2.score) return 1;
            if(tab1.score > tab2.score) return -1;
            return 0;
          }).
          slice(0, FF_MAX_SUGGESTIONS - matching_tabs.length).
          map(function(tab) {
            return ffPrepareHistoryTabForOmnibox(tab, words);
          })
        );
      });
    }

    if(matching_tabs.length === 0) { return; }

    callback(matching_tabs.slice(0, FF_MAX_SUGGESTIONS).map(function(tab) { return ffPrepareOpenTabForOmnibox(tab, words) }));
  });
}

chrome.omnibox.onInputChanged.addListener(
  function(text, suggest) {
    ffSearchFor(text, suggest);
  }
);

chrome.omnibox.onInputEntered.addListener(
  function(text) {
    if(FF_DEBUGGING) {
      console.debug("entered:", text);
      console.debug("history:", ffHistory);
    }

    var selected = {};

    if(text.length === 0) {
      if(ffHistory.length >= 2) {
        selected = ffHistory[ffHistory.length - 2];
      } else {
        return;
      }
    } else {
      try {
        selected = JSON.parse(text);
      } catch(e) {
        // User probably typed something but selected the first default option,
        // i.e., "Run ff command: query"
        ffSearchFor(text, function(suggestions) {
          if(suggestions.length === 0) { return; }
          var selected = JSON.parse(suggestions[0].content);
          ffHistory.push(selected);
          ffActivateTag(selected);
        });
        return;
      }
    }

    ffHistory.push(selected);
    ffActivateTag(selected);
  }
);
<|endoftext|>


<commit_before>var defaults = require('./defaults.js');
var devtools = require('./devtools.js');
var util = require('util');
var events = require('events');
var WebSocket = require('ws');

function Chrome(options, notifier) {
    var self = this;
    // options
    options = options || {};
    self.host = options.host || defaults.HOST;
    self.port = options.port || defaults.PORT;
    self.protocol = options.protocol;
    self.remote = !!(options.remote);
    self.chooseTab = options.chooseTab || function () { return 0; };
    // locals
    events.EventEmitter.call(this);
    self.notifier = notifier;
    self.callbacks = {};
    self.nextCommandId = 1;
    // operations
    connectToChrome.call(self);
}

util.inherits(Chrome, events.EventEmitter);

Chrome.prototype.inspect = function (depth, options) {
    options.customInspect = false;
    return util.inspect(this, options);
};

Chrome.prototype.send = function (method, params, callback) {
    var self = this;
    if (typeof params === 'function') {
        callback = params;
        params = undefined;
    }
    // return a promise when a callback is not provided
    if (typeof callback === 'function') {
        enqueueCommand.call(self, method, params, callback);
    } else {
        return new Promise(function (fulfill, reject) {
            enqueueCommand.call(self, method, params, function (error, response) {
                if (error) {
                    reject(response);
                } else {
                    fulfill(response);
                }
            });
        });
    }
};

Chrome.prototype.close = function (callback) {
    var self = this;
    function closeWebSocket(callback) {
        self.ws.close();
        self.ws.once('close', function () {
            self.ws.removeAllListeners();
            callback();
        });
    }
    if (typeof callback === 'function') {
        closeWebSocket(callback);
    } else {
        return new Promise(function (fulfill, reject) {
            closeWebSocket(fulfill);
        });
    }
};

function enqueueCommand(method, params, callback) {
    var self = this;
    var id = self.nextCommandId++;
    var message = {'id': id, 'method': method, 'params': params};
    self.ws.send(JSON.stringify(message));
    self.callbacks[id] = callback;
}

function arrayToObject(parameters) {
    var keyValue = {};
    parameters.forEach(function (parameter) {
        var name = parameter.name;
        delete parameter.name;
        keyValue[name] = parameter;
    });
    return keyValue;
}

function decorate(to, category, object) {
    to.category = category;
    for (var field in object) {
        // commands and events have parameters whereas types have properties
        if (category === 'type' && field === 'properties' ||
            field === 'parameters') {
            to[field] = arrayToObject(object[field]);
        } else {
            to[field] = object[field];
        }
    }
}

function addCommand(domainName, command) {
    var self = this;
    var handler = function (params, callback) {
        return self.send(domainName + '.' + command.name, params, callback);
    };
    decorate(handler, 'command', command);
    self[domainName][command.name] = handler;
}

function addEvent(domainName, event) {
    var self = this;
    var handler = function (handler) {
        self.on(domainName + '.' + event.name, handler);
    };
    decorate(handler, 'event', event);
    self[domainName][event.name] = handler;
}

function addType(domainName, type) {
    var self = this;
    var help = {};
    decorate(help, 'type', type);
    self[domainName][type.id] = help;
}

function addCommandShorthands() {
    var self = this;
    for (var domainIdx in self.protocol.domains) {
        var domain = self.protocol.domains[domainIdx];
        var domainName = domain.domain;
        self[domainName] = {};
        // add commands
        var commands = domain.commands;
        if (commands) {
            for (var commandIdx in commands) {
                var command = commands[commandIdx];
                addCommand.call(self, domainName, command);
            }
        }
        // add events
        var events = domain.events;
        if (events) {
            for (var eventIdx in events) {
                var event = events[eventIdx];
                addEvent.call(self, domainName, event);
            }
        }
        // add types
        var types = domain.types;
        if (types) {
            for (var typeIdx in types) {
                var type = types[typeIdx];
                addType.call(self, domainName, type);
            }
        }
    }
}

function connectToChrome() {
    var self = this;
    var options = {'host': self.host, 'port': self.port};
    var continueConnection = function () {
        // build the API from the protocol descriptor
        addCommandShorthands.call(self);
        devtools.List(options, function (err, tabs) {
            if (err) {
                self.notifier.emit('error', err);
            } else {
                var tabError;
                var tab;
                if (typeof self.chooseTab === 'object') {
                    tab = self.chooseTab;
                } else {
                    tab = tabs[self.chooseTab(tabs)];
                }
                if (tab) {
                    var tabDebuggerUrl = tab.webSocketDebuggerUrl;
                    if (tabDebuggerUrl) {
                        connectToWebSocket.call(self, tabDebuggerUrl);
                    } else {
                        // a WebSocket is already connected to this tab?
                        tabError = new Error('Tab does not support inspection');
                        self.notifier.emit('error', tabError);
                    }
                } else {
                    tabError = new Error('Invalid tab index');
                    self.notifier.emit('error', tabError);
                }
            }
        });
    };
    // if a protocol has been provided then use it
    if (self.protocol) {
        continueConnection();
    } else {
        // otherwise user either the local or the remore version
        options.remote = self.remote;
        devtools.Protocol(options, function (err, protocol) {
            if (err) {
                self.notifier.emit('error', err);
            } else {
                self.protocol = protocol.descriptor;
                continueConnection();
            }
        });
    }
}

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];
                // notify when there are no more pending commands
                if (Object.keys(self.callbacks).length === 0) {
                    self.emit('ready');
                }
            }
        }
        // event
        else if (message.method) {
            self.emit('event', message);
            self.emit(message.method, message.params);
        }
    });
    self.ws.on('error', function (err) {
        self.notifier.emit('error', err);
    });
}

module.exports = Chrome;
<commit_msg>Disable the 'permessage-deflate' extension<commit_after>var defaults = require('./defaults.js');
var devtools = require('./devtools.js');
var util = require('util');
var events = require('events');
var WebSocket = require('ws');

function Chrome(options, notifier) {
    var self = this;
    // options
    options = options || {};
    self.host = options.host || defaults.HOST;
    self.port = options.port || defaults.PORT;
    self.protocol = options.protocol;
    self.remote = !!(options.remote);
    self.chooseTab = options.chooseTab || function () { return 0; };
    // locals
    events.EventEmitter.call(this);
    self.notifier = notifier;
    self.callbacks = {};
    self.nextCommandId = 1;
    // operations
    connectToChrome.call(self);
}

util.inherits(Chrome, events.EventEmitter);

Chrome.prototype.inspect = function (depth, options) {
    options.customInspect = false;
    return util.inspect(this, options);
};

Chrome.prototype.send = function (method, params, callback) {
    var self = this;
    if (typeof params === 'function') {
        callback = params;
        params = undefined;
    }
    // return a promise when a callback is not provided
    if (typeof callback === 'function') {
        enqueueCommand.call(self, method, params, callback);
    } else {
        return new Promise(function (fulfill, reject) {
            enqueueCommand.call(self, method, params, function (error, response) {
                if (error) {
                    reject(response);
                } else {
                    fulfill(response);
                }
            });
        });
    }
};

Chrome.prototype.close = function (callback) {
    var self = this;
    function closeWebSocket(callback) {
        self.ws.close();
        self.ws.once('close', function () {
            self.ws.removeAllListeners();
            callback();
        });
    }
    if (typeof callback === 'function') {
        closeWebSocket(callback);
    } else {
        return new Promise(function (fulfill, reject) {
            closeWebSocket(fulfill);
        });
    }
};

function enqueueCommand(method, params, callback) {
    var self = this;
    var id = self.nextCommandId++;
    var message = {'id': id, 'method': method, 'params': params};
    self.ws.send(JSON.stringify(message));
    self.callbacks[id] = callback;
}

function arrayToObject(parameters) {
    var keyValue = {};
    parameters.forEach(function (parameter) {
        var name = parameter.name;
        delete parameter.name;
        keyValue[name] = parameter;
    });
    return keyValue;
}

function decorate(to, category, object) {
    to.category = category;
    for (var field in object) {
        // commands and events have parameters whereas types have properties
        if (category === 'type' && field === 'properties' ||
            field === 'parameters') {
            to[field] = arrayToObject(object[field]);
        } else {
            to[field] = object[field];
        }
    }
}

function addCommand(domainName, command) {
    var self = this;
    var handler = function (params, callback) {
        return self.send(domainName + '.' + command.name, params, callback);
    };
    decorate(handler, 'command', command);
    self[domainName][command.name] = handler;
}

function addEvent(domainName, event) {
    var self = this;
    var handler = function (handler) {
        self.on(domainName + '.' + event.name, handler);
    };
    decorate(handler, 'event', event);
    self[domainName][event.name] = handler;
}

function addType(domainName, type) {
    var self = this;
    var help = {};
    decorate(help, 'type', type);
    self[domainName][type.id] = help;
}

function addCommandShorthands() {
    var self = this;
    for (var domainIdx in self.protocol.domains) {
        var domain = self.protocol.domains[domainIdx];
        var domainName = domain.domain;
        self[domainName] = {};
        // add commands
        var commands = domain.commands;
        if (commands) {
            for (var commandIdx in commands) {
                var command = commands[commandIdx];
                addCommand.call(self, domainName, command);
            }
        }
        // add events
        var events = domain.events;
        if (events) {
            for (var eventIdx in events) {
                var event = events[eventIdx];
                addEvent.call(self, domainName, event);
            }
        }
        // add types
        var types = domain.types;
        if (types) {
            for (var typeIdx in types) {
                var type = types[typeIdx];
                addType.call(self, domainName, type);
            }
        }
    }
}

function connectToChrome() {
    var self = this;
    var options = {'host': self.host, 'port': self.port};
    var continueConnection = function () {
        // build the API from the protocol descriptor
        addCommandShorthands.call(self);
        devtools.List(options, function (err, tabs) {
            if (err) {
                self.notifier.emit('error', err);
            } else {
                var tabError;
                var tab;
                if (typeof self.chooseTab === 'object') {
                    tab = self.chooseTab;
                } else {
                    tab = tabs[self.chooseTab(tabs)];
                }
                if (tab) {
                    var tabDebuggerUrl = tab.webSocketDebuggerUrl;
                    if (tabDebuggerUrl) {
                        connectToWebSocket.call(self, tabDebuggerUrl);
                    } else {
                        // a WebSocket is already connected to this tab?
                        tabError = new Error('Tab does not support inspection');
                        self.notifier.emit('error', tabError);
                    }
                } else {
                    tabError = new Error('Invalid tab index');
                    self.notifier.emit('error', tabError);
                }
            }
        });
    };
    // if a protocol has been provided then use it
    if (self.protocol) {
        continueConnection();
    } else {
        // otherwise user either the local or the remore version
        options.remote = self.remote;
        devtools.Protocol(options, function (err, protocol) {
            if (err) {
                self.notifier.emit('error', err);
            } else {
                self.protocol = protocol.descriptor;
                continueConnection();
            }
        });
    }
}

function connectToWebSocket(url) {
    var self = this;
    // disable the permessage-deflate as a temporary fix for #39
    self.ws = new WebSocket(url, {'perMessageDeflate': false});
    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];
                // notify when there are no more pending commands
                if (Object.keys(self.callbacks).length === 0) {
                    self.emit('ready');
                }
            }
        }
        // event
        else if (message.method) {
            self.emit('event', message);
            self.emit(message.method, message.params);
        }
    });
    self.ws.on('error', function (err) {
        self.notifier.emit('error', err);
    });
}

module.exports = Chrome;
<|endoftext|>


<commit_before>import {
  MAP_MODE_SELECT,
  MAP_MODE_SHOW
} from '../../constants/AppConstants';

import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as UserActions from '../../actions/UserActions';
import GoogleMapsLoader from 'google-maps';
import deepEqual from 'deep-equal';

import blueMarker from '../../../img/blueMarker.png';
import 'file?name=[name].[ext]!../../../img/blueMarker.png';

// https://developers.google.com/maps/documentation/javascript/reference
// https://developers.google.com/maps/documentation/javascript/libraries
// https://developers.google.com/maps/documentation/javascript/controls

class Map extends Component {
  componentDidMount() {
    GoogleMapsLoader.load(this.configureMap.bind(this));
  }

  componentDidUpdate(prevProps) {
    if (!deepEqual(this.props.users, prevProps.users)) {
      this.loadData();
    }
    this.updateCurrentLocationMarker();
    this.updateMapStyle();
  }

  configureMap(google) {
    this.map = new google.maps.Map(document.getElementById('map'), {
      center: {lat: 0, lng: 50},
      zoom: 2,
      minZoom: 2,
      mapTypeId: google.maps.MapTypeId.SATELLITE,
      mapTypeIds: [
        google.maps.MapTypeId.HYBRID,
        google.maps.MapTypeId.ROADMAP,
        google.maps.MapTypeId.SATELLITE,
        google.maps.MapTypeId.TERRAIN
      ],
      scaleControl: true,
      mapTypeControl: true,
      mapTypeControlOptions: {
        style: google.maps.MapTypeControlStyle.DROPDOWN_MENU,
        position: google.maps.ControlPosition.LEFT_BOTTOM
      },
      streetViewControl: true,
      streetViewControlOptions: {
        position: google.maps.ControlPosition.LEFT_BOTTOM
      },
      zoomControl: true,
      zoomControlOptions: {
        position: google.maps.ControlPosition.LEFT_BOTTOM
      }
    });

    this.currentLocationMarker = new google.maps.Marker({
      icon: blueMarker
    });

    google.maps.event.addListener(this.map, 'click', event => {
      if (this.props.mapMode === MAP_MODE_SELECT) {
        this.currentLocationMarker.setPosition(event.latLng);
        this.props.actions.userSelectedLocation(event.latLng.lat(), event.latLng.lng());
      }
    });

    this.map.data.addListener('click', event => {
      if (this.props.mapMode === MAP_MODE_SHOW && this.props.onFeatureClick) {
        this.props.onFeatureClick(event.feature.getId());
      }
    });

    this.loadData();
    this.updateMapStyle();
  }

  loadData() {
    this.map.data.addGeoJson(this.props.users);
  }

  updateCurrentLocationMarker() {
    if (this.props.currentLocation) {
      this.currentLocationMarker.setPosition(this.props.currentLocation);
      this.currentLocationMarker.setMap(this.props.mapMode === MAP_MODE_SELECT ? this.map : null);
    }
  }

  updateMapStyle() {
    this.map.data.setStyle(feature => {
      if (this.props.mapMode === MAP_MODE_SELECT) {
        return {
          visible: false
        };
      }

      if (this.props.activeUserIds.indexOf(feature.getId()) !== -1) {
        return {
          icon: blueMarker
        };
      }
    });
  }

  render() {
    return (

    );
  }
}

Map.propTypes = {
  users: PropTypes.object.isRequired,
  activeUserIds: PropTypes.array,
  mapMode: PropTypes.oneOf([MAP_MODE_SELECT, MAP_MODE_SHOW]).isRequired,
  currentLocation: PropTypes.shape({
    lat: PropTypes.number.isRequired,
    lng: PropTypes.number.isRequired
  }),
  actions: PropTypes.object.isRequired,
  onFeatureClick: PropTypes.func
};

Map.defaultProps = {
  mapMode: MAP_MODE_SHOW
};

function mapStateToProps(state) {
  return {
    users: state.geoData,
    activeUserIds: state.session.activeUserIds,
    currentLocation: state.session.currentLocation,
    mapMode: state.session.mapMode
  };
}

function mapDispatchToProps(dispatch) {
  return {
    actions: bindActionCreators(UserActions, dispatch)
  };
}

export default connect(mapStateToProps, mapDispatchToProps)(Map);
<commit_msg>Closes #65 Logout bug<commit_after>import {
  MAP_MODE_SELECT,
  MAP_MODE_SHOW
} from '../../constants/AppConstants';

import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as UserActions from '../../actions/UserActions';
import GoogleMapsLoader from 'google-maps';
import deepEqual from 'deep-equal';

import blueMarker from '../../../img/blueMarker.png';
import 'file?name=[name].[ext]!../../../img/blueMarker.png';

// https://developers.google.com/maps/documentation/javascript/reference
// https://developers.google.com/maps/documentation/javascript/libraries
// https://developers.google.com/maps/documentation/javascript/controls

class Map extends Component {
  componentDidMount() {
    GoogleMapsLoader.load(this.configureMap.bind(this));
  }

  componentDidUpdate(prevProps) {
    if (!deepEqual(this.props.users, prevProps.users)) {
      this.loadData(this.props.users, prevProps.users);
    }
    this.updateCurrentLocationMarker();
    this.updateMapStyle();
  }

  configureMap(google) {
    this.map = new google.maps.Map(document.getElementById('map'), {
      center: {lat: 0, lng: 50},
      zoom: 2,
      minZoom: 2,
      mapTypeId: google.maps.MapTypeId.SATELLITE,
      mapTypeIds: [
        google.maps.MapTypeId.HYBRID,
        google.maps.MapTypeId.ROADMAP,
        google.maps.MapTypeId.SATELLITE,
        google.maps.MapTypeId.TERRAIN
      ],
      scaleControl: true,
      mapTypeControl: true,
      mapTypeControlOptions: {
        style: google.maps.MapTypeControlStyle.DROPDOWN_MENU,
        position: google.maps.ControlPosition.LEFT_BOTTOM
      },
      streetViewControl: true,
      streetViewControlOptions: {
        position: google.maps.ControlPosition.LEFT_BOTTOM
      },
      zoomControl: true,
      zoomControlOptions: {
        position: google.maps.ControlPosition.LEFT_BOTTOM
      }
    });

    this.currentLocationMarker = new google.maps.Marker({
      icon: blueMarker
    });

    google.maps.event.addListener(this.map, 'click', event => {
      if (this.props.mapMode === MAP_MODE_SELECT) {
        this.currentLocationMarker.setPosition(event.latLng);
        this.props.actions.userSelectedLocation(event.latLng.lat(), event.latLng.lng());
      }
    });

    this.map.data.addListener('click', event => {
      if (this.props.mapMode === MAP_MODE_SHOW && this.props.onFeatureClick) {
        this.props.onFeatureClick(event.feature.getId());
      }
    });

    this.loadData(this.props.users);
    this.updateMapStyle();
  }

  loadData(users, prevUsers) {
    if (prevUsers) {
      // do a diff, remove features that don't exist in new dataset
      const newData = new window.google.maps.Data();
      newData.addGeoJson(users);
      this.map.data.forEach(feature => {
        if (!newData.getFeatureById(feature.getId())) {
          this.map.data.remove(feature);
        }
      });
    }

    this.map.data.addGeoJson(users);
  }

  updateCurrentLocationMarker() {
    if (this.props.currentLocation) {
      this.currentLocationMarker.setPosition(this.props.currentLocation);
      this.currentLocationMarker.setMap(this.props.mapMode === MAP_MODE_SELECT ? this.map : null);
    }
  }

  updateMapStyle() {
    this.map.data.setStyle(feature => {
      if (this.props.mapMode === MAP_MODE_SELECT) {
        return {
          visible: false
        };
      }

      if (this.props.activeUserIds.indexOf(feature.getId()) !== -1) {
        return {
          icon: blueMarker
        };
      }
    });
  }

  render() {
    return (

    );
  }
}

Map.propTypes = {
  users: PropTypes.object.isRequired,
  activeUserIds: PropTypes.array,
  mapMode: PropTypes.oneOf([MAP_MODE_SELECT, MAP_MODE_SHOW]).isRequired,
  currentLocation: PropTypes.shape({
    lat: PropTypes.number.isRequired,
    lng: PropTypes.number.isRequired
  }),
  actions: PropTypes.object.isRequired,
  onFeatureClick: PropTypes.func
};

Map.defaultProps = {
  mapMode: MAP_MODE_SHOW
};

function mapStateToProps(state) {
  return {
    users: state.geoData,
    activeUserIds: state.session.activeUserIds,
    currentLocation: state.session.currentLocation,
    mapMode: state.session.mapMode
  };
}

function mapDispatchToProps(dispatch) {
  return {
    actions: bindActionCreators(UserActions, dispatch)
  };
}

export default connect(mapStateToProps, mapDispatchToProps)(Map);
<|endoftext|>


<commit_before>
var path = require('path');
var _ = require('lodash');
var appConfigUtils = require('./webpack-app-module-config-utils.js');
var settingsUtils = require('./settings-utils.js');

var asrCoreId = 'mmir-plugin-encoder-core.js';
var ttsCoreId = 'webAudioTextToSpeech.js';

var applyToAllSpeechConfigs = '__apply-to-all-configs__';
var ALL_SPEECH_CONFIGS_TYPE = settingsUtils.getAllSpeechConfigsType();

/**
 * mapping from file-name to load-module-ID in MediaManager/plugin-loading mechanism
 * (to be used in configuration.json)
 */
var moduleIdMap = {
	'webaudioinput.js': 'mmir-plugin-encoder-core.js'
};

/**
 * for webAudioInput module's specific service/implementations:
 * mapping from file-name to load-module-ID in MediaManager/plugin-loading mechanism
 * (to be used in configuration.json)
 */
var implFileIdMap = {
	'webasrgoogleimpl.js': 'mmir-plugin-asr-google-xhr.js',
	'webasrnuanceimpl.js': 'mmir-plugin-asr-nuance-xhr.js'
};

var deprecatedImplFileIdMap = {
	'webasratntimpl.js': 'webasrAtntImpl.js',
	'webasrgooglev1impl.js': 'webasrGooglev1Impl.js'
};

function isAsrPlugin(pluginId){
	return /^mmir-plugin-asr-/.test(pluginId);
}

function addResolveAliasPaths(alias, paths){
	for(var p in paths){
		paths[p] = path.resolve(paths[p]);
		alias[p] = paths[p];
	}
	return paths;
}

function normalizeModName(modName){
	return modName.replace(/\.js$/i, '');
}

function getPluginEntryFrom(confEntry, pluginConfigList){

	var simpleModName = normalizeModName(confEntry.mod);
	var simpleConfigName = normalizeModName(confEntry.config);
	var ctx = confEntry.ctx || false;
	return pluginConfigList.find(function(entry){
		if(typeof entry === 'string'){
			return normalizeModName(entry) === simpleModName;
		}
		return (
			normalizeModName(entry.mod) === simpleModName &&
			normalizeModName(entry.config) === simpleConfigName &&
			(!ctx || entry.ctx === ctx)
		);
	});
}

function normalizeImplName(implName){
	if(!/\.js$/i.test(implName)){
		implName += '.js';
	}
	return implName.toLowerCase();
}

function normalizeMediaManagerPluginConfig(configList){
	var id, normalized;
	configList.forEach(function(entry, index, list){
		if(typeof entry === 'string'){
			id = implFileIdMap[normalizeImplName(entry)];
			if(id){
				list[index] = id;
			}
		} else {
			if(typeof entry.mod === 'string'){
				id = moduleIdMap[normalizeImplName(entry.mod)];
				if(id){
					entry.mod = id;
				}
			}
			if(typeof entry.config === 'string'){
				normalized = normalizeImplName(entry.config);
				id = moduleIdMap[normalized];
				if(id){
					entry.config = id;
				} else if(deprecatedImplFileIdMap[normalized]){
					console.log('WARN: found deprecated media-plugin '+deprecatedImplFileIdMap[normalized]+' in configuration; this plugin will probably not work ', entry);
				}
			}
		}
	});
}

function getConfigEnv(pluginConfig, runtimeConfig){

	var env = pluginConfig && pluginConfig.env;
	if(!env && runtimeConfig.mediaManager && runtimeConfig.mediaManager.plugins){
		env = Object.keys(runtimeConfig.mediaManager.plugins);
	}
	if(!env){
		//FIXME TODO get default env-definitions from MediaManager!
		env = ['browser', 'cordova'];
	}
	if(typeof env === 'string'){
		env = [env];
	}
	// console.log('########### using env', env);
	return env;
}

/**
 * create plugin-entry in mediaManager.plugin configuration
 *
 * @param  {MMIRPluginConfig} pluginConfig the plugin-configuration
 * @param  {MMIRPluginInfo} pluginConfigInfo the plugin-info
 * @param  {string} pluginId the plugin-ID
 * @return {MediaManagerPluginEntry} the plugin-entry for mediaManager.plugin
 */
function createConfigEntry(pluginConfig, pluginConfigInfo, pluginId){
	var isAsr = isAsrPlugin(pluginId);
	var mod = isAsr? asrCoreId : ttsCoreId;
	var config = normalizeImplName(pluginId);
	var ctx = (pluginConfig && pluginConfig.ctx) || void(0);

	//{ "mod": "mmir-plugin-encoder-core.js", "config": "mmir-plugin-asr-nuance-xhr.js"}
	return {mod: mod, config: config, ctx: ctx};
}

/**
 * apply config-values from pluginConfig to runtimeConfig into entry pluginConfigInfo.pluginName
 *
 * @param  {MMIRPluginConfig} pluginConfig the plugin-configuration
 * @param  {Configuration} runtimeConfig the main configuration (JSON-like) object
 * @param  {Settings} settings the application settings (dicitionaries, speech-configs etc)
 * @param  {MMIRPluginInfo} pluginConfigInfo the plugin-info
 */
function applyPluginSpeechConfig(pluginConfig, settings, pluginConfigInfo){

	var speechConfs = pluginConfigInfo.speechConfig;
	if(speechConfs){

		var applyList = [];
		speechConfs.forEach(function(sc){
			if(pluginConfig[sc]){
				applyList.push({name: sc, value: pluginConfig[sc]});
			}
		});

		if(applyList.length > 0){

			var speechConfigs = new Map();
			settingsUtils.getSettingsFor(settings, 'speech').forEach(function(sc){
				speechConfigs.set(sc.id, sc);
			});

			var allSpeech = settings.find(function(s){ return s.type === ALL_SPEECH_CONFIGS_TYPE; });
			if(allSpeech){
				speechConfigs.set(ALL_SPEECH_CONFIGS_TYPE, allSpeech);
			}

			var val, name;
			applyList.forEach(function(config){
				val = config.value;
				name = config.name;
				if(typeof val !== 'string'){

					Object.keys(val).forEach(function(lang){
						doApplySpeechConfigValue(name, val[lang], lang, pluginConfigInfo.pluginName, speechConfigs, settings);
					});

				} else {
					doApplySpeechConfigValue(name, val, applyToAllSpeechConfigs, pluginConfigInfo.pluginName, speechConfigs, settings);
				}
			});

		}
	}
}

function doApplySpeechConfigValue(name, val, lang, pluginName, speechConfigs, settings){

		var sc = speechConfigs.get(lang);
		if(!sc){
			sc = settingsUtils.createSettingsEntryFor(lang === applyToAllSpeechConfigs? ALL_SPEECH_CONFIGS_TYPE : 'speech', {plugins: {}}, lang);
			speechConfigs.set(lang, sc);
			settings.push(sc);

		} else {

			if(!sc.value){
				//NOTE will crash, if file is a list ... for now, no support for file-list speech-configs!
				sc.value = settingsUtils.loadSettingsFrom(sc.file);
			}

			if(sc.include && sc.include !== 'inline'){
				console.log("WARN settings-utils: applying plugin speech-config to file setting, cannot include this as file, enforce inlining instead.");
				sc.include = 'inline';
			}
		}

		sc.value.plugins = sc.value.plugins || {};
		var configEntry = sc.value.plugins[pluginName];
		if(!configEntry){
			configEntry = {};
			sc.value.plugins[pluginName] = configEntry;
		}
		configEntry[name] = val;

}

/**
 * apply config-values from pluginConfig to runtimeConfig into entry pluginConfigInfo.pluginName
 *
 * @param  {MMIRPluginConfig} pluginConfig the plugin-configuration
 * @param  {Configuration} runtimeConfig the main configuration (JSON-like) object
 * @param  {MMIRPluginInfo} pluginConfigInfo the plugin-info
 */
function applyPluginConfig(pluginConfig, runtimeConfig, pluginConfigInfo){

	var config = runtimeConfig[pluginConfigInfo.pluginName] || {};
	var speechConfs = pluginConfigInfo.speechConfig? new Set(pluginConfigInfo.speechConfig) : null;
	for(var c in pluginConfig){
		if(c === 'env' || c === 'ctx' || (speechConfs && speechConfs.has(c))){
			continue;
		}
		config[c] = pluginConfig[c];
	}
	runtimeConfig[pluginConfigInfo.pluginName] = config;
}

function addConfig(pluginConfig, runtimeConfig, settings, pluginConfigInfo, pluginId){

	if(pluginConfig){
		applyPluginConfig(pluginConfig, runtimeConfig, pluginConfigInfo);
		applyPluginSpeechConfig(pluginConfig, settings, pluginConfigInfo);
	}

	var confEntry = createConfigEntry(pluginConfig, pluginConfigInfo, pluginId);

	var env = getConfigEnv(pluginConfig, runtimeConfig);
	if(!runtimeConfig.mediaManager){
		runtimeConfig.mediaManager = {};
	}
	if(!runtimeConfig.mediaManager.plugins){
		runtimeConfig.mediaManager.plugins = {};
	}
	var pConfig = runtimeConfig.mediaManager.plugins;
	var pList, cEntry;
	env.forEach(function(e){
		pList = pConfig[e];
		if(pList){
			normalizeMediaManagerPluginConfig(pList);
			cEntry = getPluginEntryFrom(confEntry, pList);
			if(cEntry){
				_.merge(cEntry, confEntry);
			} else {
				pConfig[e].push(confEntry);
			}
		} else {
			//FIXME this whould also require to add defaults like audio-output
			pConfig[e] = [confEntry]
		}
	});

	//TODO add/apply configuration for core-dependency mmir-plugin-encoder-core ~> silence-detection, if specified
}

module.exports = {
	addPluginInfos: function(pluginId, workersList, appConfig, pluginConfig, runtimeConfig, settings){

		var pluginInfo = require(pluginId + '/module-ids.gen.js');

		var paths = pluginInfo.getAll('paths', true);

		addResolveAliasPaths(appConfig.paths, paths)

		var workers = pluginInfo.getAll('workers');

		workers.forEach(function(w){
			workersList.push(paths[w]);
		});

		var includeModules = pluginInfo.getAll('modules');

		includeModules.forEach(function(mod){
			appConfigUtils.addIncludeModule(appConfig, mod, paths[mod]);
		});

		var pluginConfigInfo = require(pluginId + '/module-config.gen.js');

		if(pluginConfigInfo.pluginName){
			addConfig(pluginConfig, runtimeConfig, settings, pluginConfigInfo, pluginId);
		} else {
			console.log('ERROR invalid module-config.js for plugin '+pluginId+': missing field pluginName ', pluginConfigInfo);
		}

		console.log('plugin-utils: addPluginInfos() -> paths ', paths, ', workers ', workers, ', include modules ', includeModules, runtimeConfig);//DEBUG
	}
};
<commit_msg>added support for multi-plugin modules & non-config plugin-entries<commit_after>
var path = require('path');
var _ = require('lodash');
var appConfigUtils = require('./webpack-app-module-config-utils.js');
var settingsUtils = require('./settings-utils.js');

var asrCoreId = 'mmir-plugin-encoder-core.js';
var ttsCoreId = 'webAudioTextToSpeech.js';

var applyToAllSpeechConfigs = '__apply-to-all-configs__';
var ALL_SPEECH_CONFIGS_TYPE = settingsUtils.getAllSpeechConfigsType();

/**
 * mapping from file-name to load-module-ID in MediaManager/plugin-loading mechanism
 * (to be used in configuration.json)
 */
var moduleIdMap = {
	'webaudioinput.js': 'mmir-plugin-encoder-core.js',
	'mmir-plugin-tts-core-xhr.js': 'webAudioTextToSpeech.js'
};

/**
 * for webAudioInput module's specific service/implementations:
 * mapping from file-name to load-module-ID in MediaManager/plugin-loading mechanism
 * (to be used in configuration.json)
 */
var implFileIdMap = {
	'webasrgoogleimpl.js': 'mmir-plugin-asr-google-xhr.js',
	'webasrnuanceimpl.js': 'mmir-plugin-asr-nuance-xhr.js'
};

var deprecatedImplFileIdMap = {
	'webasratntimpl.js': 'webasrAtntImpl.js',
	'webasrgooglev1impl.js': 'webasrGooglev1Impl.js'
};

function isAsrPlugin(pluginId){
	return /^mmir-plugin-asr-/.test(pluginId);
}

function addResolveAliasPaths(alias, paths){
	for(var p in paths){
		paths[p] = path.resolve(paths[p]);
		alias[p] = paths[p];
	}
	return paths;
}

function normalizeModName(modName){
	return modName.replace(/\.js$/i, '');
}

function getPluginEntryFrom(confEntry, pluginConfigList){

	var simpleModName = normalizeModName(confEntry.mod);
	var simpleConfigName = normalizeModName(confEntry.config);
	var ctx = confEntry.ctx || false;
	return pluginConfigList.find(function(entry){
		if(typeof entry === 'string'){
			return normalizeModName(entry) === simpleModName;
		}
		return (
			normalizeModName(entry.mod) === simpleModName &&
			normalizeModName(entry.config) === simpleConfigName &&
			(!ctx || entry.ctx === ctx)
		);
	});
}

function normalizeImplName(implName){
	if(!/\.js$/i.test(implName)){
		implName += '.js';
	}
	return implName.toLowerCase();
}

function normalizeMediaManagerPluginConfig(configList){
	var id, normalized;
	configList.forEach(function(entry, index, list){
		if(typeof entry === 'string'){
			id = implFileIdMap[normalizeImplName(entry)];
			if(id){
				list[index] = id;
			}
		} else {
			if(typeof entry.mod === 'string'){
				id = moduleIdMap[normalizeImplName(entry.mod)];
				if(id){
					entry.mod = id;
				}
			}
			if(typeof entry.config === 'string'){
				normalized = normalizeImplName(entry.config);
				id = moduleIdMap[normalized];
				if(id){
					entry.config = id;
				} else if(deprecatedImplFileIdMap[normalized]){
					console.log('WARN: found deprecated media-plugin '+deprecatedImplFileIdMap[normalized]+' in configuration; this plugin will probably not work ', entry);
				}
			}
		}
	});
}

function getConfigEnv(pluginConfig, pluginInfo, runtimeConfig){

	var env = pluginConfig && pluginConfig.env;
	if(!env && pluginInfo.defaultValues && pluginInfo.defaultValues.env){
		env = pluginInfo.defaultValues.env;
		if(!Array.isArray(env)){
			env = [env];
		}
	}
	if(!env && runtimeConfig.mediaManager && runtimeConfig.mediaManager.plugins){
		env = Object.keys(runtimeConfig.mediaManager.plugins);
	}
	if(!env){
		//FIXME TODO get default env-definitions from MediaManager!
		env = ['browser', 'cordova'];
	}
	if(typeof env === 'string'){
		env = [env];
	}
	// console.log('########### using env', env);
	return env;
}

/**
 * create plugin-entry in mediaManager.plugin configuration
 *
 * @param  {MMIRPluginConfig} pluginConfig the (user supplied) plugin-configuration
 * @param  {MMIRPluginInfo} pluginConfigInfo the plugin-info
 * @param  {string} pluginId the plugin-ID
 * @return {MediaManagerPluginEntry} the plugin-entry for mediaManager.plugin
 */
function createConfigEntry(pluginConfig, pluginConfigInfo, pluginId){
	var isAsr = isAsrPlugin(pluginId);
	var mod = pluginConfigInfo.defaultValues && pluginConfigInfo.defaultValues.mod;
	// console.log('#### config entry for '+pluginConfigInfo.pluginName+' default mod? ', JSON.stringify(mod));//DEBUG
	if(!mod){
		// console.log('#### config entry for '+pluginConfigInfo.pluginName+' default mod? ', mod);//DEBUG
		mod = isAsr? asrCoreId : ttsCoreId;
	}
	var config = normalizeImplName(pluginId);//TODO
	var ctx = (pluginConfig && pluginConfig.ctx) || void(0);

	//{ "mod": "mmir-plugin-encoder-core.js", "config": "mmir-plugin-asr-nuance-xhr.js"}
	return {mod: mod, config: config, ctx: ctx};
}

/**
 * apply config-values from pluginConfig to runtimeConfig into entry pluginConfigInfo.pluginName
 *
 * @param  {MMIRPluginConfig} pluginConfig the plugin-configuration
 * @param  {Configuration} runtimeConfig the main configuration (JSON-like) object
 * @param  {Settings} settings the application settings (dicitionaries, speech-configs etc)
 * @param  {MMIRPluginInfo} pluginConfigInfo the plugin-info
 */
function applyPluginSpeechConfig(pluginConfig, settings, pluginConfigInfo){

	var speechConfs = pluginConfigInfo.speechConfig;
	if(speechConfs){

		var applyList = [];
		speechConfs.forEach(function(sc){
			if(pluginConfig[sc]){
				applyList.push({name: sc, value: pluginConfig[sc]});
			}
		});

		if(applyList.length > 0){

			var speechConfigs = new Map();
			settingsUtils.getSettingsFor(settings, 'speech').forEach(function(sc){
				speechConfigs.set(sc.id, sc);
			});

			var allSpeech = settings.find(function(s){ return s.type === ALL_SPEECH_CONFIGS_TYPE; });
			if(allSpeech){
				speechConfigs.set(ALL_SPEECH_CONFIGS_TYPE, allSpeech);
			}

			var val, name;
			applyList.forEach(function(config){
				val = config.value;
				name = config.name;
				if(typeof val !== 'string'){

					Object.keys(val).forEach(function(lang){
						doApplySpeechConfigValue(name, val[lang], lang, pluginConfigInfo.pluginName, speechConfigs, settings);
					});

				} else {
					doApplySpeechConfigValue(name, val, applyToAllSpeechConfigs, pluginConfigInfo.pluginName, speechConfigs, settings);
				}
			});

		}
	}
}

function doApplySpeechConfigValue(name, val, lang, pluginName, speechConfigs, settings){

		var sc = speechConfigs.get(lang);
		if(!sc){
			sc = settingsUtils.createSettingsEntryFor(lang === applyToAllSpeechConfigs? ALL_SPEECH_CONFIGS_TYPE : 'speech', {plugins: {}}, lang);
			speechConfigs.set(lang, sc);
			settings.push(sc);

		} else {

			if(!sc.value){
				//NOTE will crash, if file is a list ... for now, no support for file-list speech-configs!
				sc.value = settingsUtils.loadSettingsFrom(sc.file);
			}

			if(sc.include && sc.include !== 'inline'){
				console.log("WARN settings-utils: applying plugin speech-config to file setting, cannot include this as file, enforce inlining instead.");
				sc.include = 'inline';
			}
		}

		sc.value.plugins = sc.value.plugins || {};
		var configEntry = sc.value.plugins[pluginName];
		if(!configEntry){
			configEntry = {};
			sc.value.plugins[pluginName] = configEntry;
		}
		configEntry[name] = val;

}

/**
 * apply config-values from pluginConfig to runtimeConfig into entry pluginConfigInfo.pluginName
 *
 * @param  {MMIRPluginConfig} pluginConfig the plugin-configuration
 * @param  {Configuration} runtimeConfig the main configuration (JSON-like) object
 * @param  {MMIRPluginInfo} pluginConfigInfo the plugin-info
 */
function applyPluginConfig(pluginConfig, runtimeConfig, pluginConfigInfo){

	var config = runtimeConfig[pluginConfigInfo.pluginName] || {};
	var speechConfs = pluginConfigInfo.speechConfig? new Set(pluginConfigInfo.speechConfig) : null;
	for(var c in pluginConfig){
		if(c === 'env' || c === 'ctx' || c === 'mod' || (speechConfs && speechConfs.has(c))){
			continue;
		}
		config[c] = pluginConfig[c];
	}
	runtimeConfig[pluginConfigInfo.pluginName] = config;
}

function addConfig(pluginConfig, runtimeConfig, settings, pluginConfigInfo, pluginId){

	if(pluginConfig){
		applyPluginConfig(pluginConfig, runtimeConfig, pluginConfigInfo);
		applyPluginSpeechConfig(pluginConfig, settings, pluginConfigInfo);
	}

	var confEntry = createConfigEntry(pluginConfig, pluginConfigInfo, pluginId);

	var env = getConfigEnv(pluginConfig, pluginConfigInfo, runtimeConfig);

	// console.log('#### will add '+pluginConfigInfo.pluginName+' to envs ', env, ' with entry ', confEntry);//DEBUG

	if(!runtimeConfig.mediaManager){
		runtimeConfig.mediaManager = {};
	}
	if(!runtimeConfig.mediaManager.plugins){
		runtimeConfig.mediaManager.plugins = {};
	}
	var pConfig = runtimeConfig.mediaManager.plugins;
	var pList, cEntry;
	env.forEach(function(e){
		pList = pConfig[e];
		if(pList){
			normalizeMediaManagerPluginConfig(pList);
			cEntry = getPluginEntryFrom(confEntry, pList);
			if(cEntry){
				_.merge(cEntry, confEntry);
			} else {
				pConfig[e].push(confEntry);
			}
		} else {
			//FIXME this whould also require to add defaults like audio-output
			pConfig[e] = [confEntry]
		}
	});

	//TODO add/apply configuration for core-dependency mmir-plugin-encoder-core ~> silence-detection, if specified
}

module.exports = {
	addPluginInfos: function(pluginId, workersList, appConfig, pluginConfig, runtimeConfig, settings){

		var pluginInfo = require(pluginId + '/module-ids.gen.js');

		var paths = pluginInfo.getAll('paths', true);

		addResolveAliasPaths(appConfig.paths, paths)

		var workers = pluginInfo.getAll('workers');

		workers.forEach(function(w){
			workersList.push(paths[w]);
		});

		var includeModules = pluginInfo.getAll('modules');

		includeModules.forEach(function(mod){
			appConfigUtils.addIncludeModule(appConfig, mod, paths[mod]);
		});

		var pluginConfigInfo = require(pluginId + '/module-config.gen.js');

		if(pluginConfigInfo.pluginName){
			if(Array.isArray(pluginConfigInfo.pluginName)){
				pluginConfigInfo.pluginName.forEach(function(pluginName){
					addConfig(pluginConfig, runtimeConfig, settings, pluginConfigInfo.plugins[pluginName], pluginId);
				});
			} else {
				addConfig(pluginConfig, runtimeConfig, settings, pluginConfigInfo, pluginId);
			}
		} else {
			console.log('ERROR invalid module-config.js for plugin '+pluginId+': missing field pluginName ', pluginConfigInfo);
		}

		console.log('plugin-utils: addPluginInfos() -> paths ', paths, ', workers ', workers, ', include modules ', includeModules, runtimeConfig);//DEBUG
	}
};
<|endoftext|>


<commit_before>var Util, DOM, CloudFunc;
/* Функция которая возвратит обьект CloudCmd
 * @CloudFunc - обьект содержащий общий функционал
 *  клиентский и серверный
 */
(function(scope, Util, DOM, CloudFunc) {
    'use strict';

    scope.CloudCmd = new CloudCmdProto(Util, DOM, CloudFunc);

    function CloudCmdProto(Util, DOM, CloudFunc) {
        var Key, Config, Modules, Extensions,
            FileTemplate, PathTemplate, LinkTemplate, Listeners,
            Images                  = DOM.Images,
            Info                    = DOM.CurrentInfo,
            CloudCmd                = this,
            Storage                 = DOM.Storage;

        this.LIBDIR                 = '/lib/';
        this.LIBDIRCLIENT           = '/lib/client/';
        this.JSONDIR                = '/json/';
        this.HTMLDIR                = '/html/';
        this.MIN_ONE_PANEL_WIDTH    = 1155;
        this.OLD_BROWSER            = false;
        this.HOST                   =  (function() {
            var location = document.location;
            return location.protocol + '//' + location.host;
        })();

        /**
         * Функция привязываеться ко всем ссылкам и
         *  загружает содержимое каталогов
         *
         * @param pLink - ссылка
         * @param pNeedRefresh - необходимость обязательной загрузки данных с сервера
         */
        this.loadDir                = function(link, needRefresh) {
            return function(event) {
                var currentLink    = DOM.getCurrentLink(),
                    href           = currentLink.href;

                if (!link)
                    link = Util.removeStr(href, CloudCmd.HOST);

                link += '?json';

                if (link || currentLink.target !== '_blank') {
                    Images.showLoad(!needRefresh ? null: {
                        top:true
                    });

                    /* загружаем содержимое каталога */
                    CloudCmd.ajaxLoad(link, {
                        refresh: needRefresh
                    });
                }

                DOM.preventDefault(event);
            };
        };

        /**
         * функция устанавливает курсор на каталог
         * с которого мы пришли, если мы поднялись
         * в верх по файловой структуре
         * @param pDirName - имя каталога с которого мы пришли
         */
        function currentToParent(dirName) {
            var rootDir;
            /* убираем слэш с имени каталога */
            dirName     = Util.removeStr(dirName, '/');
            rootDir     = DOM.getCurrentFileByName(dirName);

            if (rootDir) {
                DOM.setCurrentFile(rootDir);
                DOM.scrollIntoViewIfNeeded(rootDir, true);
            }
        }

        /**
         * function load modules
         * @pParams = {name, path, func, dobefore, arg}
         */
        function loadModule(pParams) {
            var lName, lPath, lFunc, lDoBefore, lSlash, lAfterSlash,
                isContain;

            if (pParams) {
                lName       = pParams.name,
                lPath       = pParams.path,
                lFunc       = pParams.func,
                lDoBefore   = pParams.dobefore;

                if (Util.isString(pParams))
                    lPath = pParams;

                if (lPath && !lName) {
                    lName = Util.getStrBigFirst(lPath);
                    lName = Util.removeStr(lName, '.js');

                    lSlash = lName.indexOf('/');
                    if (lSlash > 0) {
                        lAfterSlash = lName.substr(lSlash);
                        lName = Util.removeStr(lName, lAfterSlash);
                    }
                }

                isContain = Util.isContainStr(lPath, '.js');
                if (!isContain)
                    lPath += '.js';

                if (!CloudCmd[lName]) {
                    CloudCmd[lName] = function(pArg) {
                        var path = CloudCmd.LIBDIRCLIENT + lPath;

                        Util.exec(lDoBefore);

                        return DOM.jsload(path, lFunc ||
                            function() {
                                var Proto = CloudCmd[lName];

                                if (Util.isFunction(Proto))
                                    CloudCmd[lName] = new Proto(pArg);
                            });
                    };

                    CloudCmd[lName].show = CloudCmd[lName];
                }
            }
        }

        /** Конструктор CloudClient, который
         * выполняет весь функционал по
         * инициализации
         */
        this.init                    = function() {
            var lCallBack, lFunc;

            lCallBack = function() {
                Util.loadOnLoad([
                    initModules,
                    baseInit,
                    Util.bind(CloudCmd.route, location.hash)
                ]);
            },
            lFunc = function(pCallBack) {
                CloudCmd.OLD_BROWSER = true;
                var lSrc = CloudCmd.LIBDIRCLIENT + 'polyfill.js';

                DOM.jqueryLoad(
                    DOM.retJSLoad(lSrc, pCallBack)
                );
            };

            Util.ifExec(document.body.scrollIntoViewIfNeeded, lCallBack, lFunc);
        };

        this.route                   = function(path) {
            var query, module, file, current, msg;

            if (path.length > 0) {
                query  = path.split('/');

                if (query.length > 0) {
                    module     = Util.getStrBigFirst(query[1]);
                    file       = query[2];
                    current    = DOM.getCurrentFileByName(file);

                    if (file && !current) {
                        msg = CloudFunc.formatMsg('set current file', file, 'error');
                        Util.log(msg);
                    } else {
                        DOM.setCurrentFile(current);
                        CloudCmd.execFromModule(module, 'show');
                    }
                }
            }
        };

        function initModules(pCallBack) {
            Util.ifExec(CloudCmd.Key, function() {
                Key          = new CloudCmd.Key();
                CloudCmd.Key = Key;
                Key.bind();
            }, function(callback) {
                loadModule({
                    /* привязываем клавиши к функциям */
                    path  : 'key.js',
                    func : callback
                 });
             });

            CloudCmd.getModules(function(pModules) {
                pModules            = pModules || [];

                var i, n, module, storageObj, mod, name, path,
                    STORAGE  = 'storage',
                    showLoadFunc    = Util.bind(Images.showLoad, {
                        top:true
                    }),

                    doBefore       = {
                        'edit'  : showLoadFunc,
                        'view'  : showLoadFunc,
                        'menu'  : showLoadFunc
                    },

                    load = function(name, path, func) {
                        loadModule({
                            name        : name,
                            path        : path,
                            dobefore    : func
                        });
                    };

                for (i = 0, n = pModules.length; i < n ; i++) {
                    module = pModules[i];

                    if (Util.isString(module))
                        load(null, module, doBefore[module]);
                }

                storageObj = Util.findObjByNameInArr(pModules, STORAGE),
                mod        = Util.getNamesFromObjArray(storageObj);

                for (i = 0, n = mod.length; i < n; i++) {
                    name = mod[i],
                    path = STORAGE + '/_' + name.toLowerCase();

                    load(name, path);
                }

                Util.exec(pCallBack);

            });
        }

        function baseInit(pCallBack) {
            var LIB         = CloudCmd.LIBDIR,
                LIBCLIENT   = CloudCmd.LIBDIRCLIENT,
                files       = DOM.getFiles(),
                LEFT        = CloudFunc.PANEL_LEFT,
                RIGHT       = CloudFunc.PANEL_RIGHT;

            /* выделяем строку с первым файлом                                  */
            if (files)
                DOM.setCurrentFile(files[0]);

            Listeners = CloudCmd.Listeners;
            Listeners.init();
            /* загружаем Google Analytics */
            Listeners.analytics();
            Listeners.changeLinks(LEFT);
            Listeners.changeLinks(RIGHT);

            Listeners.initKeysPanel();

            CloudCmd.getConfig(function(config) {
                var localStorage    = config.localStorage,
                    dirPath         = DOM.getCurrentDirPath();

                /* устанавливаем переменную доступности кэша                    */
                Storage.setAllowed(localStorage);
                /* Устанавливаем кэш корневого каталога                         */

                dirPath     = CloudFunc.rmLastSlash(dirPath) || '/';

                if (!Storage.get(dirPath))
                    Storage.set(dirPath, getJSONfromFileTable());
                });

            Util.exec(CloudCmd.Key);
            Util.exec(pCallBack);
        }

        function getSystemFile(pGlobal, pURL) {

            function lGetSysFile(pCallBack) {
                Util.ifExec(pGlobal, pCallBack, function(pCallBack) {
                    DOM.ajax({
                        url     : pURL,
                        success : function(pLocal) {
                            pGlobal = pLocal;
                            Util.exec(pCallBack, pLocal);
                        }
                    });
                });
            }

            return lGetSysFile;
        }

        this.setConfig              = function(config) { Config = config; };
        this.getConfig              = getSystemFile(Config,         CloudCmd.JSONDIR + 'config.json');
        this.getModules             = getSystemFile(Modules,        CloudCmd.JSONDIR + 'modules.json');
        this.getExt                 = getSystemFile(Extensions,        CloudCmd.JSONDIR + 'ext.json');
        this.getFileTemplate        = getSystemFile(FileTemplate,   CloudCmd.HTMLDIR + 'file.html');
        this.getPathTemplate        = getSystemFile(PathTemplate,   CloudCmd.HTMLDIR + 'path.html');
        this.getLinkTemplate        = getSystemFile(PathTemplate,   CloudCmd.HTMLDIR + 'link.html');

        this.execFromModule         = function(moduleName, funcName, params) {
            var obj     = CloudCmd[moduleName],
                isObj   = Util.isObject(obj);

            Util.ifExec(isObj,
                function() {
                    var obj     = CloudCmd[moduleName],
                        func    = obj[funcName];

                    Util.exec(func, params);
                },

                function(callback) {
                    Util.exec(obj, callback);
                });
        };

        this.refresh                =  function(current) {
            var NEEDREFRESH     = true,
                panel           = current && current.parentElement,
                path            = DOM.getCurrentDirPath(panel),
                link            = CloudFunc.FS + path,
                notSlashlLink   = CloudFunc.rmLastSlash(link),
                load            = CloudCmd.loadDir(notSlashlLink, NEEDREFRESH);

            load();
        };

        /**
         * Функция загружает json-данные о Файловой Системе
         * через ajax-запрос.
         * @param path - каталог для чтения
         * @param pOptions
         * { refresh, nohistory } - необходимость обновить данные о каталоге
         */
        this.ajaxLoad               = function(path, options) {
            var json, str,
                ret         = options && options.refresh,
                SLASH       = '/',
                dirPath     = DOM.getCurrentDirPath(),
                fsPath      = decodeURI(path),
                noJSONPath  = Util.removeStr(fsPath, '?json' ),
                cleanPath   = Util.removeStrOneTime(noJSONPath, CloudFunc.FS) || SLASH,
                setTitle    = function() {
                    var title;

                    dirPath = CloudFunc.rmLastSlash(dirPath) || '/';

                    if (dirPath !== cleanPath) {
                        if (!options.nohistory)
                            DOM.setHistory(noJSONPath, null, noJSONPath);

                        title   = CloudFunc.getTitle(cleanPath);
                        DOM.setTitle(title);
                    }
                };

            if (!options)
                options    = {};

            if (cleanPath === SLASH)
                noJSONPath = SLASH;

            Util.log ('reading dir: "' + cleanPath + '";');

             /* если доступен localStorage и
              * в нём есть нужная нам директория -
              * читаем данные с него и
              * выходим
              * если стоит поле обязательной перезагрузки -
              * перезагружаемся
              */
            if (!ret) {
                str = Storage.get(cleanPath);

                if (!str)
                    ret = true;
                else {
                    json = Util.parseJSON(str);
                    CloudCmd.createFileTable(json);
                    setTitle();
                }
            }

            if (ret)
                DOM.getCurrentFileContent({
                    url         : fsPath,
                    dataType    : 'json',
                    success     : function(data) {
                        var str         = Util.stringifyJSON(data),
                            MAX_SIZE    = 50000;

                        setTitle();

                        CloudCmd.createFileTable(data);
                        Util.log(str.length);

                        /* если размер данных не очень бошьой       *
                         * сохраняем их в кэше                      */
                        if (str.length < MAX_SIZE)
                            Storage.set(cleanPath, str);
                    }
                });
        };

        /**
         * Функция строит файловую таблицу
         * @param pJSON  - данные о файлах
         */
        this.createFileTable        = function(pJSON) {
            var files,
                panel           = DOM.getPanel(),
                /* getting current element if was refresh */
                lPath           = DOM.getCurrentDirPath(panel),

                lCurrent        = DOM.getCurrentFile(),
                lDir            = DOM.getCurrentDirName(),

                lName           = DOM.getCurrentName(lCurrent),
                wasRefresh      = lPath === pJSON.path,
                lFuncs          = [
                    CloudCmd.getFileTemplate,
                    CloudCmd.getPathTemplate,
                    CloudCmd.getLinkTemplate
                ];

            Util.asyncCall(lFuncs, function(pTemplate, pPathTemplate, pLinkTemplate) {
                /* очищаем панель */
                var n, found,
                    i = panel.childNodes.length;

                while(i--)
                    panel.removeChild(panel.lastChild);

                panel.innerHTML = CloudFunc.buildFromJSON(pJSON, pTemplate, pPathTemplate, pLinkTemplate);

                files = DOM.getFiles(panel);

                /* searching current file */
                if (wasRefresh) {
                    n = files.length;

                    for (i = 0; i < n ; i++) {
                        var lVarCurrent = files[i],
                            lVarName    = DOM.getCurrentName(lVarCurrent);

                        found = lVarName === lName;

                        if (found) {
                            lCurrent    = files[i];
                            break;
                        }
                    }
                }
                if (!found) /* .. */
                    lCurrent = files[0];

                DOM.setCurrentFile(lCurrent);

                Listeners.changeLinks(panel.id);

                if (lName === '..' && lDir !== '/')
                    currentToParent(lDir);
            });
        };

        /**
         * Функция генерирует JSON из html-таблицы файлов и
         * используеться при первом заходе в корень
         */
        function getJSONfromFileTable() {
            var current, name, size, owner, mode, ret,
                path        = DOM.getCurrentDirPath(),
                infoFiles   = Info.files,

                fileTable   = {
                    path    : path,
                    files   : []
                },

                files       = fileTable.files,

                i, n        = infoFiles.length;

            for (i = 0; i < n; i++) {
                current     = infoFiles[i];
                name        = DOM.getCurrentName(current);
                size        = DOM.getCurrentSize(current);
                owner       = DOM.getCurrentOwner(current);
                mode        = DOM.getCurrentMode(current);

                mode        = CloudFunc.getNumericPermissions(mode);

                if (name !== '..')
                    files.push({
                        name    : name,
                        size    : size,
                        mode    : mode,
                        owner   : owner
                    });
            }

            ret = Util.stringifyJSON(fileTable);

            return ret;
        }

        this.goToParentDir          = function() {
            var path        = Info.dirPath,
                parentPath  = Info.parentDirPath;

            if (path !== parentPath) {
                path    = parentPath;
                path    = CloudFunc.FS + CloudFunc.rmLastSlash(path);

                Util.exec(CloudCmd.loadDir(path));
            }
        };

    }
})(this, Util, DOM, CloudFunc);
<commit_msg>refactor(client) loadModule<commit_after>var Util, DOM, CloudFunc;
/* Функция которая возвратит обьект CloudCmd
 * @CloudFunc - обьект содержащий общий функционал
 *  клиентский и серверный
 */
(function(scope, Util, DOM, CloudFunc) {
    'use strict';

    scope.CloudCmd = new CloudCmdProto(Util, DOM, CloudFunc);

    function CloudCmdProto(Util, DOM, CloudFunc) {
        var Key, Config, Modules, Extensions,
            FileTemplate, PathTemplate, LinkTemplate, Listeners,
            Images                  = DOM.Images,
            Info                    = DOM.CurrentInfo,
            CloudCmd                = this,
            Storage                 = DOM.Storage;

        this.LIBDIR                 = '/lib/';
        this.LIBDIRCLIENT           = '/lib/client/';
        this.JSONDIR                = '/json/';
        this.HTMLDIR                = '/html/';
        this.MIN_ONE_PANEL_WIDTH    = 1155;
        this.OLD_BROWSER            = false;
        this.HOST                   =  (function() {
            var location = document.location;
            return location.protocol + '//' + location.host;
        })();

        /**
         * Функция привязываеться ко всем ссылкам и
         *  загружает содержимое каталогов
         *
         * @param pLink - ссылка
         * @param pNeedRefresh - необходимость обязательной загрузки данных с сервера
         */
        this.loadDir                = function(link, needRefresh) {
            return function(event) {
                var currentLink    = DOM.getCurrentLink(),
                    href           = currentLink.href;

                if (!link)
                    link = Util.removeStr(href, CloudCmd.HOST);

                link += '?json';

                if (link || currentLink.target !== '_blank') {
                    Images.showLoad(!needRefresh ? null: {
                        top:true
                    });

                    /* загружаем содержимое каталога */
                    CloudCmd.ajaxLoad(link, {
                        refresh: needRefresh
                    });
                }

                DOM.preventDefault(event);
            };
        };

        /**
         * функция устанавливает курсор на каталог
         * с которого мы пришли, если мы поднялись
         * в верх по файловой структуре
         * @param pDirName - имя каталога с которого мы пришли
         */
        function currentToParent(dirName) {
            var rootDir;
            /* убираем слэш с имени каталога */
            dirName     = Util.removeStr(dirName, '/');
            rootDir     = DOM.getCurrentFileByName(dirName);

            if (rootDir) {
                DOM.setCurrentFile(rootDir);
                DOM.scrollIntoViewIfNeeded(rootDir, true);
            }
        }

        /**
         * function load modules
         * @pParams = {name, path, func, dobefore, arg}
         */
        function loadModule(params) {
            var name, path, func, doBefore, slash, afterSlash,
                isContain;

            if (params) {
                name        = params.name,
                path        = params.path,
                func        = params.func,
                doBefore    = params.dobefore;

                if (Util.isString(params))
                    path = params;

                if (path && !name) {
                    name = Util.getStrBigFirst(path);
                    name = Util.removeStr(name, '.js');

                    slash = name.indexOf('/');
                    if (slash > 0) {
                        afterSlash = name.substr(slash);
                        name = Util.removeStr(name, afterSlash);
                    }
                }

                isContain = Util.isContainStr(path, '.js');

                if (!isContain)
                    path += '.js';

                if (!CloudCmd[name]) {
                    CloudCmd[name] = function(arg) {
                        path = CloudCmd.LIBDIRCLIENT + path;

                        Util.exec(doBefore);

                        return DOM.jsload(path, func ||
                            function() {
                                var Proto = CloudCmd[name];

                                if (Util.isFunction(Proto))
                                    CloudCmd[name] = new Proto(arg);
                            });
                    };

                    CloudCmd[name].show = CloudCmd[name];
                }
            }
        }

        /** Конструктор CloudClient, который
         * выполняет весь функционал по
         * инициализации
         */
        this.init                    = function() {
            var lCallBack, lFunc;

            lCallBack = function() {
                Util.loadOnLoad([
                    initModules,
                    baseInit,
                    Util.bind(CloudCmd.route, location.hash)
                ]);
            },
            lFunc = function(pCallBack) {
                CloudCmd.OLD_BROWSER = true;
                var lSrc = CloudCmd.LIBDIRCLIENT + 'polyfill.js';

                DOM.jqueryLoad(
                    DOM.retJSLoad(lSrc, pCallBack)
                );
            };

            Util.ifExec(document.body.scrollIntoViewIfNeeded, lCallBack, lFunc);
        };

        this.route                   = function(path) {
            var query, module, file, current, msg;

            if (path.length > 0) {
                query  = path.split('/');

                if (query.length > 0) {
                    module     = Util.getStrBigFirst(query[1]);
                    file       = query[2];
                    current    = DOM.getCurrentFileByName(file);

                    if (file && !current) {
                        msg = CloudFunc.formatMsg('set current file', file, 'error');
                        Util.log(msg);
                    } else {
                        DOM.setCurrentFile(current);
                        CloudCmd.execFromModule(module, 'show');
                    }
                }
            }
        };

        function initModules(pCallBack) {
            Util.ifExec(CloudCmd.Key, function() {
                Key          = new CloudCmd.Key();
                CloudCmd.Key = Key;
                Key.bind();
            }, function(callback) {
                loadModule({
                    /* привязываем клавиши к функциям */
                    path  : 'key.js',
                    func : callback
                 });
             });

            CloudCmd.getModules(function(pModules) {
                pModules            = pModules || [];

                var i, n, module, storageObj, mod, name, path,
                    STORAGE  = 'storage',
                    showLoadFunc    = Util.bind(Images.showLoad, {
                        top:true
                    }),

                    doBefore       = {
                        'edit'  : showLoadFunc,
                        'view'  : showLoadFunc,
                        'menu'  : showLoadFunc
                    },

                    load = function(name, path, func) {
                        loadModule({
                            name        : name,
                            path        : path,
                            dobefore    : func
                        });
                    };

                for (i = 0, n = pModules.length; i < n ; i++) {
                    module = pModules[i];

                    if (Util.isString(module))
                        load(null, module, doBefore[module]);
                }

                storageObj = Util.findObjByNameInArr(pModules, STORAGE),
                mod        = Util.getNamesFromObjArray(storageObj);

                for (i = 0, n = mod.length; i < n; i++) {
                    name = mod[i],
                    path = STORAGE + '/_' + name.toLowerCase();

                    load(name, path);
                }

                Util.exec(pCallBack);

            });
        }

        function baseInit(pCallBack) {
            var LIB         = CloudCmd.LIBDIR,
                LIBCLIENT   = CloudCmd.LIBDIRCLIENT,
                files       = DOM.getFiles(),
                LEFT        = CloudFunc.PANEL_LEFT,
                RIGHT       = CloudFunc.PANEL_RIGHT;

            /* выделяем строку с первым файлом                                  */
            if (files)
                DOM.setCurrentFile(files[0]);

            Listeners = CloudCmd.Listeners;
            Listeners.init();
            /* загружаем Google Analytics */
            Listeners.analytics();
            Listeners.changeLinks(LEFT);
            Listeners.changeLinks(RIGHT);

            Listeners.initKeysPanel();

            CloudCmd.getConfig(function(config) {
                var localStorage    = config.localStorage,
                    dirPath         = DOM.getCurrentDirPath();

                /* устанавливаем переменную доступности кэша                    */
                Storage.setAllowed(localStorage);
                /* Устанавливаем кэш корневого каталога                         */

                dirPath     = CloudFunc.rmLastSlash(dirPath) || '/';

                if (!Storage.get(dirPath))
                    Storage.set(dirPath, getJSONfromFileTable());
                });

            Util.exec(CloudCmd.Key);
            Util.exec(pCallBack);
        }

        function getSystemFile(pGlobal, pURL) {

            function lGetSysFile(pCallBack) {
                Util.ifExec(pGlobal, pCallBack, function(pCallBack) {
                    DOM.ajax({
                        url     : pURL,
                        success : function(pLocal) {
                            pGlobal = pLocal;
                            Util.exec(pCallBack, pLocal);
                        }
                    });
                });
            }

            return lGetSysFile;
        }

        this.setConfig              = function(config) { Config = config; };
        this.getConfig              = getSystemFile(Config,         CloudCmd.JSONDIR + 'config.json');
        this.getModules             = getSystemFile(Modules,        CloudCmd.JSONDIR + 'modules.json');
        this.getExt                 = getSystemFile(Extensions,        CloudCmd.JSONDIR + 'ext.json');
        this.getFileTemplate        = getSystemFile(FileTemplate,   CloudCmd.HTMLDIR + 'file.html');
        this.getPathTemplate        = getSystemFile(PathTemplate,   CloudCmd.HTMLDIR + 'path.html');
        this.getLinkTemplate        = getSystemFile(PathTemplate,   CloudCmd.HTMLDIR + 'link.html');

        this.execFromModule         = function(moduleName, funcName, params) {
            var obj     = CloudCmd[moduleName],
                isObj   = Util.isObject(obj);

            Util.ifExec(isObj,
                function() {
                    var obj     = CloudCmd[moduleName],
                        func    = obj[funcName];

                    Util.exec(func, params);
                },

                function(callback) {
                    Util.exec(obj, callback);
                });
        };

        this.refresh                =  function(current) {
            var NEEDREFRESH     = true,
                panel           = current && current.parentElement,
                path            = DOM.getCurrentDirPath(panel),
                link            = CloudFunc.FS + path,
                notSlashlLink   = CloudFunc.rmLastSlash(link),
                load            = CloudCmd.loadDir(notSlashlLink, NEEDREFRESH);

            load();
        };

        /**
         * Функция загружает json-данные о Файловой Системе
         * через ajax-запрос.
         * @param path - каталог для чтения
         * @param pOptions
         * { refresh, nohistory } - необходимость обновить данные о каталоге
         */
        this.ajaxLoad               = function(path, options) {
            var json, str,
                ret         = options && options.refresh,
                SLASH       = '/',
                dirPath     = DOM.getCurrentDirPath(),
                fsPath      = decodeURI(path),
                noJSONPath  = Util.removeStr(fsPath, '?json' ),
                cleanPath   = Util.removeStrOneTime(noJSONPath, CloudFunc.FS) || SLASH,
                setTitle    = function() {
                    var title;

                    dirPath = CloudFunc.rmLastSlash(dirPath) || '/';

                    if (dirPath !== cleanPath) {
                        if (!options.nohistory)
                            DOM.setHistory(noJSONPath, null, noJSONPath);

                        title   = CloudFunc.getTitle(cleanPath);
                        DOM.setTitle(title);
                    }
                };

            if (!options)
                options    = {};

            if (cleanPath === SLASH)
                noJSONPath = SLASH;

            Util.log ('reading dir: "' + cleanPath + '";');

             /* если доступен localStorage и
              * в нём есть нужная нам директория -
              * читаем данные с него и
              * выходим
              * если стоит поле обязательной перезагрузки -
              * перезагружаемся
              */
            if (!ret) {
                str = Storage.get(cleanPath);

                if (!str)
                    ret = true;
                else {
                    json = Util.parseJSON(str);
                    CloudCmd.createFileTable(json);
                    setTitle();
                }
            }

            if (ret)
                DOM.getCurrentFileContent({
                    url         : fsPath,
                    dataType    : 'json',
                    success     : function(data) {
                        var str         = Util.stringifyJSON(data),
                            MAX_SIZE    = 50000;

                        setTitle();

                        CloudCmd.createFileTable(data);
                        Util.log(str.length);

                        /* если размер данных не очень бошьой       *
                         * сохраняем их в кэше                      */
                        if (str.length < MAX_SIZE)
                            Storage.set(cleanPath, str);
                    }
                });
        };

        /**
         * Функция строит файловую таблицу
         * @param pJSON  - данные о файлах
         */
        this.createFileTable        = function(pJSON) {
            var files,
                panel           = DOM.getPanel(),
                /* getting current element if was refresh */
                lPath           = DOM.getCurrentDirPath(panel),

                lCurrent        = DOM.getCurrentFile(),
                lDir            = DOM.getCurrentDirName(),

                lName           = DOM.getCurrentName(lCurrent),
                wasRefresh      = lPath === pJSON.path,
                lFuncs          = [
                    CloudCmd.getFileTemplate,
                    CloudCmd.getPathTemplate,
                    CloudCmd.getLinkTemplate
                ];

            Util.asyncCall(lFuncs, function(pTemplate, pPathTemplate, pLinkTemplate) {
                /* очищаем панель */
                var n, found,
                    i = panel.childNodes.length;

                while(i--)
                    panel.removeChild(panel.lastChild);

                panel.innerHTML = CloudFunc.buildFromJSON(pJSON, pTemplate, pPathTemplate, pLinkTemplate);

                files = DOM.getFiles(panel);

                /* searching current file */
                if (wasRefresh) {
                    n = files.length;

                    for (i = 0; i < n ; i++) {
                        var lVarCurrent = files[i],
                            lVarName    = DOM.getCurrentName(lVarCurrent);

                        found = lVarName === lName;

                        if (found) {
                            lCurrent    = files[i];
                            break;
                        }
                    }
                }
                if (!found) /* .. */
                    lCurrent = files[0];

                DOM.setCurrentFile(lCurrent);

                Listeners.changeLinks(panel.id);

                if (lName === '..' && lDir !== '/')
                    currentToParent(lDir);
            });
        };

        /**
         * Функция генерирует JSON из html-таблицы файлов и
         * используеться при первом заходе в корень
         */
        function getJSONfromFileTable() {
            var current, name, size, owner, mode, ret,
                path        = DOM.getCurrentDirPath(),
                infoFiles   = Info.files,

                fileTable   = {
                    path    : path,
                    files   : []
                },

                files       = fileTable.files,

                i, n        = infoFiles.length;

            for (i = 0; i < n; i++) {
                current     = infoFiles[i];
                name        = DOM.getCurrentName(current);
                size        = DOM.getCurrentSize(current);
                owner       = DOM.getCurrentOwner(current);
                mode        = DOM.getCurrentMode(current);

                mode        = CloudFunc.getNumericPermissions(mode);

                if (name !== '..')
                    files.push({
                        name    : name,
                        size    : size,
                        mode    : mode,
                        owner   : owner
                    });
            }

            ret = Util.stringifyJSON(fileTable);

            return ret;
        }

        this.goToParentDir          = function() {
            var path        = Info.dirPath,
                parentPath  = Info.parentDirPath;

            if (path !== parentPath) {
                path    = parentPath;
                path    = CloudFunc.FS + CloudFunc.rmLastSlash(path);

                Util.exec(CloudCmd.loadDir(path));
            }
        };

    }
})(this, Util, DOM, CloudFunc);
<|endoftext|>


<commit_before>var tabGroups = {}; // TODO this is being lost on page unloading need to store elsewhere
// or need to find other solution

chrome.tabs.onUpdated.addListener((tabId, change) => {
    if(!change.url) {
        // url not changed in tab update
        return;
    }
    var baseUrl = change.url.split('#')[0].split('?')[0];
    chrome.tabs.query({ url: baseUrl + '*' }, (tabs) => {
        if(tabs.length > 1) {
            updateTabGroup(_.map(tabs, 'id'));
        } else if(tabGroups[tabId]) {
            removeFromTabGroup(tabId);
            chrome.pageAction.hide(tabId);
        }
    });
});

chrome.tabs.onRemoved.addListener(removeFromTabGroup);

function removeFromTabGroup(tabId) {
    if(tabGroups[tabId]) {
        if(tabGroups[tabId].length === 2) {
            _.each(tabGroups[tabId], (id) => {
                if(id !== tabId) {
                    chrome.pageAction.hide(id);
                }
                delete tabGroups[id];
            });
        } else {
            updateTabGroup(_.reject(tabGroups[tabId], (id) => id === tabId));
            delete tabGroups[tabId];
        }
    }
}

function updateTabGroup(tabIds) {
    var imageData = drawIcon(tabIds.length);
    _.each(tabIds, (id) => {
        tabGroups[id] = tabIds;
        chrome.pageAction.show(id);
        chrome.pageAction.setIcon({tabId: id, imageData: imageData});
        chrome.pageAction.setTitle({
            tabId: id,
            title: 'This page is open in ' + tabIds.length + ' tabs.'
        });
    });
}

function drawIcon(count) {
    var canvas = document.getElementById('canvas');
    var ctx = canvas.getContext('2d');
    var W = canvas.width;
    var H = canvas.height;
    var wMargin = 2;
    var minW = wMargin;
    var maxW = W - wMargin;
    var hOff = 5;
    var wOff = 6;

    var path = new Path2D();
    path.moveTo(maxW-wOff, 0);
    path.lineTo(minW, 0);
    path.lineTo(minW, H);
    path.lineTo(maxW, H);
    path.lineTo(maxW, hOff);
    path.lineTo(maxW-wOff, hOff);
    path.lineTo(maxW-wOff, 0);
    path.lineTo(maxW, hOff);

    ctx.clearRect(0, 0, W, H);
    ctx.stroke(path);
    ctx.font = '10px sans-serif';
    ctx.textAlign = 'center';
    ctx.textBaseline = 'middle';
    ctx.fillText(count+'×', W/2, H/2, maxW-minW);
    return ctx.getImageData(0, 0, 19, 19);
}
<commit_msg>added local storage to persist data cache across event page unloadings, found an issue with the structure being cached though<commit_after>chrome.runtime.onInstalled.addListener(() => {
    chrome.storage.local.tabGroups = {};
});

if(!chrome.storage.local.tabGroups) {
    chrome.storage.local.tabGroups = {};
}
var tabGroups = chrome.storage.local.tabGroups;

chrome.tabs.onUpdated.addListener((tabId, change) => {
    if(!change.url) {
        // url not changed in tab update
        return;
    }
    var baseUrl = change.url.split('#')[0].split('?')[0];
    // FIXME issue here, on a url change on a tab that was in a tab group if
    // the new url is unique this query might not return if the tab hasn't
    // loaded yet, need to remove from group preemptively if baseUrl changed.
    chrome.tabs.query({ url: baseUrl + '*' }, (tabs) => {
        if(tabs.length > 1) {
            updateTabGroup(_.map(tabs, 'id'));
        } else {
            removeFromTabGroup(tabId);
            chrome.pageAction.hide(tabId);
        }
    });
});

chrome.tabs.onRemoved.addListener(removeFromTabGroup);

function removeFromTabGroup(tabId) {
    if(tabGroups[tabId]) {
        if(tabGroups[tabId].length === 2) {
            _.each(tabGroups[tabId], (id) => {
                if(id !== tabId) {
                    chrome.pageAction.hide(id);
                }
                delete tabGroups[id];
            });
        } else {
            updateTabGroup(_.reject(tabGroups[tabId], (id) => id === tabId));
            delete tabGroups[tabId];
        }
    }
}

function updateTabGroup(tabIds) {
    var imageData = drawIcon(tabIds.length);
    _.each(tabIds, (id) => {
        tabGroups[id] = tabIds;
        chrome.pageAction.show(id);
        chrome.pageAction.setIcon({tabId: id, imageData: imageData});
        chrome.pageAction.setTitle({
            tabId: id,
            title: 'This page is open in ' + tabIds.length + ' tabs.'
        });
    });
}

function drawIcon(count) {
    var canvas = document.getElementById('canvas');
    var ctx = canvas.getContext('2d');
    var W = canvas.width;
    var H = canvas.height;
    var wMargin = 2;
    var minW = wMargin;
    var maxW = W - wMargin;
    var hOff = 5;
    var wOff = 6;

    var path = new Path2D();
    path.moveTo(maxW-wOff, 0);
    path.lineTo(minW, 0);
    path.lineTo(minW, H);
    path.lineTo(maxW, H);
    path.lineTo(maxW, hOff);
    path.lineTo(maxW-wOff, hOff);
    path.lineTo(maxW-wOff, 0);
    path.lineTo(maxW, hOff);

    ctx.clearRect(0, 0, W, H);
    ctx.stroke(path);
    ctx.font = '10px sans-serif';
    ctx.textAlign = 'center';
    ctx.textBaseline = 'middle';
    ctx.fillText(count+'×', W/2, H/2, maxW-minW);
    return ctx.getImageData(0, 0, 19, 19);
}
<|endoftext|>