<commit_before>module('lib.squeak.vm').requires().toRun(function() {
/*
 * Copyright (c) 2013 Bert Freudenberg
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

Object.subclass('lib.squeak.vm.Image',
'initialization', {
    initialize: function() {
        // object headers
    	this.HeaderTypeMask = 3;
    	this.HeaderTypeSizeAndClass = 0; //3-word header
    	this.HeaderTypeClass = 1;        //2-word header
    	this.HeaderTypeFree = 2;         //free block
	    this.HeaderTypeShort = 3;        //1-word header

        // Indices into SpecialObjects array
        this.splOb_NilObject = 0;
    	this.splOb_FalseObject = 1;
    	this.splOb_TrueObject = 2;
    	this.splOb_SchedulerAssociation = 3;
    	this.splOb_ClassBitmap = 4;
    	this.splOb_ClassInteger = 5;
    	this.splOb_ClassString = 6;
    	this.splOb_ClassArray = 7;
    	//this.splOb_SmalltalkDictionary = 8;  old slot 8
    	this.splOb_ClassFloat = 9;
    	this.splOb_ClassMethodContext = 10;
    	this.splOb_ClassBlockContext = 11;
    	this.splOb_ClassPoint = 12;
    	this.splOb_ClassLargePositiveInteger = 13;
    	this.splOb_TheDisplay = 14;
    	this.splOb_ClassMessage = 15;
    	this.splOb_ClassCompiledMethod = 16;
    	this.splOb_TheLowSpaceSemaphore = 17;
    	this.splOb_ClassSemaphore = 18;
    	this.splOb_ClassCharacter = 19;
    	this.splOb_SelectorDoesNotUnderstand = 20;
    	this.splOb_SelectorCannotReturn = 21;
    	this.splOb_TheInputSemaphore = 22;
    	this.splOb_SpecialSelectors = 23;
    	this.splOb_CharacterTable = 24;
    	this.splOb_SelectorMustBeBoolean = 25;
    	this.splOb_ClassByteArray = 26;
    	this.splOb_ClassProcess = 27;
    	this.splOb_CompactClasses = 28;
    	this.splOb_TheTimerSemaphore = 29;
    	this.splOb_TheInterruptSemaphore = 30;
    	this.splOb_FloatProto = 31;
    	this.splOb_SelectorCannotInterpret = 34;
    	this.splOb_MethodContextProto = 35;
    	this.splOb_BlockContextProto = 37;
    	this.splOb_ExternalObjectsArray = 38;
    	this.splOb_ClassPseudoContext = 39;
    	this.splOb_ClassTranslatedMethod = 40;
    	this.splOb_TheFinalizationSemaphore = 41;
    	this.splOb_ClassLargeNegativeInteger = 42;
    	this.splOb_ClassExternalAddress = 43;
    	this.splOb_ClassExternalStructure = 44;
    	this.splOb_ClassExternalData = 45;
    	this.splOb_ClassExternalFunction = 46;
    	this.splOb_ClassExternalLibrary = 47;
    	this.splOb_SelectorAboutToReturn = 48;

    },
},
'reading', {
    readFromBuffer: function(buffer) {
        var bytes = new Uint8Array(buffer);
        var doSwap = false,
            pos = 0;
        var readInt = function() {
            return doSwap
                ? (bytes[pos++] + (bytes[pos++] << 8) + (bytes[pos++] << 16) + (bytes[pos++] << 24))
                : ((bytes[pos++] << 24) + (bytes[pos++] << 16) + (bytes[pos++] << 8) + bytes[pos++]);
        };
        // read version
        var version = readInt();
        if (version != 6502) {
            doSwap = true; pos = 0;
            version = readInt();
            if (version != 6502) throw "bad image version";
        }
        // read header
        var headerSize = readInt();
        var endOfMemory = readInt(); //first unused location in heap
        var oldBaseAddr = readInt(); //object memory base address of image
        var specialObjectsOopInt = readInt(); //oop of array of special oops
        this.lastHash = readInt(); //Should be loaded from, and saved to the image header
        var savedWindowSize = readInt();
        var fullScreenFlag = readInt();
        var extraVMMemory = readInt();
        pos += headerSize - (9 * 4); //skip to end of header
        // read objects
        var oopMap = {};
        for (var i = 0; i < endOfMemory; ) {
            var nWords = 0;
            var classInt = 0;
            var header = readInt();
            switch (header & this.HeaderTypeMask) {
                case this.HeaderTypeSizeAndClass:
                    nWords = header >> 2;
                    classInt = readInt();
                    header = readInt();
                    i += 12;
                    break;
                case this.HeaderTypeClass:
                    classInt = header - this.HeaderTypeClass;
                    header = readInt();
                    nWords = (header >> 2) & 63;
                    i += 8;
                    break;
                case this.HeaderTypeShort:
                    nWords = (header >> 2) & 63;
                    classInt = (header >> 12) & 31; //compact class index
                    //Note classInt<32 implies compact class index
                    i += 4;
                    break;
                case this.HeaderTypeFree:
                    throw "Unexpected free block";
            }
            var baseAddr = i - 4; //0-rel byte oop of this object (base header)
            nWords--;  //length includes base header which we have already read
            var format= ((header>>8) & 15);
            var hash= ((header>>17) & 4095);

            // Note classInt and data are just raw data; no base addr adjustment and no int conversion
            var data = new Array(nWords);
            for (var j = 0; j<nWords; j++)
                data[j] = readInt();
            i += nWords*4;

            var object = new lib.squeak.vm.Object();
            object.initFromImage(classInt, format, hash, data);
            //oopMap is from old oops to new objects
            oopMap[oldBaseAddr + baseAddr] = object;
        }
        show("objects: "+ Object.keys(oopMap).length);
        //create proper objects
        var splObs         = oopMap[specialObjectsOopInt];
        var compactClasses = oopMap[splObs.bits[this.splOb_CompactClasses]].bits;
        var floatClass     = oopMap[splObs.bits[this.splOb_ClassFloat]];
        for (var oop in oopMap)
            oopMap[oop].installFromImage(oopMap, compactClasses, floatClass);
        this.specialObjectsArray = splObs;
     },
});
Object.subclass('lib.squeak.vm.Object',
'initialization', {
    initFromImage: function(cls, fmt, hsh, data) {
        // initial creation from Image, with unmapped data
        this.sqClass = cls;
        this.format = fmt;
        this.hash = hsh;
        this.bits = data;
    },
    decodeFloat: function(theBits) {
        var buffer = new ArrayBuffer(8);
        var data = new DataView(buffer);
        data.setUint32(0, theBits[0], false);
        data.setUint32(4, theBits[1], false);
        var float = data.getFloat64(0, false);
        return float;
    },

    installFromImage: function(oopMap, ccArray, floatClass) {
        //Install this object by decoding format, and rectifying pointers
        var ccInt = this.sqClass;
        // map compact classes
        if ((ccInt>0) && (ccInt<32))
            this.sqClass = oopMap[ccArray[ccInt-1]];
        else
            this.sqClass = oopMap[ccInt];
        var nWords= this.bits.length;
        if (this.format < 5) {
            //Formats 0...4 -- Pointer fields
            this.pointers = this.decodePointers(nWords, this.bits, oopMap);
            this.bits = undefined; }
        else if (this.format >= 12) {
            //Formats 12-15 -- CompiledMethods both pointers and bits
            var methodHeader = this.bits[0];
            var numLits = (methodHeader>>10) & 255;
            this.pointers = this.decodePointers(numLits+1, this.bits, oopMap); //header+lits
            this.bits = this.decodeBytes(nWords-(numLits+1), this.bits, numLits+1, this.format & 3); }
        else if (this.format >= 8) {
            //Formats 8..11 -- ByteArrays (and Strings)
            this.bits = this.decodeBytes(nWords, this.bits, 0, this.format & 3); }
        //Format 6 word objects are already OK (except Floats...)
        else if (this.sqClass == floatClass) {
            //Floats need two ints to be converted to double
            this.bits = this.decodeFloat(this.bits); }
    },

    decodePointers: function(nWords, theBits, oopMap) {
        //Convert small ints and look up object pointers in oopMap
        var ptrs = new Array(nWords);
        for (var i=0; i<nWords; i++) {
            var oldOop = theBits[i];
            if ((oldOop&1) == 1)
                ptrs[i] = oldOop >> 1;
            else
                ptrs[i] = oopMap[oldOop];
        }
        return ptrs;
    },
    decodeBytes: function(nWords, theBits, wordOffset, fmtLowBits) {
        //Adjust size for low bits and extract bytes from ints
        var nBytes = (nWords*4) - fmtLowBits;
        var newBits = new Array(nBytes);
        var wordIx = wordOffset;
        var fourBytes = 0;
        for (var i=0; i<nBytes; i++) {
            if ((i&3)==0)
                fourBytes = theBits[wordIx++];
            var pickByte = (fourBytes>>(8*(3-(i&3))))&255;
            if (pickByte>=128)
                pickByte = pickByte-256;
            newBits[i]= pickByte;
        }
        return newBits;
    }

},
'printing', {
    toString: function() {
        return Strings.format('sqObj(%s)',
            this.sqClass.constructor == lib.squeak.vm.Object ? this.sqClassName() : this.sqClass);
    },
    sqClassName: function() {
        // the 7th inst var of a class holds either the name, or the non-meta class if this is a metaclass
        var nameOrNonMetaClass = this.sqClass.pointers[6];
        var isMeta = nameOrNonMetaClass.bits == undefined;
        var nameObj = isMeta ? nameOrNonMetaClass.pointers[6] : nameOrNonMetaClass;
        var name = nameObj.bits.map(function(char) { return String.fromCharCode(char); }).join('');
        return isMeta ? name + " class" : name;
    }

});

}) // end of module
<commit_msg>use DataView for endianness conversion when reading image<commit_after>module('lib.squeak.vm').requires().toRun(function() {
/*
 * Copyright (c) 2013 Bert Freudenberg
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

Object.subclass('lib.squeak.vm.Image',
'initialization', {
    initialize: function() {
        // object headers
    	this.HeaderTypeMask = 3;
    	this.HeaderTypeSizeAndClass = 0; //3-word header
    	this.HeaderTypeClass = 1;        //2-word header
    	this.HeaderTypeFree = 2;         //free block
	    this.HeaderTypeShort = 3;        //1-word header

        // Indices into SpecialObjects array
        this.splOb_NilObject = 0;
    	this.splOb_FalseObject = 1;
    	this.splOb_TrueObject = 2;
    	this.splOb_SchedulerAssociation = 3;
    	this.splOb_ClassBitmap = 4;
    	this.splOb_ClassInteger = 5;
    	this.splOb_ClassString = 6;
    	this.splOb_ClassArray = 7;
    	//this.splOb_SmalltalkDictionary = 8;  old slot 8
    	this.splOb_ClassFloat = 9;
    	this.splOb_ClassMethodContext = 10;
    	this.splOb_ClassBlockContext = 11;
    	this.splOb_ClassPoint = 12;
    	this.splOb_ClassLargePositiveInteger = 13;
    	this.splOb_TheDisplay = 14;
    	this.splOb_ClassMessage = 15;
    	this.splOb_ClassCompiledMethod = 16;
    	this.splOb_TheLowSpaceSemaphore = 17;
    	this.splOb_ClassSemaphore = 18;
    	this.splOb_ClassCharacter = 19;
    	this.splOb_SelectorDoesNotUnderstand = 20;
    	this.splOb_SelectorCannotReturn = 21;
    	this.splOb_TheInputSemaphore = 22;
    	this.splOb_SpecialSelectors = 23;
    	this.splOb_CharacterTable = 24;
    	this.splOb_SelectorMustBeBoolean = 25;
    	this.splOb_ClassByteArray = 26;
    	this.splOb_ClassProcess = 27;
    	this.splOb_CompactClasses = 28;
    	this.splOb_TheTimerSemaphore = 29;
    	this.splOb_TheInterruptSemaphore = 30;
    	this.splOb_FloatProto = 31;
    	this.splOb_SelectorCannotInterpret = 34;
    	this.splOb_MethodContextProto = 35;
    	this.splOb_BlockContextProto = 37;
    	this.splOb_ExternalObjectsArray = 38;
    	this.splOb_ClassPseudoContext = 39;
    	this.splOb_ClassTranslatedMethod = 40;
    	this.splOb_TheFinalizationSemaphore = 41;
    	this.splOb_ClassLargeNegativeInteger = 42;
    	this.splOb_ClassExternalAddress = 43;
    	this.splOb_ClassExternalStructure = 44;
    	this.splOb_ClassExternalData = 45;
    	this.splOb_ClassExternalFunction = 46;
    	this.splOb_ClassExternalLibrary = 47;
    	this.splOb_SelectorAboutToReturn = 48;

    },
},
'reading', {
    readFromBuffer: function(buffer) {
        var data = new DataView(buffer),
            littleEndian = false,
            pos = 0;
        var readInt = function() {
            var int = data.getInt32(pos, littleEndian);
            pos += 4;
            return int;
        };
        // read version
        var version = readInt();
        if (version != 6502) {
            littleEndian = true; pos = 0;
            version = readInt();
            if (version != 6502) throw "bad image version";
        }
        // read header
        var headerSize = readInt();
        var endOfMemory = readInt(); //first unused location in heap
        var oldBaseAddr = readInt(); //object memory base address of image
        var specialObjectsOopInt = readInt(); //oop of array of special oops
        this.lastHash = readInt(); //Should be loaded from, and saved to the image header
        var savedWindowSize = readInt();
        var fullScreenFlag = readInt();
        var extraVMMemory = readInt();
        pos += headerSize - (9 * 4); //skip to end of header
        // read objects
        var oopMap = {};
        for (var i = 0; i < endOfMemory; ) {
            var nWords = 0;
            var classInt = 0;
            var header = readInt();
            switch (header & this.HeaderTypeMask) {
                case this.HeaderTypeSizeAndClass:
                    nWords = header >> 2;
                    classInt = readInt();
                    header = readInt();
                    i += 12;
                    break;
                case this.HeaderTypeClass:
                    classInt = header - this.HeaderTypeClass;
                    header = readInt();
                    nWords = (header >> 2) & 63;
                    i += 8;
                    break;
                case this.HeaderTypeShort:
                    nWords = (header >> 2) & 63;
                    classInt = (header >> 12) & 31; //compact class index
                    //Note classInt<32 implies compact class index
                    i += 4;
                    break;
                case this.HeaderTypeFree:
                    throw "Unexpected free block";
            }
            var baseAddr = i - 4; //0-rel byte oop of this object (base header)
            nWords--;  //length includes base header which we have already read
            var format = ((header>>8) & 15);
            var hash = ((header>>17) & 4095);

            // Note classInt and bits are just raw data; no base addr adjustment and no int conversion
            var bits = new Array(nWords);
            for (var j = 0; j<nWords; j++)
                bits[j] = readInt();
            i += nWords*4;

            var object = new lib.squeak.vm.Object();
            object.initFromImage(classInt, format, hash, bits);
            //oopMap is from old oops to new objects
            oopMap[oldBaseAddr + baseAddr] = object;
        }
        show("objects: "+ Object.keys(oopMap).length);
        //create proper objects
        var splObs         = oopMap[specialObjectsOopInt];
        var compactClasses = oopMap[splObs.bits[this.splOb_CompactClasses]].bits;
        var floatClass     = oopMap[splObs.bits[this.splOb_ClassFloat]];
        for (var oop in oopMap)
            oopMap[oop].installFromImage(oopMap, compactClasses, floatClass);
        this.specialObjectsArray = splObs;
     },
});
Object.subclass('lib.squeak.vm.Object',
'initialization', {
    initFromImage: function(cls, fmt, hsh, data) {
        // initial creation from Image, with unmapped data
        this.sqClass = cls;
        this.format = fmt;
        this.hash = hsh;
        this.bits = data;
    },
    decodeFloat: function(theBits) {
        var buffer = new ArrayBuffer(8);
        var data = new DataView(buffer);
        data.setUint32(0, theBits[0], false);
        data.setUint32(4, theBits[1], false);
        var float = data.getFloat64(0, false);
        return float;
    },

    installFromImage: function(oopMap, ccArray, floatClass) {
        //Install this object by decoding format, and rectifying pointers
        var ccInt = this.sqClass;
        // map compact classes
        if ((ccInt>0) && (ccInt<32))
            this.sqClass = oopMap[ccArray[ccInt-1]];
        else
            this.sqClass = oopMap[ccInt];
        var nWords= this.bits.length;
        if (this.format < 5) {
            //Formats 0...4 -- Pointer fields
            this.pointers = this.decodePointers(nWords, this.bits, oopMap);
            this.bits = undefined; }
        else if (this.format >= 12) {
            //Formats 12-15 -- CompiledMethods both pointers and bits
            var methodHeader = this.bits[0];
            var numLits = (methodHeader>>10) & 255;
            this.pointers = this.decodePointers(numLits+1, this.bits, oopMap); //header+lits
            this.bits = this.decodeBytes(nWords-(numLits+1), this.bits, numLits+1, this.format & 3); }
        else if (this.format >= 8) {
            //Formats 8..11 -- ByteArrays (and Strings)
            this.bits = this.decodeBytes(nWords, this.bits, 0, this.format & 3); }
        //Format 6 word objects are already OK (except Floats...)
        else if (this.sqClass == floatClass) {
            //Floats need two ints to be converted to double
            this.bits = this.decodeFloat(this.bits); }
    },

    decodePointers: function(nWords, theBits, oopMap) {
        //Convert small ints and look up object pointers in oopMap
        var ptrs = new Array(nWords);
        for (var i=0; i<nWords; i++) {
            var oldOop = theBits[i];
            if ((oldOop&1) == 1)
                ptrs[i] = oldOop >> 1;
            else
                ptrs[i] = oopMap[oldOop];
        }
        return ptrs;
    },
    decodeBytes: function(nWords, theBits, wordOffset, fmtLowBits) {
        //Adjust size for low bits and extract bytes from ints
        var nBytes = (nWords*4) - fmtLowBits;
        var newBits = new Array(nBytes);
        var wordIx = wordOffset;
        var fourBytes = 0;
        for (var i=0; i<nBytes; i++) {
            if ((i&3)==0)
                fourBytes = theBits[wordIx++];
            var pickByte = (fourBytes>>(8*(3-(i&3))))&255;
            if (pickByte>=128)
                pickByte = pickByte-256;
            newBits[i]= pickByte;
        }
        return newBits;
    }

},
'printing', {
    toString: function() {
        return Strings.format('sqObj(%s)',
            this.sqClass.constructor == lib.squeak.vm.Object ? this.sqClassName() : this.sqClass);
    },
    sqClassName: function() {
        // the 7th inst var of a class holds either the name, or the non-meta class if this is a metaclass
        var nameOrNonMetaClass = this.sqClass.pointers[6];
        var isMeta = nameOrNonMetaClass.bits == undefined;
        var nameObj = isMeta ? nameOrNonMetaClass.pointers[6] : nameOrNonMetaClass;
        var name = nameObj.bits.map(function(char) { return String.fromCharCode(char); }).join('');
        return isMeta ? name + " class" : name;
    }

});

}) // end of module
<|endoftext|>


<commit_before>/*jshint node:true, bitwise:true, indent:2, curly:true eqeqeq:true, immed:true, latedef:true, newcap:true, noarg:true,
regexp:true, undef:true, strict:true, trailing:true, white:true */
/*global X:true */

/**
  The X Node.js framework is comprised of 3 major components. The foundation,
  the database and the server. The foundation can be used on its own. database
  and server can be used with or without the other but both require the foundation.

  It is important to note the scoping of the namespace. Unlike most modules that
  do not expose their scope, X modules and components share the namespace without
  explicitly exporting it. The global variable X is common to any modules that are
  required after its initial instantiation

  It is possible for submodules of the framework to reserve initialization routines
  until after the framework itself has been fully loaded and initialized. There is
  an exposed routine in X called `run` that expects a single paramter that is a callback
  that will be executed in the order it was received. Currently there is no implementation
  that allows for a module to hook another unless the order of loading is known and
  the first module emits an event that the second module knows to listen for and receive.

  There are known limitations in its current implementation in the object hierarchy and
  some of the convenience mechanisms built-in.
*/

//.........................................
// Include the foundation that instantiates the
// X global namespace
require('./foundation');

(function () {
  "use strict";

  var _ = X._;

  // the first method to run once the framework has been told it is
  // ready
  X.run(function () {

    if (X.requireDatabase) require("./database");
    if (X.requireServer) require("./server");
    if (X.requireCache) {
      require('./database/cache');
      require("./database/ext/mongoose_schema");
    }

    // special case where the desired output requires calling console directly
    X.io.console(X.StringBuffer.create({ color: 'blue', prefix: null }),
      "\n================================================" +
      "\nXUPLE NODE.JS FRAMEWORK ({version})".f({ version: X.version }) +
      "\n================================================" +
      "\nThis framework is highly experimental." +
      "\n\nPlease report bugs to the project git issue tracker and for blocking" +
      "\nissues please also report those via email to Cole Davis (cole@xtuple.com)\n"
    );

    // give any running process the opportunity to save state
    // or log as gracefully as possible
    //process.once('exit', _.bind(X.cleanup, X));

    process.once('SIGINT', function () {
      X.io.console(X.StringBuffer.create({ color: 'blue', prefix: null }),
        "\n================================================" +
        "\nSIGINT CAUGHT - cleaning up before shutting down" +
        "\n================================================"
      );
      X.cleanup();
    });
  });
}());
<commit_msg>add support for various signals SIGINT, SIGHUP, SIGQUIT, SIGSEGV, SIGILL<commit_after>/*jshint node:true, bitwise:true, indent:2, curly:true eqeqeq:true, immed:true, latedef:true, newcap:true, noarg:true,
regexp:true, undef:true, strict:true, trailing:true, white:true */
/*global X:true */

/**
  The X Node.js framework is comprised of 3 major components. The foundation,
  the database and the server. The foundation can be used on its own. database
  and server can be used with or without the other but both require the foundation.

  It is important to note the scoping of the namespace. Unlike most modules that
  do not expose their scope, X modules and components share the namespace without
  explicitly exporting it. The global variable X is common to any modules that are
  required after its initial instantiation

  It is possible for submodules of the framework to reserve initialization routines
  until after the framework itself has been fully loaded and initialized. There is
  an exposed routine in X called `run` that expects a single paramter that is a callback
  that will be executed in the order it was received. Currently there is no implementation
  that allows for a module to hook another unless the order of loading is known and
  the first module emits an event that the second module knows to listen for and receive.

  There are known limitations in its current implementation in the object hierarchy and
  some of the convenience mechanisms built-in.
*/

//.........................................
// Include the foundation that instantiates the
// X global namespace
require('./foundation');

(function () {
  "use strict";

  var _ = X._, sighandler;

  sighandler = function (signal) {
    X.io.console(X.StringBuffer.create({color: "blue", prefix: null}),
      "\n================================================" +
      "\n%@ CAUGHT - cleaning up before shutting down".f(signal.toUpperCase()) +
      "\n================================================"
    );
    X.cleanup();
  };

  // the first method to run once the framework has been told it is
  // ready
  X.run(function () {

    if (X.requireDatabase) require("./database");
    if (X.requireServer) require("./server");
    if (X.requireCache) {
      require('./database/cache');
      require("./database/ext/mongoose_schema");
    }

    // special case where the desired output requires calling console directly
    X.io.console(X.StringBuffer.create({ color: 'blue', prefix: null }),
      "\n================================================" +
      "\nXUPLE NODE.JS FRAMEWORK ({version})".f({ version: X.version }) +
      "\n================================================" +
      "\nThis framework is highly experimental." +
      "\n\nPlease report bugs to the project git issue tracker and for blocking" +
      "\nissues please also report those via email to Cole Davis (cole@xtuple.com)\n"
    );

    // give any running process the opportunity to save state
    // or log as gracefully as possible
    //process.once('exit', _.bind(X.cleanup, X));

    _.forEach(["SIGINT", "SIGHUP", "SIGQUIT", "SIGKILL", "SIGSEGV", "SIGILL"], function (sig) {
      process.once(sig, _.bind(sighandler, X, sig));
    });
  });
}());
<|endoftext|>


<commit_before>/* globals bufferpm, chrome, safari */

// Put together a query string for the iframe
var buildSrc = function(data, config) {

  var src = data.local ?
    config.overlay.localendpoint :
    config.overlay.endpoint;

  var qs = '';

  config.attributes.forEach(function(attr, i){
    if ( !data[ attr.name ] ) return;
    if ( qs.length ) qs += '&';
    qs += attr.name + '=' + attr.encode( data[attr.name] );
  });

  if (qs.length) src += '?' + qs;

  return src;
};

var openPopUp = function(src, port, doneCallback) {

  // Open popups from privileged code
  port.emit('buffer_open_popup', src);

  // Bind close listener
  // Listen for when the overlay has closed itself
  bufferpm.bind("buffermessage", function(overlaydata) {
    bufferpm.unbind("buffermessage");
    setTimeout(function () {
      doneCallback(overlaydata);
    }, 0);
    window.focus();
  });
};

/**
 * This is a list of domains that have a strict Content Security Policy
 * This is a temporary work around while we figure out a long term solution
 * to this issue: https://github.com/bufferapp/buffer-chrome/issues/12
 */
var CSPWhitelist = [
  'twitter.com',
  'github.com',
  'gist.github.com',
  'education.github.com',
  'medium.com',
  'www.npmjs.org',
  'www.npmjs.com' // DEC 2014
];

// Build that overlay!
// Triggered by code working from the button up
var bufferOverlay = function(data, config, port, doneCallback) {

  if( ! doneCallback ) doneCallback = function () {};
  if( ! config ) return;

  portCache.grabPort(port);

  var src = buildSrc(data, config);
  var domain = window.location.hostname;

  if (xt.options['buffer.op.tpc-disabled'] ||
    CSPWhitelist.indexOf(domain) > -1 ) {
    return openPopUp(src, port, doneCallback);
  }

  var shouldContinue = ensureOnlyOneOverlayOpen(data, closePopup.bind(window, document, doneCallback));
  if (!shouldContinue) return;

  port.emit('buffer_overlay_open');

  // Create the iframe and add the footer:
  var iframe = document.createElement('iframe');

  iframe.allowtransparency = 'true';
  iframe.scrolling = 'no';
  iframe.id = 'buffer_overlay';
  iframe.name = 'buffer_overlay';
  iframe.style.cssText = config.overlay.getCSS();

  iframe.src = src;

  // Testing CSP issues w/ sandboxed iframe
  // var qs = src.split('?')[1];
  // iframe.src = xt.data.get('data/shared/extension.html?' + qs);

  var rightCnt = createBtnContainer('right');
  var upgradeButton = createUpgradeButton();
  var helpButton = createHelpButton();
  var dashboardButton = createDashboardButton();

  rightCnt.appendChild(upgradeButton);
  rightCnt.appendChild(helpButton);
  rightCnt.appendChild(dashboardButton);

  getExtensionUserData(function(userData) {
    upgradeButton.classList.toggle('hidden', !userData.shouldDisplayAwesomeCTA);
  });

  var leftCnt = createBtnContainer('left');
  var cancelButton = createCancelButton();

  leftCnt.appendChild(cancelButton);

  document.body.appendChild(iframe);
  document.body.appendChild(rightCnt);
  document.body.appendChild(leftCnt);

  $(document).on('click', '.buffer-btn-cancel', function() {
      closePopup(document, doneCallback);
  });

  // Bind close listener
  // Listen for when the overlay has closed itself
  bufferpm.bind('buffermessage', function(overlaydata) {
      closePopup(document, doneCallback, overlaydata);
  });

  /**
   * Listen to ESC key and close the popup when hit.
   */
  $(document).on('keyup.bufferOverlay', function(e) {
    if (e.keyCode == 27) {
      // When an overlay instance is hidden (but still open), don't let shortcuts close it
      if (!ensureOnlyOneOverlayOpen.isOverlayVisible()) return;

      closePopup(document, doneCallback);
    }
  });

  // Remove the loading image when we hear from the other side
  bufferpm.bind('buffer_loaded', function(data) {
    // Send user data to background script for caching
    if (data && data.userData) port.emit('buffer_user_data', data.userData);

    bufferpm.unbind('buffer_loaded');
    iframe.style.backgroundImage = 'none';

    $(".buffer-btn-cancel").remove();
  });
};

// Returns true if a new overlay should be open, false if we've toggled the visibility of
// and existing overlay instead.
function ensureOnlyOneOverlayOpen(data, closePopup) {
  // State can't be saved in this script, since it gets re-executed multiple times by some browsers
  // (e.g. Firefox), so we rely on the DOM instead.
  var isOverlayOpen = function() { return !!$('#buffer_overlay').length };

  // If the open intent comes from the Buffer toolbar button or a keyboard shortcut, toggle the
  // visibility of the overlay if it's already open, otherwise allow a new one to be open.
  if (data.placement == 'toolbar' || data.placement == 'hotkey') {
    if (!isOverlayOpen()) return true;

    $('#buffer_overlay, .buffer-btn-container').toggle();
    return false;
  }

  // If the open intent comes from somewhere else, discard any hidden overlay and open a new one
  closePopup();

  return true;
};

ensureOnlyOneOverlayOpen.isOverlayVisible = function() { return $('#buffer_overlay').is(':visible') };

function closePopup(document, doneCallback, overlayData) {
    $('#buffer_overlay, .buffer-btn-container').remove();

    bufferpm.unbind('buffermessage');
    bufferpm.unbind('buffer_addbutton');

    $(document).off('keyup.bufferOverlay');

    setTimeout(function () {
      doneCallback(overlayData);
    }, 0);

    window.focus();
}

// position = 'left' || 'right'
var createBtnContainer = function(position) {
  var container = document.createElement('div');
  container.setAttribute('class', 'buffer-btn-container buffer-btn-container-' + position);

  return container;
};

var createHelpButton = function() {
  var button;
  var text;

  button = document.createElement('a');
  button.href = 'https://buffer.com/app#contact-from-extension';
  button.target = '_blank';
  button.setAttribute('class', 'buffer-btn-help');

  text = document.createTextNode('Help');
  button.appendChild(text);

  button.addEventListener('click', function() {
    _bmq.trackAction(['overlay', 'help_button']);
  }, false);

  return button;
};

var createDashboardButton = function() {
  var button = document.createElement('a');
  button.href = 'https://buffer.com/app';
  button.target = '_blank';
  button.setAttribute('class', 'buffer-btn-dashboard');

  var text = document.createTextNode('Go to Buffer');
  button.appendChild(text);

  button.addEventListener('click', function() {
    _bmq.trackAction(['overlay', 'go_to_buffer_button']);
  }, false);

  return button;
};

var createCancelButton = function() {

  var button = document.createElement('button');
  button.setAttribute('class', 'buffer-btn-cancel');

  var text = document.createTextNode('Cancel');
  button.appendChild(text);

  return button;
};

var createUpgradeButton = function() {
  var button = document.createElement('a');
  button.href = 'https://buffer.com/awesome?utm_campaign=extensions_header&utm_medium=web';
  button.target = '_blank';
  button.setAttribute('class', 'buffer-btn-upgrade hidden');

  var text = document.createTextNode('Upgrade to Awesome');
  button.appendChild(text);

  button.addEventListener('click', function() {
    _bmq.trackAction(['overlay', 'upgrade_to_awesome_button']);
  }, false);

  return button;
};

// getOverlayConfig returns the configuration object for use by bufferOverlay
var getOverlayConfig = function(postData){

  var config = {};

  // Set this to true for using a local server while testing
  config.local = false;

  var segments = window.location.pathname.split('/');

  config.pocketWeb = ( window.location.host.indexOf('getpocket') !== -1 && segments[2] === 'read' );

  // Specification for gathering data for the overlay
  config.attributes = [
    {
      name: "url",
      get: function (cb) {
        if(config.pocketWeb){
          var li = document.getElementsByClassName('original')[0];
          var link = li.getElementsByTagName('a')[0].href;
          cb(link);
        }
        else{
          cb(window.location.href);
        }
      },
      encode: function (val) {
        return encodeURIComponent(val);
      }
    },
    {
      name: "text",
      get: function (cb) {
        var selectedText = document.getSelection().toString();
        var quoteChars;

        if (selectedText) {
          // If quotes surround the selected text, strip them away
          quoteChars = ['"', '“', '”', '\'', '‘', '’', '«', '»'];
          if (quoteChars.indexOf(selectedText[0]) != -1 && quoteChars.indexOf(selectedText[selectedText.length - 1]) != -1) {
            selectedText = selectedText.slice(1, selectedText.length - 1);
          }

          return cb('“' + selectedText + '”');
        }

        if (config.pocketWeb){
          var headline = document.querySelectorAll('.reader_head h1')[0];
          var title = headline && headline.textContent;
          return cb(title);
        }

        var ogTitle = document.head && document.head.querySelector('meta[property="og:title"]');
        if (ogTitle && ogTitle.content && ogTitle.content.length) {
          return cb(ogTitle.content);
        }

        cb(document.title);
      },
      encode: function (val) {
        return encodeURIComponent(val);
      }
    },
    {
      name: "retweeted_tweet_id",
      get: function (cb) {
        cb(postData.retweeted_tweet_id);
      },
      encode: function (val) {
        return encodeURIComponent(val);
      }
    },
    {
      name: "retweeted_user_id",
      get: function (cb) {
        cb(postData.retweeted_user_id);
      },
      encode: function (val) {
        return encodeURIComponent(val);
      }
    },
    {
      name: "retweeted_user_name",
      get: function (cb) {
        cb(postData.retweeted_user_name);
      },
      encode: function (val) {
        return encodeURIComponent(val);
      }
    },
    {
      name: "retweeted_user_display_name",
      get: function (cb) {
        cb(postData.retweeted_user_display_name);
      },
      encode: function (val) {
        return encodeURIComponent(val);
      }
    },
    {
      name: "retweet_comment",
      get: function (cb) {
        cb(postData.retweet_comment);
      },
      encode: function (val) {
        return encodeURIComponent(val);
      }
    },
    {
      name: "picture",
      get: function (cb) {
        cb(postData.image);
      },
      encode: function (val) {
        return encodeURIComponent(val);
      }
    },
    {
      name: "embed",
      get: function (cb) {
        cb(postData.embed);
      },
      encode: function (val) {
        return encodeURIComponent(val);
      }
    },
    {
      name: "local",
      get: function (cb) {
        cb(config.local);
      },
      encode: function (val) {
        return encodeURIComponent(val);
      }
    },
    {
      name: "version",
      get: function (cb) {
        cb(postData.version);
      },
      encode: function (val) {
        return encodeURIComponent(val);
      }
    },
    {
      name: "placement",
      get: function (cb) {
        if( postData.placement ) cb(postData.placement);
        else cb('general');
      },
      encode: function (val) {
        return encodeURIComponent(val);
      }
    }
  ];

  var loadingImgRel = 'img/black-loading-gif-small.gif';
  //TODO - Change to static.buffer when it's set up!
  var loadingImg = typeof chrome !== 'undefined' ?
    chrome.extension.getURL('data/shared/' +  loadingImgRel) :
    document.location.protocol === 'http:' ?
      'http://static.bufferapp.com/images/extensions/' + loadingImgRel :
      'https://d389zggrogs7qo.cloudfront.net/images/extensions/' + loadingImgRel;

  config.overlay = {
    endpoint: "https://buffer.com/add/",
    localendpoint: "https://local.buffer.com/add/",
    getCSS: function () {
      return [
        'border: none;',
        'height: 100%;',
        'width: 100%;',
        'position: fixed!important;',
        'z-index: 2147483646;',
        'top:0;',
        'left:0;',
        'display: block!important;',
        'max-width: 100%!important;',
        'max-height: 100%!important;',
        'padding: 0!important;',
        'background: rgba(245, 245, 245, 0.74) url(' + loadingImg +') no-repeat center center;',
        'background-size: 40px;'
      ].join('');
    }
  };

  return config;
};

// Method for handling the async firing of the cb
var executeAfter = function(done, count, args, cb) {
  if(done === count) {
    setTimeout(function(){
      cb.apply( null, args );
    }, 0);
  }
};

// Asynchronously gather data about the page and from embedded sources,
// like Twitter or Facebook. Currently the async is a bit over the top,
// and not used, but if we need async down the line, it's there.
var getData = function (postData, cb) {
  var config = getOverlayConfig( postData );
  var count = config.attributes.length;
  var done = 0;
  var data = {};

  config.attributes.forEach(function(attr, i){
    attr.get(function(d){
      done += 1;
      data[ attr.name ] = d;
      executeAfter(done, count, [ data, config ], cb);
    });
  });
};

// bufferData is triggered by the buffer_click listener in
// the buffer-browser-embed file, where it's passed a port
// to communicate with the extension and data sent from the
// background page.
var bufferData = function (port, postData) {

  if (window.top !== window) return;

  // Transform the data somewhat, and then create an overlay.
  // When it's done, fire buffer_done back to the extension
  var createOverlay = function (data, config) {
    if( data.embed ) {
      if( typeof data.embed === "object" ) {
        for( var i in data.embed ) {
          if( data.embed.hasOwnProperty(i) ) {
            data[i] = data.embed[i];
          }
        }
        if( data.embed.text && !data.embed.url ) {
          data.url = null;
        }
        data.embed = null;
      } else {
        data.text = data.embed;
        data.url = null;
        data.embed = null;
      }
    }
    bufferOverlay(data, config, port, function (overlaydata) {
      port.emit("buffer_done", overlaydata);
    });
  };

  // It all starts here.
  // createOverlay is the callback that should fire after getData has
  // gathered all the necessaries
  getData(postData, createOverlay);
};

// Cache for the port to avoid passing it around in function calls
var portCache = (function() {
  var _port;

  var exposed = {
    // Executed at the very beginning of bufferOverlay()'s execution to
    // update the cached port
    grabPort: function(port) {
      _port = port;
    },

    getPort: function() { return _port }
  };

  return exposed;
})();

// _bmq exposes the same API here as it does in background scripts, but
// here it only takes care of passing this data to the background script's
// _bmq where it will be effectively taken care of.
var _bmq = (function() {
  var _availableMethods = ['push', 'trackAction'];

  var _passForward = function(methodName) {
    var payload = {
      methodName: methodName,
      args: Array.prototype.slice.call(arguments, 1)
    };

    portCache.getPort().emit('buffer_tracking', payload);
  };

  var exposed = {};

  // Expose all _availableMethods
  _availableMethods.forEach(function(methodName) {
    exposed[methodName] = _passForward.bind(null, methodName);
  });

  return exposed;
}());

// Get some user data asynchronously; the callback will be run once immediately if such data has already
// been cached by the background script, and once shortly after displaying the overlay when the cache
// has been refreshed from the user data in the iframe
getExtensionUserData = function(cb) {
  portCache.getPort().on('buffer_user_data', cb);
};
<commit_msg>Add Flickr to CSP whitelist<commit_after>/* globals bufferpm, chrome, safari */

// Put together a query string for the iframe
var buildSrc = function(data, config) {

  var src = data.local ?
    config.overlay.localendpoint :
    config.overlay.endpoint;

  var qs = '';

  config.attributes.forEach(function(attr, i){
    if ( !data[ attr.name ] ) return;
    if ( qs.length ) qs += '&';
    qs += attr.name + '=' + attr.encode( data[attr.name] );
  });

  if (qs.length) src += '?' + qs;

  return src;
};

var openPopUp = function(src, port, doneCallback) {

  // Open popups from privileged code
  port.emit('buffer_open_popup', src);

  // Bind close listener
  // Listen for when the overlay has closed itself
  bufferpm.bind("buffermessage", function(overlaydata) {
    bufferpm.unbind("buffermessage");
    setTimeout(function () {
      doneCallback(overlaydata);
    }, 0);
    window.focus();
  });
};

/**
 * This is a list of domains that have a strict Content Security Policy
 * This is a temporary work around while we figure out a long term solution
 * to this issue: https://github.com/bufferapp/buffer-chrome/issues/12
 */
var CSPWhitelist = [
  'twitter.com',
  'github.com',
  'gist.github.com',
  'education.github.com',
  'medium.com',
  'www.npmjs.org',
  'www.npmjs.com',
  'www.flickr.com'
];

// Build that overlay!
// Triggered by code working from the button up
var bufferOverlay = function(data, config, port, doneCallback) {

  if( ! doneCallback ) doneCallback = function () {};
  if( ! config ) return;

  portCache.grabPort(port);

  var src = buildSrc(data, config);
  var domain = window.location.hostname;

  if (xt.options['buffer.op.tpc-disabled'] ||
    CSPWhitelist.indexOf(domain) > -1 ) {
    return openPopUp(src, port, doneCallback);
  }

  var shouldContinue = ensureOnlyOneOverlayOpen(data, closePopup.bind(window, document, doneCallback));
  if (!shouldContinue) return;

  port.emit('buffer_overlay_open');

  // Create the iframe and add the footer:
  var iframe = document.createElement('iframe');

  iframe.allowtransparency = 'true';
  iframe.scrolling = 'no';
  iframe.id = 'buffer_overlay';
  iframe.name = 'buffer_overlay';
  iframe.style.cssText = config.overlay.getCSS();

  iframe.src = src;

  // Testing CSP issues w/ sandboxed iframe
  // var qs = src.split('?')[1];
  // iframe.src = xt.data.get('data/shared/extension.html?' + qs);

  var rightCnt = createBtnContainer('right');
  var upgradeButton = createUpgradeButton();
  var helpButton = createHelpButton();
  var dashboardButton = createDashboardButton();

  rightCnt.appendChild(upgradeButton);
  rightCnt.appendChild(helpButton);
  rightCnt.appendChild(dashboardButton);

  getExtensionUserData(function(userData) {
    upgradeButton.classList.toggle('hidden', !userData.shouldDisplayAwesomeCTA);
  });

  var leftCnt = createBtnContainer('left');
  var cancelButton = createCancelButton();

  leftCnt.appendChild(cancelButton);

  document.body.appendChild(iframe);
  document.body.appendChild(rightCnt);
  document.body.appendChild(leftCnt);

  $(document).on('click', '.buffer-btn-cancel', function() {
      closePopup(document, doneCallback);
  });

  // Bind close listener
  // Listen for when the overlay has closed itself
  bufferpm.bind('buffermessage', function(overlaydata) {
      closePopup(document, doneCallback, overlaydata);
  });

  /**
   * Listen to ESC key and close the popup when hit.
   */
  $(document).on('keyup.bufferOverlay', function(e) {
    if (e.keyCode == 27) {
      // When an overlay instance is hidden (but still open), don't let shortcuts close it
      if (!ensureOnlyOneOverlayOpen.isOverlayVisible()) return;

      closePopup(document, doneCallback);
    }
  });

  // Remove the loading image when we hear from the other side
  bufferpm.bind('buffer_loaded', function(data) {
    // Send user data to background script for caching
    if (data && data.userData) port.emit('buffer_user_data', data.userData);

    bufferpm.unbind('buffer_loaded');
    iframe.style.backgroundImage = 'none';

    $(".buffer-btn-cancel").remove();
  });
};

// Returns true if a new overlay should be open, false if we've toggled the visibility of
// and existing overlay instead.
function ensureOnlyOneOverlayOpen(data, closePopup) {
  // State can't be saved in this script, since it gets re-executed multiple times by some browsers
  // (e.g. Firefox), so we rely on the DOM instead.
  var isOverlayOpen = function() { return !!$('#buffer_overlay').length };

  // If the open intent comes from the Buffer toolbar button or a keyboard shortcut, toggle the
  // visibility of the overlay if it's already open, otherwise allow a new one to be open.
  if (data.placement == 'toolbar' || data.placement == 'hotkey') {
    if (!isOverlayOpen()) return true;

    $('#buffer_overlay, .buffer-btn-container').toggle();
    return false;
  }

  // If the open intent comes from somewhere else, discard any hidden overlay and open a new one
  closePopup();

  return true;
};

ensureOnlyOneOverlayOpen.isOverlayVisible = function() { return $('#buffer_overlay').is(':visible') };

function closePopup(document, doneCallback, overlayData) {
    $('#buffer_overlay, .buffer-btn-container').remove();

    bufferpm.unbind('buffermessage');
    bufferpm.unbind('buffer_addbutton');

    $(document).off('keyup.bufferOverlay');

    setTimeout(function () {
      doneCallback(overlayData);
    }, 0);

    window.focus();
}

// position = 'left' || 'right'
var createBtnContainer = function(position) {
  var container = document.createElement('div');
  container.setAttribute('class', 'buffer-btn-container buffer-btn-container-' + position);

  return container;
};

var createHelpButton = function() {
  var button;
  var text;

  button = document.createElement('a');
  button.href = 'https://buffer.com/app#contact-from-extension';
  button.target = '_blank';
  button.setAttribute('class', 'buffer-btn-help');

  text = document.createTextNode('Help');
  button.appendChild(text);

  button.addEventListener('click', function() {
    _bmq.trackAction(['overlay', 'help_button']);
  }, false);

  return button;
};

var createDashboardButton = function() {
  var button = document.createElement('a');
  button.href = 'https://buffer.com/app';
  button.target = '_blank';
  button.setAttribute('class', 'buffer-btn-dashboard');

  var text = document.createTextNode('Go to Buffer');
  button.appendChild(text);

  button.addEventListener('click', function() {
    _bmq.trackAction(['overlay', 'go_to_buffer_button']);
  }, false);

  return button;
};

var createCancelButton = function() {

  var button = document.createElement('button');
  button.setAttribute('class', 'buffer-btn-cancel');

  var text = document.createTextNode('Cancel');
  button.appendChild(text);

  return button;
};

var createUpgradeButton = function() {
  var button = document.createElement('a');
  button.href = 'https://buffer.com/awesome?utm_campaign=extensions_header&utm_medium=web';
  button.target = '_blank';
  button.setAttribute('class', 'buffer-btn-upgrade hidden');

  var text = document.createTextNode('Upgrade to Awesome');
  button.appendChild(text);

  button.addEventListener('click', function() {
    _bmq.trackAction(['overlay', 'upgrade_to_awesome_button']);
  }, false);

  return button;
};

// getOverlayConfig returns the configuration object for use by bufferOverlay
var getOverlayConfig = function(postData){

  var config = {};

  // Set this to true for using a local server while testing
  config.local = false;

  var segments = window.location.pathname.split('/');

  config.pocketWeb = ( window.location.host.indexOf('getpocket') !== -1 && segments[2] === 'read' );

  // Specification for gathering data for the overlay
  config.attributes = [
    {
      name: "url",
      get: function (cb) {
        if(config.pocketWeb){
          var li = document.getElementsByClassName('original')[0];
          var link = li.getElementsByTagName('a')[0].href;
          cb(link);
        }
        else{
          cb(window.location.href);
        }
      },
      encode: function (val) {
        return encodeURIComponent(val);
      }
    },
    {
      name: "text",
      get: function (cb) {
        var selectedText = document.getSelection().toString();
        var quoteChars;

        if (selectedText) {
          // If quotes surround the selected text, strip them away
          quoteChars = ['"', '“', '”', '\'', '‘', '’', '«', '»'];
          if (quoteChars.indexOf(selectedText[0]) != -1 && quoteChars.indexOf(selectedText[selectedText.length - 1]) != -1) {
            selectedText = selectedText.slice(1, selectedText.length - 1);
          }

          return cb('“' + selectedText + '”');
        }

        if (config.pocketWeb){
          var headline = document.querySelectorAll('.reader_head h1')[0];
          var title = headline && headline.textContent;
          return cb(title);
        }

        var ogTitle = document.head && document.head.querySelector('meta[property="og:title"]');
        if (ogTitle && ogTitle.content && ogTitle.content.length) {
          return cb(ogTitle.content);
        }

        cb(document.title);
      },
      encode: function (val) {
        return encodeURIComponent(val);
      }
    },
    {
      name: "retweeted_tweet_id",
      get: function (cb) {
        cb(postData.retweeted_tweet_id);
      },
      encode: function (val) {
        return encodeURIComponent(val);
      }
    },
    {
      name: "retweeted_user_id",
      get: function (cb) {
        cb(postData.retweeted_user_id);
      },
      encode: function (val) {
        return encodeURIComponent(val);
      }
    },
    {
      name: "retweeted_user_name",
      get: function (cb) {
        cb(postData.retweeted_user_name);
      },
      encode: function (val) {
        return encodeURIComponent(val);
      }
    },
    {
      name: "retweeted_user_display_name",
      get: function (cb) {
        cb(postData.retweeted_user_display_name);
      },
      encode: function (val) {
        return encodeURIComponent(val);
      }
    },
    {
      name: "retweet_comment",
      get: function (cb) {
        cb(postData.retweet_comment);
      },
      encode: function (val) {
        return encodeURIComponent(val);
      }
    },
    {
      name: "picture",
      get: function (cb) {
        cb(postData.image);
      },
      encode: function (val) {
        return encodeURIComponent(val);
      }
    },
    {
      name: "embed",
      get: function (cb) {
        cb(postData.embed);
      },
      encode: function (val) {
        return encodeURIComponent(val);
      }
    },
    {
      name: "local",
      get: function (cb) {
        cb(config.local);
      },
      encode: function (val) {
        return encodeURIComponent(val);
      }
    },
    {
      name: "version",
      get: function (cb) {
        cb(postData.version);
      },
      encode: function (val) {
        return encodeURIComponent(val);
      }
    },
    {
      name: "placement",
      get: function (cb) {
        if( postData.placement ) cb(postData.placement);
        else cb('general');
      },
      encode: function (val) {
        return encodeURIComponent(val);
      }
    }
  ];

  var loadingImgRel = 'img/black-loading-gif-small.gif';
  //TODO - Change to static.buffer when it's set up!
  var loadingImg = typeof chrome !== 'undefined' ?
    chrome.extension.getURL('data/shared/' +  loadingImgRel) :
    document.location.protocol === 'http:' ?
      'http://static.bufferapp.com/images/extensions/' + loadingImgRel :
      'https://d389zggrogs7qo.cloudfront.net/images/extensions/' + loadingImgRel;

  config.overlay = {
    endpoint: "https://buffer.com/add/",
    localendpoint: "https://local.buffer.com/add/",
    getCSS: function () {
      return [
        'border: none;',
        'height: 100%;',
        'width: 100%;',
        'position: fixed!important;',
        'z-index: 2147483646;',
        'top:0;',
        'left:0;',
        'display: block!important;',
        'max-width: 100%!important;',
        'max-height: 100%!important;',
        'padding: 0!important;',
        'background: rgba(245, 245, 245, 0.74) url(' + loadingImg +') no-repeat center center;',
        'background-size: 40px;'
      ].join('');
    }
  };

  return config;
};

// Method for handling the async firing of the cb
var executeAfter = function(done, count, args, cb) {
  if(done === count) {
    setTimeout(function(){
      cb.apply( null, args );
    }, 0);
  }
};

// Asynchronously gather data about the page and from embedded sources,
// like Twitter or Facebook. Currently the async is a bit over the top,
// and not used, but if we need async down the line, it's there.
var getData = function (postData, cb) {
  var config = getOverlayConfig( postData );
  var count = config.attributes.length;
  var done = 0;
  var data = {};

  config.attributes.forEach(function(attr, i){
    attr.get(function(d){
      done += 1;
      data[ attr.name ] = d;
      executeAfter(done, count, [ data, config ], cb);
    });
  });
};

// bufferData is triggered by the buffer_click listener in
// the buffer-browser-embed file, where it's passed a port
// to communicate with the extension and data sent from the
// background page.
var bufferData = function (port, postData) {

  if (window.top !== window) return;

  // Transform the data somewhat, and then create an overlay.
  // When it's done, fire buffer_done back to the extension
  var createOverlay = function (data, config) {
    if( data.embed ) {
      if( typeof data.embed === "object" ) {
        for( var i in data.embed ) {
          if( data.embed.hasOwnProperty(i) ) {
            data[i] = data.embed[i];
          }
        }
        if( data.embed.text && !data.embed.url ) {
          data.url = null;
        }
        data.embed = null;
      } else {
        data.text = data.embed;
        data.url = null;
        data.embed = null;
      }
    }
    bufferOverlay(data, config, port, function (overlaydata) {
      port.emit("buffer_done", overlaydata);
    });
  };

  // It all starts here.
  // createOverlay is the callback that should fire after getData has
  // gathered all the necessaries
  getData(postData, createOverlay);
};

// Cache for the port to avoid passing it around in function calls
var portCache = (function() {
  var _port;

  var exposed = {
    // Executed at the very beginning of bufferOverlay()'s execution to
    // update the cached port
    grabPort: function(port) {
      _port = port;
    },

    getPort: function() { return _port }
  };

  return exposed;
})();

// _bmq exposes the same API here as it does in background scripts, but
// here it only takes care of passing this data to the background script's
// _bmq where it will be effectively taken care of.
var _bmq = (function() {
  var _availableMethods = ['push', 'trackAction'];

  var _passForward = function(methodName) {
    var payload = {
      methodName: methodName,
      args: Array.prototype.slice.call(arguments, 1)
    };

    portCache.getPort().emit('buffer_tracking', payload);
  };

  var exposed = {};

  // Expose all _availableMethods
  _availableMethods.forEach(function(methodName) {
    exposed[methodName] = _passForward.bind(null, methodName);
  });

  return exposed;
}());

// Get some user data asynchronously; the callback will be run once immediately if such data has already
// been cached by the background script, and once shortly after displaying the overlay when the cache
// has been refreshed from the user data in the iframe
getExtensionUserData = function(cb) {
  portCache.getPort().on('buffer_user_data', cb);
};
<|endoftext|>


<commit_before>'use strict';

const _ = require('lodash');
const assert = require('assert');
const CONST = require('../common/constants');
const errors = require('../common/errors');
const NotImplementedBySubclass = errors.NotImplementedBySubclass;

class EventMeshServer {

  /*
   * EventMeshServer
   * ===============
   *
   * Various key types are documented below.
   *
   * Top level keys:
   *    - /services
   *    - /deployments
   *
   * Resources:
   *    - /deployments/<resourceType>/<resourceId>
   * Resource attributes:
   *    - /deployments/<resourceType>/<resourceId>/options
   *    - /deployments/<resourceType>/<resourceId>/state
   *    - /deployments/<resourceType>/<resourceId>/lastoperation
   *
   * Annotations:
   *    Annotations are operations that can be performed on the resource
   *    - <annotationName>/<annotationType>/<resourceId>/<annotationId>
   *    e.g:
   *      /backup/default/<resource guid>/<a guid>
   *      /backup/bbr/<resource guid>/<a guid>
   *      /restore/default/<resource guid>/<a guid>
   * Annotations attributes:
   *    - /<annotationName>/<annotationType>/<resourceId>/<annotationId>/options
   *    - /<annotationName>/<annotationType>/<resourceId>/<annotationId>/state
   *    - /<annotationName>/<annotationType>/<resourceId>/last
   *
   */

  constructor() {}

  checkValidState(state) {
    return Promise.try(() => {
      if (_.indexOf(_.map(CONST.APISERVER.RESOURCE_STATE, (a) => a), state) < 0) {
        throw new errors.NotFound(`Could not find state ${state}`);
      }
    });
  }

  getServiceFolderName(resourceType, serviceId) {
    return `services/${resourceType}/${serviceId}`;
  }

  getResourceFolderName(resourceType, resourceId) {
    return `deployments/${resourceType}/${resourceId}`;
  }

  getAnnotationFolderName(opts) {
    assert.ok(opts.resourceId, `Property 'resourceId' is required to get annotation folder name`);
    assert.ok(opts.annotationName, `Property 'annotationName' is required to get annotation folder name`);
    assert.ok(opts.annotationType, `Property 'annotationType' is required to get annotation folder name`);
    assert.ok(opts.annotationId, `Property 'annotationId' is required to get annotation folder name`);
    return `${opts.annotationName}/${opts.annotationType}/${opts.resourceId}/${opts.annotationId}`;
  }

  registerService(resourceType, serviceId, serviceAttributesValue, servicePlansValue) {
    /* jshint unused:false */
    throw new NotImplementedBySubclass('registerService');
  }

  getServiceAttributes(resourceType, serviceId) {
    /* jshint unused:false */
    throw new NotImplementedBySubclass('getServiceAttributes');
  }

  getServicePlans(resourceType, serviceId) {
    /* jshint unused:false */
    throw new NotImplementedBySubclass('getServicePlans');
  }

  createDeploymentResource(resourceType, resourceId, val) {
    /* jshint unused:false */
    throw new NotImplementedBySubclass('createDeploymentResource');
  }

  updateResourceState(resourceType, resourceId, stateValue) {
    /* jshint unused:false */
    throw new NotImplementedBySubclass('updateResourceState');
  }

  updateResourceKey(resourceType, resourceId, key, value) {
    /* jshint unused:false */
    throw new NotImplementedBySubclass('updateResourceKey');
  }

  getResourceKeyValue(resourceType, resourceId, key) {
    /* jshint unused:false */
    throw new NotImplementedBySubclass('getResourceKeyValue');
  }

  getResourceState(resourceType, resourceId) {
    /* jshint unused:false */
    throw new NotImplementedBySubclass('getResourceState');
  }

  registerWatcher(key, callback, watchOnPrefix) {
    /* jshint unused:false */
    throw new NotImplementedBySubclass('registerWatcher');
  }

  annotateResource(opts) {
    /* jshint unused:false */
    throw new NotImplementedBySubclass('annotateResource');
  }

  updateAnnotationState(opts) {
    /* jshint unused:false */
    throw new NotImplementedBySubclass('updateAnnotationState');
  }

  updateAnnotationKey(opts) {
    /* jshint unused:false */
    throw new NotImplementedBySubclass('updateAnnotationKey');
  }

  getAnnotationKeyValue(opts) {
    /* jshint unused:false */
    throw new NotImplementedBySubclass('getAnnotationKeyValue');
  }

  getAnnotationState(opts) {
    /* jshint unused:false */
    throw new NotImplementedBySubclass('getAnnotationState');
  }
}

module.exports = EventMeshServer;<commit_msg>Remove description<commit_after>'use strict';

const _ = require('lodash');
const assert = require('assert');
const CONST = require('../common/constants');
const errors = require('../common/errors');
const NotImplementedBySubclass = errors.NotImplementedBySubclass;

class EventMeshServer {

  constructor() {}

  checkValidState(state) {
    return Promise.try(() => {
      if (_.indexOf(_.map(CONST.APISERVER.RESOURCE_STATE, (a) => a), state) < 0) {
        throw new errors.NotFound(`Could not find state ${state}`);
      }
    });
  }

  getServiceFolderName(resourceType, serviceId) {
    return `services/${resourceType}/${serviceId}`;
  }

  getResourceFolderName(resourceType, resourceId) {
    return `deployments/${resourceType}/${resourceId}`;
  }

  getAnnotationFolderName(opts) {
    assert.ok(opts.resourceId, `Property 'resourceId' is required to get annotation folder name`);
    assert.ok(opts.annotationName, `Property 'annotationName' is required to get annotation folder name`);
    assert.ok(opts.annotationType, `Property 'annotationType' is required to get annotation folder name`);
    assert.ok(opts.annotationId, `Property 'annotationId' is required to get annotation folder name`);
    return `${opts.annotationName}/${opts.annotationType}/${opts.resourceId}/${opts.annotationId}`;
  }

  registerService(resourceType, serviceId, serviceAttributesValue, servicePlansValue) {
    /* jshint unused:false */
    throw new NotImplementedBySubclass('registerService');
  }

  getServiceAttributes(resourceType, serviceId) {
    /* jshint unused:false */
    throw new NotImplementedBySubclass('getServiceAttributes');
  }

  getServicePlans(resourceType, serviceId) {
    /* jshint unused:false */
    throw new NotImplementedBySubclass('getServicePlans');
  }

  createDeploymentResource(resourceType, resourceId, val) {
    /* jshint unused:false */
    throw new NotImplementedBySubclass('createDeploymentResource');
  }

  updateResourceState(resourceType, resourceId, stateValue) {
    /* jshint unused:false */
    throw new NotImplementedBySubclass('updateResourceState');
  }

  updateResourceKey(resourceType, resourceId, key, value) {
    /* jshint unused:false */
    throw new NotImplementedBySubclass('updateResourceKey');
  }

  getResourceKeyValue(resourceType, resourceId, key) {
    /* jshint unused:false */
    throw new NotImplementedBySubclass('getResourceKeyValue');
  }

  getResourceState(resourceType, resourceId) {
    /* jshint unused:false */
    throw new NotImplementedBySubclass('getResourceState');
  }

  registerWatcher(key, callback, watchOnPrefix) {
    /* jshint unused:false */
    throw new NotImplementedBySubclass('registerWatcher');
  }

  annotateResource(opts) {
    /* jshint unused:false */
    throw new NotImplementedBySubclass('annotateResource');
  }

  updateAnnotationState(opts) {
    /* jshint unused:false */
    throw new NotImplementedBySubclass('updateAnnotationState');
  }

  updateAnnotationKey(opts) {
    /* jshint unused:false */
    throw new NotImplementedBySubclass('updateAnnotationKey');
  }

  getAnnotationKeyValue(opts) {
    /* jshint unused:false */
    throw new NotImplementedBySubclass('getAnnotationKeyValue');
  }

  getAnnotationState(opts) {
    /* jshint unused:false */
    throw new NotImplementedBySubclass('getAnnotationState');
  }
}

module.exports = EventMeshServer;<|endoftext|>


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

/*
       Licensed to the Apache Software Foundation (ASF) under one
       or more contributor license agreements.  See the NOTICE file
       distributed with this work for additional information
       regarding copyright ownership.  The ASF licenses this file
       to you under the Apache License, Version 2.0 (the
       "License"); you may not use this file except in compliance
       with the License.  You may obtain a copy of the License at

         http://www.apache.org/licenses/LICENSE-2.0

       Unless required by applicable law or agreed to in writing,
       software distributed under the License is distributed on an
       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
       KIND, either express or implied.  See the License for the
       specific language governing permissions and limitations
       under the License.
*/

var shell = require('shelljs'),
    exec  = require('./exec'),
    Q     = require('q'),
    path  = require('path'),
    appinfo = require('./appinfo'),
    build = require('./build'),
    ROOT  = path.join(__dirname, '..', '..'),
    child_process = require('child_process'),
    new_emulator = 'cordova_emulator';

/**
 * Returns a Promise for a list of emulator images in the form of objects
 * {
       name   : <emulator_name>,
       path   : <path_to_emulator_image>,
       target : <api_target>,
       abi    : <cpu>,
       skin   : <skin>
   }
 */
module.exports.list_images = function() {
    return exec('android list avds')
    .then(function(output) {
        var response = output.split('\n');
        var emulator_list = [];
        for (var i = 1; i < response.length; i++) {
            // To return more detailed information use img_obj
            var img_obj = {};
            if (response[i].match(/Name:\s/)) {
                img_obj['name'] = response[i].split('Name: ')[1].replace('\r', '');
                if (response[i + 1].match(/Path:\s/)) {
                    i++;
                    img_obj['path'] = response[i].split('Path: ')[1].replace('\r', '');
                }
                if (response[i + 1].match(/\(API\slevel\s/)) {
                    i++;
                    img_obj['target'] = response[i].replace('\r', '');
                }
                if (response[i + 1].match(/ABI:\s/)) {
                    i++;
                    img_obj['abi'] = response[i].split('ABI: ')[1].replace('\r', '');
                }
                if (response[i + 1].match(/Skin:\s/)) {
                    i++;
                    img_obj['skin'] = response[i].split('Skin: ')[1].replace('\r', '');
                }

                emulator_list.push(img_obj);
            }
            /* To just return a list of names use this
            if (response[i].match(/Name:\s/)) {
                emulator_list.push(response[i].split('Name: ')[1].replace('\r', '');
            }*/

        }
        return emulator_list;
    });
}

/**
 * Will return the closest avd to the projects target
 * or undefined if no avds exist.
 * Returns a promise.
 */
module.exports.best_image = function() {
    var project_target = this.get_target().replace('android-', '');
    return this.list_images()
    .then(function(images) {
        var closest = 9999;
        var best = images[0];
        for (i in images) {
            var target = images[i].target;
            if(target) {
                var num = target.split('(API level ')[1].replace(')', '');
                if (num == project_target) {
                    return images[i];
                } else if (project_target - num < closest && project_target > num) {
                    var closest = project_target - num;
                    best = images[i];
                }
            }
        }
        return best;
    });
}

// Returns a promise.
module.exports.list_started = function() {
    return exec('adb devices')
    .then(function(output) {
        var response = output.split('\n');
        var started_emulator_list = [];
        for (var i = 1; i < response.length; i++) {
            if (response[i].match(/device/) && response[i].match(/emulator/)) {
                started_emulator_list.push(response[i].replace(/\tdevice/, '').replace('\r', ''));
            }
        }
        return started_emulator_list;
    });
}

module.exports.get_target = function() {
    var target = shell.grep(/target=android-[\d+]/, path.join(ROOT, 'project.properties'));
    return target.split('=')[1].replace('\n', '').replace('\r', '').replace(' ', '');
}

// Returns a promise.
module.exports.list_targets = function() {
    return exec('android list targets')
    .then(function(output) {
        var target_out = output.split('\n');
        var targets = [];
        for (var i = target_out.length; i >= 0; i--) {
            if(target_out[i].match(/id:/)) {
                targets.push(targets[i].split(' ')[1]);
            }
        }
        return targets;
    });
}

/*
 * Starts an emulator with the given ID,
 * and returns the started ID of that emulator.
 * If no ID is given it will used the first image available,
 * if no image is available it will error out (maybe create one?).
 *
 * Returns a promise.
 */
module.exports.start = function(emulator_ID) {
    var self = this;
    var emulator_id, num_started, started_emulators;

    return self.list_started()
    .then(function(list) {
        started_emulators = list;
        num_started = started_emulators.length;
        if (typeof emulator_ID === 'undefined') {
            return self.list_images()
            .then(function(emulator_list) {
                if (emulator_list.length > 0) {
                    return self.best_image()
                    .then(function(best) {
                        emulator_ID = best.name;
                        console.log('WARNING : no emulator specified, defaulting to ' + emulator_ID);
                        return emulator_ID;
                    });
                } else {
                    return Q.reject('ERROR : No emulator images (avds) found, if you would like to create an\n' +
                        ' avd follow the instructions provided here:\n' +
                        ' http://developer.android.com/tools/devices/index.html\n' +
                        ' Or run \'android create avd --name <name> --target <targetID>\'\n' +
                        ' in on the command line.');
                }
            });
        } else {
            return Q(emulator_ID);
        }
    }).then(function() {
        var cmd, args;
        if(process.platform == 'win32' || process.platform == 'win64') {
            cmd = '%comspec%';
            args = ['/c', 'start', 'cmd', '/c', 'emulator', '-avd', emulator_ID];
        } else {
            cmd = 'emulator';
            args = ['-avd', emulator_ID];
        }
        var proc = child_process.spawn(cmd, args, { stdio: 'inherit', detached: true });
        proc.unref(); // Don't wait for it to finish, since the emulator will probably keep running for a long time.
    }).then(function() {
        // wait for emulator to start
        console.log('Waiting for emulator...');
        return self.wait_for_emulator(num_started);
    }).then(function(new_started) {
        if (new_started.length > 1) {
            for (i in new_started) {
                if (started_emulators.indexOf(new_started[i]) < 0) {
                    emulator_id = new_started[i];
                }
            }
        } else {
            emulator_id = new_started[0];
        }
        if (!emulator_id) return Q.reject('ERROR :  Failed to start emulator, could not find new emulator');

        //wait for emulator to boot up
        process.stdout.write('Booting up emulator (this may take a while)...');
        return self.wait_for_boot(emulator_id);
    }).then(function() {
        console.log('BOOT COMPLETE');

        //unlock screen
        return exec('adb -s ' + emulator_id + ' shell input keyevent 82');
    }).then(function() {
        //return the new emulator id for the started emulators
        return emulator_id;
    });
}

/*
 * Waits for the new emulator to apear on the started-emulator list.
 * Returns a promise with a list of newly started emulators' IDs.
 */
module.exports.wait_for_emulator = function(num_running) {
    var self = this;
    return self.list_started()
    .then(function(new_started) {
        if (new_started.length > num_running) {
            return new_started;
        } else {
            return Q.delay(1000).then(function() {
                return self.wait_for_emulator(num_running);
            });
        }
    });
}

/*
 * Waits for the boot animation property of the emulator to switch to 'stopped'
 */
module.exports.wait_for_boot = function(emulator_id) {
    var self = this;
    return exec('adb -s ' + emulator_id + ' shell getprop init.svc.bootanim')
    .then(function(output) {
        if (output.match(/stopped/)) {
            return;
        } else {
            process.stdout.write('.');
            return Q.delay(3000).then(function() {
                return self.wait_for_boot(emulator_id);
            });
        }
    });
}

/*
 * Create avd
 * TODO : Enter the stdin input required to complete the creation of an avd.
 * Returns a promise.
 */
module.exports.create_image = function(name, target) {
    console.log('Creating avd named ' + name);
    if (target) {
        return exec('android create avd --name ' + name + ' --target ' + target)
        .then(null, function(error) {
            console.error('ERROR : Failed to create emulator image : ');
            console.error(' Do you have the latest android targets including ' + target + '?');
            console.error(create.output);
        });
    } else {
        console.log('WARNING : Project target not found, creating avd with a different target but the project may fail to install.');
        return exec('android create avd --name ' + name + ' --target ' + this.list_targets()[0])
        .then(function() {
            // TODO: This seems like another error case, even though it always happens.
            console.error('ERROR : Unable to create an avd emulator, no targets found.');
            console.error('Please insure you have targets available by running the "android" command');
            return Q.reject();
        }, function(error) {
            console.error('ERROR : Failed to create emulator image : ');
            console.error(error);
        });
    }
}

/*
 * Installs a previously built application on the emulator and launches it.
 * If no target is specified, then it picks one.
 * If no started emulators are found, error out.
 * Returns a promise.
 */
module.exports.install = function(target) {
    var self = this;
    return this.list_started()
    .then(function(emulator_list) {
        if (emulator_list.length < 1) {
            return Q.reject('No started emulators found, please start an emultor before deploying your project.');
        }

        // default emulator
        target = typeof target !== 'undefined' ? target : emulator_list[0];
        if (emulator_list.indexOf(target) < 0) {
            return Q.reject('Unable to find target \'' + target + '\'. Failed to deploy to emulator.');
        }

        console.log('Installing app on emulator...');
        var apk_path = build.get_apk();
        return exec('adb -s ' + target + ' install -r "' + apk_path + '"');
    }).then(function(output) {
        if (output.match(/Failure/)) {
            return Q.reject('Failed to install apk to emulator: ' + output);
        }
        return Q();
    }, function(err) {
        return Q.reject('Failed to install apk to emulator: ' + err);
    }).then(function() {
        //unlock screen
        return exec('adb -s ' + target + ' shell input keyevent 82');
    }).then(function() {
        // launch the application
        console.log('Launching application...');
        var launchName = appinfo.getActivityName();
        cmd = 'adb -s ' + target + ' shell am start -W -a android.intent.action.MAIN -n ' + launchName;
        return exec(cmd);
    }).then(function(output) {
        console.log('LAUNCH SUCCESS');
    }, function(err) {
        return Q.reject('Failed to launch app on emulator: ' + err);
    });
}
<commit_msg>CB-5803 Fix cordova/emulate on windows.<commit_after>#!/usr/bin/env node

/*
       Licensed to the Apache Software Foundation (ASF) under one
       or more contributor license agreements.  See the NOTICE file
       distributed with this work for additional information
       regarding copyright ownership.  The ASF licenses this file
       to you under the Apache License, Version 2.0 (the
       "License"); you may not use this file except in compliance
       with the License.  You may obtain a copy of the License at

         http://www.apache.org/licenses/LICENSE-2.0

       Unless required by applicable law or agreed to in writing,
       software distributed under the License is distributed on an
       "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
       KIND, either express or implied.  See the License for the
       specific language governing permissions and limitations
       under the License.
*/

var shell = require('shelljs'),
    exec  = require('./exec'),
    Q     = require('q'),
    path  = require('path'),
    appinfo = require('./appinfo'),
    build = require('./build'),
    ROOT  = path.join(__dirname, '..', '..'),
    child_process = require('child_process'),
    new_emulator = 'cordova_emulator';

/**
 * Returns a Promise for a list of emulator images in the form of objects
 * {
       name   : <emulator_name>,
       path   : <path_to_emulator_image>,
       target : <api_target>,
       abi    : <cpu>,
       skin   : <skin>
   }
 */
module.exports.list_images = function() {
    return exec('android list avds')
    .then(function(output) {
        var response = output.split('\n');
        var emulator_list = [];
        for (var i = 1; i < response.length; i++) {
            // To return more detailed information use img_obj
            var img_obj = {};
            if (response[i].match(/Name:\s/)) {
                img_obj['name'] = response[i].split('Name: ')[1].replace('\r', '');
                if (response[i + 1].match(/Path:\s/)) {
                    i++;
                    img_obj['path'] = response[i].split('Path: ')[1].replace('\r', '');
                }
                if (response[i + 1].match(/\(API\slevel\s/)) {
                    i++;
                    img_obj['target'] = response[i].replace('\r', '');
                }
                if (response[i + 1].match(/ABI:\s/)) {
                    i++;
                    img_obj['abi'] = response[i].split('ABI: ')[1].replace('\r', '');
                }
                if (response[i + 1].match(/Skin:\s/)) {
                    i++;
                    img_obj['skin'] = response[i].split('Skin: ')[1].replace('\r', '');
                }

                emulator_list.push(img_obj);
            }
            /* To just return a list of names use this
            if (response[i].match(/Name:\s/)) {
                emulator_list.push(response[i].split('Name: ')[1].replace('\r', '');
            }*/

        }
        return emulator_list;
    });
}

/**
 * Will return the closest avd to the projects target
 * or undefined if no avds exist.
 * Returns a promise.
 */
module.exports.best_image = function() {
    var project_target = this.get_target().replace('android-', '');
    return this.list_images()
    .then(function(images) {
        var closest = 9999;
        var best = images[0];
        for (i in images) {
            var target = images[i].target;
            if(target) {
                var num = target.split('(API level ')[1].replace(')', '');
                if (num == project_target) {
                    return images[i];
                } else if (project_target - num < closest && project_target > num) {
                    var closest = project_target - num;
                    best = images[i];
                }
            }
        }
        return best;
    });
}

// Returns a promise.
module.exports.list_started = function() {
    return exec('adb devices')
    .then(function(output) {
        var response = output.split('\n');
        var started_emulator_list = [];
        for (var i = 1; i < response.length; i++) {
            if (response[i].match(/device/) && response[i].match(/emulator/)) {
                started_emulator_list.push(response[i].replace(/\tdevice/, '').replace('\r', ''));
            }
        }
        return started_emulator_list;
    });
}

module.exports.get_target = function() {
    var target = shell.grep(/target=android-[\d+]/, path.join(ROOT, 'project.properties'));
    return target.split('=')[1].replace('\n', '').replace('\r', '').replace(' ', '');
}

// Returns a promise.
module.exports.list_targets = function() {
    return exec('android list targets')
    .then(function(output) {
        var target_out = output.split('\n');
        var targets = [];
        for (var i = target_out.length; i >= 0; i--) {
            if(target_out[i].match(/id:/)) {
                targets.push(targets[i].split(' ')[1]);
            }
        }
        return targets;
    });
}

/*
 * Starts an emulator with the given ID,
 * and returns the started ID of that emulator.
 * If no ID is given it will used the first image available,
 * if no image is available it will error out (maybe create one?).
 *
 * Returns a promise.
 */
module.exports.start = function(emulator_ID) {
    var self = this;
    var emulator_id, num_started, started_emulators;

    return self.list_started()
    .then(function(list) {
        started_emulators = list;
        num_started = started_emulators.length;
        if (typeof emulator_ID === 'undefined') {
            return self.list_images()
            .then(function(emulator_list) {
                if (emulator_list.length > 0) {
                    return self.best_image()
                    .then(function(best) {
                        emulator_ID = best.name;
                        console.log('WARNING : no emulator specified, defaulting to ' + emulator_ID);
                        return emulator_ID;
                    });
                } else {
                    return Q.reject('ERROR : No emulator images (avds) found, if you would like to create an\n' +
                        ' avd follow the instructions provided here:\n' +
                        ' http://developer.android.com/tools/devices/index.html\n' +
                        ' Or run \'android create avd --name <name> --target <targetID>\'\n' +
                        ' in on the command line.');
                }
            });
        } else {
            return Q(emulator_ID);
        }
    }).then(function() {
        var cmd = 'emulator';
        var args = ['-avd', emulator_ID];
        var proc = child_process.spawn(cmd, args, { stdio: 'inherit', detached: true });
        proc.unref(); // Don't wait for it to finish, since the emulator will probably keep running for a long time.
    }).then(function() {
        // wait for emulator to start
        console.log('Waiting for emulator...');
        return self.wait_for_emulator(num_started);
    }).then(function(new_started) {
        if (new_started.length > 1) {
            for (i in new_started) {
                if (started_emulators.indexOf(new_started[i]) < 0) {
                    emulator_id = new_started[i];
                }
            }
        } else {
            emulator_id = new_started[0];
        }
        if (!emulator_id) return Q.reject('ERROR :  Failed to start emulator, could not find new emulator');

        //wait for emulator to boot up
        process.stdout.write('Booting up emulator (this may take a while)...');
        return self.wait_for_boot(emulator_id);
    }).then(function() {
        console.log('BOOT COMPLETE');

        //unlock screen
        return exec('adb -s ' + emulator_id + ' shell input keyevent 82');
    }).then(function() {
        //return the new emulator id for the started emulators
        return emulator_id;
    });
}

/*
 * Waits for the new emulator to apear on the started-emulator list.
 * Returns a promise with a list of newly started emulators' IDs.
 */
module.exports.wait_for_emulator = function(num_running) {
    var self = this;
    return self.list_started()
    .then(function(new_started) {
        if (new_started.length > num_running) {
            return new_started;
        } else {
            return Q.delay(1000).then(function() {
                return self.wait_for_emulator(num_running);
            });
        }
    });
}

/*
 * Waits for the boot animation property of the emulator to switch to 'stopped'
 */
module.exports.wait_for_boot = function(emulator_id) {
    var self = this;
    return exec('adb -s ' + emulator_id + ' shell getprop init.svc.bootanim')
    .then(function(output) {
        if (output.match(/stopped/)) {
            return;
        } else {
            process.stdout.write('.');
            return Q.delay(3000).then(function() {
                return self.wait_for_boot(emulator_id);
            });
        }
    });
}

/*
 * Create avd
 * TODO : Enter the stdin input required to complete the creation of an avd.
 * Returns a promise.
 */
module.exports.create_image = function(name, target) {
    console.log('Creating avd named ' + name);
    if (target) {
        return exec('android create avd --name ' + name + ' --target ' + target)
        .then(null, function(error) {
            console.error('ERROR : Failed to create emulator image : ');
            console.error(' Do you have the latest android targets including ' + target + '?');
            console.error(create.output);
        });
    } else {
        console.log('WARNING : Project target not found, creating avd with a different target but the project may fail to install.');
        return exec('android create avd --name ' + name + ' --target ' + this.list_targets()[0])
        .then(function() {
            // TODO: This seems like another error case, even though it always happens.
            console.error('ERROR : Unable to create an avd emulator, no targets found.');
            console.error('Please insure you have targets available by running the "android" command');
            return Q.reject();
        }, function(error) {
            console.error('ERROR : Failed to create emulator image : ');
            console.error(error);
        });
    }
}

/*
 * Installs a previously built application on the emulator and launches it.
 * If no target is specified, then it picks one.
 * If no started emulators are found, error out.
 * Returns a promise.
 */
module.exports.install = function(target) {
    var self = this;
    return this.list_started()
    .then(function(emulator_list) {
        if (emulator_list.length < 1) {
            return Q.reject('No started emulators found, please start an emultor before deploying your project.');
        }

        // default emulator
        target = typeof target !== 'undefined' ? target : emulator_list[0];
        if (emulator_list.indexOf(target) < 0) {
            return Q.reject('Unable to find target \'' + target + '\'. Failed to deploy to emulator.');
        }

        console.log('Installing app on emulator...');
        var apk_path = build.get_apk();
        return exec('adb -s ' + target + ' install -r "' + apk_path + '"');
    }).then(function(output) {
        if (output.match(/Failure/)) {
            return Q.reject('Failed to install apk to emulator: ' + output);
        }
        return Q();
    }, function(err) {
        return Q.reject('Failed to install apk to emulator: ' + err);
    }).then(function() {
        //unlock screen
        return exec('adb -s ' + target + ' shell input keyevent 82');
    }).then(function() {
        // launch the application
        console.log('Launching application...');
        var launchName = appinfo.getActivityName();
        cmd = 'adb -s ' + target + ' shell am start -W -a android.intent.action.MAIN -n ' + launchName;
        return exec(cmd);
    }).then(function(output) {
        console.log('LAUNCH SUCCESS');
    }, function(err) {
        return Q.reject('Failed to launch app on emulator: ' + err);
    });
}
<|endoftext|>