<commit_before>import {Loader} from 'aurelia-loader';
import {relativeToFile, join} from 'aurelia-path';
import {Container} from 'aurelia-dependency-injection';
import {Metadata, ResourceType, Origin} from 'aurelia-metadata';
import {ValueConverter} from 'aurelia-binding';
import {CustomElement} from './custom-element';
import {AttachedBehavior} from './attached-behavior';
import {TemplateController} from './template-controller';
import {ViewEngine} from './view-engine';
import {ResourceRegistry} from './resource-registry';

var id = 0;

function nextId(){
  return ++id;
}

export class ResourceCoordinator {
  static inject(){ return [Loader, Container, ViewEngine, ResourceRegistry]; }
	constructor(loader, container, viewEngine, appResources){
		this.loader = loader;
		this.container = container;
		this.viewEngine = viewEngine;
		this.importedModules = {};
    this.importedAnonymous = {};
    this.appResources = appResources;
		viewEngine.resourceCoordinator = this;
	}

  getExistingModuleAnalysis(id){
    return this.importedModules[id] || this.importedAnonymous[id];
  }

  loadViewModelInfo(moduleImport, moduleMember){
    return this._loadAndAnalyzeModuleForElement(moduleImport, moduleMember, this.importedAnonymous);
  }

  loadElement(moduleImport, moduleMember, viewStategy){
    return this._loadAndAnalyzeModuleForElement(moduleImport, moduleMember, this.importedModules).then(info => {
      var type = info.type;

      if(type.isLoaded){
        return type;
      }

      type.isLoaded = true;

      return type.load(this.container, info.value, viewStategy);
    });
  }

  _loadAndAnalyzeModuleForElement(moduleImport, moduleMember, cache){
    var existing = cache[moduleImport];

    if(existing){
      return Promise.resolve(existing.element);
    }

    return this.loader.loadModule(moduleImport).then(elementModule => {
      var analysis = analyzeModule(elementModule, moduleMember),
          resources = analysis.resources,
          container = this.container,
          loads = [], type, current, i , ii;

      if(!analysis.element){
        throw new Error(`No element found in module "${moduleImport}".`);
      }

      analysis.analyze(container);

      for(i = 0, ii = resources.length; i < ii; ++i){
        current = resources[i];
        type = current.type;

        if(!type.isLoaded){
          type.isLoaded = true;
          loads.push(type.load(container, current.value));
        }
      }

      cache[analysis.id] = analysis;

      return Promise.all(loads).then(() => analysis.element);
    });
  }

  importResources(imports, resourceManifestUrl){
    var i, ii, current, annotation, existing,
        lookup = {},
        finalModules = [],
        importIds = [], analysis, type;

    var container = this.container;

    for(i = 0, ii = imports.length; i < ii; ++i){
      current = imports[i];
      annotation = Origin.get(current);

      if(!annotation){
        analysis = analyzeModule({'default':current});
        analysis.analyze(container);
        type = (analysis.element || analysis.resources[0]).type;

        if(resourceManifestUrl){
          annotation = new Origin(relativeToFile("./" + type.name, resourceManifestUrl));
        }else{
          annotation = new Origin(join(this.appResources.baseResourceUrl, type.name));
        }

        Origin.set(current, annotation);
      }

      existing = lookup[annotation.moduleId];

      if(!existing){
        existing = {};
        importIds.push(annotation.moduleId);
        finalModules.push(existing);
        lookup[annotation.moduleId] = existing;
      }

      existing[nextId()] = current;
    }

    return this.importResourcesFromModules(finalModules, importIds);
  }

  importResourcesFromModuleIds(importIds){
    return this.loader.loadAllModules(importIds).then(imports => {
      return this.importResourcesFromModules(imports, importIds);
    });
  }

  importResourcesFromModules(imports, importIds){
    var loads = [], i, ii, analysis, type, key, annotation,
                j, jj, resources, current,
                existing = this.importedModules,
                container = this.container,
                allAnalysis = new Array(imports.length);

    if(!importIds){
      importIds = new Array(imports.length);

      for(i = 0, ii = imports.length; i < ii; ++i){
        current = imports[i];

        for(key in current){
          type = current[key];
          annotation = Origin.get(type);
          if(annotation){
            importIds[i] = annotation.moduleId;
            break;
          }
        }
      }
    }

    for(i = 0, ii = imports.length; i < ii; ++i){
      analysis = existing[importIds[i]];

      if(analysis){
        allAnalysis[i] = analysis;
        continue;
      }

      analysis = analyzeModule(imports[i]);
      analysis.analyze(container);
      existing[analysis.id] = analysis;
      allAnalysis[i] = analysis;
      resources = analysis.resources;

      for(j = 0, jj = resources.length; j < jj; ++j){
        current = resources[j];
        type = current.type;

        if(!type.isLoaded){
          type.isLoaded = true;
          loads.push(type.load(container, current.value));
        }
      }

      if(analysis.element){
        type = analysis.element.type;

        if(!type.isLoaded){
          type.isLoaded = true;
          loads.push(type.load(container, analysis.element.value));
        }
      }
    }

    return Promise.all(loads).then(() => allAnalysis);
  }
}

class ResourceModule {
  constructor(source, element, resources){
    var i, ii, org;

    this.source = source;
    this.element = element;
    this.resources = resources;

    if(element){
      org = Origin.get(element.value);
    }else if(resources.length){
      org = Origin.get(resources[0].value);
    }else{
      org = Origin.get(source);
    }

    if(org){
      this.id = org.moduleId;
    }
  }

  analyze(container){
    var current = this.element,
        resources = this.resources,
        i, ii;

    if(current){
      if(!current.type.isAnalyzed){
        current.type.isAnalyzed = true;
        current.type.analyze(container, current.value);
      }
    }

    for(i = 0, ii = resources.length; i < ii; ++i){
      current = resources[i];

      if('analyze' in current.type && !current.type.isAnalyzed){
        current.type.isAnalyzed = true;
        current.type.analyze(container, current.value);
      }
    }
  }

  register(registry, name){
    var i, ii, resources = this.resources;

    if(this.element){
      this.element.type.register(registry, name);
      name = null;
    }

    for(i = 0, ii = resources.length; i < ii; ++i){
      resources[i].type.register(registry, name);
      name = null;
    }
  }
}

function analyzeModule(moduleInstance, viewModelMember){
  var viewModelType, fallback, annotation, key, meta,
      exportedValue, resources = [], name, conventional;

  if(typeof moduleInstance === 'function'){
    moduleInstance = {'default': moduleInstance};
  }

  if(viewModelMember){
    viewModelType = moduleInstance[viewModelMember];
  }

  for(key in moduleInstance){
    exportedValue = moduleInstance[key];

    if(key === viewModelMember || typeof exportedValue !== 'function'){
      continue;
    }

    meta = Metadata.on(exportedValue);
    annotation = meta.first(ResourceType);

    if(annotation){
      if(!viewModelType && annotation instanceof CustomElement){
        viewModelType = exportedValue;
      }else{
        resources.push({type:annotation,value:exportedValue});
      }
    } else {
      name = exportedValue.name;

      if(conventional = CustomElement.convention(name)){
        if(!viewModelType){
          meta.add(conventional);
          viewModelType = exportedValue;
        }else{
          resources.push({type:conventional,value:exportedValue});
        }
      } else if(conventional = AttachedBehavior.convention(name)){
        resources.push({type:conventional,value:exportedValue});
      } else if(conventional = TemplateController.convention(name)){
        resources.push({type:conventional,value:exportedValue});
      } else if(conventional = ValueConverter.convention(name)) {
        resources.push({type:conventional,value:exportedValue});
      } else if(!fallback){
        fallback = exportedValue;
      }
    }
  }

  viewModelType = viewModelType || fallback;

  return new ResourceModule(
    moduleInstance,
    viewModelType ? {
        value: viewModelType,
        type: Metadata.on(viewModelType).first(CustomElement) || new CustomElement()
      } : null,
    resources
    );
}
<commit_msg>fix(resource-coordinator): do not cache dynamic view models<commit_after>import {Loader} from 'aurelia-loader';
import {relativeToFile, join} from 'aurelia-path';
import {Container} from 'aurelia-dependency-injection';
import {Metadata, ResourceType, Origin} from 'aurelia-metadata';
import {ValueConverter} from 'aurelia-binding';
import {CustomElement} from './custom-element';
import {AttachedBehavior} from './attached-behavior';
import {TemplateController} from './template-controller';
import {ViewEngine} from './view-engine';
import {ResourceRegistry} from './resource-registry';

var id = 0;

function nextId(){
  return ++id;
}

export class ResourceCoordinator {
  static inject(){ return [Loader, Container, ViewEngine, ResourceRegistry]; }
	constructor(loader, container, viewEngine, appResources){
		this.loader = loader;
		this.container = container;
		this.viewEngine = viewEngine;
		this.importedModules = {};
    this.appResources = appResources;
		viewEngine.resourceCoordinator = this;
	}

  getExistingModuleAnalysis(id){
    return this.importedModules[id];
  }

