<commit_before>define( ['dojo/_base/array',
         'JBrowse/Util',
         './Util',
         'JBrowse/Model/SimpleFeature'
        ],
        function( array, Util, BAMUtil, SimpleFeature ) {

var SEQRET_DECODER = ['=', 'A', 'C', 'x', 'G', 'x', 'x', 'x', 'T', 'x', 'x', 'x', 'x', 'x', 'x', 'N'];
var CIGAR_DECODER  = ['M', 'I', 'D', 'N', 'S', 'H', 'P', '=', 'X', '?', '?', '?', '?', '?', '?', '?'];

var readInt   = BAMUtil.readInt;
var readShort = BAMUtil.readShort;
var readFloat = BAMUtil.readFloat;

var Feature = Util.fastDeclare(
{
    constructor: function( args ) {
        this.file  = args.file;
        this.data  = {
            type: 'match',
            source: args.store.source
        };
        this.bytes = {
            start: args.bytes.start,
            end: args.bytes.end,
            byteArray: args.bytes.byteArray
        };

        this._coreParse();
    },

    get: function( field) {
        return this._get( field.toLowerCase() );
    },

    // same as get(), except requires lower-case arguments.  used
    // internally to save lots of calls to field.toLowerCase()
    _get: function( field ) {
        return field in this.data ? this.data[field] : // have we already parsed it out?
            function(field) {
                var v = this.data[field] =
                    this[field]            ? this[field]()            : // maybe we have a special parser for it
                    this._flagMasks[field] ? this._parseFlag( field ) : // or is it a flag?
                                             this._parseTag( field );   // otherwise, look for it in the tags
                return v;
            }.call(this,field);
    },

    tags: function() {
        return this._get('_tags');
    },

    _tags: function() {
        this._parseAllTags();

        var tags = [ 'seq', 'seq_reverse_complemented', 'unmapped','qc_failed','duplicate','secondary_alignment','supplementary_alignment' ];

        if( ! this._get('unmapped') )
            tags.push( 'start', 'end', 'strand', 'score', 'qual', 'MQ', 'CIGAR', 'length_on_ref', 'template_length' );
        if( this._get('multi_segment_template') ) {
            tags.push( 'multi_segment_all_correctly_aligned',
                       'multi_segment_next_segment_unmapped',
                       'multi_segment_next_segment_reversed',
                       'multi_segment_first',
                       'multi_segment_last',
                       'next_segment_position'
                     );
        }
        tags = tags.concat( this._tagList || [] );

        var d = this.data;
        for( var k in d ) {
            if( d.hasOwnProperty( k ) && k[0] != '_'
                && k != 'multi_segment_all_aligned'
                && k != 'next_seq_id')
                tags.push( k );
        }

        var seen = {};
        tags = array.filter( tags, function(t) {
            if( t in this.data && this.data[t] === undefined )
                return false;

            var lt = t.toLowerCase();
            var s = seen[lt];
            seen[lt] = true;
            return ! s;
        },this);

        return tags;
    },

    parent: function() {
        return undefined;
    },

    children: function() {
        return this._get('subfeatures');
    },

    id: function() {
        return this._get('name')+'/'+this._get('md')+'/'+this._get('cigar')+'/'+this._get('start')+'/'+this._get('multi_segment_next_segment_reversed');
    },

    multi_segment_all_aligned: function() {
        return this._get('multi_segment_all_correctly_aligned');
    },

    // special parsers
    /**
     * Mapping quality score.
     */
    mq: function() {
        var mq = (this._get('_bin_mq_nl') & 0xff00) >> 8;
        return mq == 255 ? undefined : mq;
    },
    score: function() {
        return this._get('mq');
    },
    qual: function() {
        if( this._get('unmapped') )
            return undefined;

        var qseq = [];
        var byteArray = this.bytes.byteArray;
        var p = this.bytes.start + 36 + this._get('_l_read_name') + this._get('_n_cigar_op')*4 + this._get('_seq_bytes');
        var lseq = this._get('seq_length');
        for (var j = 0; j < lseq; ++j) {
            qseq.push( byteArray[p + j] );
        }
        return qseq.join(' ');
    },
    strand: function() {
        var xs = this._get('xs');
        return xs ? ( xs == '-' ? -1 : 1 ) :
               this._get('seq_reverse_complemented') ? -1 :  1;
    },
    multi_segment_next_segment_strand: function() {
      if(this._get('multi_segment_next_segment_unmapped'))
        return undefined;
      return this._get('multi_segment_next_segment_reversed') ? -1 : 1;
    },

    /**
     * Length in characters of the read name.
     */
    _l_read_name: function() {
        return this._get('_bin_mq_nl') & 0xff;
    },
    /**
     * number of bytes in the sequence field
     */
    _seq_bytes: function() {
        return (this._get('seq_length') + 1) >> 1;
    },
    seq: function() {
        var seq = '';
        var byteArray = this.bytes.byteArray;
        var p = this.bytes.start + 36 + this._get('_l_read_name') + this._get('_n_cigar_op')*4;
        var seqBytes = this._get('_seq_bytes');
        for (var j = 0; j < seqBytes; ++j) {
            var sb = byteArray[p + j];
            seq += SEQRET_DECODER[(sb & 0xf0) >> 4];
            seq += SEQRET_DECODER[(sb & 0x0f)];
        }
        return seq;
    },
    name: function() {
        return this._get('_read_name');
    },
    _read_name: function() {
        var byteArray = this.bytes.byteArray;
        var readName = '';
        var nl = this._get('_l_read_name');
        var p = this.bytes.start + 36;
        for (var j = 0; j < nl-1; ++j) {
            readName += String.fromCharCode(byteArray[p+j]);
        }
        return readName;
    },
    _n_cigar_op: function() {
        return this._get('_flag_nc') & 0xffff;
    },
    cigar: function() {
        if( this._get('unmapped') )
            return undefined;

        var byteArray   = this.bytes.byteArray;
        var numCigarOps = this._get('_n_cigar_op');
        var p = this.bytes.start + 36 + this._get('_l_read_name');
        var cigar = '';
        var lref = 0;
        for (var c = 0; c < numCigarOps; ++c) {
            var cigop = readInt(byteArray, p);
            var lop = cigop >> 4;
            var op = CIGAR_DECODER[cigop & 0xf];
            cigar += lop + op;

            // soft clip, hard clip, and insertion don't count toward
            // the length on the reference
            if( op != 'H' && op != 'S' && op != 'I' )
                lref += lop;

            p += 4;
        }

        this.data.length_on_ref = lref;
        return cigar;
    },
    next_segment_position: function() {
        var nextSegment = this.file.indexToChr[this._get('_next_refid')];
        if( nextSegment )
            return nextSegment.name+':'+this._get('_next_pos');
        else
            return undefined;
    },
    subfeatures: function() {
        var cigar = this._get('cigar');
        if( cigar )
            return this._cigarToSubfeats( cigar );

        return undefined;
    },
    length_on_ref: function() {
        var c = this._get('cigar'); // the length_on_ref is set as a
                                   // side effect of the CIGAR parsing
        return this.data.length_on_ref;
    },
    _flags: function() {
        return (this.get('_flag_nc') & 0xffff0000) >> 16;
    },
    end: function() {
        return this._get('start') + ( this._get('length_on_ref') || this._get('seq_length') || undefined );
    },

    seq_id: function() {
        if( this._get('unmapped') )
            return undefined;

        return ( this.file.indexToChr[ this._refID ] || {} ).name;
    },

    next_seq_id: function() {
        if( this._get('multi_segment_next_segment_unmapped') )
            return undefined;
        return ( this.file.indexToChr[this._get('_next_refid')] || {} ).name;
    },

    _bin_mq_nl: function() {
        with( this.bytes )
            return readInt( byteArray, start + 12  );
    },
    _flag_nc: function() {
        with( this.bytes )
            return readInt( byteArray, start + 16 );
    },
    seq_length: function() {
        with( this.bytes )
            return readInt( byteArray, start + 20 );
    },
    _next_refid: function() {
        with( this.bytes )
            return readInt( byteArray, start + 24 );
    },
    _next_pos: function() {
        with( this.bytes )
            return readInt( byteArray, start + 28 );
    },
    template_length: function() {
        with( this.bytes )
            return readInt( byteArray, start + 32 );
    },

    /**
     * parse the core data: ref ID and start
     */
    _coreParse: function() {
        with( this.bytes ) {
            this._refID      = readInt( byteArray, start + 4 );
            this.data.start  = readInt( byteArray, start + 8 );
        }
    },

    /**
     * Get the value of a tag, parsing the tags as far as necessary.
     * Only called if we have not already parsed that field.
     */
    _parseTag: function( tagName ) {
        // if all of the tags have been parsed and we're still being
        // called, we already know that we have no such tag, because
        // it would already have been cached.
        if( this._allTagsParsed )
            return undefined;

        this._tagList = this._tagList || [];
        var byteArray = this.bytes.byteArray;
        var p = this._tagOffset || this.bytes.start + 36 + this._get('_l_read_name') + this._get('_n_cigar_op')*4 + this._get('_seq_bytes') + this._get('seq_length');

        var blockEnd = this.bytes.end;
        while( p < blockEnd && lcTag != tagName ) {
            var tag      = String.fromCharCode( byteArray[p], byteArray[ p+1 ] );
            var lcTag    = tag.toLowerCase();
            var type = String.fromCharCode( byteArray[ p+2 ] );
            p += 3;

            var value;
            switch( type.toLowerCase() ) {
            case 'a':
                value = String.fromCharCode( byteArray[p] );
                p += 1;
                break;
            case 'i':
                value = readInt(byteArray, p );
                p += 4;
                break;
            case 'c':
                value = byteArray[p];
                p += 1;
                break;
            case 's':
                value = readShort(byteArray, p);
                p += 2;
                break;
            case 'f':
                value = readFloat( byteArray, p );
                p += 4;
                break;
            case 'z':
            case 'h':
                value = '';
                while( p <= blockEnd ) {
                    var cc = byteArray[p++];
                    if( cc == 0 ) {
                        break;
                    }
                    else {
                        value += String.fromCharCode(cc);
                    }
                }
                break;
            default:
                console.warn( "Unknown BAM tag type '"+type
                              +"', tags may be incomplete"
                            );
                value = undefined;
                p = blockEnd; // stop parsing tags
            }

            this._tagOffset = p;

            this._tagList.push( tag );
            if( lcTag == tagName )
                return value;
            else {
                this.data[ lcTag ] = value;
            }
        }
        this._allTagsParsed = true;
        return undefined;
    },
    _parseAllTags: function() {
        this._parseTag(); // calling _parseTag with no arg just parses
        // all the tags and returns the last one
    },

    _flagMasks: {
        multi_segment_template:              0x1,
        multi_segment_all_correctly_aligned: 0x2,
        unmapped:                            0x4,
        multi_segment_next_segment_unmapped: 0x8,
        seq_reverse_complemented:            0x10,
        multi_segment_next_segment_reversed: 0x20,
        multi_segment_first:                 0x40,
        multi_segment_last:                  0x80,
        secondary_alignment:                 0x100,
        qc_failed:                           0x200,
        duplicate:                           0x400,
        supplementary_alignment:             0x800
    },

    _parseFlag: function( flagName ) {
        return !!( this._get('_flags') & this._flagMasks[flagName] );
    },

    _parseCigar: function( cigar ) {
        return array.map( cigar.match(/\d+\D/g), function( op ) {
           return [ op.match(/\D/)[0].toUpperCase(), parseInt( op ) ];
        });
    },

    /**
     *  take a cigar string, and initial position, return an array of subfeatures
     */
    _cigarToSubfeats: function(cigar)    {
        var subfeats = [];
        var min = this._get('start');
        var max;
        var ops = this._parseCigar( cigar );
        for (var i = 0; i < ops.length; i++)  {
            var lop = ops[i][1];
            var op = ops[i][0];  // operation type
            // converting "=" to "E" to avoid possible problems later with non-alphanumeric type name
            if (op === "=")  { op = "E"; }

            switch (op) {
            case 'M':
            case 'D':
            case 'N':
            case 'E':
            case 'X':
                max = min + lop;
                break;
            case 'I':
                max = min;
                break;
            case 'P':  // not showing padding deletions (possibly change this later -- could treat same as 'I' ?? )
            case 'H':  // not showing hard clipping (since it's unaligned, and offset arg meant to be beginning of aligned part)
            case 'S':  // not showing soft clipping (since it's unaligned, and offset arg meant to be beginning of aligned part)
                break;
                // other possible cases
            }
            if( op !== 'N' ) {
                subfeats.push(
                    new SimpleFeature(
                        {
                            data: {
                                type: op,
                                start: min,
                                end: max,
                                strand: this._get('strand'),
                                cigar_op: lop+op
                            },
                            parent: this
                        })
                );
            }
            min = max;
        }
        return subfeats;
    }

});

return Feature;
});
<commit_msg>Remove XS parsing from the strand function. Fixes #520. See #473<commit_after>define( ['dojo/_base/array',
         'JBrowse/Util',
         './Util',
         'JBrowse/Model/SimpleFeature'
        ],
        function( array, Util, BAMUtil, SimpleFeature ) {

var SEQRET_DECODER = ['=', 'A', 'C', 'x', 'G', 'x', 'x', 'x', 'T', 'x', 'x', 'x', 'x', 'x', 'x', 'N'];
var CIGAR_DECODER  = ['M', 'I', 'D', 'N', 'S', 'H', 'P', '=', 'X', '?', '?', '?', '?', '?', '?', '?'];

var readInt   = BAMUtil.readInt;
var readShort = BAMUtil.readShort;
var readFloat = BAMUtil.readFloat;

var Feature = Util.fastDeclare(
{
    constructor: function( args ) {
        this.file  = args.file;
        this.data  = {
            type: 'match',
            source: args.store.source
        };
        this.bytes = {
            start: args.bytes.start,
            end: args.bytes.end,
            byteArray: args.bytes.byteArray
        };

        this._coreParse();
    },

    get: function( field) {
        return this._get( field.toLowerCase() );
    },

    // same as get(), except requires lower-case arguments.  used
    // internally to save lots of calls to field.toLowerCase()
    _get: function( field ) {
        return field in this.data ? this.data[field] : // have we already parsed it out?
            function(field) {
                var v = this.data[field] =
                    this[field]            ? this[field]()            : // maybe we have a special parser for it
                    this._flagMasks[field] ? this._parseFlag( field ) : // or is it a flag?
                                             this._parseTag( field );   // otherwise, look for it in the tags
                return v;
            }.call(this,field);
    },

    tags: function() {
        return this._get('_tags');
    },

    _tags: function() {
        this._parseAllTags();

        var tags = [ 'seq', 'seq_reverse_complemented', 'unmapped','qc_failed','duplicate','secondary_alignment','supplementary_alignment' ];

        if( ! this._get('unmapped') )
            tags.push( 'start', 'end', 'strand', 'score', 'qual', 'MQ', 'CIGAR', 'length_on_ref', 'template_length' );
        if( this._get('multi_segment_template') ) {
            tags.push( 'multi_segment_all_correctly_aligned',
                       'multi_segment_next_segment_unmapped',
                       'multi_segment_next_segment_reversed',
                       'multi_segment_first',
                       'multi_segment_last',
                       'next_segment_position'
                     );
        }
        tags = tags.concat( this._tagList || [] );

        var d = this.data;
        for( var k in d ) {
            if( d.hasOwnProperty( k ) && k[0] != '_'
                && k != 'multi_segment_all_aligned'
                && k != 'next_seq_id')
                tags.push( k );
        }

        var seen = {};
        tags = array.filter( tags, function(t) {
            if( t in this.data && this.data[t] === undefined )
                return false;

            var lt = t.toLowerCase();
            var s = seen[lt];
            seen[lt] = true;
            return ! s;
        },this);

        return tags;
    },

    parent: function() {
        return undefined;
    },

    children: function() {
        return this._get('subfeatures');
    },

    id: function() {
        return this._get('name')+'/'+this._get('md')+'/'+this._get('cigar')+'/'+this._get('start')+'/'+this._get('multi_segment_next_segment_reversed');
    },

    multi_segment_all_aligned: function() {
        return this._get('multi_segment_all_correctly_aligned');
    },

    // special parsers
    /**
     * Mapping quality score.
     */
    mq: function() {
        var mq = (this._get('_bin_mq_nl') & 0xff00) >> 8;
        return mq == 255 ? undefined : mq;
    },
    score: function() {
        return this._get('mq');
    },
    qual: function() {
        if( this._get('unmapped') )
            return undefined;

        var qseq = [];
        var byteArray = this.bytes.byteArray;
        var p = this.bytes.start + 36 + this._get('_l_read_name') + this._get('_n_cigar_op')*4 + this._get('_seq_bytes');
        var lseq = this._get('seq_length');
        for (var j = 0; j < lseq; ++j) {
            qseq.push( byteArray[p + j] );
        }
        return qseq.join(' ');
    },
    strand: function() {
        return this._get('seq_reverse_complemented') ? -1 :  1;
    },
    multi_segment_next_segment_strand: function() {
      if(this._get('multi_segment_next_segment_unmapped'))
        return undefined;
      return this._get('multi_segment_next_segment_reversed') ? -1 : 1;
    },

    /**
     * Length in characters of the read name.
     */
    _l_read_name: function() {
        return this._get('_bin_mq_nl') & 0xff;
    },
    /**
     * number of bytes in the sequence field
     */
    _seq_bytes: function() {
        return (this._get('seq_length') + 1) >> 1;
    },
    seq: function() {
        var seq = '';
        var byteArray = this.bytes.byteArray;
        var p = this.bytes.start + 36 + this._get('_l_read_name') + this._get('_n_cigar_op')*4;
        var seqBytes = this._get('_seq_bytes');
        for (var j = 0; j < seqBytes; ++j) {
            var sb = byteArray[p + j];
            seq += SEQRET_DECODER[(sb & 0xf0) >> 4];
            seq += SEQRET_DECODER[(sb & 0x0f)];
        }
        return seq;
    },
    name: function() {
        return this._get('_read_name');
    },
    _read_name: function() {
        var byteArray = this.bytes.byteArray;
        var readName = '';
        var nl = this._get('_l_read_name');
        var p = this.bytes.start + 36;
        for (var j = 0; j < nl-1; ++j) {
            readName += String.fromCharCode(byteArray[p+j]);
        }
        return readName;
    },
    _n_cigar_op: function() {
        return this._get('_flag_nc') & 0xffff;
    },
    cigar: function() {
        if( this._get('unmapped') )
            return undefined;

        var byteArray   = this.bytes.byteArray;
        var numCigarOps = this._get('_n_cigar_op');
        var p = this.bytes.start + 36 + this._get('_l_read_name');
        var cigar = '';
        var lref = 0;
        for (var c = 0; c < numCigarOps; ++c) {
            var cigop = readInt(byteArray, p);
            var lop = cigop >> 4;
            var op = CIGAR_DECODER[cigop & 0xf];
            cigar += lop + op;

            // soft clip, hard clip, and insertion don't count toward
            // the length on the reference
            if( op != 'H' && op != 'S' && op != 'I' )
                lref += lop;

            p += 4;
        }

        this.data.length_on_ref = lref;
        return cigar;
    },
    next_segment_position: function() {
        var nextSegment = this.file.indexToChr[this._get('_next_refid')];
        if( nextSegment )
            return nextSegment.name+':'+this._get('_next_pos');
        else
            return undefined;
    },
    subfeatures: function() {
        var cigar = this._get('cigar');
        if( cigar )
            return this._cigarToSubfeats( cigar );

        return undefined;
    },
    length_on_ref: function() {
        var c = this._get('cigar'); // the length_on_ref is set as a
                                   // side effect of the CIGAR parsing
        return this.data.length_on_ref;
    },
    _flags: function() {
        return (this.get('_flag_nc') & 0xffff0000) >> 16;
    },
    end: function() {
        return this._get('start') + ( this._get('length_on_ref') || this._get('seq_length') || undefined );
    },

    seq_id: function() {
        if( this._get('unmapped') )
            return undefined;

        return ( this.file.indexToChr[ this._refID ] || {} ).name;
    },

    next_seq_id: function() {
        if( this._get('multi_segment_next_segment_unmapped') )
            return undefined;
        return ( this.file.indexToChr[this._get('_next_refid')] || {} ).name;
    },

    _bin_mq_nl: function() {
        with( this.bytes )
            return readInt( byteArray, start + 12  );
    },
    _flag_nc: function() {
        with( this.bytes )
            return readInt( byteArray, start + 16 );
    },
    seq_length: function() {
        with( this.bytes )
            return readInt( byteArray, start + 20 );
    },
    _next_refid: function() {
        with( this.bytes )
            return readInt( byteArray, start + 24 );
    },
    _next_pos: function() {
        with( this.bytes )
            return readInt( byteArray, start + 28 );
    },
    template_length: function() {
        with( this.bytes )
            return readInt( byteArray, start + 32 );
    },

    /**
     * parse the core data: ref ID and start
     */
    _coreParse: function() {
        with( this.bytes ) {
            this._refID      = readInt( byteArray, start + 4 );
            this.data.start  = readInt( byteArray, start + 8 );
        }
    },

    /**
     * Get the value of a tag, parsing the tags as far as necessary.
     * Only called if we have not already parsed that field.
     */
    _parseTag: function( tagName ) {
        // if all of the tags have been parsed and we're still being
        // called, we already know that we have no such tag, because
        // it would already have been cached.
        if( this._allTagsParsed )
            return undefined;

        this._tagList = this._tagList || [];
        var byteArray = this.bytes.byteArray;
        var p = this._tagOffset || this.bytes.start + 36 + this._get('_l_read_name') + this._get('_n_cigar_op')*4 + this._get('_seq_bytes') + this._get('seq_length');

        var blockEnd = this.bytes.end;
        while( p < blockEnd && lcTag != tagName ) {
            var tag      = String.fromCharCode( byteArray[p], byteArray[ p+1 ] );
            var lcTag    = tag.toLowerCase();
            var type = String.fromCharCode( byteArray[ p+2 ] );
            p += 3;

            var value;
            switch( type.toLowerCase() ) {
            case 'a':
                value = String.fromCharCode( byteArray[p] );
                p += 1;
                break;
            case 'i':
                value = readInt(byteArray, p );
                p += 4;
                break;
            case 'c':
                value = byteArray[p];
                p += 1;
                break;
            case 's':
                value = readShort(byteArray, p);
                p += 2;
                break;
            case 'f':
                value = readFloat( byteArray, p );
                p += 4;
                break;
            case 'z':
            case 'h':
                value = '';
                while( p <= blockEnd ) {
                    var cc = byteArray[p++];
                    if( cc == 0 ) {
                        break;
                    }
                    else {
                        value += String.fromCharCode(cc);
                    }
                }
                break;
            default:
                console.warn( "Unknown BAM tag type '"+type
                              +"', tags may be incomplete"
                            );
                value = undefined;
                p = blockEnd; // stop parsing tags
            }

            this._tagOffset = p;

            this._tagList.push( tag );
            if( lcTag == tagName )
                return value;
            else {
                this.data[ lcTag ] = value;
            }
        }
        this._allTagsParsed = true;
        return undefined;
    },
    _parseAllTags: function() {
        this._parseTag(); // calling _parseTag with no arg just parses
        // all the tags and returns the last one
    },

    _flagMasks: {
        multi_segment_template:              0x1,
        multi_segment_all_correctly_aligned: 0x2,
        unmapped:                            0x4,
        multi_segment_next_segment_unmapped: 0x8,
        seq_reverse_complemented:            0x10,
        multi_segment_next_segment_reversed: 0x20,
        multi_segment_first:                 0x40,
        multi_segment_last:                  0x80,
        secondary_alignment:                 0x100,
        qc_failed:                           0x200,
        duplicate:                           0x400,
        supplementary_alignment:             0x800
    },

    _parseFlag: function( flagName ) {
        return !!( this._get('_flags') & this._flagMasks[flagName] );
    },

    _parseCigar: function( cigar ) {
        return array.map( cigar.match(/\d+\D/g), function( op ) {
           return [ op.match(/\D/)[0].toUpperCase(), parseInt( op ) ];
        });
    },

    /**
     *  take a cigar string, and initial position, return an array of subfeatures
     */
    _cigarToSubfeats: function(cigar)    {
        var subfeats = [];
        var min = this._get('start');
        var max;
        var ops = this._parseCigar( cigar );
        for (var i = 0; i < ops.length; i++)  {
            var lop = ops[i][1];
            var op = ops[i][0];  // operation type
            // converting "=" to "E" to avoid possible problems later with non-alphanumeric type name
            if (op === "=")  { op = "E"; }

            switch (op) {
            case 'M':
            case 'D':
            case 'N':
            case 'E':
            case 'X':
                max = min + lop;
                break;
            case 'I':
                max = min;
                break;
            case 'P':  // not showing padding deletions (possibly change this later -- could treat same as 'I' ?? )
            case 'H':  // not showing hard clipping (since it's unaligned, and offset arg meant to be beginning of aligned part)
            case 'S':  // not showing soft clipping (since it's unaligned, and offset arg meant to be beginning of aligned part)
                break;
                // other possible cases
            }
            if( op !== 'N' ) {
                subfeats.push(
                    new SimpleFeature(
                        {
                            data: {
                                type: op,
                                start: min,
                                end: max,
                                strand: this._get('strand'),
                                cigar_op: lop+op
                            },
                            parent: this
                        })
                );
            }
            min = max;
        }
        return subfeats;
    }

});

return Feature;
});
<|endoftext|>


