<commit_before>var compact = require('./compact')
var createSST = require('./sst')
var mem = require('./mem')
var mkdirp = require('mkdirp')
var path = require('path')
var merge = require('pull-merge')
var u = require('./util')
var zeros = '00000000'
var Promise = require('bluebird');

var pull = require('pull-stream')
var para = require('pull-paramap')

function pad(n) {
    n = n.toString()
    return zeros.substring(n.length) + n
}

function isEmpty(o) {
    if (null == o) return true
    for (var k in o)
        return false
    return true
}

module.exports = function (createSST, createMemtable, createManifest) {

    return function (location, opts) {
        var memtable = mem(), counter = 0, db, compacting = false, _snapshot
        var tables = tables || [memtable]
        var manifest, tables, seq

        function nextTableName(type) {
            return type + '-' + pad(++seq) + '.json'
        }

        return db = {
            //should i separate all this stuff out so that
            //you can simulate the database all in memory?
            open: function (opts, cb) {
                if (!cb) cb = opts, opts = {}
                mkdirp(location, function (err) {
                    if (err) return cb(err)
                    manifest = createManifest(path.join(location, 'manifest.json'))
                    manifest.open(function (err) {
                        if (err) return cb(err)
                        //if the manifest is empty create an empty memory table.
                        if (isEmpty(manifest.data)) {
                            seq = 1
                            var filename = path.join(location, 'log-' + pad(seq) + '.json')
                            var _memtable = createMemtable(filename)
                            manifest.update({tables: ['log-' + pad(seq) + '.json'], seq: seq}, function (err) {
                                if (err) return cb(err)
                                _memtable.open(function (err) {
                                    if (err) cb(err)
                                    memtable = _memtable
                                    _snapshot = [memtable]
                                    cb(null, db)
                                })
                            })
                        } else {
                            //open all the tables.
                            var _tables = manifest.data.tables || []
                            var n = tables.length
                            var _seq = 0

                            _tables.forEach(function (name, i) {
                                var m = /^(log|sst)-(\d+)\.json$/.exec(name)
                                var type = m[1], table
                                _seq = Math.max(m[2], _seq)

                                var create = type == 'log' ? createMemtable : createSST
                                var table = tables[i] = create(path.join(location, name))

                                table.open(next)
                            })
                            function next(err) {
                                if (err) return n = -1, cb(err)
                                if (--n) return
                                seq = _seq + 1
                                db.nextSnapshot(tables)
                                memtable = tables[0]
                                cb(null, db)
                            }
                        }
                    })
                })
                return db
            },
            //get the current snapshot.
            nextSnapshot: function (ary) {
                _snapshot = ary
                return db
            },
            snapshot: function () {
                if (_snapshot) {
                    tables = _snapshot;
                    _snapshot = null
                }
                return tables
            },
            //step through all the databases, and look for next
            get: function (key, cb) {
                var tables = db.snapshot()
                    ;
                (function next(i) {
                    //if we ran out of tables, err
                    //gets need to use snapshots too...
                    //but since they will return from the memtable
                    //synchronously they will only need to track the SSTs
                    //which probably means they can all share a snapshot
                    //unless there is a compaction happening.

                    //just have an SST snapshot,
                    //keep a count of current gets
                    //and don't delete any ssts until it's freed.

                    if (!tables[i]) return cb(new Error('not found'))
                    tables[i].get(key, function (err, value) {
                        if (err) return next(i + 1)
                        return cb(null, value)
                    })
                })(0)
                return db
            },
            //only write to the FIRST table.
            put: function (key, value) {
                return new Promise(function(resolve, reject){
                    memtable.put(key, value, function (err) {
                        if (!(++counter % 1000))
                            db.compact();

                        !!err ? reject(err) : resolve();
                    });
                });
            },
            del: function (del, cb) {
                return memtable.del(key, function (err) {
                    if (!(++counter % 1000)) db.compact()
                    cb(err)
                })
            },
            batch: function (ops, cb) {
                return memtable.batch(ops, function (err) {
                    if (!(++counter % 1000)) db.compact()
                    cb(err)
                })
            },
            createReadStream: function (opts) {
                //merge streams from all the tables
                //it's probably okay to not have a snapshot on the memtable.
                //hmm, on the other hand, it will be fairly cheap...
                //otherwise I'd need to implement a skiplist, so you can have
                //streams that handle inserts.

                //TRACK HOW MANY ITERATORS ARE USING THIS SNAPSHOT.

                //read snapshots on the memtable are very simple, because it just
                //figures out the range sync and copies it to an array
                //so that doesn't need to be tracked specially.

                var tables = db.snapshot()

                console.log('readStream',
                    tables.map(function (e) {
                        return e.location
                    })
                )

                var stream = tables[0].createReadStream(opts)
                console.log('start', tables[0].location)
                for (var i = 1; i < tables.length; i++) {
                    console.log('merge', tables[i].location)
                    stream = merge(tables[i].createReadStream(opts), stream, u.compare)
                }
                return stream
            },
            close: function (cb) {
                pull(
                    pull.values(db.snapshot()),
                    para(function (db) {
                        db.close(cb)
                    }),
                    pull.drain(cb)
                )
            },

            compact: function (cb) {
                cb = cb || function () {
                    }
                //make a temp snapshot for use while compacting.
                //only one compaction at a time.
                if (compacting) return cb()
                compacting = true
                // create a new memtable, to save any writes while we are compacting.
                // save it in the manifest FIRST. That way we can be sure that we don't
                // loose that data if we crash while compacting.

                //add a new memtable to the manifest
                var name = nextTableName('log')
                var tables = db.snapshot()

                function getNames(tables) {
                    return tables.map(function (e) {
                        if (!e.location) throw new Error(e.type + 'table is missing location')
                        return path.basename(e.location)
                    })
                }

                var names = getNames(tables)
                names.unshift(name)
                var _memtable = createMemtable(path.join(location, name))
                _memtable.open(function (err) {
                    //uphdate the mainfest to include the new memtable AND the old one.
                    //this is so we don't forget any writes.
                    manifest.update({tables: names, seq: seq}, function (err) {
                        if (err) return cb(err)
                        if (err) return cb(err)
                        //switch to the new memtable.
                        var __memtable = memtable
                        tables = db.snapshot()
                        memtable = _memtable
                        //wait for the old memtable to drain.
                        tables[0].freeze(function () {
                            db.nextSnapshot([memtable].concat(tables))

                            //maybe the api for sst should have a write stream,
                            //except it errors unles it's all in order,
                            //and you can only use it once?
                            var newSST = path.join(location, nextTableName('sst'))
                            var _tables = tables.slice()
                            _tables = compact(_tables, createSST.createStream(newSST, function (err, sst) {
                                if (err) return cb(err)
                                //now that we have generated the sst,
                                //save the new table set in the manifest,
                                //so that if the process crashes we will reload the right data.
                                //hmm, maybe we could just start using it... sst table are immutable, anyway.
                                //the most important thing to have right is the memtables.
                                //when an sst is no longer used (been compacted + no more iterators)
                                //then it's safe to delete.
                                _tables = [memtable, sst].concat(_tables)
                                manifest.update({tables: getNames(_tables), seq: seq}, function (err) {
                                    if (err) return cb(err)
                                    compacting = false
                                    db.nextSnapshot(_tables)
                                    if (cb) cb()
                                })
                            }))
                        })
                    })
                })
            }
        }
    }
}
<commit_msg>Converting functions to return promises :PUT<commit_after>var compact = require('./compact')
var createSST = require('./sst')
var mem = require('./mem')
var mkdirp = require('mkdirp')
var path = require('path')
var merge = require('pull-merge')
var u = require('./util')
var zeros = '00000000'
var Promise = require('bluebird');

var pull = require('pull-stream')
var para = require('pull-paramap')

function pad(n) {
    n = n.toString()
    return zeros.substring(n.length) + n
}

