<commit_before>/*
 * Copyright (c) 2011-2013, Yahoo! Inc.  All rights reserved.
 * Copyrights licensed under the New BSD License.
 * See the accompanying LICENSE file for terms.
 */

/*global require, module*/
/*jslint sloppy:true, nomen:true, white:true*/

var RE_TRAILING_SLASHES = /\/+$/;

/**
 * Export a function which can parse tunnel requests.
 * @param {Object} config The configuration.
 * @return {Object} The parser.
 */
module.exports = function (config) {
    var liburl      = require('url'),
        libpath     = require('path'),
        appConfig   = config.store.getAppConfig({}) || {},
        staticPrefix,
        tunnelPrefix;

    staticPrefix = appConfig.staticHandling && appConfig.staticHandling.prefix;
    tunnelPrefix = appConfig.tunnelPrefix;

    if (staticPrefix) {
        staticPrefix = staticPrefix.replace(RE_TRAILING_SLASHES, '');
        staticPrefix = '/' + staticPrefix;
    }
    if (tunnelPrefix) {
        tunnelPrefix = tunnelPrefix.replace(RE_TRAILING_SLASHES, '');
        tunnelPrefix = '/' + tunnelPrefix;
    }

    // normalize() will squash multiple slashes into one slash.
    staticPrefix = libpath.normalize(staticPrefix) || '/static';
    tunnelPrefix = libpath.normalize(tunnelPrefix) || '/tunnel';

    return function (req, res, next) {
        var hasTunnelPrefix = req.url.indexOf(tunnelPrefix) === 0,
            hasTunnelHeader = req.headers['x-mojito-header'] === 'tunnel',
            name,
            type,
            path,
            parts;

        // If we are not tunneling get out of here fast!
        if (!hasTunnelPrefix && !hasTunnelHeader) {
            return next();
        }

        /**
        Tunnel examples

        RPC tunnel:
        /tunnel (or it could just have the tunnel header)

        Type tunnel:
        /static/{type}/definition.json
        /{tunnelPrefix}/{type}/definition.json // custom prefix
        /tunnel/static/{type}/definition.json  // according to a UT

        Spec tunnel:
        /static/{type}/specs/default.json
        /{staticPrefix}/{type}/specs/default.json  // custom prefix
        /tunnel/static/{type}/specs/default.json   // according to a UT
        **/

        path = liburl.parse(req.url).pathname;

        // Normalization step to handle `/{tunnelPrefix}`, `/{staticPrefix}`,
        // and `/{tunnelPrefix}/{staticPrefix}` URLs.
        path = path.replace(staticPrefix, '')
                   .replace(tunnelPrefix, '');

        if (path) {
            // Get the basename without the .json extension.
            name = libpath.basename(path, '.json');

            // Time to get dirty.
            parts = path.split('/');

            // Get the mojit type.
            type = parts[1];

            // "Spec" tunnel request
            if (parts[parts.length - 2] === 'specs') {
                req._tunnel.specsReq = {
                    type: type,
                    name: name
                };
            }
            // "Type" tunnel request
            else if (name === 'definition') {
                req._tunnel.typeReq = {
                    type: type
                };
            }
        }
        // "RPC" tunnel request
        else if (req.url === tunnelPrefix && req.method === 'POST') {
            req._tunnel.rpcReq = {};
        }

        return next();
    };
};
<commit_msg>fix bug where configured prefixes can be undefined<commit_after>/*
 * Copyright (c) 2011-2013, Yahoo! Inc.  All rights reserved.
 * Copyrights licensed under the New BSD License.
 * See the accompanying LICENSE file for terms.
 */

/*global require, module*/
/*jslint sloppy:true, nomen:true, white:true*/

var RE_TRAILING_SLASHES = /\/+$/;

/**
 * Export a function which can parse tunnel requests.
 * @param {Object} config The configuration.
 * @return {Object} The parser.
 */
module.exports = function (config) {
    var liburl      = require('url'),
        libpath     = require('path'),
        appConfig   = config.store.getAppConfig({}) || {},
        staticPrefix,
        tunnelPrefix;

    staticPrefix = appConfig.staticHandling && appConfig.staticHandling.prefix;
    tunnelPrefix = appConfig.tunnelPrefix;

    // normalize() will squash multiple slashes into one slash.
    if (staticPrefix) {
        staticPrefix = staticPrefix.replace(RE_TRAILING_SLASHES, '');
        staticPrefix = libpath.normalize('/' + staticPrefix);
    }
    if (tunnelPrefix) {
        tunnelPrefix = tunnelPrefix.replace(RE_TRAILING_SLASHES, '');
        tunnelPrefix = libpath.normalize('/' + tunnelPrefix);
    }

    staticPrefix = staticPrefix || '/static';
    tunnelPrefix = tunnelPrefix || '/tunnel';

    return function (req, res, next) {
        var hasTunnelPrefix = req.url.indexOf(tunnelPrefix) === 0,
            hasTunnelHeader = req.headers['x-mojito-header'] === 'tunnel',
            name,
            type,
            path,
            parts;

        // If we are not tunneling get out of here fast!
        if (!hasTunnelPrefix && !hasTunnelHeader) {
            return next();
        }

        /**
        Tunnel examples

        RPC tunnel:
        /tunnel (or it could just have the tunnel header)

        Type tunnel:
        /static/{type}/definition.json
        /{tunnelPrefix}/{type}/definition.json // custom prefix
        /tunnel/static/{type}/definition.json  // according to a UT

        Spec tunnel:
        /static/{type}/specs/default.json
        /{staticPrefix}/{type}/specs/default.json  // custom prefix
        /tunnel/static/{type}/specs/default.json   // according to a UT
        **/

        path = liburl.parse(req.url).pathname;

        // Normalization step to handle `/{tunnelPrefix}`, `/{staticPrefix}`,
        // and `/{tunnelPrefix}/{staticPrefix}` URLs.
        path = path.replace(staticPrefix, '')
                   .replace(tunnelPrefix, '');

        if (path) {
            // Get the basename without the .json extension.
            name = libpath.basename(path, '.json');

            // Time to get dirty.
            parts = path.split('/');

            // Get the mojit type.
            type = parts[1];

            // "Spec" tunnel request
            if (parts[parts.length - 2] === 'specs') {
                req._tunnel.specsReq = {
                    type: type,
                    name: name
                };
            }
            // "Type" tunnel request
            else if (name === 'definition') {
                req._tunnel.typeReq = {
                    type: type
                };
            }
        }
        // "RPC" tunnel request
        else if (req.url === tunnelPrefix && req.method === 'POST') {
            req._tunnel.rpcReq = {};
        }

        return next();
    };
};
<|endoftext|>


<commit_before>// Firefox doesn't support assert for some reason.  Pickle barrel kumquat.
console.assert = console.assert || function () {}

jQuery.fn.extend({
  fuzzyCheckboxes: function (selector) {
    this.on('click', selector, function () {
      $(this).find(':checkbox').click()
    })
    this.on('click', selector + ' :checkbox', function (e) {
      e.stopPropagation()
    })
    return this
  }
})

var Sparkle = (function ($) { 'use strict';

  function Sparkle(rootSelector) {
    this.$root = $(rootSelector)
    this.$root.fuzzyCheckboxes('.task-done')

    this.u = new Undoer()
  }

  Sparkle.prototype._transition = function (newState) {
    var oldState = this.state
    if (newState !== oldState) {
      console.log('Transitioning from %s -> %s', oldState, newState)
      this.state = newState
    }

    // Roll back the previous state
    this.u.run()

    // Set CSS classes for styling
    this.$root.removeClass(oldState).addClass(newState)

    // Enable/disable input fields
    this.$root.find('input').prop('disabled', newState === 'locked')
  }

  Sparkle.prototype.switchIdle = function () {
    this._transition('idle')

    var thisObj = this
    this.u.onoff(this.$root, 'focus', '.task-title', function () {
      var $taskData = $(this).closest('.task-data')
      thisObj.switchEditing($taskData)
    })
  }

  Sparkle.prototype.switchEditing = function ($taskData) {
    this._transition('editing')

    // Plan can't reload while user is editing a task
    this.cancelReload()

    // Make a box thingy
    this.cursor = new TaskEditor($taskData)

    // When user clicks outside task, save changes
    var thisObj = this
    this.u.onoff($taskData, 'blur', '.task-title', function () {
      thisObj.save()
    })
  }

  Sparkle.prototype.save = function () {
    console.assert(this.cursor, 'The task being edited cannot be null')

    // The user shouldn't be able to make changes while it's saving
    this.switchLocked()

    // Send the request
    var thisObj = this
    return this.cursor.save().then(function () {
      return thisObj.reload()
    }).fail(function () {
      thisObj.connectionLost()
    })
  }

  Sparkle.prototype.switchLocked = function ($taskData) {
    this._transition('locked')
  }

  // Schedule a reload of the plan.
  Sparkle.prototype.reload = function () {
    console.log('Reloading plan')

    this.cancelReload()

    var thisObj = this
    this._reloadDeferred = $.get('/', {plain: true}, function (data) {
      console.assert(
        thisObj.state !== 'editing',
        'The plan should never reload while the user is editing it')
      thisObj.$root.html(data)
      thisObj.switchIdle()
      thisObj._reloadTimer = setTimeout(function () { thisObj.reload() }, 5000)
    }).fail(function (ajax) {
      if (ajax.statusText !== 'abort') {
        // This failed because of an actual error, not cancelReload()
        thisObj.connectionLost()
      }
    })

    return this._reloadDeferred
  }

  // Cancel a reload in progress.
  Sparkle.prototype.cancelReload = function () {
    if (this._reloadDeferred) {
      this._reloadDeferred.abort()
      delete this._reloadDeferred
    }
    if (this._reloadTimer) {
      clearTimeout(this._reloadTimer)
      delete this._reloadTimer
    }
  }

  Sparkle.prototype.connectionLost = function () {
    this.switchLocked()
    alert('Connection lost. Reload the page and try again.')
  }

  function TaskEditor($taskData) {
    this.id = $taskData.closest('.task').data('id')
    console.log('Editing task <%s>', this.id)
    this.$taskData = $taskData
    $taskData.find('.task-title').attr('contenteditable', 'true')
  }

  TaskEditor.prototype.save = function () {
    console.log('Saving task <%s>', this.id)

    var done_ = this.$taskData.find('.task-done :checkbox').prop('checked')
    var $taskTitle = this.$taskData.find('.task-title')
    var title_ = $taskTitle.text()

    // Make task read-only again
    $taskTitle.removeAttr('contenteditable')

    // Send it awayways
    return $.post('/api/v0/tasks/'+this.id+'/data',
                  JSON.stringify({done: done_, title: title_}))
  }

  return Sparkle

})(jQuery)