<commit_before>// OS Monitoring for Node.js

// Copyright (c) 2012-2013 Laurent Fortin
//
// 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.

var util     = require('util'),
    os       = require('os'),
    events   = require('events'),
    stream   = require('stream'),
    _        = require('underscore'),
    critical = os.cpus().length,
    defaults = {
      delay     : 3000,
      critical1 : critical,
      critical5 : critical,
      critical15: critical,
      freemem   : 0,
      uptime    : 0,
      silent    : false,
      stream    : false
    };

// constructor
var Monitor = function() {
  if(stream.Readable) {
    stream.Readable.call(this, {highWaterMark: 102400});
  } else {
    events.EventEmitter.call(this);
  }

  this._monitorState = {
    running: false,
    streamBuffering: true,
    interval: undefined,
    config: _.clone(defaults)
  };
};

if(stream.Readable) {
  util.inherits(Monitor, stream.Readable);
} else {
  util.inherits(Monitor, events.EventEmitter);
}

Monitor.prototype.version = '0.1.4';

// readable stream implementation requirement
Monitor.prototype._read = function(size, cb) {
  this._monitorState.streamBuffering = true;
};

Monitor.prototype.sendEvent = function(event, data) {
  // for EventEmitter
  this.emit(event, data);
  // for readable Stream
  if(this._monitorState.config.stream && this._monitorState.streamBuffering) {
    var prettyJSON = os.EOL + JSON.stringify(data, null, 2);
    if( !this.push(new Buffer(prettyJSON)) ) {
      this._monitorState.streamBuffering = false;
    }
  }
};