  loadViewModelInfo(moduleImport, moduleMember){
    return this._loadAndAnalyzeModuleForElement(moduleImport, moduleMember);
  }

  loadElement(moduleImport, moduleMember, viewStategy){
    return this._loadAndAnalyzeModuleForElement(moduleImport, moduleMember, this.importedModules).then(info => {
      var type = info.type;

      if(type.isLoaded){
        return type;
      }

      type.isLoaded = true;

      return type.load(this.container, info.value, viewStategy);
    });
  }

  _loadAndAnalyzeModuleForElement(moduleImport, moduleMember, cache){
    var existing = cache && cache[moduleImport];

    if(existing){
      return Promise.resolve(existing.element);
    }

    return this.loader.loadModule(moduleImport).then(elementModule => {
      var analysis = analyzeModule(elementModule, moduleMember),
          resources = analysis.resources,
          container = this.container,
          loads = [], type, current, i , ii;

      if(!analysis.element){
        throw new Error(`No element found in module "${moduleImport}".`);
      }

      analysis.analyze(container);

      for(i = 0, ii = resources.length; i < ii; ++i){
        current = resources[i];
        type = current.type;

        if(!type.isLoaded){
          type.isLoaded = true;
          loads.push(type.load(container, current.value));
        }
      }

      if(cache){
        cache[analysis.id] = analysis;
      }

      return Promise.all(loads).then(() => analysis.element);
    });
  }

  importResources(imports, resourceManifestUrl){
    var i, ii, current, annotation, existing,
        lookup = {},
        finalModules = [],
        importIds = [], analysis, type;

    var container = this.container;

    for(i = 0, ii = imports.length; i < ii; ++i){
      current = imports[i];
      annotation = Origin.get(current);

      if(!annotation){
        analysis = analyzeModule({'default':current});
        analysis.analyze(container);
        type = (analysis.element || analysis.resources[0]).type;

        if(resourceManifestUrl){
          annotation = new Origin(relativeToFile("./" + type.name, resourceManifestUrl));
        }else{
          annotation = new Origin(join(this.appResources.baseResourceUrl, type.name));
        }

        Origin.set(current, annotation);
      }

      existing = lookup[annotation.moduleId];

      if(!existing){
        existing = {};
        importIds.push(annotation.moduleId);
        finalModules.push(existing);
        lookup[annotation.moduleId] = existing;
      }

      existing[nextId()] = current;
    }

    return this.importResourcesFromModules(finalModules, importIds);
  }

  importResourcesFromModuleIds(importIds){
    return this.loader.loadAllModules(importIds).then(imports => {
      return this.importResourcesFromModules(imports, importIds);
    });
  }

  importResourcesFromModules(imports, importIds){
    var loads = [], i, ii, analysis, type, key, annotation,
                j, jj, resources, current,
                existing = this.importedModules,
                container = this.container,
                allAnalysis = new Array(imports.length);

    if(!importIds){
      importIds = new Array(imports.length);

      for(i = 0, ii = imports.length; i < ii; ++i){
        current = imports[i];

        for(key in current){
          type = current[key];
          annotation = Origin.get(type);
          if(annotation){
            importIds[i] = annotation.moduleId;
            break;
          }
        }
      }
    }

    for(i = 0, ii = imports.length; i < ii; ++i){
      analysis = existing[importIds[i]];

      if(analysis){
        allAnalysis[i] = analysis;
        continue;
      }

      analysis = analyzeModule(imports[i]);
      analysis.analyze(container);
      existing[analysis.id] = analysis;
      allAnalysis[i] = analysis;
      resources = analysis.resources;

      for(j = 0, jj = resources.length; j < jj; ++j){
        current = resources[j];
        type = current.type;

        if(!type.isLoaded){
          type.isLoaded = true;
          loads.push(type.load(container, current.value));
        }
      }

      if(analysis.element){
        type = analysis.element.type;

        if(!type.isLoaded){
          type.isLoaded = true;
          loads.push(type.load(container, analysis.element.value));
        }
      }
    }

    return Promise.all(loads).then(() => allAnalysis);
  }
}

class ResourceModule {
  constructor(source, element, resources){
    var i, ii, org;

    this.source = source;
    this.element = element;
    this.resources = resources;

    if(element){
      org = Origin.get(element.value);
    }else if(resources.length){
      org = Origin.get(resources[0].value);
    }else{
      org = Origin.get(source);
    }

    if(org){
      this.id = org.moduleId;
    }
  }

  analyze(container){
    var current = this.element,
        resources = this.resources,
        i, ii;

    if(current){
      if(!current.type.isAnalyzed){
        current.type.isAnalyzed = true;
        current.type.analyze(container, current.value);
      }
    }

    for(i = 0, ii = resources.length; i < ii; ++i){
      current = resources[i];

      if('analyze' in current.type && !current.type.isAnalyzed){
        current.type.isAnalyzed = true;
        current.type.analyze(container, current.value);
      }
    }
  }

  register(registry, name){
    var i, ii, resources = this.resources;

    if(this.element){
      this.element.type.register(registry, name);
      name = null;
    }

    for(i = 0, ii = resources.length; i < ii; ++i){
      resources[i].type.register(registry, name);
      name = null;
    }
  }
}

function analyzeModule(moduleInstance, viewModelMember){
  var viewModelType, fallback, annotation, key, meta,
      exportedValue, resources = [], name, conventional;

  if(typeof moduleInstance === 'function'){
    moduleInstance = {'default': moduleInstance};
  }

  if(viewModelMember){
    viewModelType = moduleInstance[viewModelMember];
  }

  for(key in moduleInstance){
    exportedValue = moduleInstance[key];

    if(key === viewModelMember || typeof exportedValue !== 'function'){
      continue;
    }

    meta = Metadata.on(exportedValue);
    annotation = meta.first(ResourceType);

    if(annotation){
      if(!viewModelType && annotation instanceof CustomElement){
        viewModelType = exportedValue;
      }else{
        resources.push({type:annotation,value:exportedValue});
      }
    } else {
      name = exportedValue.name;

      if(conventional = CustomElement.convention(name)){
        if(!viewModelType){
          meta.add(conventional);
          viewModelType = exportedValue;
        }else{
          resources.push({type:conventional,value:exportedValue});
        }
      } else if(conventional = AttachedBehavior.convention(name)){
        resources.push({type:conventional,value:exportedValue});
      } else if(conventional = TemplateController.convention(name)){
        resources.push({type:conventional,value:exportedValue});
      } else if(conventional = ValueConverter.convention(name)) {
        resources.push({type:conventional,value:exportedValue});
      } else if(!fallback){
        fallback = exportedValue;
      }
    }
  }

  viewModelType = viewModelType || fallback;

  return new ResourceModule(
    moduleInstance,
    viewModelType ? {
        value: viewModelType,
        type: Metadata.on(viewModelType).first(CustomElement) || new CustomElement()
      } : null,
    resources
    );
}
<|endoftext|>


<commit_before>const {app, BrowserWindow, ipcMain} = require('electron');

var path = require('path');

var jayson = require('jayson');
// tree-kill has better cross-platform handling of
// killing a process.  child-process.kill was unreliable
var kill = require('tree-kill');

let client = jayson.client.http('http://localhost:5279/lbryapi');

// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let win
// Also keep the daemon subprocess alive
let subpy
// set to true when the quitting sequence has started
let quitting = false;

function createWindow () {
  // Create the browser window.
  //win = new BrowserWindow({x: 0, y: 0, width: 1440, height: 414, backgroundColor: '#155b4a'})
  win = new BrowserWindow({backgroundColor: '#155b4a'})
  win.maximize()

  //win.webContents.openDevTools()

  // and load the index.html of the app.
  win.loadURL(`file://${__dirname}/dist/index.html`)

  // Emitted when the window is closed.
  win.on('closed', () => {
    // Dereference the window object, usually you would store windows
    // in an array if your app supports multi windows, this is the time
    // when you should delete the corresponding element.
    win = null
  })
};

function lauchDaemon() {
  if (subpy) {
    return;
  }
  console.log(`${__dirname}`);
  executable = path.join(__dirname, 'dist', 'lbrynet-daemon');
  subpy = require('child_process').spawn(executable)
  // Need to handle the data event instead of attaching to
  // process.stdout because the latter doesn't work. I believe on
  // windows it buffers stdout and we don't get any meaningful output
  subpy.stdout.on('data', (buf) => {console.log(String(buf).trim());});
  subpy.stderr.on('data', (buf) => {console.log(String(buf).trim());});
  subpy.on('exit', () => {
    console.log('The daemon has exited. Quitting the app');
    subpy = null;
    if (quitting) {
      app.quit();
    } else {
      // TODO: maybe it would be better to restart the daemon?
      win.loadURL(`file://${__dirname}/dist/warning.html`);
      setTimeout(app.quit, 5000)
    }
  });
  console.log('lbrynet daemon has launched')
}

// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', function(){
    // Check if the daemon is already running. If we get
    // an error its because its not running
    console.log('Checking for lbrynet daemon')
    client.request(
        'status', [],
        function (err, res) {
	    if (err) {
		console.log('lbrynet daemon needs to be launched')
		lauchDaemon();
	    } else {
		console.log('lbrynet daemon is already running')
	    }
	}
    );
    createWindow();
});

// Quit when all windows are closed.
app.on('window-all-closed', () => {
  // On macOS it is common for applications and their menu bar
  // to stay active until the user quits explicitly with Cmd + Q
  if (process.platform !== 'darwin') {
    app.quit()
  }
})

app.on('before-quit', (event) => {
  if (subpy == null) {
    return
  }
  event.preventDefault();
  shutdownDaemon();
})

app.on('activate', () => {
  // On macOS it's common to re-create a window in the app when the
  // dock icon is clicked and there are no other windows open.
  if (win === null) {
    createWindow()
  }
})

function shutdownDaemon() {
  console.log('Shutdown triggered');
  if (subpy == null) {
    // TODO: In this case, we didn't start the process so I'm hesitant
    //       to shut it down. We might want to send a stop command
    //       though instead of just letting it run.
    console.log('Not killing lbrynet because we did not start it')
    return
  }
  if (win) {
    win.loadURL(`file://${__dirname}/dist/quit.html`);
  }
  quitting = true;
  console.log('Killing lbrynet-daemon process');
  kill(subpy.pid, undefined, (err) => {
    console.log('Killed lbrynet-daemon process');
  });
}

ipcMain.on('shutdown', shutdownDaemon);
<commit_msg>cleanup app comments<commit_after>const {app, BrowserWindow, ipcMain} = require('electron');
const path = require('path');
const jayson = require('jayson');
// tree-kill has better cross-platform handling of
// killing a process.  child-process.kill was unreliable
const kill = require('tree-kill');

let client = jayson.client.http('http://localhost:5279/lbryapi');
// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let win
// Also keep the daemon subprocess alive
let subpy
// set to true when the quitting sequence has started
let quitting = false;

function createWindow () {
  win = new BrowserWindow({backgroundColor: '#155b4a'})
  win.maximize()

  //win.webContents.openDevTools()

  win.loadURL(`file://${__dirname}/dist/index.html`)

  win.on('closed', () => {
    win = null
  })
};

function lauchDaemon() {
  if (subpy) {
    return;
  }
  console.log(`${__dirname}`);
  executable = path.join(__dirname, 'dist', 'lbrynet-daemon');
  subpy = require('child_process').spawn(executable)
  // Need to handle the data event instead of attaching to
  // process.stdout because the latter doesn't work. I believe on
  // windows it buffers stdout and we don't get any meaningful output
  subpy.stdout.on('data', (buf) => {console.log(String(buf).trim());});
  subpy.stderr.on('data', (buf) => {console.log(String(buf).trim());});
  subpy.on('exit', () => {
    console.log('The daemon has exited. Quitting the app');
    subpy = null;
    if (quitting) {
      // If quitting is True it means that we were expecting the daemon
      // to be shutdown so we can quit right away
      app.quit();
    } else {
      // Otherwise, this shutdown was a surprise so display a warning
      // and schedule a quit
      //
      // TODO: maybe it would be better to restart the daemon?
      win.loadURL(`file://${__dirname}/dist/warning.html`);
      setTimeout(app.quit, 5000)
    }
  });
  console.log('lbrynet daemon has launched')
}

app.on('ready', function(){
    launchDaemonIfNotRunning();
    createWindow();
});

function launchDaemonIfNotRunning() {
    // Check if the daemon is already running. If we get
    // an error its because its not running
    console.log('Checking for lbrynet daemon');
    client.request(
        'status', [],
        function (err, res) {
	    if (err) {
		console.log('lbrynet daemon needs to be launched')
		lauchDaemon();
	    } else {
		console.log('lbrynet daemon is already running')
	    }
	}
    );
}

// Quit when all windows are closed.
app.on('window-all-closed', () => {
  // On macOS it is common for applications and their menu bar
  // to stay active until the user quits explicitly with Cmd + Q
  if (process.platform !== 'darwin') {
    app.quit()
  }
})

app.on('before-quit', (event) => {
  if (subpy == null) {
    return
  }
  event.preventDefault();
  shutdownDaemon();
})

app.on('activate', () => {
  // On macOS it's common to re-create a window in the app when the
  // dock icon is clicked and there are no other windows open.
  if (win === null) {
    createWindow()
  }
})

function shutdownDaemon() {
  console.log('Shutdown triggered');
  if (subpy == null) {
    // TODO: In this case, we didn't start the process so I'm hesitant
    //       to shut it down. We might want to send a stop command
    //       though instead of just letting it run.
    console.log('Not killing lbrynet because we did not start it')
    return
  }
  if (win) {
    win.loadURL(`file://${__dirname}/dist/quit.html`);
  }
  quitting = true;
  console.log('Killing lbrynet-daemon process');
  kill(subpy.pid, undefined, (err) => {
    console.log('Killed lbrynet-daemon process');
  });
}

ipcMain.on('shutdown', shutdownDaemon);
<|endoftext|>


<commit_before>/*==================================================
 Copyright 2014 Jade Gu
 http://weibo.com/islucifier
 Released under the MIT license
 refresh.js 2014.7.17
 ==================================================*/
;(function(window, document, undefined) {
	//严格模式
	'use strict';
	var head = document.getElementsByTagName('head')[0],
		observe,
		method = {
			//初始化，判断是否为现代浏览器
			//将对象的属性拷贝到新对象上
			//IE低版本浏览器，新对象为DOM对象
			init: (function() {
				var init;
				if ('create' in Object) {
					observe = addSetter;
					addEvent = null;
					init = function(obj) {
						var _obj,
							_old,
							key;
						//对象的__hasSetter属性拥有被侦听属性的同名属性
						//该同名属性将保存该属性变化时的callback值
						if (!('__hasSetter' in obj)) {
							_old = {};
							_obj = obj;
							obj = Object.create(proto);
							for (key in _obj) {
								_obj.hasOwnProperty(key) && (obj[key] = _old[key] = _obj[key]);
							}
							obj._old = _old;
							obj.__hasSetter = {};
						}
						return obj;
					};
				} else if ('onpropertychange' in head) {
					observe = addEvent;
					addSetter = null;
					var isIE6 = navigator.userAgent.indexOf("MSIE 6.0") > 0;
					init = function(obj) {
						var _obj,
							_old,
							key;
						if (!('__hasSetter' in obj)) {
							_old = {};
							_obj = obj;
							obj = document.createElement('obj');
							head.appendChild(obj).parentNode.removeChild(obj);
							//IE6删除DOM元素后，事件无法触发
							isIE6 && head.appendChild(obj);
							//拷贝参数对象中的属性
							for (key in _obj) {
								if (_obj.hasOwnProperty(key)) {
									obj[key] = _old[key] = _obj[key];
								}
							}
							//拷贝「原型」中的方法
							for (key in proto) {
								if (proto.hasOwnProperty(key)) {
									obj[key] = proto[key];
								}
							}
							obj._old = _old;
							obj.__hasSetter = {};
						}
						return obj;
					};
				}
				return init;
			})(),
			//检查参数类型并返回类型值
			type: function(obj) {
				if (obj == null) {
					return String(obj);
				}
				return typeof obj === "object" || typeof obj === "function" ?
					class2type[core_toString.call(obj)] || "object" :
					typeof obj;
			},
			getRandomStr: function() {
				return Math.random().toString(36).substr(2);
			}
		},
		//被观察对象的原型
		proto = {
			//添加属性，_old属性与其同步更新
			add: function(key, value) {
				this[key] = this._old[key] = value || 'default value';
				return this;
			},
			//删除属性，处理了兼容性
			remove: (function() {
				return observe === addSetter ? function(key) {
					delete this[key];
					delete this._old[key];
					delete this.__hasSetter[key];
					return this;
				} : function(key) {
					this.removeAttribute(key);
					delete this._old[key];
					delete this.__hasSetter[key];
					return this;
				};
			})(),
			//遍历，简单循环
			each: function(callback) {
				for (var key in this._old) {
					callback.call(this, key, this[key]);
				}
				return this;
			},
			//合并，循环调用add方法
			extend: function(src) {
				for (var key in src) {
					this.add(key, src[key]);
				}
				return this;
			},
			//添加属性侦听器，属性不存在则add一个
			on: function() {
				var args = arguments,
					type = method.type(args[0]),
					callback,
					key;
				if (args.length === 1) {
					if (type === 'object') {
						args = args[0];
						for (key in args) {
							callback = args[key];
							if (typeof callback === 'function') {
								if (!(key in this) && !(key.split('.')[0] in this)) {
									this.add(key);
								}
								observe(this, key, callback);
							}
						}
					} else if (type === 'function') {
						callback = args[0];
						for (key in this._old) {
							observe(this, key, callback);
						}
					}
				} else if (args.length === 2) {
					key = args[0];
					callback = args[1];
					if (type === 'string' && typeof callback === 'function') {
						if (!(key in this) && !(key.split('.')[0] in this)) {
							this.add(key);
						}
						observe(this, key, callback);
					}
				}
				return this;
			},
			//解除属性侦听器，如果不传参数，则解除所有
			off: function() {
				var args = arguments,
					key,
					len,
					i;
				if (!args.length) {
					this.__hasSetter = {};
				} else {
					for (i = 0, len = args.length; i < len; i += 1) {
						key = args[i];
						if (typeof key === 'string') {
							if (key.indexOf('.') !== -1) {
								if (key.indexOf('.') === 0) {
									key = key.replace(/\./g, '');
									for (var prop in this.__hasSetter) {
										key in this.__hasSetter[prop] && delete this.__hasSetter[prop][key];
									}
								} else {
									key = key.split('.');
									key[1] in this.__hasSetter[key[0]] && delete this.__hasSetter[key[0]][key[1]];
								}
							} else if (key in this.__hasSetter) {
								delete this.__hasSetter[key];
							}
						}
					}
				}
				return this;
			}
		},
		class2type = {},
		core_toString = class2type.toString;
	"Boolean Number String Function Array Date RegExp Object Error".replace(/[^ ]+/g, function(name) {
		class2type["[object " + name + "]"] = name.toLowerCase();
	});

	//在现代浏览器中，用Object.defineProperty实现属性侦听
	function addSetter(obj, prop, callback) {
		var name = prop.split('.'),
			value;
		prop = name[0];
		name = name[1];
		if (!(prop in obj.__hasSetter)) {
			value = obj[prop];
			obj.__hasSetter[prop] = {};
			Object.defineProperty(obj, prop, {
				set: function(v) {
					value = v;
					if (prop in this.__hasSetter) {
						for (var key in this.__hasSetter[prop]) {
							this.__hasSetter[prop][key].call(this, prop, v);
						}
					}
				},
				get: function() {
					return value;
				}
			});
		}
		obj.__hasSetter[prop][name || 'observe-' + method.getRandomStr()] = callback;
		return obj;

	}
	//在IE系列的落后浏览器中，用dom元素的onpropertychange事件来实现
	function addEvent(obj, prop, callback) {
		var name = prop.split('.');
		prop = name[0];
		name = name[1];
		prop in obj.__hasSetter || (obj.__hasSetter[prop] = {});
		obj.__hasSetter[prop][name || 'observe-' + method.getRandomStr()] = callback;
		if (!obj.onpropertychange) {
			obj.onpropertychange = function(e) {
				var prop = (e || window.event).propertyName;
				if (prop in this.__hasSetter) {
					for (var key in this.__hasSetter[prop]) {
						this.__hasSetter[prop][key].call(this, prop, this[prop]);
					}
				}
			};
		}
		//返回的新对象是一个DOM对象
		return obj;
	}
	window.observe = method.init;
}(window, document));
<commit_msg>Revert "增加事件命名空间，与jQuery的事件命名空间工作方式保持一致"<commit_after>/*==================================================
 Copyright 2014 Jade Gu
 http://weibo.com/islucifier
 Released under the MIT license
 refresh.js 2014.7.16
 ==================================================*/