jQuery(function () {
  var s = new Sparkle('#plan')
  s.reload()
})
<commit_msg>Pressing Enter saves<commit_after>// Firefox doesn't support assert for some reason.  Pickle barrel kumquat.
console.assert = console.assert || function () {}

jQuery.fn.extend({
  fuzzyCheckboxes: function (selector) {
    this.on('click', selector, function () {
      $(this).find(':checkbox').click()
    })
    this.on('click', selector + ' :checkbox', function (e) {
      e.stopPropagation()
    })
    return this
  }
})

var Sparkle = (function ($) { 'use strict';

  function Sparkle(rootSelector) {
    this.$root = $(rootSelector)
    this.$root.fuzzyCheckboxes('.task-done')

    this.u = new Undoer()
  }

  Sparkle.prototype._transition = function (newState) {
    var oldState = this.state
    if (newState !== oldState) {
      console.log('Transitioning from %s -> %s', oldState, newState)
      this.state = newState
    }

    // Roll back the previous state
    this.u.run()

    // Set CSS classes for styling
    this.$root.removeClass(oldState).addClass(newState)

    // Enable/disable input fields
    this.$root.find('input').prop('disabled', newState === 'locked')
  }

  Sparkle.prototype.switchIdle = function () {
    this._transition('idle')

    var thisObj = this
    this.u.onoff(this.$root, 'focus', '.task-title', function () {
      var $taskData = $(this).closest('.task-data')
      thisObj.switchEditing($taskData)
    })
  }

  Sparkle.prototype.switchEditing = function ($taskData) {
    this._transition('editing')

    // Plan can't reload while user is editing a task
    this.cancelReload()

    // Make a box thingy
    this.cursor = new TaskEditor($taskData)

    // When user clicks outside task, save changes
    var thisObj = this
    var $taskTitle = $taskData.find('.task-title')
    this.u.onoff($taskTitle, 'blur', function () {
      thisObj.save()
    }).onoff($taskTitle, 'keydown', function (e) {
      if (e.which === 13 && !e.shiftKey) {
        // <Return>
        thisObj.save()
      }
    })
  }

  Sparkle.prototype.save = function () {
    console.assert(this.cursor, 'The task being edited cannot be null')

    // The user shouldn't be able to make changes while it's saving
    this.switchLocked()

    // Send the request
    var thisObj = this
    return this.cursor.save().then(function () {
      return thisObj.reload()
    }).fail(function () {
      thisObj.connectionLost()
    })
  }

  Sparkle.prototype.switchLocked = function ($taskData) {
    this._transition('locked')
  }

  // Schedule a reload of the plan.
  Sparkle.prototype.reload = function () {
    console.log('Reloading plan')

    this.cancelReload()

    var thisObj = this
    this._reloadDeferred = $.get('/', {plain: true}, function (data) {
      console.assert(
        thisObj.state !== 'editing',
        'The plan should never reload while the user is editing it')
      thisObj.$root.html(data)
      thisObj.switchIdle()
      thisObj._reloadTimer = setTimeout(function () { thisObj.reload() }, 5000)
    }).fail(function (ajax) {
      if (ajax.statusText !== 'abort') {
        // This failed because of an actual error, not cancelReload()
        thisObj.connectionLost()
      }
    })

    return this._reloadDeferred
  }

  // Cancel a reload in progress.
  Sparkle.prototype.cancelReload = function () {
    if (this._reloadDeferred) {
      this._reloadDeferred.abort()
      delete this._reloadDeferred
    }
    if (this._reloadTimer) {
      clearTimeout(this._reloadTimer)
      delete this._reloadTimer
    }
  }

  Sparkle.prototype.connectionLost = function () {
    this.switchLocked()
    alert('Connection lost. Reload the page and try again.')
  }

  function TaskEditor($taskData) {
    this.id = $taskData.closest('.task').data('id')
    console.log('Editing task <%s>', this.id)
    this.$taskData = $taskData
    $taskData.find('.task-title').attr('contenteditable', 'true')
  }

  TaskEditor.prototype.save = function () {
    console.log('Saving task <%s>', this.id)

    var done_ = this.$taskData.find('.task-done :checkbox').prop('checked')
    var $taskTitle = this.$taskData.find('.task-title')
    var title_ = $taskTitle.text()

    // Make task read-only again
    $taskTitle.removeAttr('contenteditable')

    // Send it awayways
    return $.post('/api/v0/tasks/'+this.id+'/data',
                  JSON.stringify({done: done_, title: title_}))
  }

  return Sparkle

})(jQuery)

jQuery(function () {
  var s = new Sparkle('#plan')
  s.reload()
})
<|endoftext|>


<commit_before>/**
 * Client interface to the Hoodie server and CouchDB, an instance of this
 * will be passed to each worker so we can pool changes feed subscriptions.
 */

/**
 * TODO: The changes pooling is quite naive atm. There are certainly futher
 * optimizations that could be made. For example:
 *
 *  - If subscribed to all docs and another feed requests a filter we could use
 *    the single all docs feed and apply the filter locally.
 *  - If one feed requests include docs and another then requests the same
 *    resource without included docs we can strip them from the results locally
 *    (or just provide them anyway).
 *  - We can also detect when feeds requested with different since values are
 *    synchronized and drop down to a single feed.
 *  - If a feed is requested with since='now' and any existing feeds have a
 *    since update seq value that's lower than the current update seq for the
 *    db, we can simply use that feed from this point on.
 */

var url = require('url'),
    async = require('async'),
    couchr = require('couchr'),
    follow = require('follow'),
    db_updates = require('./db_updates'),
    _ = require('underscore');

/**
 * Creates a new client using the provided config and password
 */

// TODO: add callback so we can start listening to db events before starting
// the workers
exports.createClient = function (config, password) {
    return {
        couch: exports.createCouchClient(config, password)
    };
};

/**
 * Provides an authenticated interface to CouchDB with pooling
 * of change feed requests
 */

exports.createCouchClient = function (config, password) {
    var couch = {};

    var base_url = url.parse(config.couch.url);
    base_url.auth = 'admin:' + encodeURIComponent(password);

    function couchURL(path) {
        return url.resolve(base_url, path);
    }

    var methods = ['get', 'post', 'head', 'put', 'del', 'copy'];
    methods.forEach(function (method) {
        couch[method] = function (path, data, opt, callback) {
            return couchr[method](couchURL(path), data, opt, callback);
        };
    });

    // idle feeds pool
    var idle = [];

    // number of concurrent db feed refreshes
    var concurrency = 4;

    // the updated db queue, processed feeds are put back in the idle pool
    var updated = async.queue(function (db, callback) {

        async.mapSeries(db.requests, function (req, cb) {
            // don't do continuous or longpoll feeds
            req.query.feed = 'normal';
            // request the changes feed for the query
            couch.get(req.url + '/_changes', req.query, function (err, data) {
                if (err) {
                    // let the worker handle errors
                    req.callback(err);
                }
                else {
                    // update the since value so the next request
                    // continues from this point
                    req.query.since = data.last_seq;
                    data.results.forEach(function (r) {
                        req.callback(null, r);
                    });
                }
                return cb(null, req);
            });
        },
        function (err, requests) {
            if (err) {
                // we're passing errors to workers, so this shouldn't happen
                return callback(err);
            }
            db.requests = requests;
            // move the db to end of the idle list
            idle.push(db);
            return callback();
        });

    }, concurrency);

    // remove a db from the idle list
    function removeDB(db) {
        for (var i = 0; i < idle.length; i++) {
            if (idle[i].db === db) {
                return idle.splice(i, 1)[0];
            }
        }
    }

    // take a db off the idle list and queue it for a refresh
    function refreshDB(db) {
        var data = removeDB(db);
        // might not have any subscribers
        if (data) {
            updated.push(data);
        }
    }

    // periodically pop the oldest databases off the idle pool to check
    // we haven't missed anything
    setInterval(function () {
        var db = idle.shift();
        if (db) {
            updated.push(db);
        }
    }, 5000);

    couch.changes = function (db, query, callback) {
        // query parameter is optional
        if (!callback) {
            callback = query;
            query = {};
        }
        if (query.limit || query.descending) {
            // TODO: don't bother trying to pool these, just make request
        }
        var req = {
            // db name
            db: db,
            // authenticated url to db
            url: couchURL('/' + encodeURIComponent(db)),
            // used by the update queue workers
            callback: callback,
            // store the query parameters
            query: query
        };

        // adds the changes request to the pool and refreshes db
        function start() {
            exports.addToPool(idle, req);
            refreshDB(db);
        }

        // only show changes after subscribe time by default
        if (!query.hasOwnProperty('since') || query.since === 'now') {
            // update the since parameter before gettings changes
            couch.get(req.url, function (err, data) {
                if (err) {
                    return callback(err);
                }
                req.query.since = data.update_seq;
                start();
            });
        }
        else {
            // we already have a 'since' value
            start();
        }
    };

    db_updates.listen(base_url, function (err, data) {
        if (data.type === 'deleted') {
            removeDB(data.db_name);
        }
        else if (data.type === 'updated') {
            refreshDB(data.db_name);
        }
    });

    return couch;
};

/**
 * Adds a request to a pool
 */

exports.addToPool = function (pool, req) {
    var db = _.detect(pool, function (x) {
        return x.db === req.db;
    });
    if (db) {
        db.requests.push(req);
    }
    else {
        pool.push({db: req.db, requests: [req]});
    }
    return pool;
};
<commit_msg>refactor changes request pooling code a bit<commit_after>/**
 * Client interface to the Hoodie server and CouchDB, an instance of this
 * will be passed to each worker so we can pool changes feed subscriptions.
 */

var url = require('url'),
    async = require('async'),
    couchr = require('couchr'),
    follow = require('follow'),
    db_updates = require('./db_updates'),
    _ = require('underscore');

/**
 * Creates a new client using the provided config and password
 */

exports.createClient = function (config, password) {
    return {
        couch: exports.createCouchClient(config, password)
    };
};

/**
 * Provides an authenticated interface to CouchDB with pooling
 * of change feed requests
 */