Monitor.prototype.start = function(options) {

  var self = this;

  self.stop()
      .config(options);

  this._monitorState.interval  = setInterval(function() {
    var info = {
      loadavg  : os.loadavg(),
      uptime   : os.uptime(),
      freemem  : os.freemem(),
      totalmem : os.totalmem()
    },
    config = self._monitorState.config,
    freemem  = (config.freemem < 1) ? config.freemem * info.totalmem : config.freemem;

    if(!config.silent) {
      self.sendEvent('monitor', _.extend({type: 'monitor'}, info));
    }
    if(info.loadavg[0] > config.critical1) {
      self.sendEvent('loadavg1', _.extend({type: 'loadavg1'}, info));
    }
    if(info.loadavg[1] > config.critical5) {
      self.sendEvent('loadavg5', _.extend({type: 'loadavg5'}, info));
    }
    if(info.loadavg[2] > config.critical15) {
      self.sendEvent('loadavg15', _.extend({type: 'loadavg15'}, info));
    }
    if(info.freemem < freemem) {
      self.sendEvent('freemem', _.extend({type: 'freemem'}, info));
    }
    if(Number(config.uptime) && info.uptime > Number(config.uptime)) {
      self.sendEvent('uptime', _.extend({type: 'uptime'}, info));
    }
  }, this._monitorState.config.delay);

  if(!self.isRunning()) {
    this._monitorState.running = true;
    self.sendEvent('start', {type: 'start'});
  }

  return self;
};