;(function(window, document, undefined) {
	//严格模式
	'use strict';
	var head = document.getElementsByTagName('head')[0],
		observe,
		method = {
			//初始化，判断是否为现代浏览器
			//将对象的属性拷贝到新对象上
			//IE低版本浏览器，新对象为DOM对象
			init: (function() {
				var init;
				if ('create' in Object) {
					observe = addSetter;
					addEvent = null;
					init = function(obj) {
						var _obj,
							_old,
							key;
						//对象的__hasSetter属性拥有被侦听属性的同名属性
						//该同名属性将保存该属性变化时的callback值
						if (!('__hasSetter' in obj)) {
							_old = {};
							_obj = obj;
							obj = Object.create(proto);
							for (key in _obj) {
								_obj.hasOwnProperty(key) && (obj[key] = _old[key] = _obj[key]);
							}
							obj._old = _old;
							obj.__hasSetter = {};
						}
						return obj;
					};
				} else if ('onpropertychange' in head) {
					observe = addEvent;
					addSetter = null;
					var isIE6 = navigator.userAgent.indexOf("MSIE 6.0") > 0;
					init = function(obj) {
						var _obj,
							_old,
							key;
						if (!('__hasSetter' in obj)) {
							_old = {};
							_obj = obj;
							obj = document.createElement('obj');
							head.appendChild(obj).parentNode.removeChild(obj);
							//IE6删除DOM元素后，事件无法触发
							isIE6 && head.appendChild(obj);
							//拷贝参数对象中的属性
							for (key in _obj) {
								if (_obj.hasOwnProperty(key)) {
									obj[key] = _old[key] = _obj[key];
								}
							}
							//拷贝「原型」中的方法
							for (key in proto) {
								if (proto.hasOwnProperty(key)) {
									obj[key] = proto[key];
								}
							}
							obj._old = _old;
							obj.__hasSetter = {};
						}
						return obj;
					};
				}
				return init;
			})(),
			//检查参数类型并返回类型值
			type: function(obj) {
				if (obj == null) {
					return String(obj);
				}
				return typeof obj === "object" || typeof obj === "function" ?
					class2type[core_toString.call(obj)] || "object" :
					typeof obj;
			}
		},
		//被观察对象的原型
		proto = {
			//添加属性，_old属性与其同步更新
			add: function(key, value) {
				this[key] = this._old[key] = value || 'default value';
			},
			//删除属性，处理了兼容性
			remove: (function() {
				return observe === addSetter ? function(key) {
					delete this[key];
					delete this._old[key];
					delete this.__hasSetter[key];
					return this;
				} : function(key) {
					this.removeAttribute(key);
					delete this._old[key];
					delete this.__hasSetter[key];
					return this;
				};
			})(),
			//遍历，简单循环
			each: function(callback) {
				for (var key in this._old) {
					callback.call(this, key, this[key]);
				}
				return this;
			},
			//合并，循环调用add方法
			extend: function(src) {
				for (var key in src) {
					this.add(key, src[key]);
				}
				return this;
			},
			//添加属性侦听器，属性不存在则add一个
			on: function() {
				var args = arguments,
					type = method.type(args[0]),
					callback,
					key;
				if (args.length === 1) {
					if (type === 'object') {
						args = args[0];
						for (key in args) {
							callback = args[key];
							if (typeof callback === 'function') {
								if (!(key in this)) {
									this.add(key);
								}
								observe(this, key, callback);
							}
						}
					} else if (type === 'function') {
						callback = args[0];
						for (key in this._old) {
							observe(this, key, callback);
						}
					}
				} else if (args.length === 2) {
					key = args[0];
					callback = args[1];
					if (type === 'string' && typeof callback === 'function') {
						if (!(key in this)) {
							this.add(key);
						}
						observe(this, key, callback);
					}
				}
				return this;
			},
			//解除属性侦听器，如果不传参数，则解除所有
			off: function() {
				var args = arguments,
					key,
					len,
					i;
				if (!args.length) {
					this.__hasSetter = {};
				} else {
					args = Array.prototype.slice.call(args);
					for (i = 0, len = args.length; i < len; i += 1) {
						key = args[i];
						typeof key === 'string' && key in this.__hasSetter && delete this.__hasSetter[key];
					}
				}
				return this;
			}
		},
		class2type = {},
		core_toString = class2type.toString;
	"Boolean Number String Function Array Date RegExp Object Error".replace(/[^ ]+/g, function(name) {
		class2type["[object " + name + "]"] = name.toLowerCase();
	});

	//在现代浏览器中，用Object.defineProperty实现属性侦听
	function addSetter(obj, prop, callback) {
		if (prop in obj.__hasSetter) {
			obj.__hasSetter[prop] = callback;
			return obj;
		}
		var value = obj[prop];
		obj.__hasSetter[prop] = callback;
		return Object.defineProperty(obj, prop, {
			set: function(v) {
				value = v;
				prop in obj.__hasSetter && obj.__hasSetter[prop].call(obj, prop, v);
			},
			get: function() {
				return value;
			}
		});
	}
	//在IE系列的落后浏览器中，用dom元素的onpropertychange事件来实现
	function addEvent(obj, prop, callback) {
		obj.__hasSetter[prop] = callback;
		if (!obj.onpropertychange) {
			var e = window.event;
			obj.onpropertychange = function() {
				var key = e.propertyName;
				key in obj.__hasSetter && obj.__hasSetter[key].call(obj, key, obj[key]);
			};
		}
		//返回的新对象是一个DOM对象
		return obj;
	}
	window.observe = method.init;
})(window, document);
<|endoftext|>


<commit_before>require.config({
  baseDir: 'app',
  shim : {
    bootstrap : {
      deps : ['jquery']
    }
  },
  paths: {
    jquery: 'vendor/jquery/dist/jquery',
    bootstrap: 'vendor/bootstrap/dist/js/bootstrap',
    handlebars: 'vendor/handlebars/handlebars',
    d3: 'vendor/d3/d3'
  }
});