exports.createCouchClient = function (config, password) {
    var couch = {};

    // add auth details to couch url
    var base_url = url.parse(config.couch.url);
    base_url.auth = 'admin:' + encodeURIComponent(password);

    // makes urls requested by workers authenticated by default
    function couchURL(path) {
        return url.resolve(base_url, path);
    }

    // wrap all HTTP methods from couchr
    var methods = ['get', 'post', 'head', 'put', 'del', 'copy'];
    methods.forEach(function (method) {
        couch[method] = function (path, data, opt, callback) {
            return couchr[method](couchURL(path), data, opt, callback);
        };
    });

    // idle feeds pool
    var idle = [];

    // number of concurrent db feed refreshes
    var concurrency = 4;

    // the updated db queue, processed feeds are put back in the idle pool
    var updated = async.queue(function (db, callback) {
        // make all requests associated with the db
        exports.doRequests(couch, db, function (err, db) {
            if (err) {
                return callback(err);
            }
            // add the db back into the idle list
            idle.push(db);
            // finish this task
            return callback();
        });
    }, concurrency);

    // periodically pop the oldest databases off the
    // idle pool to check we haven't missed anything
    setInterval(function () {
        exports.refreshOldest(updated, idle);
    }, 5000);

    couch.changes = function (db, query, callback) {
        // query parameter is optional
        if (!callback) {
            callback = query;
            query = {};
        }
        if (query.limit || query.descending || query.feed === 'normal') {
            // don't attempt to pool one-off requests
            var url = couchURL(encodeURIComponent(db) + '/_changes');
            return couch.get(url, q, callback);
        }

        // create a new request object
        var req = {
            // db name
            db: db,
            // authenticated url to db
            db_url: couchURL('/' + encodeURIComponent(db)),
            // used by the update queue workers
            callback: callback,
            // store the query parameters
            query: query
        };

        // make sure the request has a since parameter set
        exports.updateSince(couch, req, function (err, req) {
            if (err) {
                return callback(err);
            }
            // add the changes request to the pool
            exports.addToPool(idle, req);
            // queue the new request for immediate update
            exports.refreshDB(updated, idle, db);
        });
    };

    // start listening to db update events from couchdb
    db_updates.listen(base_url, function (err, data) {
        if (data.type === 'deleted') {
            exports.removeDB(idle, data.db_name);
        }
        else if (data.type === 'updated') {
            exports.refreshDB(updated, idle, data.db_name);
        }
    });

    return couch;
};

/**
 * Makes all requests registered for the provided db object,
 * returning an updated db object (with the requests including
 * the new since value).
 */

exports.doRequests = function (couch, db, callback) {
    // pre-apply the first argument of requestChanges
    var doRequest = _.partial(exports.requestChanges, couch);
    // make each request in series and return the updated request objects
    async.mapSeries(db.requests, doRequest, function (err, reqs) {
        if (err) {
            // we're passing errors to workers, so this shouldn't happen
            return callback(err);
        }
        // update the db object
        db.requests = reqs;
        return callback(null, db);
    });
};

/**
 * Makes a request to the changes feed, calling the request object's
 * callback with the results returning an updated request object with
 * the new since value.
 */

exports.requestChanges = function (couch, req, callback) {
    // don't do continuous or longpoll feeds
    req.query.feed = 'normal';
    // request the changes feed for the query
    couch.get(req.db_url + '/_changes', req.query, function (err, data) {
        if (err) {
            // let the worker handle errors
            req.callback(err);
        }
        else {
            // update the since value so the next request
            // continues from this point
            req.query.since = data.last_seq;
            data.results.forEach(function (r) {
                req.callback(null, r);
            });
        }
        return callback(null, req);
    });
};

/**
 * Adds a changes request to a pool
 */

exports.addToPool = function (pool, req) {
    var db = _.detect(pool, function (x) {
        return x.db === req.db;
    });
    if (db) {
        db.requests.push(req);
    }
    else {
        pool.push({db: req.db, requests: [req]});
    }
    return pool;
};

/**
 * Remove a db from the changes request pool
 */

exports.removeDB = function (pool, db) {
    for (var i = 0; i < pool.length; i++) {
        if (pool[i].db === db) {
            return pool.splice(i, 1)[0];
        }
    }
}

/**
 * Remove a db from the changes request pool and add it to
 * the refresh queue
 */

exports.refreshDB = function (queue, pool, db) {
    var data = exports.removeDB(pool, db);
    // might not have any subscribers
    if (data) {
        queue.push(data);
    }
}

/**
 * Move the oldest request (longest since last changes update) out
 * of the changes request pool and onto the the update queue
 */

exports.refreshOldest = function (queue, pool) {
    var db = pool.shift();
    if (db) {
        queue.push(db);
    }
};

/**
 * Ensure the request has a value set for the since parameter. If the
 * since parameter is missing or set to 'now', fetch the db-level URL
 * to update it to the current seq
 */

exports.updateSince = function (couch, req, callback) {
    if (!req.query.hasOwnProperty('since') || req.query.since === 'now') {
        // get the latest update seq from couchdb
        couch.get(req.db_url, function (err, data) {
            if (err) {
                return callback(err);
            }
            req.query.since = data.update_seq;
            return callback(null, req);
        });
    }
    else {
        // be consistently asynchronous
        return process.nextTick(function () {
            return callback(null, req);
        });
    }
};
<|endoftext|>


<commit_before>window.onload = APPStart;

var known_paths = [];

// Page onload event handler
function APPStart() {
    state = false;
    guid_id = "";

    $(document).ready(function(){
        // keep it for later with different id
        //$('#peer_page').load('peer_page.html');

        if ("WebSocket" in window) {
            // using something from other js - seems fine
            var ws = new FancyWebSocket("ws://:/ws");

            // the usual suspects
            ws.bind('open', function(){
                $('#statusMessage').text("connected");
                ws.send('start');
                // register hash UUID
            });
            ws.bind('close', function(){
                gracefullyClose();
            });

            ws.bind('init', function(data){
               showPath(data);
            });

            ws.bind('searching', function(data){
               spinPath(data);
            });

            ws.bind('update', function(data){
               updateNetView(data);
            });

            window.onbeforeunload = function(event) {
                 socket.close();
            };
        } else {
            // The browser doesn't support WebSocket
            alert("WebSocket NOT supported by your Browser!");
        }

        var Accordion = function(el, multiple) {
            this.el = el || {};
            this.multiple = multiple || false;

            var links = this.el.find('.link');

            links.on('click', {el: this.el, multiple: this.multiple}, this.dropdown)
        }

        Accordion.prototype.dropdown = function(e) {
            var $el = e.data.el;
            $this = $(this),
            $next = $this.next();

            $next.slideToggle();
            $this.parent().toggleClass('open');

            if (!e.data.multiple) {
                $el.find('.submenu').not($next).slideUp().parent().removeClass('open');
            };
        }

        var accordion = new Accordion($('#accordion'), false);
    });
}

function spinPath(data) {
    let on_off = data[1];

    if (data[0].type === 'net') {
       let spinner = $('#host_search_progress').find('span');

       if (on_off === true) {
           spinner.removeClass('d-none');
       } else {
           //spinner.removeClass('d-none');
           setTimeout(_ => spinner.addClass('d-none'), 100);
       }
    } else {
        let path_nr = data[0].cnt.nr
        let spinner = $('#path_obj' + path_nr).find('span');

        if (on_off === true) {
            spinner.removeClass('d-none');
        } else {
            spinner.addClass('d-none');
            spinner.replaceWith( "<svg width='1em' height='1em' viewBox='0 0 16 18'"
                               + " class='bi bi-circle-fill' fill='currentColor'"
                               + " xmlns='http://www.w3.org/2000/svg'>"
                               + " <circle cx='8' cy='8' r='8'/></svg>");
        }
    }
}

function showPath(data) {
    // encapsulate paths json
    let paths = data.paths;

    let path_len = paths.length;
    for (let i=0; i < path_len; i++) {
        let path_nr = paths[i].nr;
        // register for graceful closing
        known_paths.push(path_nr);
        let obj_id =  "path_obj" + path_nr;
        // create obj
        let new_el_html = "
"
                         + paths[i].name + "	"
                         +""
                         + "
";
        new_el = $.parseHTML(new_el_html);
        // append it as an object but wait since div creation need little time
        $("#paths_searched").append(new_el);
    }
}

function updateNetView(data) {
    if (data.view === 'add') {
        let peer_id = data.cnt.id;
        let addresses = data.cnt.addr;

        let obj_id =  "host_obj_" + peer_id;
        // tooltip is the multi-address for that peer
        let tooltip = "";
        for(let i = 0; i < addresses.length; i++){
            tooltip += "=" + addresses[i] + "=";
        }
        // create obj
        let new_el_html = "
"
                         + "<button id='netbutton_" + guid_id + "' class='btn btn-light' "
                         + "value='" + peer_id + "'"
                         + "onClick='netButtonClick(this.id, this.value)'>"
                         + peer_id
                         + "</button>"
                         + "
";
        new_el = $.parseHTML(new_el_html);
        // append it as an object
        $("#found_peers").append(new_el);
        $('#' + obj_id).hide().fadeIn(500);
    }
    else if (data.view === 'remove') {
        // delete object
        let peer_id = data.cnt;

        let obj_id =  "host_obj_" + peer_id;
        $('#' + obj_id).fadeOut(1500).remove();
    } else {
        console.log("The view '" + data.view + "' is not implemented yet!");
    }
}

function gracefullyClose() {
      // stop host spinner
      let spinnerSearchHost = $('#host_search_progress').find('span');
      spinnerSearchHost.addClass('d-none');
      // stop path spinners
      for (let i=0; i < known_paths.length; i++) {
          let spinnerPath = $('#path_obj' + known_paths[i]).find('span');
          spinnerPath.addClass('d-none');
      }

      $('#statusMessage').text("not connected");
      document.getElementById("overlay").style.display = "block";
}

function netButtonClick(guid, peer) {
    // if exists go back
    if ($("#frame_" + peer).length)
        return

    // get frame (table id)
    let identificator_frame = guid.split("_")[1];

    // collapse old entry
    $("#frame_"+identificator_frame).collapse('toggle');

    // create entry
    let new_foreign_row = '

Foreign: ' + peer
                        + '

';
    $("#accordion").append(new_foreign_row);

    // load foreign page
    $("#page_" + peer).load("http://:8088/peer_page.html");
}

<commit_msg>[webui] fix first accordion entry to be activated<commit_after>window.onload = APPStart;

var known_paths = [];