Monitor.prototype.stop = function() {

  clearInterval(this._monitorState.interval);

  if(this.isRunning()) {
    this._monitorState.running = false;
    this.sendEvent('stop', {type: 'stop'});
  }

  return this;
};

Monitor.prototype.config = function(options) {

  if(_.isObject(options)) {
    _.extend(this._monitorState.config, options);
    this.sendEvent('config', {type: 'config', options: _.clone(options)});
  }

  return this._monitorState.config;
};

Monitor.prototype.isRunning = function() {
  return !!this._monitorState.running;
};

Monitor.prototype.throttle = function(event, handler, wait) {
  var self     = this,
      _handler = _.wrap(handler, function(fn) {
                   if(self.isRunning()) {
                     fn.apply(this, _.toArray(arguments).slice(1));
                   }
                 });
  return self.on.call(self, event, _.throttle(_handler, wait || this._monitorState.config.throttle));
};

// deprecated stuff
Monitor.prototype.setConfig = util.deprecate(Monitor.prototype.config);

// expose OS module
Monitor.prototype.os = os;

// expose Underscore
Monitor.prototype._ = _;

// create object
var Osm = new Monitor();

// expose main class
Osm.Monitor = Monitor;

module.exports = Osm;

<commit_msg>useless vars<commit_after>// OS Monitoring for Node.js

// Copyright (c) 2012-2013 Laurent Fortin
//
// 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.

var util     = require('util'),
    os       = require('os'),
    events   = require('events'),
    stream   = require('stream'),
    _        = require('underscore'),
    critical = os.cpus().length,
    defaults = {
      delay     : 3000,
      critical1 : critical,
      critical5 : critical,
      critical15: critical,
      freemem   : 0,
      uptime    : 0,
      silent    : false,
      stream    : false
    };

// constructor
var Monitor = function() {
  if(stream.Readable) {
    stream.Readable.call(this, {highWaterMark: 102400});
  } else {
    events.EventEmitter.call(this);
  }

  this._monitorState = {
    running: false,
    streamBuffering: true,
    interval: undefined,
    config: _.clone(defaults)
  };
};

if(stream.Readable) {
  util.inherits(Monitor, stream.Readable);
} else {
  util.inherits(Monitor, events.EventEmitter);
}

Monitor.prototype.version = '0.1.4';

// readable stream implementation requirement
Monitor.prototype._read = function() {
  this._monitorState.streamBuffering = true;
};

Monitor.prototype.sendEvent = function(event, data) {
  // for EventEmitter
  this.emit(event, data);
  // for readable Stream
  if(this._monitorState.config.stream && this._monitorState.streamBuffering) {
    var prettyJSON = os.EOL + JSON.stringify(data, null, 2);
    if( !this.push(new Buffer(prettyJSON)) ) {
      this._monitorState.streamBuffering = false;
    }
  }
};

Monitor.prototype.start = function(options) {

  var self = this;

  self.stop()
      .config(options);

  this._monitorState.interval  = setInterval(function() {
    var info = {
      loadavg  : os.loadavg(),
      uptime   : os.uptime(),
      freemem  : os.freemem(),
      totalmem : os.totalmem()
    },
    config = self._monitorState.config,
    freemem  = (config.freemem < 1) ? config.freemem * info.totalmem : config.freemem;

    if(!config.silent) {
      self.sendEvent('monitor', _.extend({type: 'monitor'}, info));
    }
    if(info.loadavg[0] > config.critical1) {
      self.sendEvent('loadavg1', _.extend({type: 'loadavg1'}, info));
    }
    if(info.loadavg[1] > config.critical5) {
      self.sendEvent('loadavg5', _.extend({type: 'loadavg5'}, info));
    }
    if(info.loadavg[2] > config.critical15) {
      self.sendEvent('loadavg15', _.extend({type: 'loadavg15'}, info));
    }
    if(info.freemem < freemem) {
      self.sendEvent('freemem', _.extend({type: 'freemem'}, info));
    }
    if(Number(config.uptime) && info.uptime > Number(config.uptime)) {
      self.sendEvent('uptime', _.extend({type: 'uptime'}, info));
    }
  }, this._monitorState.config.delay);

  if(!self.isRunning()) {
    this._monitorState.running = true;
    self.sendEvent('start', {type: 'start'});
  }

  return self;
};

Monitor.prototype.stop = function() {

  clearInterval(this._monitorState.interval);

  if(this.isRunning()) {
    this._monitorState.running = false;
    this.sendEvent('stop', {type: 'stop'});
  }

  return this;
};

Monitor.prototype.config = function(options) {

  if(_.isObject(options)) {
    _.extend(this._monitorState.config, options);
    this.sendEvent('config', {type: 'config', options: _.clone(options)});
  }

  return this._monitorState.config;
};

Monitor.prototype.isRunning = function() {
  return !!this._monitorState.running;
};

Monitor.prototype.throttle = function(event, handler, wait) {
  var self     = this,
      _handler = _.wrap(handler, function(fn) {
                   if(self.isRunning()) {
                     fn.apply(this, _.toArray(arguments).slice(1));
                   }
                 });
  return self.on.call(self, event, _.throttle(_handler, wait || this._monitorState.config.throttle));
};

// deprecated stuff
Monitor.prototype.setConfig = util.deprecate(Monitor.prototype.config);

// expose OS module
Monitor.prototype.os = os;

// expose Underscore
Monitor.prototype._ = _;

// create object
var Osm = new Monitor();

// expose main class
Osm.Monitor = Monitor;

module.exports = Osm;

<|endoftext|>


<commit_before>let isVideoPlaying = false;