require(['jquery', 'bootstrap', 'handlebars', 'd3'], function($, bootstrap, Handlebars, d3) {
  d3.csv('app/csv/fwk-infographic.csv')
    .get(function(err, rows) {
      if ( err ) throw err;
      var m = {};
      // get unique elements in order
      var order = rows.map(function(r) {
        return r.bucket;
      }).filter(function(a,i,all) {
        return i == 0 ? true : all[i-1] !== a;
      });
      var pps = 0, ppn = 0;
      rows.forEach(function(row) {
        var a = m[row.bucket] || [];
        // my formula (stars/2) + forks + (commits / releases) + (contributors * 10) - openIssues + (tag-toal * 0.8)
        if ( row.releases != '' ) {
          row.githubScore = Math.ceil(
            (parseInt(row.stars)/2)
              + parseInt(row.forks)
              + (parseInt(row.releases) == 0 ? parseInt(row.commits) : parseInt(row.commits) / parseInt(row.releases) )
              + (parseInt(row.contributors == '' ? '0': row.contributors) * 10)
              - parseInt(row.openIssues));
          if ( row.bucket == 'productivity-plateau' ) {
            pps += row.githubScore;
            ppn++;
          }
        }
        if ( row.stackoverflowQuestions != '' ) {
          row.stackoverflowScore = Math.ceil(parseInt(row.stackoverflowQuestions) * 0.8);
        } else {
          row.stackoverflowScore = 0;
        }
        a.push(row);
        m[row.bucket] = a;
      });
      var fwks = [];
      // ext-js
      order.forEach(function(grp) {
        m[grp].forEach(function(fwk) {
          if ( fwk.githubScore == undefined ) {
            fwk.githubScore = Math.ceil(pps/ppn);
          }
          fwk.score = fwk.githubScore + fwk.stackoverflowScore;
        });
        fwks = fwks.concat(m[grp]);
      })
      var arr = [];
      var render = function() {
        var page = $('#page');
        page.empty();
        var template = Handlebars.compile($('#grp-tmpl').html());
        order.forEach(function(grp) {
          arr = arr.concat(m[grp]);
          page.append(template({ groupName: grp.replace(/-/g, ' '), frameworks: m[grp] }));
        });
      };
      render();

      // http://bl.ocks.org/mbostock/1705868
      var width = 1000, height = 600;
      var color = d3.scale.category20();
      var o = d3.scale.ordinal()
          .domain(arr.map(function(fwk) { return fwk.score; }))
          .rangeRoundPoints([0, 50]).range();

      var svg = d3.select('#infographic').append('svg')
          .attr('width', width)
          .attr('height', height);

      var path = svg.append("path")
          .attr('d','m 7.177613,550.54409 c 27.69768,-149.13861 47.544599,-307.80481 99.397767,-452.1335 57.14814,-159.06944 99.25843,30.22313 119.3492,92.155891 20.81677,64.174559 42.49347,128.109889 62.90327,192.397229 48.6483,153.23261 145.3455,-43.20565 169.0116,-77.8645 21.541,-31.54563 38.3884,-74.66755 127.97,-82.51039 128.88667,-11.28411 324.61649,-8.28367 412.26209,-9.86591')
          .style('fill', 'none')
          .attr('stroke', '#000')
          .attr('stroke-width', '3px');

      var colors = ['#00ced1', '#ee82ee', '#00ff7f', '#ffa07a', '#ffd700'];
      colors = [ '#386cb0', '#ffff99', '#fdc086', '#beaed4', '#7fc97f' ];
      colors = [ '#d7191c', '#fdae61', '#ffffbf', '#a6d96a', '#1a9641' ]
      var splits = [.1, .2, .1, .2, .4];
      function cumulative(i) {
        return splits.slice(0, i).reduce(function(a,b) { return a+b }, 0);
      }
      var rect = svg.append('g')
          .attr('id', 'backgrounds')
          .selectAll('rect')
          .data(splits)
          .enter()
          .append('rect')
          .attr('x', function(d, i) { return cumulative(i) * width })
          .attr('y', 0)
          .attr('width', function(d, i) { return d * width })
          .attr('height', height)
          .style('fill', function(d, i) { return colors[i]; })
          .style('fill-opacity', '.2');

      var l = path.node().getTotalLength() / fwks.length;
      var circle = svg.append('g')
          .attr('id', 'circles')
          .selectAll('circle')
          .data(fwks)
          .enter()
          .append("circle")
          .attr("r", '5px')//function(d,i) { return o[i] })
          .style('fill', function(d,i) { return color(i); })
          .attr('stroke', '#fff')
          .attr('transform', function(d, i) { var p = path.node().getPointAtLength(i * l); console.log(p.x, p.y); return 'translate(' + p.x + ',' + p.y + ')' ;})
          .attr('stroke-width', '0px');

      var text = svg.append('g')
          .attr('id', 'labels')
          .selectAll("text")
          .data(fwks)
          .enter().append("text")
          .attr("class", "label")
          .attr('transform', function(d, i) {
            var p = path.node().getPointAtLength(i * l);
            console.log(p.x, p.y);
            return 'translate(' + Math.ceil(p.x + 5) + ',' + (p.y + (i%2 == 0 ? -5 : 12)) + ')';
          })
          .text(function(d) { return d.name ; });

    });

/*
  transition();

  function transition() {
    circle.transition()
      .duration(10000)
      .attrTween("transform", translateAlong(path.node()))
      .each("end", transition);
  }
*/
  // Returns an attrTween for translating along the specified path element.
  function translateAlong(path) {
    var l = path.getTotalLength();
    return function(d, i, a) {
      return function(t) {
        var p = path.getPointAtLength(t * l);
        return "translate(" + p.x + "," + p.y + ")";
      };
    };
  }

})
<commit_msg>grouping circles and labels<commit_after>require.config({
  baseDir: 'app',
  shim : {
    bootstrap : {
      deps : ['jquery']
    }
  },
  paths: {
    jquery: 'vendor/jquery/dist/jquery',
    bootstrap: 'vendor/bootstrap/dist/js/bootstrap',
    handlebars: 'vendor/handlebars/handlebars',
    d3: 'vendor/d3/d3'
  }
});

require(['jquery', 'bootstrap', 'handlebars', 'd3'], function($, bootstrap, Handlebars, d3) {
  d3.csv('app/csv/fwk-infographic.csv')
    .get(function(err, rows) {
      if ( err ) throw err;
      var m = {};
      // get unique elements in order
      var order = rows.map(function(r) {
        return r.bucket;
      }).filter(function(a,i,all) {
        return i == 0 ? true : all[i-1] !== a;
      });
      var pps = 0, ppn = 0;
      rows.forEach(function(row) {
        var a = m[row.bucket] || [];
        // my formula (stars/2) + forks + (commits / releases) + (contributors * 10) - openIssues + (tag-toal * 0.8)
        if ( row.releases != '' ) {
          row.githubScore = Math.ceil(
            (parseInt(row.stars)/2)
              + parseInt(row.forks)
              + (parseInt(row.releases) == 0 ? parseInt(row.commits) : parseInt(row.commits) / parseInt(row.releases) )
              + (parseInt(row.contributors == '' ? '0': row.contributors) * 10)
              - parseInt(row.openIssues));
          if ( row.bucket == 'productivity-plateau' ) {
            pps += row.githubScore;
            ppn++;
          }
        }
        if ( row.stackoverflowQuestions != '' ) {
          row.stackoverflowScore = Math.ceil(parseInt(row.stackoverflowQuestions) * 0.8);
        } else {
          row.stackoverflowScore = 0;
        }
        a.push(row);
        m[row.bucket] = a;
      });
      var fwks = [];
      // ext-js
      order.forEach(function(grp) {
        m[grp].forEach(function(fwk) {
          if ( fwk.githubScore == undefined ) {
            fwk.githubScore = Math.ceil(pps/ppn);
          }
          fwk.score = fwk.githubScore + fwk.stackoverflowScore;
        });
        fwks = fwks.concat(m[grp]);
      })
      var arr = [];
      var render = function() {
        var page = $('#page');
        page.empty();
        var template = Handlebars.compile($('#grp-tmpl').html());
        order.forEach(function(grp) {
          arr = arr.concat(m[grp]);
          page.append(template({ groupName: grp.replace(/-/g, ' '), frameworks: m[grp] }));
        });
      };
      render();

      // http://bl.ocks.org/mbostock/1705868
      var width = 1000, height = 600;
      var color = d3.scale.category20();
      var o = d3.scale.ordinal()
          .domain(arr.map(function(fwk) { return fwk.score; }))
          .rangeRoundPoints([0, 50]).range();

      var svg = d3.select('#infographic').append('svg')
          .attr('width', width)
          .attr('height', height);

      var path = svg.append("path")
          .attr('d','m 7.177613,550.54409 c 27.69768,-149.13861 47.544599,-307.80481 99.397767,-452.1335 57.14814,-159.06944 99.25843,30.22313 119.3492,92.155891 20.81677,64.174559 42.49347,128.109889 62.90327,192.397229 48.6483,153.23261 145.3455,-43.20565 169.0116,-77.8645 21.541,-31.54563 38.3884,-74.66755 127.97,-82.51039 128.88667,-11.28411 324.61649,-8.28367 412.26209,-9.86591')
          .style('fill', 'none')
          .attr('stroke', '#000')
          .attr('stroke-width', '3px');

      var colors = ['#00ced1', '#ee82ee', '#00ff7f', '#ffa07a', '#ffd700'];
      colors = [ '#386cb0', '#ffff99', '#fdc086', '#beaed4', '#7fc97f' ];
      colors = [ '#d7191c', '#fdae61', '#ffffbf', '#a6d96a', '#1a9641' ]
      var splits = [.1, .2, .1, .2, .4];
      function cumulative(i) {
        return splits.slice(0, i).reduce(function(a,b) { return a+b }, 0);
      }
      var rect = svg.append('g')
          .attr('id', 'backgrounds')
          .selectAll('rect')
          .data(splits)
          .enter()
          .append('rect')
          .attr('x', function(d, i) { return cumulative(i) * width })
          .attr('y', 0)
          .attr('width', function(d, i) { return d * width })
          .attr('height', height)
          .style('fill', function(d, i) { return colors[i]; })
          .style('fill-opacity', '.2');

      var l = path.node().getTotalLength() / fwks.length;
      var circle = svg.append('g')
          .attr('id', 'circles');
          .selectAll('circle')
          .data(fwks)
          .enter()
          .append("circle")
          .attr("r", '5px')//function(d,i) { return o[i] })
          .style('fill', function(d,i) { return color(i); })
          .attr('stroke', '#fff')
          .attr('transform', function(d, i) {
            var p = path.node().getPointAtLength(i * l);
            return 'translate(' + p.x + ',' + p.y + ')';
          })
          .attr('stroke-width', '0px');

      var text = svg.append('g')
          .attr('id', 'labels')
          .selectAll("text")
          .data(fwks)
          .enter().append("text")
          .attr("class", "label")
          .attr('transform', function(d, i) {
            var p = path.node().getPointAtLength(i * l);
            return 'translate(' + Math.ceil(p.x + 5) + ',' + (p.y + (i%2 == 0 ? -5 : 12)) + ')';
          })
          .text(function(d) { return d.name ; });

    });

/*
  transition();

  function transition() {
    circle.transition()
      .duration(10000)
      .attrTween("transform", translateAlong(path.node()))
      .each("end", transition);
  }
*/
  // Returns an attrTween for translating along the specified path element.
  function translateAlong(path) {
    var l = path.getTotalLength();
    return function(d, i, a) {
      return function(t) {
        var p = path.getPointAtLength(t * l);
        return "translate(" + p.x + "," + p.y + ")";
      };
    };
  }

})
<|endoftext|>