function isEmpty(o) {
    if (null == o) return true
    for (var k in o)
        return false
    return true
}

module.exports = function (createSST, createMemtable, createManifest) {

    return function (location, opts) {
        var memtable = mem(), counter = 0, db, compacting = false, _snapshot
        var tables = tables || [memtable]
        var manifest, tables, seq

        function nextTableName(type) {
            return type + '-' + pad(++seq) + '.json'
        }

        return db = {
            //should i separate all this stuff out so that
            //you can simulate the database all in memory?
            open: function (opts, cb) {
                if (!cb) cb = opts, opts = {}
                mkdirp(location, function (err) {
                    if (err) return cb(err)
                    manifest = createManifest(path.join(location, 'manifest.json'))
                    manifest.open(function (err) {
                        if (err) return cb(err)
                        //if the manifest is empty create an empty memory table.
                        if (isEmpty(manifest.data)) {
                            seq = 1
                            var filename = path.join(location, 'log-' + pad(seq) + '.json')
                            var _memtable = createMemtable(filename)
                            manifest.update({tables: ['log-' + pad(seq) + '.json'], seq: seq}, function (err) {
                                if (err) return cb(err)
                                _memtable.open(function (err) {
                                    if (err) cb(err)
                                    memtable = _memtable
                                    _snapshot = [memtable]
                                    cb(null, db)
                                })
                            })
                        } else {
                            //open all the tables.
                            var _tables = manifest.data.tables || []
                            var n = tables.length
                            var _seq = 0

                            _tables.forEach(function (name, i) {
                                var m = /^(log|sst)-(\d+)\.json$/.exec(name)
                                var type = m[1], table
                                _seq = Math.max(m[2], _seq)

                                var create = type == 'log' ? createMemtable : createSST
                                var table = tables[i] = create(path.join(location, name))

                                table.open(next)
                            })
                            function next(err) {
                                if (err) return n = -1, cb(err)
                                if (--n) return
                                seq = _seq + 1
                                db.nextSnapshot(tables)
                                memtable = tables[0]
                                cb(null, db)
                            }
                        }
                    })
                })
                return db
            },
            //get the current snapshot.
            nextSnapshot: function (ary) {
                _snapshot = ary
                return db
            },
            snapshot: function () {
                if (_snapshot) {
                    tables = _snapshot;
                    _snapshot = null
                }
                return tables
            },
            //step through all the databases, and look for next
            get: function (key) {
                return new Promise(function(resolve, reject){
                    var tables = db.snapshot();

                    (function next(i) {
                        //if we ran out of tables, err
                        //gets need to use snapshots too...
                        //but since they will return from the memtable
                        //synchronously they will only need to track the SSTs
                        //which probably means they can all share a snapshot
                        //unless there is a compaction happening.

                        //just have an SST snapshot,
                        //keep a count of current gets
                        //and don't delete any ssts until it's freed.

                        if (!tables[i])
                            return reject(new Error('Not Found'));

                        tables[i].get(key, function (err, value) {
                            if (err) return next(i + 1);
                            return resolve(value);
                        })
                    })(0)
                });
            },
            //only write to the FIRST table.
            put: function (key, value) {
                return new Promise(function(resolve, reject){
                    memtable.put(key, value, function (err) {
                        if (!(++counter % 1000))
                            db.compact();

                        !!err ? reject(err) : resolve(value);
                    });
                });
            },
            del: function (del, cb) {
                return memtable.del(key, function (err) {
                    if (!(++counter % 1000)) db.compact()
                    cb(err)
                })
            },
            batch: function (ops, cb) {
                return memtable.batch(ops, function (err) {
                    if (!(++counter % 1000)) db.compact()
                    cb(err)
                })
            },
            createReadStream: function (opts) {
                //merge streams from all the tables
                //it's probably okay to not have a snapshot on the memtable.
                //hmm, on the other hand, it will be fairly cheap...
                //otherwise I'd need to implement a skiplist, so you can have
                //streams that handle inserts.

                //TRACK HOW MANY ITERATORS ARE USING THIS SNAPSHOT.

                //read snapshots on the memtable are very simple, because it just
                //figures out the range sync and copies it to an array
                //so that doesn't need to be tracked specially.

                var tables = db.snapshot()

                console.log('readStream',
                    tables.map(function (e) {
                        return e.location
                    })
                )

                var stream = tables[0].createReadStream(opts)
                console.log('start', tables[0].location)
                for (var i = 1; i < tables.length; i++) {
                    console.log('merge', tables[i].location)
                    stream = merge(tables[i].createReadStream(opts), stream, u.compare)
                }
                return stream
            },
            close: function (cb) {
                pull(
                    pull.values(db.snapshot()),
                    para(function (db) {
                        db.close(cb)
                    }),
                    pull.drain(cb)
                )
            },

            compact: function (cb) {
                cb = cb || function () {
                    }
                //make a temp snapshot for use while compacting.
                //only one compaction at a time.
                if (compacting) return cb()
                compacting = true
                // create a new memtable, to save any writes while we are compacting.
                // save it in the manifest FIRST. That way we can be sure that we don't
                // loose that data if we crash while compacting.

                //add a new memtable to the manifest
                var name = nextTableName('log')
                var tables = db.snapshot()

                function getNames(tables) {
                    return tables.map(function (e) {
                        if (!e.location) throw new Error(e.type + 'table is missing location')
                        return path.basename(e.location)
                    })
                }

                var names = getNames(tables)
                names.unshift(name)
                var _memtable = createMemtable(path.join(location, name))
                _memtable.open(function (err) {
                    //uphdate the mainfest to include the new memtable AND the old one.
                    //this is so we don't forget any writes.
                    manifest.update({tables: names, seq: seq}, function (err) {
                        if (err) return cb(err)
                        if (err) return cb(err)
                        //switch to the new memtable.
                        var __memtable = memtable
                        tables = db.snapshot()
                        memtable = _memtable
                        //wait for the old memtable to drain.
                        tables[0].freeze(function () {
                            db.nextSnapshot([memtable].concat(tables))

                            //maybe the api for sst should have a write stream,
                            //except it errors unles it's all in order,
                            //and you can only use it once?
                            var newSST = path.join(location, nextTableName('sst'))
                            var _tables = tables.slice()
                            _tables = compact(_tables, createSST.createStream(newSST, function (err, sst) {
                                if (err) return cb(err)
                                //now that we have generated the sst,
                                //save the new table set in the manifest,
                                //so that if the process crashes we will reload the right data.
                                //hmm, maybe we could just start using it... sst table are immutable, anyway.
                                //the most important thing to have right is the memtables.
                                //when an sst is no longer used (been compacted + no more iterators)
                                //then it's safe to delete.
                                _tables = [memtable, sst].concat(_tables)
                                manifest.update({tables: getNames(_tables), seq: seq}, function (err) {
                                    if (err) return cb(err)
                                    compacting = false
                                    db.nextSnapshot(_tables)
                                    if (cb) cb()
                                })
                            }))
                        })
                    })
                })
            }
        }
    }
}
<|endoftext|>