// Page onload event handler
function APPStart() {
    state = false;
    guid_id = "";

    $(document).ready(function(){
        // keep it for later with different id
        //$('#peer_page').load('peer_page.html');

        if ("WebSocket" in window) {
            // using something from other js - seems fine
            var ws = new FancyWebSocket("ws://:/ws");

            // the usual suspects
            ws.bind('open', function(){
                $('#statusMessage').text("connected");
                ws.send('start');
                // register hash UUID
            });
            ws.bind('close', function(){
                gracefullyClose();
            });

            ws.bind('init', function(data){
               showPath(data);
            });

            ws.bind('searching', function(data){
               spinPath(data);
            });

            ws.bind('update', function(data){
               updateNetView(data);
            });

            window.onbeforeunload = function(event) {
                 socket.close();
            };

            // dealing with accordion
            var Accordion = function(el, multiple) {
                this.el = el || {};
                this.multiple = multiple || false;

                var links = this.el.find('.link');

                links.on('click', {el: this.el, multiple: this.multiple}, this.dropdown)
            }
            Accordion.prototype.dropdown = function(e) {
                var $el = e.data.el;
                $this = $(this),
                $next = $this.next();

                $next.slideToggle();
                $this.parent().toggleClass('open');

                if (!e.data.multiple) {
                    $el.find('.submenu').not($next).slideUp().parent().removeClass('open');
                };
            }
            var accordion = new Accordion($('#accordion'), false);

            // click accordion.div.div to show first entry!!
            $("#accordion div div").click();

        } else {
            // The browser doesn't support WebSocket
            alert("WebSocket NOT supported by your Browser!");
        }
    });
}

function spinPath(data) {
    let on_off = data[1];

    if (data[0].type === 'net') {
       let spinner = $('#host_search_progress').find('span');

       if (on_off === true) {
           spinner.removeClass('d-none');
       } else {
           //spinner.removeClass('d-none');
           setTimeout(_ => spinner.addClass('d-none'), 100);
       }
    } else {
        let path_nr = data[0].cnt.nr
        let spinner = $('#path_obj' + path_nr).find('span');

        if (on_off === true) {
            spinner.removeClass('d-none');
        } else {
            spinner.addClass('d-none');
            spinner.replaceWith( "<svg width='1em' height='1em' viewBox='0 0 16 18'"
                               + " class='bi bi-circle-fill' fill='currentColor'"
                               + " xmlns='http://www.w3.org/2000/svg'>"
                               + " <circle cx='8' cy='8' r='8'/></svg>");
        }
    }
}

function showPath(data) {
    // encapsulate paths json
    let paths = data.paths;

    let path_len = paths.length;
    for (let i=0; i < path_len; i++) {
        let path_nr = paths[i].nr;
        // register for graceful closing
        known_paths.push(path_nr);
        let obj_id =  "path_obj" + path_nr;
        // create obj
        let new_el_html = "
"
                         + paths[i].name + "	"
                         +""
                         + "
";
        new_el = $.parseHTML(new_el_html);
        // append it as an object but wait since div creation need little time
        $("#paths_searched").append(new_el);
    }
}

function updateNetView(data) {
    if (data.view === 'add') {
        let peer_id = data.cnt.id;
        let addresses = data.cnt.addr;

        let obj_id =  "host_obj_" + peer_id;
        // tooltip is the multi-address for that peer
        let tooltip = "";
        for(let i = 0; i < addresses.length; i++){
            tooltip += "=" + addresses[i] + "=";
        }
        // create obj
        let new_el_html = "
"
                         + "<button id='netbutton_" + guid_id + "' class='btn btn-light' "
                         + "value='" + peer_id + "'"
                         + "onClick='netButtonClick(this.id, this.value)'>"
                         + peer_id
                         + "</button>"
                         + "
";
        new_el = $.parseHTML(new_el_html);
        // append it as an object
        $("#found_peers").append(new_el);
        $('#' + obj_id).hide().fadeIn(500);
    }
    else if (data.view === 'remove') {
        // delete object
        let peer_id = data.cnt;

        let obj_id =  "host_obj_" + peer_id;
        $('#' + obj_id).fadeOut(1500).remove();
    } else {
        console.log("The view '" + data.view + "' is not implemented yet!");
    }
}

function gracefullyClose() {
      // stop host spinner
      let spinnerSearchHost = $('#host_search_progress').find('span');
      spinnerSearchHost.addClass('d-none');
      // stop path spinners
      for (let i=0; i < known_paths.length; i++) {
          let spinnerPath = $('#path_obj' + known_paths[i]).find('span');
          spinnerPath.addClass('d-none');
      }

      $('#statusMessage').text("not connected");
      document.getElementById("overlay").style.display = "block";
}

function netButtonClick(guid, peer) {
    // if exists go back
    if ($("#frame_" + peer).length)
        return

    // get frame (table id)
    let identificator_frame = guid.split("_")[1];

    // collapse old entry
    $("#frame_"+identificator_frame).collapse('toggle');

    // create entry
    let new_foreign_row = '

Foreign: ' + peer
                        + '

';
    $("#accordion").append(new_foreign_row);

    // load foreign page
    $("#page_" + peer).load("http://:8088/peer_page.html", function( response, status, xhr ) {
      if ( status == "error" ) {
        var msg = "Sorry but there was an error: ";
        $( "#page_" + peer).html( msg + xhr.status + " " + xhr.statusText );
      }
    });
}

<|endoftext|>


<commit_before>/*global window, $, Backbone, _*/

/**
 * @module helpers/form_helper
 * @description Helper for _form binding_, deals with models, collections, input, select, jQuery plugins.
 * @see file helpers/form_helper.js
 */

"use strict";

var postRenderingHelper = require("./post_rendering_helper");

/**
 * Helper to bond collections on list dom.
 * @param  {object} jQuery selector         - A selector which should contains the list of element which dom list element.
 * @param  {Backbone.Collection} collection - The collection where the reconstructed elements will be injected.
 * @param  {object} options                 - {isSilent: boolean}
 * @return {undefined}
 */
var _formCollectionBinder = function forCollectionBinder(selector, collection, options) {
    options = options || {};
    options.isSilent = options.isSilent !== undefined ? options.isSilent : true;
    if (selector !== undefined && selector !== null && collection instanceof Backbone.Collection) {
        //collection.reset(null, {silent: true}); // The collection is cleared.
        var index = 0;
        //@todo: Check if there is a problem on collection save, the selector could have been change, do a clone.
        Array.prototype.forEach.call(selector, function(modelLineSelector) {
            //var model = new collection.model();
            this.formModelBinder({
                    inputs: $('input, textarea', modelLineSelector),
                    options: $('select', modelLineSelector)
                },
                collection.at(index), //Model to populate.
                options
            );
            //collection.add(model,options);
            index++;
        }, this);
    } else {
        console.warn("The selector is not an object", selector);
    }
};

/**
 * Model binder. Takes the data from the dom element and fill the model. Each dom element should have a _data-name_ property.
 * @param  {object} data          - An object withe the structure {inputs: jQuerySelector, options: jQuerySelector} w<here jQuery Selector which contains the dom element of a model. inputs for inputs, options for selects.
 * @param  {Backbone.Model} model - a Backbone.Model where the dom data will be injected to populate the fields.
 * @param  {object} options       - The default options are: {isSilent: true}
 * @return {undefined}
 */
var _formModelBinder = function formModelBinder(data, model, options) {
    options = options || {};
    options.isSilent = options.isSilent !== undefined ? options.isSilent : true;
    var modelContainer = {};
    if (data.inputs !== null && data.inputs !== undefined) {
        modelContainer = _.extend(modelContainer, this.formInputModelBinder(data.inputs, model, options));
    }
    if (data.options !== null && data.options !== undefined) {
        modelContainer = _.extend(modelContainer, this.formOptionModelBinder(data.options, model, options));
    }
    model.set(modelContainer, {
        silent: options.isSilent
    });
};

// inputs must be a selector with option:selected inside and model a BackBone model.
/**
 * Bind the model. If the model has a decorator (ie) a jQuery plugin, the data-decorator is use in order to
 * @param  {object} inputs           - Selector containing all the inputs.
 * @param  {Backbone.Model} model    - The model on which there is .
 * @param  {object} options          - The default options are: {isSilent: true, isForceModelBinding: false}.
 * @return {[type]}         [description]
 */
var _formInputModelBinder = function formInputModelBinder(inputs, model, options) {
    options = options || {};
    options.isSilent = options.isSilent !== undefined ? options.isSilent : true;
    //parameters checkings
    if (typeof inputs === "undefined" || inputs === null) {
        throw ("inputs are not defined");
    }
    if (typeof model === "undefined" || model === null) {
        throw ("the model is not defined");
    }
    var modelContainer = {};
    inputs.each(function() {
        var input = this;
        if(input.hasAttribute('data-no-parse')){
            return;
        }
        //console.log('input', input);
        var currentvalue;
        //we switch on all html5 values
        //If a decorator is define, it should be use in order to get back the value.
        var decorator = input.getAttribute('data-decorator');

        if (decorator) {
            currentvalue = postRenderingHelper.callParser({
                selector: $(input),
                helperName: decorator
            });

        } else if (input.tagName === "TEXTAREA") {
            currentvalue = input.value;
        } else { //See if an if on currentValue is nececessary.
            switch (input.getAttribute('type')) {
                case "checkbox":
                    currentvalue = input.multiple ? input.value : input.checked;
                    break;
                case "number":
                    var inputValue = input.value === "" ? undefined : input.value;
                    currentvalue = (inputValue !== undefined && inputValue !== null) ? +inputValue : undefined;
                    break;
                case "radio":
                    if (input.checked) {
                        currentvalue = _parseRadioValue(input.value);
                    } else {
                        currentvalue = modelContainer[this.getAttribute('data-name')];
                    }
                    break;
                default:
                    currentvalue = input.value === "" ? undefined : input.value;
            }

        }

        if (input.multiple) {
            if (input.checked) {
                if (modelContainer[this.getAttribute('data-name')] === undefined) {
                    modelContainer[this.getAttribute('data-name')] = [currentvalue];
                } else {
                    modelContainer[this.getAttribute('data-name')].push(currentvalue);
                }
            }
        } else {
            modelContainer[this.getAttribute('data-name')] = currentvalue;
        }
    });
    if (options.isForceModelSet) {
        model.set(modelContainer, {
            silent: options.isSilent
        });
    }
    return modelContainer;
};

var _parseRadioValue = function parseRadioValue(radioValue) {
    switch (radioValue) {
        case "null":
            return undefined;
        case "true":
            return true;
        case "false":
            return false;
        default:
            return radioValue;
    }
};

// formOptionModelBinder permet de convertir l'ensemble des options set d'un formulaire en model en fonction de leur attribut data-name.
// options must be a option:select and model a BackBone model.
var _formOptionModelBinder = function formOptionModelBinder(optionsSets, model, options) {
    options = options || {};
    options.isSilent = options.isSilent !== undefined ? options.isSilent : true;
    //parameters checkings
    if (typeof optionsSets === "undefined" || optionsSets === null) {
        throw ("options are not defined");
    }
    if (typeof model === "undefined" || model === null) {
        throw ("the model is not defined");
    }

    var selectedValue, modelContainer = {};
    //For each option we take the value selected. We had this value to the model, only if the user doesn't choose the empty string.
    optionsSets.each(function() {
        if(this.hasAttribute('data-no-parse')){
            return;
        }
        var attributeName = this.getAttribute('data-name');
        //A multiple option will be define with select2
        var select = this;
        var decorator = select.getAttribute('data-decorator');
        if (decorator) {
            selectedValue = postRenderingHelper.callParser({
                selector: $(select),
                helperName: decorator
            });
        } else {
            if (this.hasAttribute('multiple')) {
                selectedValue = $(this).val() || []; // TODO : RGE si select2, il faut utiliser $(this).select2('val')
            } else {
                selectedValue = this.value;
            }

        }
        modelContainer[attributeName] = selectedValue === "undefined" ? undefined : selectedValue;
    });
    if (options.isForceModelSet) {
        model.set(modelContainer, {
            silent: options.isSilent
        });
    }
    return modelContainer;

};