const Player = React.createClass({
  onStateChange: function (event) {
    switch (event.data) {
      case YT.PlayerState.PLAYING:
        isVideoPlaying = true;
        Socket.emit('video/start', this.state.player.getVideoData()['video_id']);
        break;
      case YT.PlayerState.PAUSED:
        Socket.emit('video/pause', this.state.player.getVideoData()['video_id']);
        break;
      case YT.PlayerState.BUFFERING:
        Socket.emit('video/buffer', this.state.player.getVideoData()['video_id']);
        break;
      case YT.PlayerState.ENDED:
        isVideoPlaying = false;
        Socket.emit('video/end', this.state.player.getVideoData()['video_id']);
        break;
    };
  },
  updateVideo: function (id) {
    this.setState((state) => {
      return {
        id: id,
        player: state.player
      };
    });

    this.state.player.cueVideoById(id);
  },
  playVideo: function (id) {
    this.setState((state) => {
      return {
        id: id,
        player: state.player
      };
    });

    this.state.player.loadVideoById(id);
  },
  removeVideo: function () {
    this.state.player.stopVideo();
  },
  componentDidMount: function () {
    this.setState({
      id: null,
      // YT may not be loaded at this time, need to find a solution...
      // That's probably why I can't put this in a getInitialState method
      player: new YT.Player('player', {
          events: {
            onStateChange: this.onStateChange
          }
        })
    });

    Socket.on('video/cue', this.updateVideo);
    Socket.on('video/play', this.playVideo);
    Socket.on('video/remove', this.removeVideo);
  },
  render: function () {
    return

;
  }
});

const PlaylistItem = React.createClass({
  play: function () {
    if (this.props.id) {
      Socket.emit('video/play', this.props);
    }
  },
  remove: function () {
    if (this.props.id) {
      window.dispatchEvent(new CustomEvent('playlist.removeVideo', { detail: { video: this.props } }));
      Socket.emit('video/remove', this.props.id);
    }
  },
  render: function () {
    return (

          <button className="btn btn-secondary btn-sm remove"
                  onClick={this.remove}
                  title="Remove this video">x</button>

              {this.props.title}

{this.props.channel}

    );
  }
});

const Playlist = React.createClass({
  getInitialState: function () {
    return { videos: [] };
  },
  componentDidMount: function () {
    // ToDo - retrieve playlist from backend

    window.addEventListener('playlist.addVideo', this.addVideo);
    window.addEventListener('playlist.cueVideo', this.cueVideo);
    window.addEventListener('playlist.removeVideo', this.removeVideo);
  },
  componentWillUnmount: function () {
    window.removeEventListener('playlist.addVideo', this.addVideo, false);
    window.removeEventListener('playlist.cueVideo', this.cueVideo, false);
    window.removeEventListener('playlist.removeVideo', this.removeVideo, false);
  },
  addVideo: function (event) {
    // Add the video in first position if no video playing
    // Else add it in second position

    let video = event.detail.video;

    if (this.isInPlaylist(video))
      return;

    this.setState(state => {
      if (isVideoPlaying) {
        state.videos.splice(1, 0, video);
      } else {
        state.videos.splice(0, 0, video);
      }

      return state;
    });
  },
  cueVideo: function (event) {
    // Add the video in last position
    var video = event.detail.video;

    if (this.isInPlaylist(video))
      return;

    this.setState(state => {
      state.videos.push(video);

      return state;
    });
  },
  removeVideo: function (event) {
    var video = event.detail.video;

    if (!this.isInPlaylist(video))
      return;

    this.setState(state => {
      state.videos = _.reject(state.videos, video);

      return state;
    })
  },
  isInPlaylist: function (video) {
    return _.some(this.state.videos, video);
  },
  render: function () {
    let videos = [];
    for (var index in this.state.videos) {
      videos.push(
        <PlaylistItem
          key={this.state.videos[index].id}
          id={this.state.videos[index].id}
          thumbnail={this.state.videos[index].thumbnail}
          title={this.state.videos[index].title}
          channel={this.state.videos[index].channel} />
        );
    }

    return (

        {videos}

    );
  }
});

const CurrentPlaylist = React.createClass({
  render: function () {
    return (

        <Playlist />
        <Player />

    );
  }
});

module.exports = CurrentPlaylist;
<commit_msg>Raise a video in the playlist<commit_after>let isVideoPlaying = false;

const Player = React.createClass({
  onStateChange: function (event) {
    switch (event.data) {
      case YT.PlayerState.PLAYING:
        isVideoPlaying = true;
        Socket.emit('video/start', this.state.player.getVideoData()['video_id']);
        break;
      case YT.PlayerState.PAUSED:
        Socket.emit('video/pause', this.state.player.getVideoData()['video_id']);
        break;
      case YT.PlayerState.BUFFERING:
        Socket.emit('video/buffer', this.state.player.getVideoData()['video_id']);
        break;
      case YT.PlayerState.ENDED:
        isVideoPlaying = false;
        Socket.emit('video/end', this.state.player.getVideoData()['video_id']);
        break;
    };
  },
  updateVideo: function (id) {
    this.setState((state) => {
      return {
        id: id,
        player: state.player
      };
    });

    this.state.player.cueVideoById(id);
  },
  playVideo: function (id) {
    this.setState((state) => {
      return {
        id: id,
        player: state.player
      };
    });

    this.state.player.loadVideoById(id);
  },
  removeVideo: function () {
    this.state.player.stopVideo();
  },
  componentDidMount: function () {
    this.setState({
      id: null,
      // YT may not be loaded at this time, need to find a solution...
      // That's probably why I can't put this in a getInitialState method
      player: new YT.Player('player', {
          events: {
            onStateChange: this.onStateChange
          }
        })
    });

    Socket.on('video/cue', this.updateVideo);
    Socket.on('video/play', this.playVideo);
    Socket.on('video/remove', this.removeVideo);
  },
  render: function () {
    return

;
  }
});

const PlaylistItem = React.createClass({
  raise: function () {
    if (this.props.id) {
      window.dispatchEvent(new CustomEvent('playlist.raiseVideo', { detail: { video: this.props } }));
      Socket.emit('video/play', this.props);
    }
  },
  remove: function () {
    if (this.props.id) {
      window.dispatchEvent(new CustomEvent('playlist.removeVideo', { detail: { video: this.props } }));
      Socket.emit('video/remove', this.props.id);
    }
  },
  render: function () {
    return (

          <button className="btn btn-secondary btn-sm remove"
                  onClick={this.remove}
                  title="Remove this video">x</button>

              {this.props.title}

{this.props.channel}

    );
  }
});

const Playlist = React.createClass({
  getInitialState: function () {
    return { videos: [] };
  },
  componentDidMount: function () {
    // ToDo - retrieve playlist from backend

    window.addEventListener('playlist.addVideo', this.addVideo);
    window.addEventListener('playlist.cueVideo', this.cueVideo);
    window.addEventListener('playlist.removeVideo', this.removeVideo);
    window.addEventListener('playlist.raiseVideo', this.raiseVideo);
  },
  componentWillUnmount: function () {
    window.removeEventListener('playlist.addVideo', this.addVideo, false);
    window.removeEventListener('playlist.cueVideo', this.cueVideo, false);
    window.removeEventListener('playlist.removeVideo', this.removeVideo, false);
    window.removeEventListener('playlist.raiseVideo', this.raiseVideo, false);
  },
  addVideo: function (event) {
    // Add the video in first position if no video playing
    // Else add it in second position

    let video = event.detail.video;

    if (this.isInPlaylist(video))
      return;

    this.setState(state => {
      if (isVideoPlaying) {
        state.videos.splice(1, 0, video);
      } else {
        state.videos.splice(0, 0, video);
      }

      return state;
    });
  },
  cueVideo: function (event) {
    // Add the video in last position
    var video = event.detail.video;

    if (this.isInPlaylist(video))
      return;

    this.setState(state => {
      state.videos.push(video);

      return state;
    });
  },
  removeVideo: function (event) {
    var video = event.detail.video;

    if (!this.isInPlaylist(video))
      return;

    this.setState(state => {
      state.videos = _.reject(state.videos, video);

      return state;
    });
  },
  raiseVideo: function (event) {
    var video = event.detail.video;

    if (!this.isInPlaylist(video))
      return;

    this.setState(state => {
      state.videos = _.reject(state.videos, video);
      if (isVideoPlaying) {
        state.videos.splice(1, 0, video);
      } else {
        state.videos.splice(0, 0, video);
      }

      return state;
    });
  },
  isInPlaylist: function (video) {
    return _.some(this.state.videos, video);
  },
  render: function () {
    let videos = [];
    for (var index in this.state.videos) {
      videos.push(
        <PlaylistItem
          key={this.state.videos[index].id}
          id={this.state.videos[index].id}
          thumbnail={this.state.videos[index].thumbnail}
          title={this.state.videos[index].title}
          channel={this.state.videos[index].channel} />
        );
    }

    return (

        {videos}

    );
  }
});

const CurrentPlaylist = React.createClass({
  render: function () {
    return (

        <Playlist />
        <Player />

    );
  }
});

module.exports = CurrentPlaylist;
<|endoftext|>


<commit_before><commit_msg>Add title for editor frame<commit_after><|endoftext|>