<commit_before>module.exports = {

//---------------------------------------------------------------------
// Action Name
//
// This is the name of the action displayed in the editor.
//---------------------------------------------------------------------

name: "Replace",

//---------------------------------------------------------------------
// Action Section
//
// This is the section the action will fall into.
//---------------------------------------------------------------------

section: "Other Stuff",

//---------------------------------------------------------------------
// Action Subtitle
//
// This function generates the subtitle displayed next to the name.
//---------------------------------------------------------------------

subtitle: function(data) {
	return `Replaces Text`;
},

//---------------------------------------------------------------------
	// DBM Mods Manager Variables (Optional but nice to have!)
	//
	// These are variables that DBM Mods Manager uses to show information
	// about the mods for people to see in the list.
	//---------------------------------------------------------------------

	// Who made the mod (If not set, defaults to "DBM Mods")
	author: "EliteArtz",

	// The version of the mod (Defaults to 1.0.0)
	version: "1.8.7", //Added in 1.8.7

	// A short description to show on the mod line for this mod (Must be on a single line)
	short_description: "Replaces your message what you wan't.",

	// If it depends on any other mods by name, ex: WrexMODS if the mod uses something from WrexMods

//---------------------------------------------------------------------
// Action Storage Function
//
// Stores the relevant variable info for the editor.
//---------------------------------------------------------------------

variableStorage: function(data, varType) {
	const type = parseInt(data.storage);
	if(type !== varType) return;
	let dataType = 'Replaced Text';
	return ([data.varName, dataType]);
},
//---------------------------------------------------------------------
// Action Fields
//
// These are the fields for the action. These fields are customized
// by creating elements with corresponding IDs in the HTML. These
// are also the names of the fields stored in the action's JSON data.
//---------------------------------------------------------------------

fields: ["replacemsg", "replaceto", "storage", "varName", "ifEach"],

//---------------------------------------------------------------------
// Command HTML
//
// This function returns a string containing the HTML used for
// editting actions.
//
// The "isEvent" parameter will be true if this action is being used
// for an event. Due to their nature, events lack certain information,
// so edit the HTML to reflect this.
//
// The "data" parameter stores constants for select elements to use.
// Each is an array: index 0 for commands, index 1 for events.
// The names are: sendTargets, members, roles, channels,
//                messages, servers, variables
//---------------------------------------------------------------------

html: function(isEvent, data) {
	return `

	   Mod Info:

	   Made by EliteArtz!

		Replace Text:

		<textarea id="replacemsg" rows="2" placeholder="Insert message here..." style="width: 99%; font-family: monospace; white-space: nowrap; resize: none;"></textarea>

	   Replace to:

	   <input id="replaceto" class="round" type="text">

        <select id="ifEach" class="round" style="float: right; width: 45%;">
            <option value="1">Whole content</option>
            <option value="0" selected>For Each Word</option>
        </select>

		Store Result In:

		<select id="storage" class="round" onchange="glob.variableChange(this, 'varNameContainer')">
			${data.variables[0]}
		</select>

		Variable Name:

		<input id="varName" class="round" type="text">

`
},

//---------------------------------------------------------------------
// Action Editor Init Code
//
// When the HTML is first applied to the action editor, this code
// is also run. This helps add modifications or setup reactionary
// functions for the DOM elements.
//---------------------------------------------------------------------

init: function() {
	const {glob, document} = this;

	glob.variableChange(document.getElementById('storage'), 'varNameContainer');
},

//---------------------------------------------------------------------
// Action Bot Function
//
// This is the function for the action within the Bot's Action class.
// Keep in mind event calls won't have access to the "msg" parameter,
// so be sure to provide checks for variable existance.
//---------------------------------------------------------------------

action: function(cache) {
	//Global Variable's
	const data = cache.actions[cache.index];
	var result = {};

    // Code
	try {
		const replaceTEXT = this.evalMessage(data.replacemsg, cache);
		const replaceTO = this.evalMessage(data.replaceto, cache);
		if (replaceTEXT) {
			if (replaceTO) {
                if (data.ifEach === "1") {

                    result = replaceTEXT.replace(replaceTEXT, replaceTO); //This is the action that we're running if everything is Okay.

                    const storage = parseInt(data.storage);
                    const varName = this.evalMessage(data.varName, cache);
                    this.storeValue(result, storage, varName, cache);

                } else if (data.ifEach === "0") {

                    result = replaceTEXT.replace(/(\w+)/g, replaceTO); //This is the action that we're running if everything is Okay.

					const storage = parseInt(data.storage);
                    const varName = this.evalMessage(data.varName, cache);
                    this.storeValue(result, storage, varName, cache);
                }
			} else {
				console.log('No insert in "Replace To"...'); //logs it in the console if nothing were inserted...
            }
		} else {
		    console.log(`No insert in "Replace Message"...`); //logs it in the console if nothing were inserted...
		}
	} catch (e) {
		console.error("ERROR!" + e + e.stack); //logs if there was an error
	}

	this.callNextAction(cache);
},

//---------------------------------------------------------------------
// Action Bot Mod
//
// Upon initialization of the bot, this code is run. Using the bot's
// DBM namespace, one can add/modify existing functions if necessary.
// In order to reduce conflictions between mods, be sure to alias
// functions you wish to overwrite.
//---------------------------------------------------------------------

mod: function(DBM) {
}

}; // End of module
<commit_msg>Moved replace_MOD.js to Deprecated<commit_after>module.exports = {

//---------------------------------------------------------------------
// Action Name
//
// This is the name of the action displayed in the editor.
//---------------------------------------------------------------------

name: "Replace",

//---------------------------------------------------------------------
// Action Section
//
// This is the section the action will fall into.
//---------------------------------------------------------------------

section: "Deprecated",

//---------------------------------------------------------------------
// Action Subtitle
//
// This function generates the subtitle displayed next to the name.
//---------------------------------------------------------------------

subtitle: function(data) {
	return `Replaces Text`;
},

//---------------------------------------------------------------------
	// DBM Mods Manager Variables (Optional but nice to have!)
	//
	// These are variables that DBM Mods Manager uses to show information
	// about the mods for people to see in the list.
	//---------------------------------------------------------------------

	// Who made the mod (If not set, defaults to "DBM Mods")
	author: "EliteArtz",

	// The version of the mod (Defaults to 1.0.0)
	version: "1.8.7", //Added in 1.8.7

	// A short description to show on the mod line for this mod (Must be on a single line)
	short_description: "Replaces your message what you wan't.",

	// If it depends on any other mods by name, ex: WrexMODS if the mod uses something from WrexMods

//---------------------------------------------------------------------
// Action Storage Function
//
// Stores the relevant variable info for the editor.
//---------------------------------------------------------------------

variableStorage: function(data, varType) {
	const type = parseInt(data.storage);
	if(type !== varType) return;
	let dataType = 'Replaced Text';
	return ([data.varName, dataType]);
},
//---------------------------------------------------------------------
// Action Fields
//
// These are the fields for the action. These fields are customized
// by creating elements with corresponding IDs in the HTML. These
// are also the names of the fields stored in the action's JSON data.
//---------------------------------------------------------------------

fields: ["replacemsg", "replaceto", "storage", "varName", "ifEach"],

//---------------------------------------------------------------------
// Command HTML
//
// This function returns a string containing the HTML used for
// editting actions.
//
// The "isEvent" parameter will be true if this action is being used
// for an event. Due to their nature, events lack certain information,
// so edit the HTML to reflect this.
//
// The "data" parameter stores constants for select elements to use.
// Each is an array: index 0 for commands, index 1 for events.
// The names are: sendTargets, members, roles, channels,
//                messages, servers, variables
//---------------------------------------------------------------------

html: function(isEvent, data) {
	return `

	   Mod Info:

	   Made by EliteArtz!

		Replace Text:

		<textarea id="replacemsg" rows="2" placeholder="Insert message here..." style="width: 99%; font-family: monospace; white-space: nowrap; resize: none;"></textarea>

	   Replace to:

	   <input id="replaceto" class="round" type="text">

        <select id="ifEach" class="round" style="float: right; width: 45%;">
            <option value="1">Whole content</option>
            <option value="0" selected>For Each Word</option>
        </select>

		Store Result In:

		<select id="storage" class="round" onchange="glob.variableChange(this, 'varNameContainer')">
			${data.variables[0]}
		</select>

		Variable Name:

		<input id="varName" class="round" type="text">

`
},

//---------------------------------------------------------------------
// Action Editor Init Code
//
// When the HTML is first applied to the action editor, this code
// is also run. This helps add modifications or setup reactionary
// functions for the DOM elements.
//---------------------------------------------------------------------

init: function() {
	const {glob, document} = this;

	glob.variableChange(document.getElementById('storage'), 'varNameContainer');
},

//---------------------------------------------------------------------
// Action Bot Function
//
// This is the function for the action within the Bot's Action class.
// Keep in mind event calls won't have access to the "msg" parameter,
// so be sure to provide checks for variable existance.
//---------------------------------------------------------------------

action: function(cache) {
	//Global Variable's
	const data = cache.actions[cache.index];
	var result = {};

    // Code
	try {
		const replaceTEXT = this.evalMessage(data.replacemsg, cache);
		const replaceTO = this.evalMessage(data.replaceto, cache);
		if (replaceTEXT) {
			if (replaceTO) {
                if (data.ifEach === "1") {

                    result = replaceTEXT.replace(replaceTEXT, replaceTO); //This is the action that we're running if everything is Okay.

                    const storage = parseInt(data.storage);
                    const varName = this.evalMessage(data.varName, cache);
                    this.storeValue(result, storage, varName, cache);

                } else if (data.ifEach === "0") {

                    result = replaceTEXT.replace(/(\w+)/g, replaceTO); //This is the action that we're running if everything is Okay.

					const storage = parseInt(data.storage);
                    const varName = this.evalMessage(data.varName, cache);
                    this.storeValue(result, storage, varName, cache);
                }
			} else {
				console.log('No insert in "Replace To"...'); //logs it in the console if nothing were inserted...
            }
		} else {
		    console.log(`No insert in "Replace Message"...`); //logs it in the console if nothing were inserted...
		}
	} catch (e) {
		console.error("ERROR!" + e + e.stack); //logs if there was an error
	}

	this.callNextAction(cache);
},

//---------------------------------------------------------------------
// Action Bot Mod
//
// Upon initialization of the bot, this code is run. Using the bot's
// DBM namespace, one can add/modify existing functions if necessary.
// In order to reduce conflictions between mods, be sure to alias
// functions you wish to overwrite.
//---------------------------------------------------------------------

mod: function(DBM) {
}

}; // End of module
<|endoftext|>