//#Generate a form from a model.
var _modelFormGenerator = function modelFormGenerator() {

};

var formHelper = {
    formModelBinder: _formModelBinder,
    formInputModelBinder: _formInputModelBinder,
    formOptionModelBinder: _formOptionModelBinder,
    modelFormGenerator: _modelFormGenerator,
    formCollectionBinder: _formCollectionBinder
};
module.exports = formHelper;<commit_msg>Fix #63 change the helper.<commit_after>/*global window, $, Backbone, _*/

/**
 * @module helpers/form_helper
 * @description Helper for _form binding_, deals with models, collections, input, select, jQuery plugins.
 * @see file helpers/form_helper.js
 */

"use strict";

var postRenderingHelper = require("./post_rendering_helper");

/**
 * Helper to bond collections on list dom.
 * @param  {object} jQuery selector         - A selector which should contains the list of element which dom list element.
 * @param  {Backbone.Collection} collection - The collection where the reconstructed elements will be injected.
 * @param  {object} options                 - {isSilent: boolean}
 * @return {undefined}
 */
var _formCollectionBinder = function forCollectionBinder(selector, collection, options) {
    options = options || {};
    options.isSilent = options.isSilent !== undefined ? options.isSilent : true;
    if (selector !== undefined && selector !== null && collection instanceof Backbone.Collection) {
        //collection.reset(null, {silent: true}); // The collection is cleared.
        var index = 0;
        //@todo: Check if there is a problem on collection save, the selector could have been change, do a clone.
        Array.prototype.forEach.call(selector, function(modelLineSelector) {
            //var model = new collection.model();
            this.formModelBinder({
                    inputs: $('input, textarea', modelLineSelector),
                    options: $('select', modelLineSelector)
                },
                collection.at(index), //Model to populate.
                options
            );
            //collection.add(model,options);
            index++;
        }, this);
    } else {
        console.warn("The selector is not an object", selector);
    }
};

/**
 * Model binder. Takes the data from the dom element and fill the model. Each dom element should have a _data-name_ property.
 * @param  {object} data          - An object withe the structure {inputs: jQuerySelector, options: jQuerySelector} w<here jQuery Selector which contains the dom element of a model. inputs for inputs, options for selects.
 * @param  {Backbone.Model} model - a Backbone.Model where the dom data will be injected to populate the fields.
 * @param  {object} options       - The default options are: {isSilent: true}
 * @return {undefined}
 */
var _formModelBinder = function formModelBinder(data, model, options) {
    options = options || {};
    options.isSilent = options.isSilent !== undefined ? options.isSilent : true;
    var modelContainer = {};
    if (data.inputs !== null && data.inputs !== undefined) {
        modelContainer = _.extend(modelContainer, this.formInputModelBinder(data.inputs, model, options));
    }
    if (data.options !== null && data.options !== undefined) {
        modelContainer = _.extend(modelContainer, this.formOptionModelBinder(data.options, model, options));
    }
    model.set(modelContainer, {
        silent: options.isSilent
    });
};

// inputs must be a selector with option:selected inside and model a BackBone model.
/**
 * Bind the model. If the model has a decorator (ie) a jQuery plugin, the data-decorator is use in order to
 * @param  {object} inputs           - Selector containing all the inputs.
 * @param  {Backbone.Model} model    - The model on which there is .
 * @param  {object} options          - The default options are: {isSilent: true, isForceModelBinding: false}.
 * @return {[type]}         [description]
 */
var _formInputModelBinder = function formInputModelBinder(inputs, model, options) {
    options = options || {};
    options.isSilent = options.isSilent !== undefined ? options.isSilent : true;
    //parameters checkings
    if (typeof inputs === "undefined" || inputs === null) {
        throw ("inputs are not defined");
    }
    if (typeof model === "undefined" || model === null) {
        throw ("the model is not defined");
    }
    var modelContainer = {};
    inputs.each(function() {
        var input = this;
        if(input.hasAttribute('data-no-parse')){
            return;
        }
        //console.log('input', input);
        var currentvalue;
        //we switch on all html5 values
        //If a decorator is define, it should be use in order to get back the value.
        var decorator = input.getAttribute('data-decorator');

        if (decorator) {
            currentvalue = postRenderingHelper.callParser({
                selector: $(input),
                helperName: decorator
            });

        } else if (input.tagName === "TEXTAREA") {
            currentvalue = input.value;
        } else { //See if an if on currentValue is nececessary.
            switch (input.getAttribute('type')) {
                case "checkbox":
                    currentvalue = input.multiple ? input.value : input.checked;
                    break;
                case "number":
                    var inputValue = input.value === "" ? undefined : input.value;
                    currentvalue = (inputValue !== undefined && inputValue !== null) ? +inputValue : undefined;
                    break;
                case "radio":
                    if (input.checked) {
                        currentvalue = _parseRadioValue(input.value);
                    } else {
                        currentvalue = modelContainer[this.getAttribute('data-name')];
                    }
                    break;
                default:
                    currentvalue = input.value === "" ? null : input.value;
            }

        }

        if (input.multiple) {
            if (input.checked) {
                if (modelContainer[this.getAttribute('data-name')] === undefined) {
                    modelContainer[this.getAttribute('data-name')] = [currentvalue];
                } else {
                    modelContainer[this.getAttribute('data-name')].push(currentvalue);
                }
            }
        } else {
            modelContainer[this.getAttribute('data-name')] = currentvalue;
        }
    });
    if (options.isForceModelSet) {
        model.set(modelContainer, {
            silent: options.isSilent
        });
    }
    return modelContainer;
};

var _parseRadioValue = function parseRadioValue(radioValue) {
    switch (radioValue) {
        case "null":
            return undefined;
        case "true":
            return true;
        case "false":
            return false;
        default:
            return radioValue;
    }
};

// formOptionModelBinder permet de convertir l'ensemble des options set d'un formulaire en model en fonction de leur attribut data-name.
// options must be a option:select and model a BackBone model.
var _formOptionModelBinder = function formOptionModelBinder(optionsSets, model, options) {
    options = options || {};
    options.isSilent = options.isSilent !== undefined ? options.isSilent : true;
    //parameters checkings
    if (typeof optionsSets === "undefined" || optionsSets === null) {
        throw ("options are not defined");
    }
    if (typeof model === "undefined" || model === null) {
        throw ("the model is not defined");
    }

    var selectedValue, modelContainer = {};
    //For each option we take the value selected. We had this value to the model, only if the user doesn't choose the empty string.
    optionsSets.each(function() {
        if(this.hasAttribute('data-no-parse')){
            return;
        }
        var attributeName = this.getAttribute('data-name');
        //A multiple option will be define with select2
        var select = this;
        var decorator = select.getAttribute('data-decorator');
        if (decorator) {
            selectedValue = postRenderingHelper.callParser({
                selector: $(select),
                helperName: decorator
            });
        } else {
            if (this.hasAttribute('multiple')) {
                selectedValue = $(this).val() || [];
            } else {
                selectedValue = this.value === "" ? undefined : this.value;
            }

        }
        modelContainer[attributeName] = selectedValue === "undefined" ? undefined : selectedValue;
    });
    if (options.isForceModelSet) {
        model.set(modelContainer, {
            silent: options.isSilent
        });
    }
    return modelContainer;

};

//#Generate a form from a model.
var _modelFormGenerator = function modelFormGenerator() {

};

var formHelper = {
    formModelBinder: _formModelBinder,
    formInputModelBinder: _formInputModelBinder,
    formOptionModelBinder: _formOptionModelBinder,
    modelFormGenerator: _modelFormGenerator,
    formCollectionBinder: _formCollectionBinder
};
module.exports = formHelper;<|endoftext|>


<commit_before>// qunit tests for xscrolly.js
//
// To fake scrolling in tests, do:
//
//     $container.scrollTop(TARGET_HEIGHT * 20); xsy.process();
//
// to bypass event handling and callback throttling.

/* globals $, XScrollY */

var $container,
    xsy,
    TARGET_HEIGHT = 20,
    n_changeFired = 0,  // number of times `change` was fired if set up
    countChange = function() { ++n_changeFired; };

test('basic properties are set after init', function() {
  var xsy = new XScrollY({
    targets: '#qunit-fixture li'
  });

  ok(xsy.$scrollElement.length);
  ok(xsy.$targets.length);
  equal(xsy.$targets.length, $('#qunit-fixture li').length);
  equal(xsy.$targets.length, xsy.offsets.length);
});

module('basics', {
  setup: function() {
    n_changeFired = 0;
    $container = $('#qunit-fixture ul');
  }
});

test('change event fires', function() {
  var xsy = new XScrollY({
    container: $container,
    targets: '#qunit-fixture li',
    change: countChange
  });

  equal(xsy.$scrollElement.scrollTop(), 0,
    'container should start scrolled to the top');
  equal(xsy.$active[0], xsy.$targets[0],
    'the first target should be active');
  $container.scrollTop(TARGET_HEIGHT * 3);
  equal(xsy.$scrollElement.scrollTop(), TARGET_HEIGHT * 3);
  equal(n_changeFired, 1);
});

test('change event fires twice', function() {
  var xsy = new XScrollY({
    container: $container,
    targets: '#qunit-fixture li',
    change: countChange
  });

  $container.scrollTop(TARGET_HEIGHT * 20); xsy.process();
  equal(n_changeFired, 2);
});

test('getActiveOffset', function() {
  xsy = new XScrollY({
    container: $container,
    targets: '#qunit-fixture li'
  });
  equal(xsy.activeOffset, TARGET_HEIGHT * 0,
    'Scroll depth: ' + $container.scrollTop());
  $container.scrollTop(TARGET_HEIGHT / 2); xsy.process();
  equal(xsy.activeOffset, TARGET_HEIGHT * 0,
    'Scroll depth: ' + $container.scrollTop());
  $container.scrollTop(TARGET_HEIGHT); xsy.process();
  equal(xsy.activeOffset, TARGET_HEIGHT,
    'Scroll depth: ' + $container.scrollTop());
  $container.scrollTop(TARGET_HEIGHT * 2 - 1); xsy.process();
  equal(xsy.activeOffset, TARGET_HEIGHT,
    'Scroll depth: ' + $container.scrollTop());
  $container.scrollTop(TARGET_HEIGHT * 2 + 1); xsy.process();
  equal(xsy.activeOffset, TARGET_HEIGHT * 2,
    'Scroll depth: ' + $container.scrollTop());
});