<commit_before>var fs = require('fs')
  , nconf = require('nconf')
  , serialport = require("serialport")
  , sqlite3 = require('sqlite3')
  , express = require('express')
  , io = require('socket.io')
  , http = require('http')
  , cronJob = require('cron').CronJob
  ;

// Configuration
nconf.argv()
     .env()
     .file({ file: 'config.json' });
nconf.defaults({
    'DEVICE': '/dev/ttyACM0',
    'PORT': '12000'
});

// Database setup
var db = new sqlite3.Database('checkinout.db');
db.serialize(function() {
    db.run("CREATE TABLE people (id INTEGER PRIMARY KEY NOT NULL, name VARCHAR(60), cardid VARCHAR(25) UNIQUE, checkin DATETIME, checkinid INTEGER)",
	   function(err) {
	       if (err) {
		   console.log('People database already exists')
	       }
	   });

    db.run("CREATE TABLE checkinlist (id INTEGER PRIMARY KEY NOT NULL, cardid VARCHAR(25), eventtime DATETIME, eventtype INTEGER, FOREIGN KEY(cardid) REFERENCES people(cardid))",
	   function(err) {
	       if (err) {
		   console.log('Checkin list database already exists')
	       }
	   });

    db.run("CREATE TABLE tokens (id INTEGER PRIMARY KEY NOT NULL, token VARCHAR(25))",
	   function(err) {
	       if (err) {
		   console.log('Tokens database already exists')
	       }
	   });
});

var cleanStaleCheckins = function() {
    var now = new Date()
    var threshold = now - 16 * 60 * 60 * 1000;  // that's 16h ago

    db.serialize(function() {
	db.run("UPDATE people SET checkin=NULL WHERE id in (SELECT id FROM people WHERE checkin<?)", threshold, function (err, row) {
	    if (err) {
		console.log("Error removing stale checkins!")
	    };
	});
	db.all("SELECT name,checkin FROM people WHERE checkin IS NOT NULL", function(err, rows) {
	    var out = { people:  [] };
	    for (var i = 0; i < rows.length; i++) {
		out.people.push(rows[i]);
	    }
	    sio.sockets.emit('people', out);
	});
    });
}

// Set up web interface
var app = express();
app.use(express.logger());
app.use(express.bodyParser());
app.use(express.static(__dirname + '/public'));
app.set('view engine', 'ejs');

var port = nconf.get('PORT');
var server = http.createServer(app);
server.listen(port, function(){
  console.log("Express server listening on port " + port);
});

var sio = io.listen(server);
sio.set('transports', [
    'websocket'
  , 'xhr-polling'
  , 'jsonp-polling'
]);

// Serial Port setup
var SerialPort = serialport.SerialPort;
var serialPort = new SerialPort(nconf.get('DEVICE'), {
    baudrate: 115200,
    parser: serialport.parsers.readline("\n")
});

serialPort.on("open", function () {
    console.log("SerialPort Open")
    serialPort.on('data', function(data) {
	var chkdata = data.split(",");
	var ID = chkdata[0];
	var event = chkdata[1];
	console.log("ID: "+ID);
	console.log("Event: "+event);
	addCheckinEvent(ID, event);
    });
});

var addCheckinEvent = function(id, event) {
    var date = new Date();

    db.serialize(function() {
	db.get("SELECT * FROM people WHERE cardid=? LIMIT 1", id, function(err, row) {
    	    if (typeof row === 'undefined') {
		db.run("INSERT INTO people (name, cardid) VALUES(?, ?)", "", id);
		console.log("Added new card: "+id);
    	    } else {
		console.log("Exisiting card: "+row.cardid);
	    }
	});

	db.run("INSERT INTO checkinlist (cardid, eventtime, eventtype) VALUES(?, ?, ?)", id, date, event, function (err, row) {
	    if (err) {
		console.log("Error inserting event!")
	    };
	});

	db.get("SELECT id FROM checkinlist WHERE cardid=? AND eventtime=? LIMIT 1", id, date, function(err, row) {
	    db.serialize(function() {
		if (err) {
		    console.log("Event insertion consitency fail");
		} else {
		    var checkinid = row.id;
		    var eventdate = date;
		    if (event == 1) {
			console.log("CHECKIN!");
		    } else if (event == 2) {
			console.log("CHECKOUT!");
			eventdate = null;
		    }
		    db.run("UPDATE people SET checkin=?,checkinid=? WHERE cardid=?", eventdate, checkinid, id, function(err){
			if (err) {
			    console.log("People database consistency fail");
			}
		    });
		};

		db.all("SELECT name,checkin FROM people WHERE checkin IS NOT NULL", function(err, rows) {
		    var out = { people:  [] };
		    for (var i = 0; i < rows.length; i++) {
			out.people.push(rows[i]);
		    }
		    sio.sockets.emit('people', out);
		});
	    });
	});
    });
}

console.log("Arduino on: "+nconf.get('DEVICE'));

sio.sockets.on('connection', function(socket){
    var hs = socket.handshake;

    db.all("SELECT name,checkin FROM people WHERE checkin IS NOT NULL", function(err, rows) {
	var out = { people:  [] };
	for (var i = 0; i < rows.length; i++) {
	    out.people.push(rows[i]);
	}
	socket.emit('people', out);
    });

    socket.on('disconnect', function(){
	console.log('A socket with sessionID '+hs.sessionID+' disconnected.');
    });
});

var allowCrossDomain = function(req, res, next) {
    res.header('Access-Control-Allow-Origin', "*");
    next();
};
app.use(allowCrossDomain);

app.get('/inspace', function(req, res){
    var out = { people: [] }
    db.all("SELECT name,checkin FROM people WHERE checkin IS NOT NULL", function(err, rows) {
	for (var i = 0; i < rows.length; i++) {
	    out.people.push(rows[i]);
	}
	res.jsonp(out);
    });
});

app.get('/', function(req, res){
    res.send("Not quite there yet.");
});

cleanStaleCheckins(); // Clean stale on startup
var job = new cronJob({  cronTime: '*/15 * * * *'  // do a cleaning every 15 minutes
		       , onTick: cleanStaleCheckins
		       , start: true
		      });
<commit_msg>create admin interface<commit_after>var fs = require('fs')
  , nconf = require('nconf')
  , serialport = require("serialport")
  , sqlite3 = require('sqlite3')
  , express = require('express')
  , io = require('socket.io')
  , http = require('http')
  , cronJob = require('cron').CronJob
  , crypto = require('crypto')
  ;

// Configuration
nconf.argv()
     .env()
     .file({ file: 'config.json' });
nconf.defaults({
    'DEVICE': '/dev/ttyACM0',
    'PORT': '12000'
});

// Database setup
var db = new sqlite3.Database('checkinout.db');
db.serialize(function() {
    db.run("CREATE TABLE people (id INTEGER PRIMARY KEY NOT NULL, name VARCHAR(60), cardid VARCHAR(25) UNIQUE, checkin DATETIME, checkinid INTEGER)",
	   function(err) {
	       if (err) {
		   console.log('People database already exists')
	       }
	   });

    db.run("CREATE TABLE checkinlist (id INTEGER PRIMARY KEY NOT NULL, cardid VARCHAR(25), eventtime DATETIME, eventtype INTEGER, FOREIGN KEY(cardid) REFERENCES people(cardid))",
	   function(err) {
	       if (err) {
		   console.log('Checkin list database already exists')
	       }
	   });

    db.run("CREATE TABLE tokens (id INTEGER PRIMARY KEY NOT NULL, token VARCHAR(25))",
	   function(err) {
	       if (err) {
		   console.log('Tokens database already exists')
	       }
	   });
});

var cleanStaleCheckins = function() {
    var now = new Date()
    var threshold = now - 16 * 60 * 60 * 1000;  // that's 16h ago

    db.serialize(function() {
	db.run("UPDATE people SET checkin=NULL WHERE id in (SELECT id FROM people WHERE checkin<?)", threshold, function (err, row) {
	    if (err) {
		console.log("Error removing stale checkins!")
	    };
	});
	db.all("SELECT name,checkin FROM people WHERE checkin IS NOT NULL", function(err, rows) {
	    var out = { people:  [] };
	    for (var i = 0; i < rows.length; i++) {
		out.people.push(rows[i]);
	    }
	    sio.sockets.emit('people', out);
	});
    });
}

// Set up web interface
var app = express();
app.use(express.logger());
app.use(express.bodyParser());
app.use(express.static(__dirname + '/public'));
app.set('view engine', 'ejs');

var port = nconf.get('PORT');
var server = http.createServer(app);
server.listen(port, function(){
  console.log("Express server listening on port " + port);
});

var sio = io.listen(server);
sio.set('transports', [
    'websocket'
  , 'xhr-polling'
  , 'jsonp-polling'
]);