<commit_before>//////////////////////////////////
//JQLite
//////////////////////////////////

var jqCache = {},
    jqName = 'ng-' + new Date().getTime(),
    jqId = 1,
    addEventListener = (window.document.attachEvent ?
      function(element, type, fn) {element.attachEvent('on' + type, fn);} :
      function(element, type, fn) {element.addEventListener(type, fn, false);}),
    removeEventListener = (window.document.detachEvent ?
      function(element, type, fn) {element.detachEvent('on' + type, fn); } :
      function(element, type, fn) { element.removeEventListener(type, fn, false); });

function jqNextId() { return (jqId++); }

function jqClearData(element) {
  var cacheId = element[jqName],
      cache = jqCache[cacheId];
  if (cache) {
    foreach(cache.bind || {}, function(fn, type){
      removeEventListener(element, type, fn);
    });
    delete jqCache[cacheId];
    if (msie)
      element[jqName] = ''; // ie does not allow deletion of attributes on elements.
    else
      delete element[jqName];
  }
}

function getStyle(element) {
  var current = {}, style = element[0].style, value, name, i;
  if (typeof style.length == 'number') {
    for(i = 0; i < style.length; i++) {
      name = style[i];
      current[name] = style[name];
    }
  } else {
    for (name in style) {
      value = style[name];
      if (1*name != name && name != 'cssText' && value && typeof value == 'string' && value !='false')
        current[name] = value;
    }
  }
  return current;
}

function JQLite(element) {
  if (isElement(element)) {
    this[0] = element;
    this.length = 1;
  } else if (isDefined(element.length) && element.item) {
    for(var i=0; i < element.length; i++) {
      this[i] = element[i];
    }
    this.length = element.length;
  }
}

JQLite.prototype = {
  data: function(key, value) {
    var element = this[0],
        cacheId = element[jqName],
        cache = jqCache[cacheId || -1];
    if (isDefined(value)) {
      if (!cache) {
        element[jqName] = cacheId = jqNextId();
        cache = jqCache[cacheId] = {};
      }
      cache[key] = value;
    } else {
      return cache ? cache[key] : _null;
    }
  },

  removeData: function(){
    jqClearData(this[0]);
  },

  dealoc: function(){
    (function dealoc(element){
      jqClearData(element);
      for ( var i = 0, children = element.childNodes; i < children.length; i++) {
        dealoc(children[i]);
      }
    })(this[0]);
  },

  bind: function(type, fn){
    var self = this,
        element = self[0],
        bind = self.data('bind'),
        eventHandler;
    if (!bind) this.data('bind', bind = {});
    foreach(type.split(' '), function(type){
      eventHandler = bind[type];
      if (!eventHandler) {
        bind[type] = eventHandler = function(event) {
          if (!event.preventDefault) {
            event.preventDefault = function(){
              event.returnValue = false;
            };
          }
          foreach(eventHandler.fns, function(fn){
            fn.call(self, event);
          });
        };
        eventHandler.fns = [];
        addEventListener(element, type, eventHandler);
      }
      eventHandler.fns.push(fn);
    });
  },

  trigger: function(type) {
    var evnt = document.createEvent('MouseEvent');
    evnt.initMouseEvent(type, true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, _null);
    this[0].dispatchEvent(evnt);
  },

  replaceWith: function(replaceNode) {
    this[0].parentNode.replaceChild(jqLite(replaceNode)[0], this[0]);
  },

  children: function() {
    return new JQLite(this[0].childNodes);
  },

  append: function(node) {
    var self = this[0];
    node = jqLite(node);
    foreach(node, function(child){
      self.appendChild(child);
    });
  },

  remove: function() {
    this.dealoc();
    var parentNode = this[0].parentNode;
    if (parentNode) parentNode.removeChild(this[0]);
  },

  removeAttr: function(name) {
    this[0].removeAttribute(name);
  },

  after: function(element) {
    this[0].parentNode.insertBefore(jqLite(element)[0], this[0].nextSibling);
  },

  hasClass: function(selector) {
    var className = " " + selector + " ";
    if ( (" " + this[0].className + " ").replace(/[\n\t]/g, " ").indexOf( className ) > -1 ) {
      return true;
    }
    return false;
  },

  removeClass: function(selector) {
    this[0].className = trim((" " + this[0].className + " ").replace(/[\n\t]/g, " ").replace(" " + selector + " ", ""));
  },

  toggleClass: function(selector, condition) {
   var self = this;
   (condition ? self.addClass : self.removeClass).call(self, selector);
  },

  addClass: function( selector ) {
    if (!this.hasClass(selector)) {
      this[0].className = trim(this[0].className + ' ' + selector);
    }
  },

  css: function(name, value) {
    var style = this[0].style;
    if (isString(name)) {
      if (isDefined(value)) {
        style[name] = value;
      } else {
        return style[name];
      }
    } else {
      extend(style, name);
    }
  },

  attr: function(name, value){
    var e = this[0];
    if (isObject(name)) {
      foreach(name, function(value, name){
        e.setAttribute(name, value);
      });
    } else if (isDefined(value)) {
      e.setAttribute(name, value);
    } else {
      var attributes = e.attributes,
          item = attributes ? attributes.getNamedItem(name) : _undefined;
      return item && item.specified ? item.value : _undefined;
    }
  },

  text: function(value) {
    if (isDefined(value)) {
      this[0].textContent = value;
    }
    return this[0].textContent;
  },

  val: function(value) {
    if (isDefined(value)) {
      this[0].value = value;
    }
    return this[0].value;
  },

  html: function(value) {
    if (isDefined(value)) {
      var i = 0, childNodes = this[0].childNodes;
      for ( ; i < childNodes.length; i++) {
        jqLite(childNodes[i]).dealoc();
      }
      this[0].innerHTML = value;
    }
    return this[0].innerHTML;
  },

  parent: function() {
    return jqLite(this[0].parentNode);
  },

  clone: function() { return jqLite(this[0].cloneNode(true)); }
};