test('scroll', function() {
  var n_scrollFired = 0;
  xsy = new XScrollY({
    container: $container,
    targets: '#qunit-fixture li',
    change: countChange,
    scroll: function() {
      ++n_scrollFired;
    }
  });

  equal(n_changeFired, 1);
  equal(n_scrollFired, 1);
  $container.scrollTop(TARGET_HEIGHT / 2); xsy.process();
  equal(n_changeFired, 1);
  equal(n_scrollFired, 2);
  $container.scrollTop(TARGET_HEIGHT); xsy.process();
  equal(n_changeFired, 2);
  equal(n_scrollFired, 3);
  $container.scrollTop(TARGET_HEIGHT * 3 / 2); xsy.process();
  equal(n_changeFired, 2);
  equal(n_scrollFired, 4);
});

test('active property is updated on scroll', function() {
  xsy = new XScrollY({
    container: $container,
    targets: '#qunit-fixture li',
    change: countChange
  });

  equal(xsy.$scrollElement.scrollTop(), 0,
    'container should start scrolled to the top');
  equal(xsy.$active[0], xsy.$targets[0],
    'the first target should be active');
  $container.scrollTop(TARGET_HEIGHT * 3); xsy.process();
  equal(xsy.$active[0].innerHTML, xsy.$targets[3].innerHTML,
    'the third target should be active');
  equal(n_changeFired, 2);
});

test('offset option works', function() {
  var xsy = new XScrollY({
    container: $container,
    offset: TARGET_HEIGHT * 3,
    targets: '#qunit-fixture li',
    change: countChange
  });

  equal($container.find('li').height(), TARGET_HEIGHT,
    'TARGET_HEIGHT variable should be the same as <li> height for testing');
  equal(xsy.$scrollElement.scrollTop(), 0,
    'container should start scrolled to the top');
  equal(xsy.$active[0].innerHTML, xsy.$targets[3].innerHTML,
    'the third target should be active');
  equal(n_changeFired, 1);
});

test('unveil is only called once', function() {
  var n_unveilFired = 0;
  var xsy = new XScrollY({
    container: $container,
    targets: '#qunit-fixture li',
    change: countChange,
    unveil: function() {
      ++n_unveilFired;
    }
  });

  equal(n_changeFired, 1);
  equal(n_unveilFired, 1);
  $container.scrollTop(TARGET_HEIGHT * 3); xsy.process();
  equal(n_changeFired, 2);
  equal(n_unveilFired, 2);
  $container.scrollTop(TARGET_HEIGHT * 6); xsy.process();
  equal(n_changeFired, 3);
  equal(n_unveilFired, 3);
  $container.scrollTop(TARGET_HEIGHT * 0); xsy.process();
  equal(n_changeFired, 4);
  equal(n_unveilFired, 3);
  $container.scrollTop(TARGET_HEIGHT * 3); xsy.process();
  equal(n_changeFired, 5);
  equal(n_unveilFired, 3);
  $container.scrollTop(TARGET_HEIGHT * 6); xsy.process();
  equal(n_changeFired, 6);
  equal(n_unveilFired, 3);
});

test('start', function() {
  var startActive;
  xsy = new XScrollY({
    container: $container,
    targets: '#qunit-fixture li',
    start: function($el) {
      startActive = $el;
      this.visible().html('x');
    }
  });
  equal(startActive[0], xsy.$targets[0]);  // This may change in the future.
  equal(xsy.$targets.text(), 'xxxxxxxxxxABCDEFGHIJKLMNOPQRSTUVWXYZ');
});

test('event callback args', function() {
  var changeActive, changeOldActive,
      unveilActive, unveilOldActive,
      scrollActive, scrollOldActive;
  xsy = new XScrollY({
    container: $container,
    targets: '#qunit-fixture li'
  });

  equal(xsy.$active[0], xsy.$targets[0],
    'the first target should be active');
  xsy.option('change', function($el) { changeActive = $el; changeOldActive = this.$active; });
  xsy.option('unveil', function($el) { unveilActive = $el; unveilOldActive = this.$active; });
  xsy.option('scroll', function($el) { scrollActive = $el; scrollOldActive = this.$active; });
  $container.scrollTop(TARGET_HEIGHT * 3); xsy.process();

  equal(changeActive.text(), '3');
  equal(changeOldActive.text(), '0');

  equal(unveilActive.text(), '3');
  equal(unveilOldActive.text(), '0');

  equal(scrollActive.text(), '3');
  equal(scrollOldActive.text(), '0');
});

module('target selection', {
  setup: function() {
    $container = $('#qunit-fixture ul');
    xsy = new XScrollY({
      container: $container,
      targets: '#qunit-fixture li'
    });
  }
});

test('slice -  slicing at the limits returns all', function() {
  var total = xsy.$targets.length;
  equal(xsy.slice(-Infinity, Infinity).length, total);
  equal(xsy.slice(0, Infinity).length, total);
});

test('visible', function() {
  equal(xsy.visible().text(), '0123456789');
  $container.scrollTop(TARGET_HEIGHT * 3); xsy.process();
  equal(xsy.visible().text(), '3456789ABC');

  equal(xsy.visible(TARGET_HEIGHT * 2).text(), '56789ABCDE');
  equal(xsy.visible(TARGET_HEIGHT * 2, TARGET_HEIGHT).text(), '456789ABCDEF');
});

test('above', function() {
  equal(xsy.above().text(), '');
  $container.scrollTop(TARGET_HEIGHT * 3); xsy.process();
  equal(xsy.above().text(), '012');

  equal(xsy.above(TARGET_HEIGHT * 2).text(), '01234');
  equal(xsy.above(TARGET_HEIGHT * 2, TARGET_HEIGHT).text(), '012345');
});

test('aboves', function() {
  equal(xsy.aboves().text(), '0123456789');
  $container.scrollTop(TARGET_HEIGHT * 3); xsy.process();
  equal(xsy.aboves().text(), '0123456789ABC');

  equal(xsy.aboves(TARGET_HEIGHT * 2).text(), '0123456789ABCDE');
  equal(xsy.aboves(TARGET_HEIGHT * 2, TARGET_HEIGHT).text(), '0123456789ABCDEF');
});

test('below', function() {
  equal(xsy.below().text(), '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ');
  $container.scrollTop(TARGET_HEIGHT * 3); xsy.process();
  equal(xsy.below().text(), '3456789ABCDEFGHIJKLMNOPQRSTUVWXYZ');

  equal(xsy.below(TARGET_HEIGHT * 2).text(), '56789ABCDEFGHIJKLMNOPQRSTUVWXYZ');
  equal(xsy.below(TARGET_HEIGHT * 2, TARGET_HEIGHT).text(), '456789ABCDEFGHIJKLMNOPQRSTUVWXYZ');
});