// Serial Port setup
var SerialPort = serialport.SerialPort;
var serialPort = new SerialPort(nconf.get('DEVICE'), {
    baudrate: 115200,
    parser: serialport.parsers.readline("\n")
});

serialPort.on("open", function () {
    console.log("SerialPort Open")
    serialPort.on('data', function(data) {
	var chkdata = data.split(",");
	var ID = chkdata[0];
	var event = chkdata[1];
	console.log("ID: "+ID);
	console.log("Event: "+event);
	addCheckinEvent(ID, event);
    });
});

var addCheckinEvent = function(id, event) {
    var date = new Date();

    db.serialize(function() {
	db.get("SELECT * FROM people WHERE cardid=? LIMIT 1", id, function(err, row) {
    	    if (typeof row === 'undefined') {
		db.run("INSERT INTO people (name, cardid) VALUES(?, ?)", "", id);
		console.log("Added new card: "+id);
    	    } else {
		console.log("Exisiting card: "+row.cardid);
	    }
	});

	db.run("INSERT INTO checkinlist (cardid, eventtime, eventtype) VALUES(?, ?, ?)", id, date, event, function (err, row) {
	    if (err) {
		console.log("Error inserting event!")
	    };
	});

	db.get("SELECT id FROM checkinlist WHERE cardid=? AND eventtime=? LIMIT 1", id, date, function(err, row) {
	    db.serialize(function() {
		if (err) {
		    console.log("Event insertion consitency fail");
		} else {
		    var checkinid = row.id;
		    var eventdate = date;
		    if (event == 1) {
			console.log("CHECKIN!");
		    } else if (event == 2) {
			console.log("CHECKOUT!");
			eventdate = null;
		    }
		    db.run("UPDATE people SET checkin=?,checkinid=? WHERE cardid=?", eventdate, checkinid, id, function(err){
			if (err) {
			    console.log("People database consistency fail");
			}
		    });
		};

		db.all("SELECT name,checkin FROM people WHERE checkin IS NOT NULL", function(err, rows) {
		    var out = { people:  [] };
		    for (var i = 0; i < rows.length; i++) {
			out.people.push(rows[i]);
		    }
		    sio.sockets.emit('people', out);
		});
	    });
	});
    });
}

console.log("Arduino on: "+nconf.get('DEVICE'));

sio.sockets.on('connection', function(socket){
    var hs = socket.handshake;

    db.all("SELECT name,checkin FROM people WHERE checkin IS NOT NULL", function(err, rows) {
	var out = { people:  [] };
	for (var i = 0; i < rows.length; i++) {
	    out.people.push(rows[i]);
	}
	socket.emit('people', out);
    });

    socket.on('disconnect', function(){
	console.log('A socket with sessionID '+hs.sessionID+' disconnected.');
    });
});

var allowCrossDomain = function(req, res, next) {
    res.header('Access-Control-Allow-Origin', "*");
    next();
};
app.use(allowCrossDomain);

app.get('/inspace', function(req, res){
    var out = { people: [] }
    db.all("SELECT name,checkin FROM people WHERE checkin IS NOT NULL", function(err, rows) {
	for (var i = 0; i < rows.length; i++) {
	    out.people.push(rows[i]);
	}
	res.jsonp(out);
    });
});

var showAdmin = function(req, res) {
    var out = { people: [] }
    db.all("SELECT name,checkin FROM people WHERE checkin IS NOT NULL", function(err, rows) {
	for (var i = 0; i < rows.length; i++) {
	    out.people.push(rows[i]);
	}
	res.render('admin',
		   { layout: false
		     , people: out.people
		   }
		  );
    });
}

// Show the admin interface
app.get('/admin', showAdmin);

// Remove selected checked in user with password
app.post('/admin', function(req, res) {
    var checkout = req.body.checkout;
    if (checkout) {
	var pass = req.body.token;
	var token = crypto.createHash('md5').update(pass).digest("hex");
	console.log(token);
	db.get("SELECT COUNT(*) FROM tokens WHERE token=?", token, function(err, row) {
	    var matched = row['COUNT(*)'] == 1;
	    if (matched) {
		db.serialize(function() {
		    for (var i = 0 ; i < checkout.length ; i++ ) {
			var removed = checkout[i].split("@");
			var name = removed[0]
			  , checkin = Number(removed[1]);
			db.run("UPDATE people SET checkin=NULL WHERE name=? AND checkin=?", name, checkin, function (err, row) {
			    if (err) {
				console.log("Error removing stale checkins!")
			    };
			});
		    }
		    db.all("SELECT name,checkin FROM people WHERE checkin IS NOT NULL", function(err, rows) {
			var out = { people:  [] };
			for (var i = 0; i < rows.length; i++) {
			    out.people.push(rows[i]);
			}
			sio.sockets.emit('people', out);
			showAdmin(req, res);
		    });
		});
	    } else {
		showAdmin(req, res);
	    }
	});
    } else {
	showAdmin(req, res);
    }
});

app.get('/', function(req, res){
    res.send("Not quite there yet.");
});

cleanStaleCheckins(); // Clean stale on startup
var job = new cronJob({  cronTime: '*/15 * * * *'  // do a cleaning every 15 minutes
		       , onTick: cleanStaleCheckins
		       , start: true
		      });
<|endoftext|>