<commit_before>/* jshint node:true */
/**
 * PUSH Notification server
 * (c) Telefonica Digital, 2012 - All rights reserved
 * License: GNU Affero V3 (see LICENSE file)
 * Fernando Rodríguez Sela <frsela@tid.es>
 * Guillermo Lopez Leal <gll@tid.es>
 */

'use strict';

var Log = require('../common/Logger'),
    urlparser = require('url'),
    config = require('../config.js'),
    consts = config.consts,
    fs = require('fs'),
    cluster = require('cluster'),
    MsgBroker = require('../common/MsgBroker'),
    DataStore = require('../common/DataStore'),
    errorcodes = require('../common/constants').errorcodes.GENERAL,
    errorcodesAS = require('../common/constants').errorcodes.AS,
    Pages = require('../common/Pages.js'),
    Maintenance = require('../common/Maintenance.js'),
    Helpers = require('../common/Helpers.js');

function NS_AS() {
    this.ip = '';
    this.port = 0;
    this.ssl = false;
    this.kSimplePushASFrontendVersionv1 = 'v1';
    this.server = null;
    this.closing = false;
    this.closingCorrectly = false;
    this.dataStoreReady = false;
    this.msgBrokerReady = false;
    this.readyTimeout = undefined;
}

NS_AS.prototype = {

    checkReady: function() {
        if (this.dataStoreReady && this.msgBrokerReady) {
            Log.debug(
                'NS_AS::checkReady --> We are ready. Clearing any readyTimeout'
            );
            clearTimeout(this.readyTimeout);
        } else {
            Log.debug('NS_AS::checkReady --> Not ready yet. dataStoreReady=' +
                this.dataStoreReady +
                ', msgBrokerReady=' + this.msgBrokerReady);
        }
        return this.dataStoreReady && this.msgBrokerReady;
    },

    start: function() {
        Log.info('NS_AS::start()');
        var conf = config.NS_AS.interface;
        this.ip = conf.ip;
        this.port = conf.port;
        this.ssl = conf.ssl;

        var closed = 0;
        var errored = false;
        var forked = 0;

        if (cluster.isMaster) {
            // Fork workers.
            for (var i = 0; i < config.NS_AS.numProcesses; i++) {
                cluster.fork();
                forked++;
            }

            cluster.on('exit', function(worker, code) {
                if (code !== 0) {
                    Log.error(Log.messages.ERROR_WORKERERROR, {
                        'id': worker.id,
                        'pid': worker.process.pid,
                        'code': code
                    });
                    if (forked > 20) {
                        Log.critical(
                            'NS_AS::start --> Please, check logs, there have been too much re-spawns'
                        );
                    } else {
                        Log.info('NS_AS::start --> Spawning a new worker…');
                        cluster.fork();
                        forked++;
                        --closed;
                        errored = true;
                    }
                } else {
                    Log.info('NS_AS::start -- wrk' + worker.id +
                        ' with PID ' + worker.process.pid +
                        ' exited correctly');
                }
                ++closed;
                if (closed === config.NS_AS.numProcesses) {
                    if (errored) {
                        Log.error(
                            'NS_AS::start() -- Closing INCORRECTLY. Check errors for a worker'
                        );
                        process.exit(1);
                    } else {
                        Log.info(
                            'NS_AS::start() -- Closing. That\'s all folks!'
                        );
                        process.exit(0);
                    }
                }
            });

        } else {
            var self = this;
            // Create a new HTTP(S) Server
            if (this.ssl) {
                var options = {
                    ca: Helpers.getCaChannel(),
                    key: fs.readFileSync(consts.key),
                    cert: fs.readFileSync(consts.cert),
                    requestCert: false,
                    rejectUnauthorized: false
                };
                this.server = require('https').createServer(options, this.onHTTPMessage
                    .bind(this));
            } else {
                this.server = require('http').createServer(this.onHTTPMessage.bind(
                    this));
            }
            this.server.listen(this.port, this.ip);
            Log.info('NS_AS::start --> HTTP' + (this.ssl ? 'S' : '') +
                ' push AS server starting on ' + this.ip + ':' + this.port);

            // Events from MsgBroker
            MsgBroker.once('ready', function() {
                Log.info('NS_AS::start --> MsgBroker ready and connected');
                self.msgBrokerReady = true;
                self.checkReady();
            });
            MsgBroker.on('closed', function() {
                if (self.closingCorrectly) {
                    Log.info('NS_AS::start --> Closed MsgBroker');
                    return;
                }
                Log.critical(Log.messages.CRITICAL_MBDISCONNECTED, {
                    'class': 'NS_AS',
                    'method': 'start'
                });
                self.msgBrokerReady = false;
                self.stop();
            });

            //Events from DataStore
            DataStore.once('ready', function() {
                Log.info('NS_AS::start --> DataStore ready and connected');
                self.dataStoreReady = true;
                self.checkReady();
            });
            DataStore.on('closed', function() {
                if (self.closingCorrectly) {
                    Log.info('NS_AS::start --> Closed DataStore');
                    return;
                }
                Log.critical(Log.messages.CRITICAL_DBDISCONNECTED, {
                    'class': 'NS_AS',
                    'method': 'start'
                });
                self.dataStoreReady = false;
                self.stop();
            });

            //Wait until we have setup our events listeners
            process.nextTick(function() {
                MsgBroker.start();
                DataStore.start();
            });

            //Check if we are alive
            this.readyTimeout = setTimeout(function() {
                Log.debug('NS_AS::start --> readyTimeout fired');
                if (!self.checkReady()) {
                    Log.critical(Log.messages.CRITICAL_NOTREADY);
                }
            }, 30 * 1000); //Wait 30 seconds
        }
    },

    stop: function(correctly) {
        Log.info('NS_AS::stop() called with correctly=' + correctly);
        if (cluster.isMaster) {
            var timeouts = [];
            Object.keys(cluster.workers).forEach(function(id) {
                timeouts[id] = setTimeout(function() {
                    Log.info('NS_AS::stop --> Killing worker ' + id);
                    cluster.workers[id].destroy();
                }, 4000);
                cluster.workers[id].on('disconnect', function() {
                    Log.info('NS_AS::stop --> Worker ' + id +
                        ' disconnected');
                    clearTimeout(timeouts[id]);
                });
            });
            setTimeout(function() {
                process.exit(1);
            }, 10000);

        } else {
            this.closingCorrectly = correctly;
            Log.info('NS_AS::stop --> Closing NS_AS server');
            clearTimeout(this.readyTimeout);

            //Closing connection with MsgBroker and DataStore
            DataStore.removeAllListeners();
            MsgBroker.removeAllListeners();
            MsgBroker.stop();
            DataStore.stop();

            //Closing connections from the server
            this.server.close();

            setTimeout(function() {
                Log.info('Suiciding worker with id=' + cluster.worker.id +
                    '. Bye!');
                cluster.worker.destroy();
            }, 3000);
        }
    },

    onHTTPMessage: function(request, response) {
        var self = this;

        response.res = function responseHTTP(errorCode) {
            Log.debug('NS_AS::responseHTTP: ', errorCode);
            this.statusCode = errorCode[0];
            this.setHeader('access-control-allow-origin', '*');
            if (consts.PREPRODUCTION_MODE) {
                this.setHeader('Content-Type', 'text/plain');
                if (this.statusCode === 200) {
                    this.write('{"status":"ACCEPTED"}');
                } else {
                    this.write('{"status":"ERROR", "reason":"' + errorCode[1] +
                        '"}');
                }
            }
            this.end();
        };

        if (!self.checkReady()) {
            Log.debug(
                'NS_AS::onHTTPMessage --> Message rejected, we are not ready yet'
            );
            response.res(errorcodes.NOT_READY);
            return;
        }

        Log.debug('NS_AS::onHTTPMessage --> Received request for ' + request.url);
        var url = urlparser.parse(request.url, true);
        var path = url.pathname.split('/');

        // CORS support
        if (request.method === 'OPTIONS') {
            Log.debug('NS_AS::onHTTPMessage --> Received an OPTIONS method');
            response.setHeader('Access-Control-Allow-Origin', '*');
            response.setHeader('Access-Control-Allow-Methods',
                'PUT, GET, OPTIONS');
            response.end();
            return;
        }

        // Frontend for the Mozilla SimplePush API
        if (request.method === 'PUT') {
            Log.debug('NS_AS::onHTTPMessage --> Received a PUT');
            var body = '';
            request.on('data', function(data) {
                body += data;
                // Max. payload: "version=9007199254740992" => lenght: 24
                if (body.length > 25) {
                    request.tooBig = true;
                    request.emit('end');
                    Log.debug(
                        'NS_AS::onHTTPMessage --> Message rejected, too long for this API'
                    );
                    response.res(errorcodesAS.BAD_MESSAGE_BODY_TOO_BIG);
                }
            });
            request.on('end', function() {
                if (request.tooBig) {
                    return;
                }
                self.simplepushRequest(request, body, response);
            });

        } else if (request.method === 'GET') {
            switch (path[1]) {
                case 'about':
                    if (consts.PREPRODUCTION_MODE) {
                        var text = '';
                        try {
                            var p = new Pages();
                            p.setTemplate('views/about.tmpl');
                            text = p.render(function(t) {
                                switch (t) {
                                    case '{{GIT_VERSION}}':
                                        return require('fs').readFileSync(
                                            'version.info');
                                    case '{{MODULE_NAME}}':
                                        return 'Application Server Frontend';
                                    default:
                                        return '';
                                }
                            });
                        } catch (e) {
                            text = 'No version.info file';
                        }
                        response.setHeader('Content-Type', 'text/html');
                        response.statusCode = 200;
                        response.write(text);
                        response.end();
                        return;
                    } else {
                        response.res(errorcodes.NOT_ALLOWED_ON_PRODUCTION_SYSTEM);
                        return;
                    }
                    break;

                case 'status':
                    // Return status mode to be used by load-balancers
                    response.setHeader('Content-Type', 'text/html');
                    if (Maintenance.getStatus()) {
                        response.statusCode = 503;
                        response.write('Under Maintenance');
                    } else {
                        response.statusCode = 200;
                        response.write('OK');
                    }
                    response.end();
                    break;

                default:
                    Log.debug('NS_AS::onHTTPMessage --> messageType "' + path[1] +
                        '" not recognized');
                    response.res(errorcodesAS.BAD_URL);
                    return;
            }

        } else {
            response.statusCode = 400;
            response.end();
        }
    },

    simplepushRequest: function(request, body, response) {
        /**
         * Check if we are closing. If so, send a 500 status
         * to the client. That means: "retry again later"
         */
        if (this.closing) {
            response.end(500);
            return;
        }

        /**
         * Check if the request is correct
         */
        var URI = request.url.split('/');
        var appToken = URI[3];
        var versions = String(body).split('=');
        var version = versions[1] || -1;

        if (URI.length < 3 ||
            URI[1] !== this.kSimplePushASFrontendVersionv1 ||
            URI[2] !== 'notify' || !appToken ||
            versions[0] !== 'version' || !Helpers.isVersion(version)) {
            response.statusCode = 400;
            response.end('{ reason: "Bad request"}');
            Log.info('NS_AS::simplepushRequest --> Bad request ' + body);
            return;
        }

        //Now, we are safe to start using the path and data
        Log.notify(Log.messages.NOTIFY_APPTOKEN_VERSION, {
            appToken: appToken,
            version: version,
            ip: request.connection.remoteAddress || null
        });

        // Send the OK response always, this free some server resources
        response.statusCode = 200;
        //CORS support
        response.setHeader('Access-Control-Allow-Origin', '*');
        response.end('{}');

        //And now we proccess the notification.
        DataStore.getInfoForAppToken(appToken, function(error, appInfo) {
            if (!appInfo || !appInfo.ch || !Array.isArray(appInfo.no)) {
                return;
            }
            (appInfo.no).forEach(function(nodeId) {
                var msg = DataStore.newVersion(nodeId, appToken,
                    appInfo.ch, version);
                MsgBroker.push('newMessages', msg);
            });
        });
    }
};