test('belows', function() {
  equal(xsy.belows().text(), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ');
  $container.scrollTop(TARGET_HEIGHT * 3); xsy.process();
  equal(xsy.belows().text(), 'DEFGHIJKLMNOPQRSTUVWXYZ');

  equal(xsy.belows(TARGET_HEIGHT * 2).text(), 'FGHIJKLMNOPQRSTUVWXYZ');
  equal(xsy.belows(TARGET_HEIGHT * 2, TARGET_HEIGHT).text(), 'EFGHIJKLMNOPQRSTUVWXYZ');
});

test('slice with custom targets', function() {
  // using .visible

  equal(xsy.visible().text(), '0123456789');
  equal(xsy.visible(0).text(), '0123456789');
  equal(xsy.visible(0, 0, xsy.$targets).text(), '0123456789');
  equal(xsy.visible(0, 0, xsy.$targets.filter(':nth-child(odd)')).text(), '02468');
  equal(xsy.visible(0, 0, xsy.$targets.filter(':nth-child(even)')).text(), '13579');
  // shortcut syntax
  equal(xsy.visible(xsy.$targets).text(), '0123456789');
});

module('multiple columns', {
  setup: function() {
    $container = $('#qunit-fixture ul');
    $('#qunit-fixture li').css({
      float: 'left',
      width: '50%'
    });
    xsy = new XScrollY({
      container: $container,
      targets: '#qunit-fixture li'
    });
  }
});

test('$active is multiple elements', function() {
  equal(xsy.$active.text(), '01');
  $container.scrollTop(TARGET_HEIGHT * 2); xsy.process();
  equal(xsy.$active.text(), '45');
  equal(xsy.above().text(), '0123');
  equal(xsy.visible().text(), '456789ABCDEFGHIJKLMN');
  equal(xsy.belows().text(), 'OPQRSTUVWXYZ');
});
<commit_msg>add test for _updateOffsets method<commit_after>// qunit tests for xscrolly.js
//
// To fake scrolling in tests, do:
//
//     $container.scrollTop(TARGET_HEIGHT * 20); xsy.process();
//
// to bypass event handling and callback throttling.

/* globals $, XScrollY */

var $container,
    xsy,
    TARGET_HEIGHT = 20,
    n_changeFired = 0,  // number of times `change` was fired if set up
    countChange = function() { ++n_changeFired; };

test('basic properties are set after init', function() {
  var xsy = new XScrollY({
    targets: '#qunit-fixture li'
  });

  ok(xsy.$scrollElement.length);
  ok(xsy.$targets.length);
  equal(xsy.$targets.length, $('#qunit-fixture li').length);
  equal(xsy.$targets.length, xsy.offsets.length);
});

module('basics', {
  setup: function() {
    n_changeFired = 0;
    $container = $('#qunit-fixture ul');
  }
});

test('_updateOffsets works', function() {
  var xsy = new XScrollY({
    container: $container,
    targets: '#qunit-fixture li'
  });
  var targets = $('#qunit-fixture li');
  var offsets = [];
  var map = {};
  xsy._updateOffsets(targets, offsets, map);
  // Assert the number of offsets should match the number of targets because our
  // test targets are block elements and don't sit at the same scroll offset
  equal(targets.length, offsets.length);
  // Assert there should be only one element in the '0' offset
  equal(map[0].length, 1);
  // Assert the first element in the map is also the first target
  equal(map[0][0], targets[0]);
});

test('change event fires', function() {
  var xsy = new XScrollY({
    container: $container,
    targets: '#qunit-fixture li',
    change: countChange
  });

  equal(xsy.$scrollElement.scrollTop(), 0,
    'container should start scrolled to the top');
  equal(xsy.$active[0], xsy.$targets[0],
    'the first target should be active');
  $container.scrollTop(TARGET_HEIGHT * 3);
  equal(xsy.$scrollElement.scrollTop(), TARGET_HEIGHT * 3);
  equal(n_changeFired, 1);
});

test('change event fires twice', function() {
  var xsy = new XScrollY({
    container: $container,
    targets: '#qunit-fixture li',
    change: countChange
  });

  $container.scrollTop(TARGET_HEIGHT * 20); xsy.process();
  equal(n_changeFired, 2);
});

test('getActiveOffset', function() {
  xsy = new XScrollY({
    container: $container,
    targets: '#qunit-fixture li'
  });
  equal(xsy.activeOffset, TARGET_HEIGHT * 0,
    'Scroll depth: ' + $container.scrollTop());
  $container.scrollTop(TARGET_HEIGHT / 2); xsy.process();
  equal(xsy.activeOffset, TARGET_HEIGHT * 0,
    'Scroll depth: ' + $container.scrollTop());
  $container.scrollTop(TARGET_HEIGHT); xsy.process();
  equal(xsy.activeOffset, TARGET_HEIGHT,
    'Scroll depth: ' + $container.scrollTop());
  $container.scrollTop(TARGET_HEIGHT * 2 - 1); xsy.process();
  equal(xsy.activeOffset, TARGET_HEIGHT,
    'Scroll depth: ' + $container.scrollTop());
  $container.scrollTop(TARGET_HEIGHT * 2 + 1); xsy.process();
  equal(xsy.activeOffset, TARGET_HEIGHT * 2,
    'Scroll depth: ' + $container.scrollTop());
});

test('scroll', function() {
  var n_scrollFired = 0;
  xsy = new XScrollY({
    container: $container,
    targets: '#qunit-fixture li',
    change: countChange,
    scroll: function() {
      ++n_scrollFired;
    }
  });

  equal(n_changeFired, 1);
  equal(n_scrollFired, 1);
  $container.scrollTop(TARGET_HEIGHT / 2); xsy.process();
  equal(n_changeFired, 1);
  equal(n_scrollFired, 2);
  $container.scrollTop(TARGET_HEIGHT); xsy.process();
  equal(n_changeFired, 2);
  equal(n_scrollFired, 3);
  $container.scrollTop(TARGET_HEIGHT * 3 / 2); xsy.process();
  equal(n_changeFired, 2);
  equal(n_scrollFired, 4);
});

test('active property is updated on scroll', function() {
  xsy = new XScrollY({
    container: $container,
    targets: '#qunit-fixture li',
    change: countChange
  });

  equal(xsy.$scrollElement.scrollTop(), 0,
    'container should start scrolled to the top');
  equal(xsy.$active[0], xsy.$targets[0],
    'the first target should be active');
  $container.scrollTop(TARGET_HEIGHT * 3); xsy.process();
  equal(xsy.$active[0].innerHTML, xsy.$targets[3].innerHTML,
    'the third target should be active');
  equal(n_changeFired, 2);
});

test('offset option works', function() {
  var xsy = new XScrollY({
    container: $container,
    offset: TARGET_HEIGHT * 3,
    targets: '#qunit-fixture li',
    change: countChange
  });

  equal($container.find('li').height(), TARGET_HEIGHT,
    'TARGET_HEIGHT variable should be the same as <li> height for testing');
  equal(xsy.$scrollElement.scrollTop(), 0,
    'container should start scrolled to the top');
  equal(xsy.$active[0].innerHTML, xsy.$targets[3].innerHTML,
    'the third target should be active');
  equal(n_changeFired, 1);
});

test('unveil is only called once', function() {
  var n_unveilFired = 0;
  var xsy = new XScrollY({
    container: $container,
    targets: '#qunit-fixture li',
    change: countChange,
    unveil: function() {
      ++n_unveilFired;
    }
  });

  equal(n_changeFired, 1);
  equal(n_unveilFired, 1);
  $container.scrollTop(TARGET_HEIGHT * 3); xsy.process();
  equal(n_changeFired, 2);
  equal(n_unveilFired, 2);
  $container.scrollTop(TARGET_HEIGHT * 6); xsy.process();
  equal(n_changeFired, 3);
  equal(n_unveilFired, 3);
  $container.scrollTop(TARGET_HEIGHT * 0); xsy.process();
  equal(n_changeFired, 4);
  equal(n_unveilFired, 3);
  $container.scrollTop(TARGET_HEIGHT * 3); xsy.process();
  equal(n_changeFired, 5);
  equal(n_unveilFired, 3);
  $container.scrollTop(TARGET_HEIGHT * 6); xsy.process();
  equal(n_changeFired, 6);
  equal(n_unveilFired, 3);
});

test('start', function() {
  var startActive;
  xsy = new XScrollY({
    container: $container,
    targets: '#qunit-fixture li',
    start: function($el) {
      startActive = $el;
      this.visible().html('x');
    }
  });
  equal(startActive[0], xsy.$targets[0]);  // This may change in the future.
  equal(xsy.$targets.text(), 'xxxxxxxxxxABCDEFGHIJKLMNOPQRSTUVWXYZ');
});

test('event callback args', function() {
  var changeActive, changeOldActive,
      unveilActive, unveilOldActive,
      scrollActive, scrollOldActive;
  xsy = new XScrollY({
    container: $container,
    targets: '#qunit-fixture li'
  });

  equal(xsy.$active[0], xsy.$targets[0],
    'the first target should be active');
  xsy.option('change', function($el) { changeActive = $el; changeOldActive = this.$active; });
  xsy.option('unveil', function($el) { unveilActive = $el; unveilOldActive = this.$active; });
  xsy.option('scroll', function($el) { scrollActive = $el; scrollOldActive = this.$active; });
  $container.scrollTop(TARGET_HEIGHT * 3); xsy.process();

  equal(changeActive.text(), '3');
  equal(changeOldActive.text(), '0');

  equal(unveilActive.text(), '3');
  equal(unveilOldActive.text(), '0');

  equal(scrollActive.text(), '3');
  equal(scrollOldActive.text(), '0');
});

module('target selection', {
  setup: function() {
    $container = $('#qunit-fixture ul');
    xsy = new XScrollY({
      container: $container,
      targets: '#qunit-fixture li'
    });
  }
});

test('slice -  slicing at the limits returns all', function() {
  var total = xsy.$targets.length;
  equal(xsy.slice(-Infinity, Infinity).length, total);
  equal(xsy.slice(0, Infinity).length, total);
});

test('visible', function() {
  equal(xsy.visible().text(), '0123456789');
  $container.scrollTop(TARGET_HEIGHT * 3); xsy.process();
  equal(xsy.visible().text(), '3456789ABC');

  equal(xsy.visible(TARGET_HEIGHT * 2).text(), '56789ABCDE');
  equal(xsy.visible(TARGET_HEIGHT * 2, TARGET_HEIGHT).text(), '456789ABCDEF');
});

test('above', function() {
  equal(xsy.above().text(), '');
  $container.scrollTop(TARGET_HEIGHT * 3); xsy.process();
  equal(xsy.above().text(), '012');

  equal(xsy.above(TARGET_HEIGHT * 2).text(), '01234');
  equal(xsy.above(TARGET_HEIGHT * 2, TARGET_HEIGHT).text(), '012345');
});

test('aboves', function() {
  equal(xsy.aboves().text(), '0123456789');
  $container.scrollTop(TARGET_HEIGHT * 3); xsy.process();
  equal(xsy.aboves().text(), '0123456789ABC');

  equal(xsy.aboves(TARGET_HEIGHT * 2).text(), '0123456789ABCDE');
  equal(xsy.aboves(TARGET_HEIGHT * 2, TARGET_HEIGHT).text(), '0123456789ABCDEF');
});

test('below', function() {
  equal(xsy.below().text(), '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ');
  $container.scrollTop(TARGET_HEIGHT * 3); xsy.process();
  equal(xsy.below().text(), '3456789ABCDEFGHIJKLMNOPQRSTUVWXYZ');

  equal(xsy.below(TARGET_HEIGHT * 2).text(), '56789ABCDEFGHIJKLMNOPQRSTUVWXYZ');
  equal(xsy.below(TARGET_HEIGHT * 2, TARGET_HEIGHT).text(), '456789ABCDEFGHIJKLMNOPQRSTUVWXYZ');
});

test('belows', function() {
  equal(xsy.belows().text(), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ');
  $container.scrollTop(TARGET_HEIGHT * 3); xsy.process();
  equal(xsy.belows().text(), 'DEFGHIJKLMNOPQRSTUVWXYZ');

  equal(xsy.belows(TARGET_HEIGHT * 2).text(), 'FGHIJKLMNOPQRSTUVWXYZ');
  equal(xsy.belows(TARGET_HEIGHT * 2, TARGET_HEIGHT).text(), 'EFGHIJKLMNOPQRSTUVWXYZ');
});

test('slice with custom targets', function() {
  // using .visible

  equal(xsy.visible().text(), '0123456789');
  equal(xsy.visible(0).text(), '0123456789');
  equal(xsy.visible(0, 0, xsy.$targets).text(), '0123456789');
  equal(xsy.visible(0, 0, xsy.$targets.filter(':nth-child(odd)')).text(), '02468');
  equal(xsy.visible(0, 0, xsy.$targets.filter(':nth-child(even)')).text(), '13579');
  // shortcut syntax
  equal(xsy.visible(xsy.$targets).text(), '0123456789');
});

module('multiple columns', {
  setup: function() {
    $container = $('#qunit-fixture ul');
    $('#qunit-fixture li').css({
      float: 'left',
      width: '50%'
    });
    xsy = new XScrollY({
      container: $container,
      targets: '#qunit-fixture li'
    });
  }
});

test('$active is multiple elements', function() {
  equal(xsy.$active.text(), '01');
  $container.scrollTop(TARGET_HEIGHT * 2); xsy.process();
  equal(xsy.$active.text(), '45');
  equal(xsy.above().text(), '0123');
  equal(xsy.visible().text(), '456789ABCDEFGHIJKLMN');
  equal(xsy.belows().text(), 'OPQRSTUVWXYZ');
});
<|endoftext|>


<commit_before>var Client = require("./client");
var fs = require("fs");
var superagent = require("superagent");
var errors = require("./errors");
var MEDIA_PATH = "media";

function sendFile(request, file, mediaType, callback) {
	var stream = null;
	request.buffer().type(mediaType || "application/octet-stream");
	if (typeof file === "string") {
		stream = fs.createReadStream(file);
	}
	else if (Buffer.isBuffer(file)) {
		request.set("Content-Length", file.length);
		request.write(file);
		request.end(callback);
		return;
	}
	else if (typeof file.pipe === "function" && typeof file.read === "function" && typeof file.on === "function") {
		stream = file;
	}
	if (stream) {
		//  request.on("response") sometimes could not be called
		request.req.on("response", function (res) {
			request.res = res;
			var response = new superagent.Response(request);
			callback(response);
		});
		stream.pipe(request);
		return;
	}

	callback({ statusCode : 400, text: "Invalid data to send", body : { message : "Invalid data to send" } });
}

module.exports = {
	/**
	 * Get a list of your media files
	 * @param client Client instance
	 * @param query query parameters
	 * @param callback callback function
	 * @example
	 * bandwidth.Media.list(client, function(err, list){});
	 */
	list : function (client, query, callback) {
		if (arguments.length === 1) {
			callback = client;
			client = new Client();
		}
		else if (arguments.length === 2) {
			callback = query;
			if (client instanceof Client) {
				query = {};
			}
			else {
				query = client;
				client = new Client();
			}
		}

		client.makeRequest("get", client.concatUserPath(MEDIA_PATH), query, callback);
	},

	/**
	 * Uploads a media file (or stream, or buffer) to the name you choose
	 * @param client Client instance
	 * @param name file name
	 * @param mediaTypr media type of file
	 * @param callback callback function
	 * @example
	 * bandwidth.Media.upload(client, "file.pdf", stream, "application/pdf", function(err){});
	 */
	upload : function (client, name, data, mediaType, callback) {
		if (arguments.length === 3) {
			callback = data;
			data = name;
			name = client;
			client = new Client();
			mediaType = null;
		}
		else if (arguments.length === 4) {
			callback = mediaType;
			if (client instanceof Client) {
				mediaType = null;
			}
			else {
				mediaType = data;
				data = name;
				name = client;
				client = new Client();
			}
		}

		var request = client.createRequest("put", client.concatUserPath(MEDIA_PATH) + "/" + encodeURIComponent(name));
		sendFile(request, data, mediaType, function (res) {
			client.checkResponse(res, callback);
		});
	},

	/**
	 * Downloads a media file you uploaded to given destination(file or stream)
	 * @param client Client instance
	 * @param name file name
	 * @param destination file name or stream to write downloaded file
	 * @returns request object
	 * @example
	 * bandwidth.Media.download(client, "file1.pdf", "/tmp/file1.pdf").end(function(err, res){});
	 */
	download : function (client, name, destination) {
		if (arguments.length <= 2) {
			if (client instanceof Client) {
				destination = null;
			}
			else {
				destination = name;
				name = client;
				client = new Client();
			}
		}

		var request = client.createRequest("get", client.concatUserPath(MEDIA_PATH) + "/" + encodeURIComponent(name));
		if (destination) {
			var stream = null;
			if (typeof destination === "string") {
				stream = fs.createWriteStream(destination);
			}
			else if (typeof destination.write === "function") {
				stream = destination;
			}

			if (stream) {
				return request.pipe(stream);
			}
		}

		return request;
	},

	/**
	 * Permanently deletes a media file you uploaded
	 * @param client Client instance
	 * @param name file name
	 * @param callback callback function
	 * @example
	 * bandwidth.delete(client, "file1.pdf", function(err){});
	 */
	delete : function (client, name, callback) {
		if (arguments.length === 2) {
			callback = name;
			name = client;
			client = new Client();
		}

		client.makeRequest("del", client.concatUserPath(MEDIA_PATH) + "/" + encodeURIComponent(name), callback);
	},

	/**
	 * Get information about a media file
	 * @param client Client instance
	 * @param name file name
	 * @param callback
	 * @example
	 * bandwidth.Media.getInfo(client, "file1.wav", function(err, res){});
	 */
	getInfo : function (client, name, callback) {
		if (arguments.length === 2) {
			callback = name;
			name = client;
			client = new Client();
		}

		var request = client.createRequest("head", client.concatUserPath(MEDIA_PATH) + "/" + encodeURIComponent(name));
		request.end(function (err, res) {
			if (res.ok) {
				return callback(null,
					{
						contentType: res.type,
						contentLength: res.header["content-length"]
					});
			}

			return callback(new errors.BandwidthError("Http code " + res.status, res.status));
		});
	},

	/**
	 * Get a media file binary contents
	 * @param client Client instance
	 * @param name file name
	 * @param callback
	 * @example
	 * bandwidth.Media.getContents(client, "file1.wav", function(err, res){});
	 */
	getContents : function (client, name, callback) {
		if (arguments.length === 2) {
			callback = name;
			name = client;
			client = new Client();
		}
		var path = client.concatUserPath(MEDIA_PATH) + "/" + encodeURIComponent(name);
		client.rawGetRequest(path)
			.on("error", function (err) {
				callback(err, null);
			})
			.on("response", function (res) {
				var buf = [];
				res.on("data", function (chunk) {
					buf.push(chunk);
				});
				res.on("end", function () {
					var data = Buffer.concat(buf);
					callback(null, {
						contentType: res.headers["content-type"].split(";")[0],
						contentLength: data.length,
						contentBody: data
					});
				});
			});
	}
};
<commit_msg>Added missing spaces<commit_after>var Client = require("./client");
var fs = require("fs");
var superagent = require("superagent");
var errors = require("./errors");
var MEDIA_PATH = "media";

function sendFile(request, file, mediaType, callback) {
	var stream = null;
	request.buffer().type(mediaType || "application/octet-stream");
	if (typeof file === "string") {
		stream = fs.createReadStream(file);
	}
	else if (Buffer.isBuffer(file)) {
		request.set("Content-Length", file.length);
		request.write(file);
		request.end(callback);
		return;
	}
	else if (typeof file.pipe === "function" && typeof file.read === "function" && typeof file.on === "function") {
		stream = file;
	}
	if (stream) {
		//  request.on("response") sometimes could not be called
		request.req.on("response", function (res) {
			request.res = res;
			var response = new superagent.Response(request);
			callback(response);
		});
		stream.pipe(request);
		return;
	}

	callback({ statusCode : 400, text : "Invalid data to send", body : { message : "Invalid data to send" } });
}

module.exports = {
	/**
	 * Get a list of your media files
	 * @param client Client instance
	 * @param query query parameters
	 * @param callback callback function
	 * @example
	 * bandwidth.Media.list(client, function(err, list){});
	 */
	list : function (client, query, callback) {
		if (arguments.length === 1) {
			callback = client;
			client = new Client();
		}
		else if (arguments.length === 2) {
			callback = query;
			if (client instanceof Client) {
				query = {};
			}
			else {
				query = client;
				client = new Client();
			}
		}

		client.makeRequest("get", client.concatUserPath(MEDIA_PATH), query, callback);
	},

	/**
	 * Uploads a media file (or stream, or buffer) to the name you choose
	 * @param client Client instance
	 * @param name file name
	 * @param mediaTypr media type of file
	 * @param callback callback function
	 * @example
	 * bandwidth.Media.upload(client, "file.pdf", stream, "application/pdf", function(err){});
	 */
	upload : function (client, name, data, mediaType, callback) {
		if (arguments.length === 3) {
			callback = data;
			data = name;
			name = client;
			client = new Client();
			mediaType = null;
		}
		else if (arguments.length === 4) {
			callback = mediaType;
			if (client instanceof Client) {
				mediaType = null;
			}
			else {
				mediaType = data;
				data = name;
				name = client;
				client = new Client();
			}
		}

		var request = client.createRequest("put", client.concatUserPath(MEDIA_PATH) + "/" + encodeURIComponent(name));
		sendFile(request, data, mediaType, function (res) {
			client.checkResponse(res, callback);
		});
	},

	/**
	 * Downloads a media file you uploaded to given destination(file or stream)
	 * @param client Client instance
	 * @param name file name
	 * @param destination file name or stream to write downloaded file
	 * @returns request object
	 * @example
	 * bandwidth.Media.download(client, "file1.pdf", "/tmp/file1.pdf").end(function(err, res){});
	 */
	download : function (client, name, destination) {
		if (arguments.length <= 2) {
			if (client instanceof Client) {
				destination = null;
			}
			else {
				destination = name;
				name = client;
				client = new Client();
			}
		}

		var request = client.createRequest("get", client.concatUserPath(MEDIA_PATH) + "/" + encodeURIComponent(name));
		if (destination) {
			var stream = null;
			if (typeof destination === "string") {
				stream = fs.createWriteStream(destination);
			}
			else if (typeof destination.write === "function") {
				stream = destination;
			}

			if (stream) {
				return request.pipe(stream);
			}
		}

		return request;
	},

	/**
	 * Permanently deletes a media file you uploaded
	 * @param client Client instance
	 * @param name file name
	 * @param callback callback function
	 * @example
	 * bandwidth.delete(client, "file1.pdf", function(err){});
	 */
	delete : function (client, name, callback) {
		if (arguments.length === 2) {
			callback = name;
			name = client;
			client = new Client();
		}

		client.makeRequest("del", client.concatUserPath(MEDIA_PATH) + "/" + encodeURIComponent(name), callback);
	},

	/**
	 * Get information about a media file
	 * @param client Client instance
	 * @param name file name
	 * @param callback
	 * @example
	 * bandwidth.Media.getInfo(client, "file1.wav", function(err, res){});
	 */
	getInfo : function (client, name, callback) {
		if (arguments.length === 2) {
			callback = name;
			name = client;
			client = new Client();
		}

		var request = client.createRequest("head", client.concatUserPath(MEDIA_PATH) + "/" + encodeURIComponent(name));
		request.end(function (err, res) {
			if (res.ok) {
				return callback(null,
					{
						contentType : res.type,
						contentLength : res.header["content-length"]
					});
			}

			return callback(new errors.BandwidthError("Http code " + res.status, res.status));
		});
	},

	/**
	 * Get a media file binary contents
	 * @param client Client instance
	 * @param name file name
	 * @param callback
	 * @example
	 * bandwidth.Media.getContents(client, "file1.wav", function(err, res){});
	 */
	getContents : function (client, name, callback) {
		if (arguments.length === 2) {
			callback = name;
			name = client;
			client = new Client();
		}
		var path = client.concatUserPath(MEDIA_PATH) + "/" + encodeURIComponent(name);
		client.rawGetRequest(path)
			.on("error", function (err) {
				callback(err, null);
			})
			.on("response", function (res) {
				var buf = [];
				res.on("data", function (chunk) {
					buf.push(chunk);
				});
				res.on("end", function () {
					var data = Buffer.concat(buf);
					callback(null, {
						contentType :   res.headers["content-type"].split(";")[0],
						contentLength : data.length,
						contentBody :   data
					});
				});
			});
	}
};
<|endoftext|>