if (msie) {
  extend(JQLite.prototype, {
    text: function(value) {
      var e = this[0];
      // NodeType == 3 is text node
      if (e.nodeType == 3) {
        if (isDefined(value)) e.nodeValue = value;
        return e.nodeValue;
      } else {
        if (isDefined(value)) e.innerText = value;
        return e.innerText;
      }
    },

    trigger: function(type) {
      this[0].fireEvent('on' + type);
    }
  });
}
<commit_msg>properly handle event's stopPropagation() and preventDefault() method in IE<commit_after>//////////////////////////////////
//JQLite
//////////////////////////////////

var jqCache = {},
    jqName = 'ng-' + new Date().getTime(),
    jqId = 1,
    addEventListener = (window.document.attachEvent ?
      function(element, type, fn) {element.attachEvent('on' + type, fn);} :
      function(element, type, fn) {element.addEventListener(type, fn, false);}),
    removeEventListener = (window.document.detachEvent ?
      function(element, type, fn) {element.detachEvent('on' + type, fn); } :
      function(element, type, fn) { element.removeEventListener(type, fn, false); });

function jqNextId() { return (jqId++); }

function jqClearData(element) {
  var cacheId = element[jqName],
      cache = jqCache[cacheId];
  if (cache) {
    foreach(cache.bind || {}, function(fn, type){
      removeEventListener(element, type, fn);
    });
    delete jqCache[cacheId];
    if (msie)
      element[jqName] = ''; // ie does not allow deletion of attributes on elements.
    else
      delete element[jqName];
  }
}

function getStyle(element) {
  var current = {}, style = element[0].style, value, name, i;
  if (typeof style.length == 'number') {
    for(i = 0; i < style.length; i++) {
      name = style[i];
      current[name] = style[name];
    }
  } else {
    for (name in style) {
      value = style[name];
      if (1*name != name && name != 'cssText' && value && typeof value == 'string' && value !='false')
        current[name] = value;
    }
  }
  return current;
}

function JQLite(element) {
  if (isElement(element)) {
    this[0] = element;
    this.length = 1;
  } else if (isDefined(element.length) && element.item) {
    for(var i=0; i < element.length; i++) {
      this[i] = element[i];
    }
    this.length = element.length;
  }
}

JQLite.prototype = {
  data: function(key, value) {
    var element = this[0],
        cacheId = element[jqName],
        cache = jqCache[cacheId || -1];
    if (isDefined(value)) {
      if (!cache) {
        element[jqName] = cacheId = jqNextId();
        cache = jqCache[cacheId] = {};
      }
      cache[key] = value;
    } else {
      return cache ? cache[key] : _null;
    }
  },

  removeData: function(){
    jqClearData(this[0]);
  },

  dealoc: function(){
    (function dealoc(element){
      jqClearData(element);
      for ( var i = 0, children = element.childNodes; i < children.length; i++) {
        dealoc(children[i]);
      }
    })(this[0]);
  },

  bind: function(type, fn){
    var self = this,
        element = self[0],
        bind = self.data('bind'),
        eventHandler;
    if (!bind) this.data('bind', bind = {});
    foreach(type.split(' '), function(type){
      eventHandler = bind[type];
      if (!eventHandler) {
        bind[type] = eventHandler = function(event) {
          if (!event.preventDefault) {
            event.preventDefault = function(){
              event.returnValue = false; //ie
            };
          }
          if (!event.stopPropagation) {
            event.stopPropagation = function() {
              event.cancelBubble = true; //ie
            }
          }
          foreach(eventHandler.fns, function(fn){
            fn.call(self, event);
          });
        };
        eventHandler.fns = [];
        addEventListener(element, type, eventHandler);
      }
      eventHandler.fns.push(fn);
    });
  },

  trigger: function(type) {
    var evnt = document.createEvent('MouseEvent');
    evnt.initMouseEvent(type, true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, _null);
    this[0].dispatchEvent(evnt);
  },

  replaceWith: function(replaceNode) {
    this[0].parentNode.replaceChild(jqLite(replaceNode)[0], this[0]);
  },

  children: function() {
    return new JQLite(this[0].childNodes);
  },

  append: function(node) {
    var self = this[0];
    node = jqLite(node);
    foreach(node, function(child){
      self.appendChild(child);
    });
  },

  remove: function() {
    this.dealoc();
    var parentNode = this[0].parentNode;
    if (parentNode) parentNode.removeChild(this[0]);
  },

  removeAttr: function(name) {
    this[0].removeAttribute(name);
  },

  after: function(element) {
    this[0].parentNode.insertBefore(jqLite(element)[0], this[0].nextSibling);
  },

  hasClass: function(selector) {
    var className = " " + selector + " ";
    if ( (" " + this[0].className + " ").replace(/[\n\t]/g, " ").indexOf( className ) > -1 ) {
      return true;
    }
    return false;
  },

  removeClass: function(selector) {
    this[0].className = trim((" " + this[0].className + " ").replace(/[\n\t]/g, " ").replace(" " + selector + " ", ""));
  },

  toggleClass: function(selector, condition) {
   var self = this;
   (condition ? self.addClass : self.removeClass).call(self, selector);
  },

  addClass: function( selector ) {
    if (!this.hasClass(selector)) {
      this[0].className = trim(this[0].className + ' ' + selector);
    }
  },

  css: function(name, value) {
    var style = this[0].style;
    if (isString(name)) {
      if (isDefined(value)) {
        style[name] = value;
      } else {
        return style[name];
      }
    } else {
      extend(style, name);
    }
  },

  attr: function(name, value){
    var e = this[0];
    if (isObject(name)) {
      foreach(name, function(value, name){
        e.setAttribute(name, value);
      });
    } else if (isDefined(value)) {
      e.setAttribute(name, value);
    } else {
      // the extra argument is to get the right thing for a.href in IE, see jQuery code
      return e.getAttribute(name, 2);
    }
  },

  text: function(value) {
    if (isDefined(value)) {
      this[0].textContent = value;
    }
    return this[0].textContent;
  },

  val: function(value) {
    if (isDefined(value)) {
      this[0].value = value;
    }
    return this[0].value;
  },

  html: function(value) {
    if (isDefined(value)) {
      var i = 0, childNodes = this[0].childNodes;
      for ( ; i < childNodes.length; i++) {
        jqLite(childNodes[i]).dealoc();
      }
      this[0].innerHTML = value;
    }
    return this[0].innerHTML;
  },

  parent: function() {
    return jqLite(this[0].parentNode);
  },

  clone: function() { return jqLite(this[0].cloneNode(true)); }
};

if (msie) {
  extend(JQLite.prototype, {
    text: function(value) {
      var e = this[0];
      // NodeType == 3 is text node
      if (e.nodeType == 3) {
        if (isDefined(value)) e.nodeValue = value;
        return e.nodeValue;
      } else {
        if (isDefined(value)) e.innerText = value;
        return e.innerText;
      }
    },

    trigger: function(type) {
      this[0].fireEvent('on' + type);
    }
  });
}
<|endoftext|>


<commit_before>/*
 * api.js - The Sajari JavaScript API SDK
 */

var query = require("./query");
var url = require("./website/utils/url");

var request = require('superagent');
var jsonp = require('superagent-jsonp'); // To use jsonp chain: .use(jsonp)
var debug = require('debug');
var error = debug('app:error');
var log = debug('app:log');

/*
 * Sajari Search API SDK
 * https://www.sajari.com/
 *
 * @param {string} company - Your company ID, see your dashboard
 * @param {string} collection - Your collection, see your dashboard
 * @param {Object} [opts]
 */
function API(company, collection, opts) {
  var usage = 'Usage: API(company, collection, opts)';

  if (!company) {
    error('Please provide a company ID. ' + usage);
    return
  }
  if (!collection) {
    error('Please provide a collection ID. ' + usage);
    return
  }
  this.company = company;
  this.collection = collection;

  opts = opts || {};
  for (var o in opts) {
    this[o] = opts[o];
  }
  if (this.jsonp === undefined) {
    this.jsonp = false;
  }

  this.query = new query();

  this.hosts = {
    api : "https://www.sajari.com/api/",
    index : "https://re.sajari.com"
  }

  log('API init done, %j', this);
}