<commit_before>const webpack = require('webpack');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const PostCSSAssetsPlugin = require('postcss-assets-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const ManifestPlugin = require('webpack-manifest-plugin');
const StyleLintPlugin = require('stylelint-webpack-plugin');
const SuppressChunksPlugin = require('suppress-chunks-webpack-plugin').default;
const path = require('path');
const glob = require('glob');
const paths = require('./paths');
const config = require(`${paths.project}/wee.json`);
const { buildEntries, calcBreakpoints } = require('./helpers');

const extractSCSS = new ExtractTextPlugin({
	// Add path to file name to output into it's own directory
	filename: `../styles/${config.style.output.filename}`,
	allChunks: true
});

const plugins = [];

if (config.script.vendor.enabled) {
	plugins.push(
		// Put any node_modules scripts into seperate bundle
		new webpack.optimize.CommonsChunkPlugin({
			name: config.script.vendor.options.name,
			minChunks: module => module.context && module.context.includes('node_modules')
		})
	);

	// Add hashed module ids
	plugins.push(new webpack.HashedModuleIdsPlugin());
}

if (config.script.manifest.enabled) {
	plugins.push(
		// Create a manifest json file of the output chunks
		new ManifestPlugin({
			// Move manifest file back one directory
			fileName: `../${config.script.manifest.options.name}.json`,
			// Map over manifest and remove reference to style path,
			// for some reason overriding the publicPath in
			// extractTextPlugin doesn't work
			map(manifest) {
				if (manifest.path.includes('../styles/')) {
					manifest.path = manifest.path.replace('../styles/', '');
				}

				return manifest;
			}
		})
	);
}

if (config.script.chunking.enabled) {
	const { name, minChunks } = config.script.chunking.options;

	plugins.push(
		// Enable chunking
		new webpack.optimize.CommonsChunkPlugin({ name, minChunks })
	);
}

module.exports = {
	entry: buildEntries(config.script.entry, config.style.entry),
	output: {
		filename: config.script.output.filename,
		path: paths.output.scripts,
		publicPath: paths.root,
	},
	module: {
		rules: [
			// Eslint config
			{
				enforce: 'pre',
				test: /\.(js|vue)$/,
				exclude: /node_modules/,
				include: [
					paths.scripts
				],
				loader: 'eslint-loader',
				options: {
					configFile: 'build/.eslintrc'
				}
			},

			// Vue loader
			{
				test: /\.vue$/,
				loader: 'vue-loader'
			},

			// Babel loader
			{
				test: /\.js$/,
				loader: 'babel-loader',
				exclude: /node_modules\/(?!wee-core)/,
				options: {
					// NOTE: Any changes to options need to be updated
					// in .babelrc - vue-loader uses .babelrc
					plugins: [
						// Consolidates/reuses babel helpers
						require('babel-plugin-transform-runtime'),
						// Use object rest and spread plugin
						require('babel-plugin-transform-object-rest-spread')
					],
					// Use the preset-env and target the latest 2 versions of every major browser as
					// well as IE 11
					presets: [
						['env', { targets: { browsers: ['last 2 versions', 'ie 11'] } }]
					]
				}
			},

			// Sass loader config
			{
				test: /\.scss$/,
				use: extractSCSS.extract({
					// Fallback to style loader
					fallback: 'style-loader',
					publicPath: '../../../',
					use: [
						{ loader: 'css-loader' },
						{
							loader: 'sass-loader',
							options: {
								// Include wee-core so we can @import core files
								// using the tilda character: @import "~wee-core/styles/mixins.scss"
								includePaths: [
									`${paths.weeCore}`
								]
							}
						},

						// Sass resource loader allows us to enable global access to
						// files. Note: Only include files that don't include any CSS, as
						// they will be output each time.
						{
							loader: 'sass-resources-loader',
							options: {
								resources: [
									`${paths.styles}/variables.scss`,
									`${paths.styles}/mixins.scss`,
								]
							},
						},
					],
				}),
			},
			{
				test: /\.(ttf|eot|woff|woff2)$/,
				loader: 'file-loader',
				options: {
					name: 'fonts/[name].[ext]',
					// Adds the path for the font inside the CSS file
					publicPath: '../',
					// Move fonts one level back from scripts directory
					outputPath: '../'
				},
			},
		],
	},
	plugins: [
		// Delete output directory contents
		new CleanWebpackPlugin([
			paths.assets
		], {
			root: paths.project
		}),

		// Extract css into single file
		extractSCSS,

		// Process with postcss after extract text plugin does it's thing
		new PostCSSAssetsPlugin({
			test: /\.css$/,
			log: true,
			plugins: [
				// Use custom media query @ rules
				require('postcss-variable-media')({
					breakpoints: calcBreakpoints(config.style.breakpoints, config.style.breakpointOffset)
				}),
				// Autoprefix css properties
				require('autoprefixer')(),
				// Pack same CSS media query rules into one media query rule
				require('css-mqpacker')(),
				// Minify css
				require('cssnano')({
					safe: true
				}),
			],
		}),

		// Copy images from source to public
		new CopyWebpackPlugin([
			{ from: paths.images, to: paths.output.images },
		]),

		// Lint styles
		new StyleLintPlugin({
			configFile: `${paths.build}/.stylelintrc`,
			syntax: 'scss'
		}),

		// Supress the creation of any style specific entry points
		new SuppressChunksPlugin(Object.keys(config.style.entry)),

		...plugins,
	],
	resolve: {
		modules: [
			`${paths.weeCore}/scripts`,
			paths.nodeModules
		]
	},
}
<commit_msg>Only lint dirty modules<commit_after>const webpack = require('webpack');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const PostCSSAssetsPlugin = require('postcss-assets-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const ManifestPlugin = require('webpack-manifest-plugin');
const StyleLintPlugin = require('stylelint-webpack-plugin');
const SuppressChunksPlugin = require('suppress-chunks-webpack-plugin').default;
const path = require('path');
const glob = require('glob');
const paths = require('./paths');
const config = require(`${paths.project}/wee.json`);
const { buildEntries, calcBreakpoints } = require('./helpers');

const extractSCSS = new ExtractTextPlugin({
	// Add path to file name to output into it's own directory
	filename: `../styles/${config.style.output.filename}`,
	allChunks: true
});

const plugins = [];

if (config.script.vendor.enabled) {
	plugins.push(
		// Put any node_modules scripts into seperate bundle
		new webpack.optimize.CommonsChunkPlugin({
			name: config.script.vendor.options.name,
			minChunks: module => module.context && module.context.includes('node_modules')
		})
	);

	// Add hashed module ids
	plugins.push(new webpack.HashedModuleIdsPlugin());
}

if (config.script.manifest.enabled) {
	plugins.push(
		// Create a manifest json file of the output chunks
		new ManifestPlugin({
			// Move manifest file back one directory
			fileName: `../${config.script.manifest.options.name}.json`,
			// Map over manifest and remove reference to style path,
			// for some reason overriding the publicPath in
			// extractTextPlugin doesn't work
			map(manifest) {
				if (manifest.path.includes('../styles/')) {
					manifest.path = manifest.path.replace('../styles/', '');
				}

				return manifest;
			}
		})
	);
}

if (config.script.chunking.enabled) {
	const { name, minChunks } = config.script.chunking.options;

	plugins.push(
		// Enable chunking
		new webpack.optimize.CommonsChunkPlugin({ name, minChunks })
	);
}

module.exports = {
	entry: buildEntries(config.script.entry, config.style.entry),
	output: {
		filename: config.script.output.filename,
		path: paths.output.scripts,
		publicPath: paths.root,
	},
	module: {
		rules: [
			// Eslint config
			{
				enforce: 'pre',
				test: /\.(js|vue)$/,
				exclude: /node_modules/,
				include: [
					paths.scripts
				],
				loader: 'eslint-loader',
				options: {
					configFile: 'build/.eslintrc'
				}
			},

			// Vue loader
			{
				test: /\.vue$/,
				loader: 'vue-loader'
			},

			// Babel loader
			{
				test: /\.js$/,
				loader: 'babel-loader',
				exclude: /node_modules\/(?!wee-core)/,
				options: {
					// NOTE: Any changes to options need to be updated
					// in .babelrc - vue-loader uses .babelrc
					plugins: [
						// Consolidates/reuses babel helpers
						require('babel-plugin-transform-runtime'),
						// Use object rest and spread plugin
						require('babel-plugin-transform-object-rest-spread')
					],
					// Use the preset-env and target the latest 2 versions of every major browser as
					// well as IE 11
					presets: [
						['env', { targets: { browsers: ['last 2 versions', 'ie 11'] } }]
					]
				}
			},

			// Sass loader config
			{
				test: /\.scss$/,
				use: extractSCSS.extract({
					// Fallback to style loader
					fallback: 'style-loader',
					publicPath: '../../../',
					use: [
						{ loader: 'css-loader' },
						{
							loader: 'sass-loader',
							options: {
								// Include wee-core so we can @import core files
								// using the tilda character: @import "~wee-core/styles/mixins.scss"
								includePaths: [
									`${paths.weeCore}`
								]
							}
						},

						// Sass resource loader allows us to enable global access to
						// files. Note: Only include files that don't include any CSS, as
						// they will be output each time.
						{
							loader: 'sass-resources-loader',
							options: {
								resources: [
									`${paths.styles}/variables.scss`,
									`${paths.styles}/mixins.scss`,
								]
							},
						},
					],
				}),
			},
			{
				test: /\.(ttf|eot|woff|woff2)$/,
				loader: 'file-loader',
				options: {
					name: 'fonts/[name].[ext]',
					// Adds the path for the font inside the CSS file
					publicPath: '../',
					// Move fonts one level back from scripts directory
					outputPath: '../'
				},
			},
		],
	},
	plugins: [
		// Delete output directory contents
		new CleanWebpackPlugin([
			paths.assets
		], {
			root: paths.project
		}),

		// Extract css into single file
		extractSCSS,

		// Process with postcss after extract text plugin does it's thing
		new PostCSSAssetsPlugin({
			test: /\.css$/,
			log: true,
			plugins: [
				// Use custom media query @ rules
				require('postcss-variable-media')({
					breakpoints: calcBreakpoints(config.style.breakpoints, config.style.breakpointOffset)
				}),
				// Autoprefix css properties
				require('autoprefixer')(),
				// Pack same CSS media query rules into one media query rule
				require('css-mqpacker')(),
				// Minify css
				require('cssnano')({
					safe: true
				}),
			],
		}),

		// Copy images from source to public
		new CopyWebpackPlugin([
			{ from: paths.images, to: paths.output.images },
		]),

		// Lint styles
		new StyleLintPlugin({
			configFile: `${paths.build}/.stylelintrc`,
			// Skip linting on start, and only lint dirty modules
			lintDirtyModulesOnly: true,
			syntax: 'scss'
		}),

		// Supress the creation of any style specific entry points
		new SuppressChunksPlugin(Object.keys(config.style.entry)),

		...plugins,
	],
	resolve: {
		modules: [
			`${paths.weeCore}/scripts`,
			paths.nodeModules
		]
	},
}
<|endoftext|>


<commit_before><!doctype html>

<commit_msg>Update test4-template2.js<commit_after>        var idx_title=[-1,-1];
        var idx_post=[-1,-1];
        var i=0;
        while (i<sheetdata.length) {
          if (sheetdata[i].gsx$keyword.$t == 'title') {
            idx_title[0]=i;
            i++;
            while (i<sheetdata.length && sheetdata[i].gsx$keyword.$t == 'title') {
              i++;
            }
            idx_title[1]=i-1;
            continue;
          }
          if (sheetdata[i].gsx$keyword.$t == 'post') {
            idx_post[0]=i;
            i++;
            while (i<sheetdata.length && sheetdata[i].gsx$keyword.$t == 'post') {
              i++;
            }
            idx_post[1]=i-1;
            continue;
          }
          i++;

        }
        document.write('<title>' + sheetdata[idx_title[0]].gsx$value1.$t + '</title>');
        document.write('');
        document.write('');
        document.write('
template2
');
        for (i=idx_post[0]; i<=idx_post[1]; i++) {
          document.write('post_value1[' + sheetdata[i].gsx$value1.$t + ']
');
          document.write('post_value2[' + sheetdata[i].gsx$value2.$t + ']
');
        }
        document.write('');
        document.write('');
<|endoftext|>