// Exports
exports.NS_AS = NS_AS;
<commit_msg>Remote address can be lost if connection is closed and console.log is asyn<commit_after>/* jshint node:true */
/**
 * PUSH Notification server
 * (c) Telefonica Digital, 2012 - All rights reserved
 * License: GNU Affero V3 (see LICENSE file)
 * Fernando Rodríguez Sela <frsela@tid.es>
 * Guillermo Lopez Leal <gll@tid.es>
 */

'use strict';

var Log = require('../common/Logger'),
    urlparser = require('url'),
    config = require('../config.js'),
    consts = config.consts,
    fs = require('fs'),
    cluster = require('cluster'),
    MsgBroker = require('../common/MsgBroker'),
    DataStore = require('../common/DataStore'),
    errorcodes = require('../common/constants').errorcodes.GENERAL,
    errorcodesAS = require('../common/constants').errorcodes.AS,
    Pages = require('../common/Pages.js'),
    Maintenance = require('../common/Maintenance.js'),
    Helpers = require('../common/Helpers.js');

function NS_AS() {
    this.ip = '';
    this.port = 0;
    this.ssl = false;
    this.kSimplePushASFrontendVersionv1 = 'v1';
    this.server = null;
    this.closing = false;
    this.closingCorrectly = false;
    this.dataStoreReady = false;
    this.msgBrokerReady = false;
    this.readyTimeout = undefined;
}