API.prototype = {
  /*
   * Search
   *
   */
  search: function(data, success, failure) {
    opts = {
      method : "POST",
      success: success,
      failure: failure,
    }
    return this.send("search", opts, data);
  },

  /*
   * Popular recommendations
   *
   */
  popular: function(data, success, failure) {
    opts = {
      method : "POST",
      success: success,
      failure: failure,
    }
    return this.send("popular", opts, data);
  },

  /*
   * Related recommendations
   *
   */
  related: function(data, success, failure) {
    opts = {
      method : "POST",
      success: success,
      failure: failure,
    }
    return this.send("related", opts, data);
  },

  /*
   * Recent recommendations
   *
   */
  recent: function(data, success, failure) {
    opts = {
      method : "POST",
      success: success,
      failure: failure,
    }
    return this.send("recent", opts, data);
  },

  /*
   * Best recommendations
   *
   */
  best: function(data, success, failure) {
    opts = {
      method : "POST",
      success: success,
      failure: failure,
    }
    return this.send("best", opts, data);
  },

  /**
   * Send tracking data to a URL via an image request. Automatically adds extra arguments for identity, company, and collection.
   */
  pixel: function(data, path) {
    log("Pixel: " + data);
    var img = new Image();
    img.onerror = function() {
        log('Failed sending: ' + img.src);
    };
    img.onload = function() {
        log('Successful send: ' + img.src);
    };
    // Merge in the company and collection data
    data.company = this.company;
    data.collection = this.collection;

    if (path === undefined) {
      return
    }
    img.src = url.augmentUri(this.hosts.index + path, data);
  },

  /**
   * Merge two group of args, the "priority" object args will overwrite
   * the "secondary" object args if there is a clash in keys. This also
   * supports url encoded strings, which are decoded into an object
   */
  mergeArgs: function(priority, secondary) {
    if (typeof priority === 'string') {
      priority = url.decodeUriArgs(priority +'');
    }
    if (typeof secondary === 'string') {
      secondary = url.decodeUriArgs(secondary+'');
    }
    for (var k in priority) {
        secondary[k] = priority[k];
    }
    return secondary
  },

  /**
   * Sends the API request and handles the response
   */
  send: function(path, opts, data) {
    this.query.se++;

    // Merge in company and collection
    data = this.mergeArgs(data, {"company": this.company, "collection": this.collection, 'q.se': this.query.se});

    var path = this.hosts.api + path;
    if (this.jsonp) {
      // Add the data to the JSONP URL destination
      path = url.augmentUri(path, data);
    }
    switch(opts.method) {
      case "POST":
          var req = request.post(path);
          break;
      case "GET":
          var req = request.get(path);
          break;
      // TODO - What else?
      default:
          return opts.failure();
    }

    // If basic auth is enabled, add it
    if (this.basicauth) {
      req.auth(this.basicauth.user, this.basicauth.path);
    }

    // Add data to the req if we aren't using JSONP (which needs
    // to be art of the URL, which was merged already above)
    if (this.jsonp) {
      req.use(jsonp);
    } else {
      req.send(data);
    }

    // Send the request and handle the response
    req.end(function(err, res){
      if (err) {
        opts.failure();
      } else {
        opts.success(res.body);
      }
    })
  }
};

module.exports = API;
<commit_msg>Fix bug where recommendations were incrementing the sequence<commit_after>/*
 * api.js - The Sajari JavaScript API SDK
 */

var query = require("./query");
var url = require("./website/utils/url");

var request = require('superagent');
var jsonp = require('superagent-jsonp'); // To use jsonp chain: .use(jsonp)
var debug = require('debug');
var error = debug('app:error');
var log = debug('app:log');

/*
 * Sajari Search API SDK
 * https://www.sajari.com/
 *
 * @param {string} company - Your company ID, see your dashboard
 * @param {string} collection - Your collection, see your dashboard
 * @param {Object} [opts]
 */
function API(company, collection, opts) {
  var usage = 'Usage: API(company, collection, opts)';

  if (!company) {
    error('Please provide a company ID. ' + usage);
    return
  }
  if (!collection) {
    error('Please provide a collection ID. ' + usage);
    return
  }
  this.company = company;
  this.collection = collection;

  opts = opts || {};
  for (var o in opts) {
    this[o] = opts[o];
  }
  if (this.jsonp === undefined) {
    this.jsonp = false;
  }

  this.query = new query();

  this.hosts = {
    api : "https://www.sajari.com/api/",
    index : "https://re.sajari.com"
  }

  log('API init done, %j', this);
}

API.prototype = {
  /*
   * Search
   *
   */
  search: function(data, success, failure) {
    opts = {
      method : "POST",
      success: success,
      failure: failure,
    }
    this.query.se++;
    return this.send("search", opts, data);
  },

  /*
   * Popular recommendations
   *
   */
  popular: function(data, success, failure) {
    opts = {
      method : "POST",
      success: success,
      failure: failure,
    }
    return this.send("popular", opts, data);
  },

  /*
   * Related recommendations
   *
   */
  related: function(data, success, failure) {
    opts = {
      method : "POST",
      success: success,
      failure: failure,
    }
    return this.send("related", opts, data);
  },

  /*
   * Recent recommendations
   *
   */
  recent: function(data, success, failure) {
    opts = {
      method : "POST",
      success: success,
      failure: failure,
    }
    return this.send("recent", opts, data);
  },

  /*
   * Best recommendations
   *
   */
  best: function(data, success, failure) {
    opts = {
      method : "POST",
      success: success,
      failure: failure,
    }
    return this.send("best", opts, data);
  },

  /**
   * Send tracking data to a URL via an image request. Automatically adds extra arguments for identity, company, and collection.
   */
  pixel: function(data, path) {
    log("Pixel: " + data);
    var img = new Image();
    img.onerror = function() {
        log('Failed sending: ' + img.src);
    };
    img.onload = function() {
        log('Successful send: ' + img.src);
    };
    // Merge in the company and collection data
    data.company = this.company;
    data.collection = this.collection;

    if (path === undefined) {
      return
    }
    img.src = url.augmentUri(this.hosts.index + path, data);
  },

  /**
   * Merge two group of args, the "priority" object args will overwrite
   * the "secondary" object args if there is a clash in keys. This also
   * supports url encoded strings, which are decoded into an object
   */
  mergeArgs: function(priority, secondary) {
    if (typeof priority === 'string') {
      priority = url.decodeUriArgs(priority +'');
    }
    if (typeof secondary === 'string') {
      secondary = url.decodeUriArgs(secondary+'');
    }
    for (var k in priority) {
        secondary[k] = priority[k];
    }
    return secondary
  },

  /**
   * Sends the API request and handles the response
   */
  send: function(path, opts, data) {
    // Merge in company and collection
    data = this.mergeArgs(data, {"company": this.company, "collection": this.collection, 'q.se': this.query.se});

    var path = this.hosts.api + path;
    if (this.jsonp) {
      // Add the data to the JSONP URL destination
      path = url.augmentUri(path, data);
    }
    switch(opts.method) {
      case "POST":
          var req = request.post(path);
          break;
      case "GET":
          var req = request.get(path);
          break;
      // TODO - What else?
      default:
          return opts.failure();
    }

    // If basic auth is enabled, add it
    if (this.basicauth) {
      req.auth(this.basicauth.user, this.basicauth.path);
    }

    // Add data to the req if we aren't using JSONP (which needs
    // to be art of the URL, which was merged already above)
    if (this.jsonp) {
      req.use(jsonp);
    } else {
      req.send(data);
    }

    // Send the request and handle the response
    req.end(function(err, res){
      if (err) {
        opts.failure();
      } else {
        opts.success(res.body);
      }
    })
  }
};

module.exports = API;
<|endoftext|>


<commit_before>/*
 * Copyright (c) 2016, Natacha Porté
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

var cfg_endpoint = null;
var cfg_data_field = null;
var cfg_sign_algo = "";
var cfg_sign_field = "";
var cfg_sign_field_format = "";
var cfg_sign_key = "";
var cfg_sign_key_format = "";
var cfg_extra_fields = [];
var cfg_wakeup_time = -1;

var to_send = [];
var senders = [new XMLHttpRequest(), new XMLHttpRequest()];
var i_sender = 1;
var jsSHA = require("sha");

function sendPayload(payload) {
   var data = new FormData();
   data.append(cfg_data_field, payload);

   if (cfg_sign_field) {
      var sha = new jsSHA(cfg_sign_algo, "TEXT");
      sha.setHMACKey(cfg_sign_key, cfg_sign_key_format);
      sha.update(payload);
      data.append(cfg_sign_field, sha.getHMAC(cfg_sign_field_format));
   }

   if (cfg_extra_fields.length > 0) {
      for (var i = 0; i < cfg_extra_fields.length; i += 1) {
         var decoded = decodeURIComponent(cfg_extra_fields[i]).split("=");
         var name = decoded.shift();
         var value = decoded.join("=");
         data.append(name, value);
      }
   }

   i_sender = 1 - i_sender;
   senders[i_sender].open("POST", cfg_endpoint, true);
   senders[i_sender].send(data);
}

function sendHead() {
   if (to_send.length < 1) return;
   sendPayload(to_send[0].split(";")[1]);
}

function enqueue(key, line) {
   to_send.push(key + ";" + line);
   localStorage.setItem("toSend", to_send.join("|"));
   if (to_send.length === 1) {
      sendHead();
   }
}

function uploadDone() {
   var sent_key = to_send.shift().split(";")[0];
   localStorage.setItem("toSend", to_send.join("|"));
   localStorage.setItem("lastSent", sent_key);
   if (to_send.length() === 0) {
      Pebble.sendAppMessage({ "lastPosted": parseInt(sent_key, 10) });
   }
   sendHead();
}

function uploadError() { console.log(this.statusText); }

senders[0].addEventListener("load", uploadDone);
senders[0].addEventListener("error", uploadError);
senders[1].addEventListener("load", uploadDone);
senders[1].addEventListener("error", uploadError);

Pebble.addEventListener("ready", function(e) {
   console.log("Battery- JS ready");

   var str_to_send = localStorage.getItem("toSend");
   to_send = str_to_send ? str_to_send.split("|") : [];

   var str_extra_fields = localStorage.getItem("extraFields");
   cfg_extra_fields = str_extra_fields ? str_extra_fields.split(",") : [];

   cfg_endpoint = localStorage.getItem("cfgEndpoint");
   cfg_data_field = localStorage.getItem("cfgDataField");
   cfg_sign_algo = localStorage.getItem("cfgSignAlgorithm");
   cfg_sign_field = localStorage.getItem("cfgSignFieldName");
   cfg_sign_field_format = localStorage.getItem("cfgSignFieldFormat");
   cfg_sign_key = localStorage.getItem("cfgSignKey");
   cfg_sign_key_format = localStorage.getItem("cfgSignKeyFormat");
   cfg_wakeup_time = parseInt(localStorage.getItem("cfgWakeupTime") || "-1", 10);

   if (cfg_endpoint && cfg_data_field) {
      Pebble.sendAppMessage({ "lastSent":
       parseInt(localStorage.getItem("lastSent") || "0", 10) });
   }

   if (to_send.length >= 1) {
      sendHead();
   }
});

Pebble.addEventListener("appmessage", function(e) {
   if (e.payload.dataKey && e.payload.dataLine) {
      enqueue(e.payload.dataKey, e.payload.dataLine);
   }
});

Pebble.addEventListener("showConfiguration", function() {
   var settings = "?v=1.0";

   if (cfg_endpoint) {
      settings += "&url=" + encodeURIComponent(cfg_endpoint);
   }

   if (cfg_data_field) {
      settings += "&data_field=" + encodeURIComponent(cfg_data_field);
   }

   if (cfg_sign_field) {
      settings += "&s_algo=" + encodeURIComponent(cfg_sign_algo)
       + "&s_field=" + encodeURIComponent(cfg_sign_field)
       + "&s_fieldf=" + encodeURIComponent(cfg_sign_field_format)
       + "&s_key=" + encodeURIComponent(cfg_sign_key)
       + "&s_keyf=" + encodeURIComponent(cfg_sign_key_format);
   }

   if (cfg_wakeup_time >= 0) {
      settings += "&wakeup=" + cfg_wakeup_time.toString(10);
   }

   if (cfg_extra_fields.length > 0) {
      settings += "&extra=" + cfg_extra_fields.join(",");
   }

   Pebble.openURL("https://cdn.rawgit.com/faelys/battery-minus/v1.0/config.html" + settings);
});

Pebble.addEventListener("webviewclosed", function(e) {
   var configData = JSON.parse(e.response);
   var wasConfigured = (cfg_endpoint && cfg_data_field);

   if (configData.url) {
      cfg_endpoint = decodeURIComponent(configData.url);
      localStorage.setItem("cfgEndpoint", cfg_endpoint);
   }

   if (configData.dataField) {
      cfg_data_field = configData.dataField;
      localStorage.setItem("cfgDataField", cfg_data_field);
   }

   if (configData.signAlgorithm) {
      cfg_sign_algo = configData.signAlgorithm;
      localStorage.setItem("cfgSignAlgorithm", cfg_sign_algo);
   }

   if (configData.signFieldName) {
      cfg_sign_field = configData.signFieldName;
      localStorage.setItem("cfgSignFieldName", cfg_sign_field);
   }

   if (configData.signFieldFormat) {
      cfg_sign_field_format = configData.signFieldFormat;
      localStorage.setItem("cfgSignFieldFormat", cfg_sign_field_format);
   }

   if (configData.signKey) {
      cfg_sign_key = decodeURIComponent(configData.signKey);
      localStorage.setItem("cfgSignKey", cfg_sign_key);
   }

   if (configData.signKeyFormat) {
      cfg_sign_key_format = configData.signKeyFormat;
      localStorage.setItem("cfgSignKeyFormat", cfg_sign_key_format);
   }

   if (configData.wakeupTime !== null) {
      console.log("Received wakeupTime \"" + configData.wakeupTime + "\"");
      var wakeupComponents = configData.wakeupTime.split(":");
      if (wakeupComponents.length === 2) {
         var wakeupH = parseInt(wakeupComponents[0], 10);
         var wakeupM = parseInt(wakeupComponents[1], 10);
         if (wakeupH >= 0 && wakeupH < 24 && wakeupM >= 0 && wakeupM < 60) {
            cfg_wakeup_time = wakeupH * 60 + wakeupM;
            localStorage.setItem("cfgWakeupTime", cfg_wakeup_time);
            Pebble.sendAppMessage({ "cfgWakeupTime": cfg_wakeup_time });
         }
         else
            console.log("Invalid wakeupTime \"" + configData.wakeupTime + "\"");
      }
      else
         console.log("Invalid wakeupTime \"" + configData.wakeupTime + "\"");
   }

   if (configData.extraFields !== null) {
      cfg_extra_fields = configData.extraFields
       ? configData.extraFields.split(",") : [];
      localStorage.setItem("extraFields", cfg_extra_fields.join(","));
   }

   if (configData.resend) {
      senders[0].abort();
      senders[1].abort();
      localStorage.setItem("toSend", "");
      localStorage.setItem("lastSent", "0");
      to_send = [];
      wasConfigured = false;
   }

   if (!wasConfigured && cfg_endpoint && cfg_data_field) {
      Pebble.sendAppMessage({ "lastSent": 0 });
   }
});
<commit_msg>Fix stupid functionification of array length<commit_after>/*
 * Copyright (c) 2016, Natacha Porté
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

var cfg_endpoint = null;
var cfg_data_field = null;
var cfg_sign_algo = "";
var cfg_sign_field = "";
var cfg_sign_field_format = "";
var cfg_sign_key = "";
var cfg_sign_key_format = "";
var cfg_extra_fields = [];
var cfg_wakeup_time = -1;

var to_send = [];
var senders = [new XMLHttpRequest(), new XMLHttpRequest()];
var i_sender = 1;
var jsSHA = require("sha");

function sendPayload(payload) {
   var data = new FormData();
   data.append(cfg_data_field, payload);

   if (cfg_sign_field) {
      var sha = new jsSHA(cfg_sign_algo, "TEXT");
      sha.setHMACKey(cfg_sign_key, cfg_sign_key_format);
      sha.update(payload);
      data.append(cfg_sign_field, sha.getHMAC(cfg_sign_field_format));
   }

   if (cfg_extra_fields.length > 0) {
      for (var i = 0; i < cfg_extra_fields.length; i += 1) {
         var decoded = decodeURIComponent(cfg_extra_fields[i]).split("=");
         var name = decoded.shift();
         var value = decoded.join("=");
         data.append(name, value);
      }
   }

   i_sender = 1 - i_sender;
   senders[i_sender].open("POST", cfg_endpoint, true);
   senders[i_sender].send(data);
}

function sendHead() {
   if (to_send.length < 1) return;
   sendPayload(to_send[0].split(";")[1]);
}

function enqueue(key, line) {
   to_send.push(key + ";" + line);
   localStorage.setItem("toSend", to_send.join("|"));
   if (to_send.length === 1) {
      sendHead();
   }
}

function uploadDone() {
   var sent_key = to_send.shift().split(";")[0];
   localStorage.setItem("toSend", to_send.join("|"));
   localStorage.setItem("lastSent", sent_key);
   if (to_send.length === 0) {
      Pebble.sendAppMessage({ "lastPosted": parseInt(sent_key, 10) });
   }
   sendHead();
}

function uploadError() { console.log(this.statusText); }

senders[0].addEventListener("load", uploadDone);
senders[0].addEventListener("error", uploadError);
senders[1].addEventListener("load", uploadDone);
senders[1].addEventListener("error", uploadError);

Pebble.addEventListener("ready", function(e) {
   console.log("Battery- JS ready");

   var str_to_send = localStorage.getItem("toSend");
   to_send = str_to_send ? str_to_send.split("|") : [];

   var str_extra_fields = localStorage.getItem("extraFields");
   cfg_extra_fields = str_extra_fields ? str_extra_fields.split(",") : [];

   cfg_endpoint = localStorage.getItem("cfgEndpoint");
   cfg_data_field = localStorage.getItem("cfgDataField");
   cfg_sign_algo = localStorage.getItem("cfgSignAlgorithm");
   cfg_sign_field = localStorage.getItem("cfgSignFieldName");
   cfg_sign_field_format = localStorage.getItem("cfgSignFieldFormat");
   cfg_sign_key = localStorage.getItem("cfgSignKey");
   cfg_sign_key_format = localStorage.getItem("cfgSignKeyFormat");
   cfg_wakeup_time = parseInt(localStorage.getItem("cfgWakeupTime") || "-1", 10);

   if (cfg_endpoint && cfg_data_field) {
      Pebble.sendAppMessage({ "lastSent":
       parseInt(localStorage.getItem("lastSent") || "0", 10) });
   }

   if (to_send.length >= 1) {
      sendHead();
   }
});

Pebble.addEventListener("appmessage", function(e) {
   if (e.payload.dataKey && e.payload.dataLine) {
      enqueue(e.payload.dataKey, e.payload.dataLine);
   }
});

Pebble.addEventListener("showConfiguration", function() {
   var settings = "?v=1.0";

   if (cfg_endpoint) {
      settings += "&url=" + encodeURIComponent(cfg_endpoint);
   }

   if (cfg_data_field) {
      settings += "&data_field=" + encodeURIComponent(cfg_data_field);
   }

   if (cfg_sign_field) {
      settings += "&s_algo=" + encodeURIComponent(cfg_sign_algo)
       + "&s_field=" + encodeURIComponent(cfg_sign_field)
       + "&s_fieldf=" + encodeURIComponent(cfg_sign_field_format)
       + "&s_key=" + encodeURIComponent(cfg_sign_key)
       + "&s_keyf=" + encodeURIComponent(cfg_sign_key_format);
   }

   if (cfg_wakeup_time >= 0) {
      settings += "&wakeup=" + cfg_wakeup_time.toString(10);
   }

   if (cfg_extra_fields.length > 0) {
      settings += "&extra=" + cfg_extra_fields.join(",");
   }

   Pebble.openURL("https://cdn.rawgit.com/faelys/battery-minus/v1.0/config.html" + settings);
});

Pebble.addEventListener("webviewclosed", function(e) {
   var configData = JSON.parse(e.response);
   var wasConfigured = (cfg_endpoint && cfg_data_field);

   if (configData.url) {
      cfg_endpoint = decodeURIComponent(configData.url);
      localStorage.setItem("cfgEndpoint", cfg_endpoint);
   }

   if (configData.dataField) {
      cfg_data_field = configData.dataField;
      localStorage.setItem("cfgDataField", cfg_data_field);
   }

   if (configData.signAlgorithm) {
      cfg_sign_algo = configData.signAlgorithm;
      localStorage.setItem("cfgSignAlgorithm", cfg_sign_algo);
   }

   if (configData.signFieldName) {
      cfg_sign_field = configData.signFieldName;
      localStorage.setItem("cfgSignFieldName", cfg_sign_field);
   }

   if (configData.signFieldFormat) {
      cfg_sign_field_format = configData.signFieldFormat;
      localStorage.setItem("cfgSignFieldFormat", cfg_sign_field_format);
   }

   if (configData.signKey) {
      cfg_sign_key = decodeURIComponent(configData.signKey);
      localStorage.setItem("cfgSignKey", cfg_sign_key);
   }

   if (configData.signKeyFormat) {
      cfg_sign_key_format = configData.signKeyFormat;
      localStorage.setItem("cfgSignKeyFormat", cfg_sign_key_format);
   }

   if (configData.wakeupTime !== null) {
      console.log("Received wakeupTime \"" + configData.wakeupTime + "\"");
      var wakeupComponents = configData.wakeupTime.split(":");
      if (wakeupComponents.length === 2) {
         var wakeupH = parseInt(wakeupComponents[0], 10);
         var wakeupM = parseInt(wakeupComponents[1], 10);
         if (wakeupH >= 0 && wakeupH < 24 && wakeupM >= 0 && wakeupM < 60) {
            cfg_wakeup_time = wakeupH * 60 + wakeupM;
            localStorage.setItem("cfgWakeupTime", cfg_wakeup_time);
            Pebble.sendAppMessage({ "cfgWakeupTime": cfg_wakeup_time });
         }
         else
            console.log("Invalid wakeupTime \"" + configData.wakeupTime + "\"");
      }
      else
         console.log("Invalid wakeupTime \"" + configData.wakeupTime + "\"");
   }

   if (configData.extraFields !== null) {
      cfg_extra_fields = configData.extraFields
       ? configData.extraFields.split(",") : [];
      localStorage.setItem("extraFields", cfg_extra_fields.join(","));
   }

   if (configData.resend) {
      senders[0].abort();
      senders[1].abort();
      localStorage.setItem("toSend", "");
      localStorage.setItem("lastSent", "0");
      to_send = [];
      wasConfigured = false;
   }

   if (!wasConfigured && cfg_endpoint && cfg_data_field) {
      Pebble.sendAppMessage({ "lastSent": 0 });
   }
});
<|endoftext|>