NS_AS.prototype = {

    checkReady: function() {
        if (this.dataStoreReady && this.msgBrokerReady) {
            Log.debug(
                'NS_AS::checkReady --> We are ready. Clearing any readyTimeout'
            );
            clearTimeout(this.readyTimeout);
        } else {
            Log.debug('NS_AS::checkReady --> Not ready yet. dataStoreReady=' +
                this.dataStoreReady +
                ', msgBrokerReady=' + this.msgBrokerReady);
        }
        return this.dataStoreReady && this.msgBrokerReady;
    },

    start: function() {
        Log.info('NS_AS::start()');
        var conf = config.NS_AS.interface;
        this.ip = conf.ip;
        this.port = conf.port;
        this.ssl = conf.ssl;

        var closed = 0;
        var errored = false;
        var forked = 0;

        if (cluster.isMaster) {
            // Fork workers.
            for (var i = 0; i < config.NS_AS.numProcesses; i++) {
                cluster.fork();
                forked++;
            }

            cluster.on('exit', function(worker, code) {
                if (code !== 0) {
                    Log.error(Log.messages.ERROR_WORKERERROR, {
                        'id': worker.id,
                        'pid': worker.process.pid,
                        'code': code
                    });
                    if (forked > 20) {
                        Log.critical(
                            'NS_AS::start --> Please, check logs, there have been too much re-spawns'
                        );
                    } else {
                        Log.info('NS_AS::start --> Spawning a new worker…');
                        cluster.fork();
                        forked++;
                        --closed;
                        errored = true;
                    }
                } else {
                    Log.info('NS_AS::start -- wrk' + worker.id +
                        ' with PID ' + worker.process.pid +
                        ' exited correctly');
                }
                ++closed;
                if (closed === config.NS_AS.numProcesses) {
                    if (errored) {
                        Log.error(
                            'NS_AS::start() -- Closing INCORRECTLY. Check errors for a worker'
                        );
                        process.exit(1);
                    } else {
                        Log.info(
                            'NS_AS::start() -- Closing. That\'s all folks!'
                        );
                        process.exit(0);
                    }
                }
            });

        } else {
            var self = this;
            // Create a new HTTP(S) Server
            if (this.ssl) {
                var options = {
                    ca: Helpers.getCaChannel(),
                    key: fs.readFileSync(consts.key),
                    cert: fs.readFileSync(consts.cert),
                    requestCert: false,
                    rejectUnauthorized: false
                };
                this.server = require('https').createServer(options, this.onHTTPMessage
                    .bind(this));
            } else {
                this.server = require('http').createServer(this.onHTTPMessage.bind(
                    this));
            }
            this.server.listen(this.port, this.ip);
            Log.info('NS_AS::start --> HTTP' + (this.ssl ? 'S' : '') +
                ' push AS server starting on ' + this.ip + ':' + this.port);

            // Events from MsgBroker
            MsgBroker.once('ready', function() {
                Log.info('NS_AS::start --> MsgBroker ready and connected');
                self.msgBrokerReady = true;
                self.checkReady();
            });
            MsgBroker.on('closed', function() {
                if (self.closingCorrectly) {
                    Log.info('NS_AS::start --> Closed MsgBroker');
                    return;
                }
                Log.critical(Log.messages.CRITICAL_MBDISCONNECTED, {
                    'class': 'NS_AS',
                    'method': 'start'
                });
                self.msgBrokerReady = false;
                self.stop();
            });

            //Events from DataStore
            DataStore.once('ready', function() {
                Log.info('NS_AS::start --> DataStore ready and connected');
                self.dataStoreReady = true;
                self.checkReady();
            });
            DataStore.on('closed', function() {
                if (self.closingCorrectly) {
                    Log.info('NS_AS::start --> Closed DataStore');
                    return;
                }
                Log.critical(Log.messages.CRITICAL_DBDISCONNECTED, {
                    'class': 'NS_AS',
                    'method': 'start'
                });
                self.dataStoreReady = false;
                self.stop();
            });

            //Wait until we have setup our events listeners
            process.nextTick(function() {
                MsgBroker.start();
                DataStore.start();
            });

            //Check if we are alive
            this.readyTimeout = setTimeout(function() {
                Log.debug('NS_AS::start --> readyTimeout fired');
                if (!self.checkReady()) {
                    Log.critical(Log.messages.CRITICAL_NOTREADY);
                }
            }, 30 * 1000); //Wait 30 seconds
        }
    },

    stop: function(correctly) {
        Log.info('NS_AS::stop() called with correctly=' + correctly);
        if (cluster.isMaster) {
            var timeouts = [];
            Object.keys(cluster.workers).forEach(function(id) {
                timeouts[id] = setTimeout(function() {
                    Log.info('NS_AS::stop --> Killing worker ' + id);
                    cluster.workers[id].destroy();
                }, 4000);
                cluster.workers[id].on('disconnect', function() {
                    Log.info('NS_AS::stop --> Worker ' + id +
                        ' disconnected');
                    clearTimeout(timeouts[id]);
                });
            });
            setTimeout(function() {
                process.exit(1);
            }, 10000);

        } else {
            this.closingCorrectly = correctly;
            Log.info('NS_AS::stop --> Closing NS_AS server');
            clearTimeout(this.readyTimeout);

            //Closing connection with MsgBroker and DataStore
            DataStore.removeAllListeners();
            MsgBroker.removeAllListeners();
            MsgBroker.stop();
            DataStore.stop();

            //Closing connections from the server
            this.server.close();

            setTimeout(function() {
                Log.info('Suiciding worker with id=' + cluster.worker.id +
                    '. Bye!');
                cluster.worker.destroy();
            }, 3000);
        }
    },

    onHTTPMessage: function(request, response) {
        var self = this;

        response.res = function responseHTTP(errorCode) {
            Log.debug('NS_AS::responseHTTP: ', errorCode);
            this.statusCode = errorCode[0];
            this.setHeader('access-control-allow-origin', '*');
            if (consts.PREPRODUCTION_MODE) {
                this.setHeader('Content-Type', 'text/plain');
                if (this.statusCode === 200) {
                    this.write('{"status":"ACCEPTED"}');
                } else {
                    this.write('{"status":"ERROR", "reason":"' + errorCode[1] +
                        '"}');
                }
            }
            this.end();
        };

        if (!self.checkReady()) {
            Log.debug(
                'NS_AS::onHTTPMessage --> Message rejected, we are not ready yet'
            );
            response.res(errorcodes.NOT_READY);
            return;
        }

        Log.debug('NS_AS::onHTTPMessage --> Received request for ' + request.url);
        var url = urlparser.parse(request.url, true);
        var path = url.pathname.split('/');

        // CORS support
        if (request.method === 'OPTIONS') {
            Log.debug('NS_AS::onHTTPMessage --> Received an OPTIONS method');
            response.setHeader('Access-Control-Allow-Origin', '*');
            response.setHeader('Access-Control-Allow-Methods',
                'PUT, GET, OPTIONS');
            response.end();
            return;
        }

        // Frontend for the Mozilla SimplePush API
        if (request.method === 'PUT') {
            Log.debug('NS_AS::onHTTPMessage --> Received a PUT');
            var body = '';
            request.on('data', function(data) {
                body += data;
                // Max. payload: "version=9007199254740992" => lenght: 24
                if (body.length > 25) {
                    request.tooBig = true;
                    request.emit('end');
                    Log.debug(
                        'NS_AS::onHTTPMessage --> Message rejected, too long for this API'
                    );
                    response.res(errorcodesAS.BAD_MESSAGE_BODY_TOO_BIG);
                }
            });
            request.on('end', function() {
                if (request.tooBig) {
                    return;
                }
                self.simplepushRequest(request, body, response);
            });

        } else if (request.method === 'GET') {
            switch (path[1]) {
                case 'about':
                    if (consts.PREPRODUCTION_MODE) {
                        var text = '';
                        try {
                            var p = new Pages();
                            p.setTemplate('views/about.tmpl');
                            text = p.render(function(t) {
                                switch (t) {
                                    case '{{GIT_VERSION}}':
                                        return require('fs').readFileSync(
                                            'version.info');
                                    case '{{MODULE_NAME}}':
                                        return 'Application Server Frontend';
                                    default:
                                        return '';
                                }
                            });
                        } catch (e) {
                            text = 'No version.info file';
                        }
                        response.setHeader('Content-Type', 'text/html');
                        response.statusCode = 200;
                        response.write(text);
                        response.end();
                        return;
                    } else {
                        response.res(errorcodes.NOT_ALLOWED_ON_PRODUCTION_SYSTEM);
                        return;
                    }
                    break;

                case 'status':
                    // Return status mode to be used by load-balancers
                    response.setHeader('Content-Type', 'text/html');
                    if (Maintenance.getStatus()) {
                        response.statusCode = 503;
                        response.write('Under Maintenance');
                    } else {
                        response.statusCode = 200;
                        response.write('OK');
                    }
                    response.end();
                    break;

                default:
                    Log.debug('NS_AS::onHTTPMessage --> messageType "' + path[1] +
                        '" not recognized');
                    response.res(errorcodesAS.BAD_URL);
                    return;
            }

        } else {
            response.statusCode = 400;
            response.end();
        }
    },

    simplepushRequest: function(request, body, response) {
        /**
         * Check if we are closing. If so, send a 500 status
         * to the client. That means: "retry again later"
         */
        if (this.closing) {
            response.end(500);
            return;
        }

        /**
         * Check if the request is correct
         */
        var URI = request.url.split('/');
        var appToken = URI[3];
        var versions = String(body).split('=');
        var version = versions[1] || -1;

        if (URI.length < 3 ||
            URI[1] !== this.kSimplePushASFrontendVersionv1 ||
            URI[2] !== 'notify' || !appToken ||
            versions[0] !== 'version' || !Helpers.isVersion(version)) {
            response.statusCode = 400;
            response.end('{ reason: "Bad request"}');
            Log.info('NS_AS::simplepushRequest --> Bad request ' + body);
            return;
        }

        //Now, we are safe to start using the path and data
        var connectionIP = request.socket.remoteAddress;
        Log.notify(Log.messages.NOTIFY_APPTOKEN_VERSION, {
            appToken: appToken,
            version: version,
            ip: connectionIP || null
        });

        // Send the OK response always, this free some server resources
        response.statusCode = 200;
        //CORS support
        response.setHeader('Access-Control-Allow-Origin', '*');
        response.end('{}');

        //And now we proccess the notification.
        DataStore.getInfoForAppToken(appToken, function(error, appInfo) {
            if (!appInfo || !appInfo.ch || !Array.isArray(appInfo.no)) {
                return;
            }
            (appInfo.no).forEach(function(nodeId) {
                var msg = DataStore.newVersion(nodeId, appToken,
                    appInfo.ch, version);
                MsgBroker.push('newMessages', msg);
            });
        });
    }
};

// Exports
exports.NS_AS = NS_AS;
<|endoftext|>