<commit_before>'use strict';

const { Router: router } = require('express');

const RedisPool = require('redis-mpool');
const cartodbRedis = require('cartodb-redis');

const windshaft = require('windshaft');

const PgConnection = require('../backends/pg-connection');
const AnalysisBackend = require('../backends/analysis');
const AnalysisStatusBackend = require('../backends/analysis-status');
const DataviewBackend = require('../backends/dataview');
const TemplateMaps = require('../backends/template-maps');
const PgQueryRunner = require('../backends/pg-query-runner');
const StatsBackend = require('../backends/stats');
const AuthBackend = require('../backends/auth');

const UserLimitsBackend = require('../backends/user-limits');
const OverviewsMetadataBackend = require('../backends/overviews-metadata');
const FilterStatsApi = require('../backends/filter-stats');
const TablesExtentBackend = require('../backends/tables-extent');

const ClusterBackend = require('../backends/cluster');

const LayergroupAffectedTablesCache = require('../cache/layergroup-affected-tables');
const SurrogateKeysCache = require('../cache/surrogate-keys-cache');
const VarnishHttpCacheBackend = require('../cache/backend/varnish-http');
const FastlyCacheBackend = require('../cache/backend/fastly');
const NamedMapProviderCache = require('../cache/named-map-provider-cache');
const NamedMapsCacheEntry = require('../cache/model/named-maps-entry');
const NamedMapProviderReporter = require('../stats/reporter/named-map-provider');

const SqlWrapMapConfigAdapter = require('../models/mapconfig/adapter/sql-wrap-mapconfig-adapter');
const MapConfigNamedLayersAdapter = require('../models/mapconfig/adapter/mapconfig-named-layers-adapter');
const MapConfigBufferSizeAdapter = require('../models/mapconfig/adapter/mapconfig-buffer-size-adapter');
const AnalysisMapConfigAdapter = require('../models/mapconfig/adapter/analysis-mapconfig-adapter');
const MapConfigOverviewsAdapter = require('../models/mapconfig/adapter/mapconfig-overviews-adapter');
const TurboCartoAdapter = require('../models/mapconfig/adapter/turbo-carto-adapter');
const DataviewsWidgetsAdapter = require('../models/mapconfig/adapter/dataviews-widgets-adapter');
const AggregationMapConfigAdapter = require('../models/mapconfig/adapter/aggregation-mapconfig-adapter');
const MapConfigAdapter = require('../models/mapconfig/adapter');
const VectorMapConfigAdapter = require('../models/mapconfig/adapter/vector-mapconfig-adapter');

const ResourceLocator = require('../models/resource-locator');
const LayergroupMetadata = require('../utils/layergroup-metadata');
const RendererStatsReporter = require('../stats/reporter/renderer');

const initializeStatusCode = require('./middlewares/initialize-status-code');
const logger = require('./middlewares/logger');
const bodyParser = require('body-parser');
const servedByHostHeader = require('./middlewares/served-by-host-header');
const stats = require('./middlewares/stats');
const lzmaMiddleware = require('./middlewares/lzma');
const cors = require('./middlewares/cors');
const user = require('./middlewares/user');
const sendResponse = require('./middlewares/send-response');
const syntaxError = require('./middlewares/syntax-error');
const errorMiddleware = require('./middlewares/error-middleware');

const MapRouter = require('./map/map-router');
const TemplateRouter = require('./template/template-router');

module.exports = class ApiRouter {
    constructor ({ serverOptions, environmentOptions }) {
        this.serverOptions = serverOptions;

        const redisOptions = Object.assign({
            name: 'windshaft-server',
            unwatchOnRelease: false,
            noReadyCheck: true
        }, environmentOptions.redis);

        const redisPool = new RedisPool(redisOptions);

        redisPool.on('status', function (status) {
            var keyPrefix = 'windshaft.redis-pool.' + status.name + '.db' + status.db + '.';
            global.statsClient.gauge(keyPrefix + 'count', status.count);
            global.statsClient.gauge(keyPrefix + 'unused', status.unused);
            global.statsClient.gauge(keyPrefix + 'waiting', status.waiting);
        });

        const metadataBackend = cartodbRedis({ pool: redisPool });
        const pgConnection = new PgConnection(metadataBackend);

        const windshaftLogger = environmentOptions.log_windshaft && global.log4js ? global.log4js.getLogger('[windshaft]') : null;
        const mapStore = new windshaft.storage.MapStore({
            pool: redisPool,
            expire_time: serverOptions.grainstore.default_layergroup_ttl,
            logger: windshaftLogger
        });

        const rendererFactory = createRendererFactory({ redisPool, serverOptions, environmentOptions });

        const rendererCacheOpts = Object.assign({
            ttl: 60000, // 60 seconds TTL by default
            statsInterval: 60000 // reports stats every milliseconds defined here
        }, serverOptions.renderCache || {});

        const rendererCache = new windshaft.cache.RendererCache(rendererFactory, rendererCacheOpts);
        const rendererStatsReporter = new RendererStatsReporter(rendererCache, rendererCacheOpts.statsInterval);
        rendererStatsReporter.start();

        const tileBackend = new windshaft.backend.Tile(rendererCache);
        const attributesBackend = new windshaft.backend.Attributes();
        const concurrency = serverOptions.renderer.mapnik.poolSize +
                            serverOptions.renderer.mapnik.poolMaxWaitingClients;
        const previewBackend = new windshaft.backend.Preview(rendererCache, { concurrency });
        const mapValidatorBackend = new windshaft.backend.MapValidator(tileBackend, attributesBackend);
        const mapBackend = new windshaft.backend.Map(rendererCache, mapStore, mapValidatorBackend);

        const surrogateKeysCacheBackends = createSurrogateKeysCacheBackends(serverOptions);
        const surrogateKeysCache = new SurrogateKeysCache(surrogateKeysCacheBackends);
        const templateMaps = createTemplateMaps({ redisPool, surrogateKeysCache });

        const analysisStatusBackend = new AnalysisStatusBackend();
        const analysisBackend = new AnalysisBackend(metadataBackend, serverOptions.analysis);
        const dataviewBackend = new DataviewBackend(analysisBackend);
        const statsBackend = new StatsBackend();
        const clusterBackend = new ClusterBackend();

        const userLimitsBackend = new UserLimitsBackend(metadataBackend, {
            limits: {
                cacheOnTimeout: serverOptions.renderer.mapnik.limits.cacheOnTimeout || false,
                render: serverOptions.renderer.mapnik.limits.render || 0,
                rateLimitsEnabled: global.environment.enabledFeatures.rateLimitsEnabled
            }
        });
        const authBackend = new AuthBackend(pgConnection, metadataBackend, mapStore, templateMaps);

        const layergroupAffectedTablesCache = new LayergroupAffectedTablesCache();

        if (process.env.NODE_ENV === 'test') {
            this.layergroupAffectedTablesCache = layergroupAffectedTablesCache;
        }

        const pgQueryRunner = new PgQueryRunner(pgConnection);
        const overviewsMetadataBackend = new OverviewsMetadataBackend(pgQueryRunner);

        const filterStatsBackend = new FilterStatsApi(pgQueryRunner);
        const tablesExtentBackend = new TablesExtentBackend(pgQueryRunner);

        const mapConfigAdapter = new MapConfigAdapter(
            new MapConfigNamedLayersAdapter(templateMaps, pgConnection),
            new MapConfigBufferSizeAdapter(),
            new SqlWrapMapConfigAdapter(),
            new DataviewsWidgetsAdapter(),
            new AnalysisMapConfigAdapter(analysisBackend),
            new VectorMapConfigAdapter(pgConnection),
            new AggregationMapConfigAdapter(pgConnection),
            new MapConfigOverviewsAdapter(overviewsMetadataBackend, filterStatsBackend),
            new TurboCartoAdapter()
        );

        const resourceLocator = new ResourceLocator(global.environment);
        const layergroupMetadata = new LayergroupMetadata(resourceLocator);

        const namedMapProviderCache = new NamedMapProviderCache(
            templateMaps,
            pgConnection,
            metadataBackend,
            userLimitsBackend,
            mapConfigAdapter,
            layergroupAffectedTablesCache
        );

        const namedMapProviderReporter = new NamedMapProviderReporter({
            namedMapProviderCache,
            intervalInMilliseconds: rendererCacheOpts.statsInterval
        });

        namedMapProviderReporter.start();

        const collaborators = {
            analysisStatusBackend,
            attributesBackend,
            dataviewBackend,
            previewBackend,
            tileBackend,
            pgConnection,
            mapStore,
            userLimitsBackend,
            layergroupAffectedTablesCache,
            authBackend,
            surrogateKeysCache,
            templateMaps,
            mapBackend,
            metadataBackend,
            mapConfigAdapter,
            statsBackend,
            layergroupMetadata,
            namedMapProviderCache,
            tablesExtentBackend,
            clusterBackend
        };

        this.mapRouter = new MapRouter({ collaborators });
        this.templateRouter = new TemplateRouter({ collaborators });
    }

    route (app, routes) {
        // FIXME: we need a better way to reset cache while running tests
        if (process.env.NODE_ENV === 'test') {
            app.layergroupAffectedTablesCache = this.layergroupAffectedTablesCache;
        }

        routes.forEach(route => {
            const apiRouter = router({ mergeParams: true });
            const { paths, middlewares = [] } = route;

            middlewares.forEach(middleware => apiRouter.use(middleware()));

            apiRouter.use(logger(this.serverOptions));
            apiRouter.use(initializeStatusCode());
            apiRouter.use(bodyParser.json());
            apiRouter.use(servedByHostHeader());
            apiRouter.use(stats({
                enabled: this.serverOptions.useProfiler,
                statsClient: global.statsClient
            }));
            apiRouter.use(lzmaMiddleware());
            apiRouter.use(cors());
            apiRouter.use(user());

            this.templateRouter.route(apiRouter, route.template);
            this.mapRouter.route(apiRouter, route.map);

            apiRouter.use(sendResponse());
            apiRouter.use(syntaxError());
            apiRouter.use(errorMiddleware());

            paths.forEach(path => app.use(path, apiRouter));
        });
    }
};

function createTemplateMaps ({ redisPool, surrogateKeysCache }) {
    const templateMaps = new TemplateMaps(redisPool, {
        max_user_templates: global.environment.maxUserTemplates
    });

    function invalidateNamedMap (owner, templateName) {
        var startTime = Date.now();
        surrogateKeysCache.invalidate(new NamedMapsCacheEntry(owner, templateName), function(err) {
            var logMessage = JSON.stringify({
                username: owner,
                type: 'named_map_invalidation',
                elapsed: Date.now() - startTime,
                error: !!err ? JSON.stringify(err.message) : undefined
            });
            if (err) {
                global.logger.warn(logMessage);
            } else {
                global.logger.info(logMessage);
            }
        });
    }

    ['update', 'delete'].forEach(function(eventType) {
        templateMaps.on(eventType, invalidateNamedMap);
    });

    return templateMaps;
}

function createSurrogateKeysCacheBackends(serverOptions) {
    var cacheBackends = [];

    if (serverOptions.varnish_purge_enabled) {
        cacheBackends.push(
            new VarnishHttpCacheBackend(serverOptions.varnish_host, serverOptions.varnish_http_port)
        );
    }

    if (serverOptions.fastly &&
        !!serverOptions.fastly.enabled && !!serverOptions.fastly.apiKey && !!serverOptions.fastly.serviceId) {
        cacheBackends.push(
            new FastlyCacheBackend(serverOptions.fastly.apiKey, serverOptions.fastly.serviceId)
        );
    }

    return cacheBackends;
}

const timeoutErrorTilePath = __dirname + '/../../assets/render-timeout-fallback.png';
const timeoutErrorTile = require('fs').readFileSync(timeoutErrorTilePath, {encoding: null});

function createRendererFactory ({ redisPool, serverOptions, environmentOptions }) {
    var onTileErrorStrategy;
    if (environmentOptions.enabledFeatures.onTileErrorStrategy !== false) {
        onTileErrorStrategy = function onTileErrorStrategy$TimeoutTile(err, tile, headers, stats, format, callback) {

            function isRenderTimeoutError (err) {
                return err.message === 'Render timed out';
            }

            function isDatasourceTimeoutError (err) {
                return err.message && err.message.match(/canceling statement due to statement timeout/i);
            }

            function isTimeoutError (err) {
                return isRenderTimeoutError(err) || isDatasourceTimeoutError(err);
            }

            function isRasterFormat (format) {
                return format === 'png' || format === 'jpg';
            }

            if (isTimeoutError(err) && isRasterFormat(format)) {
                return callback(null, timeoutErrorTile, {
                    'Content-Type': 'image/png',
                }, {});
            } else {
                return callback(err, tile, headers, stats);
            }
        };
    }

    const rendererFactory = new windshaft.renderer.Factory({
        onTileErrorStrategy: onTileErrorStrategy,
        mapnik: {
            redisPool: redisPool,
            grainstore: serverOptions.grainstore,
            mapnik: serverOptions.renderer.mapnik
        },
        http: serverOptions.renderer.http,
        mvt: serverOptions.renderer.mvt,
        torque: serverOptions.renderer.torque
    });

    return rendererFactory;
}
<commit_msg>linter<commit_after>'use strict';

const { Router: router } = require('express');

const RedisPool = require('redis-mpool');
const cartodbRedis = require('cartodb-redis');

const windshaft = require('windshaft');

const PgConnection = require('../backends/pg-connection');
const AnalysisBackend = require('../backends/analysis');
const AnalysisStatusBackend = require('../backends/analysis-status');
const DataviewBackend = require('../backends/dataview');
const TemplateMaps = require('../backends/template-maps');
const PgQueryRunner = require('../backends/pg-query-runner');
const StatsBackend = require('../backends/stats');
const AuthBackend = require('../backends/auth');

const UserLimitsBackend = require('../backends/user-limits');
const OverviewsMetadataBackend = require('../backends/overviews-metadata');
const FilterStatsApi = require('../backends/filter-stats');
const TablesExtentBackend = require('../backends/tables-extent');

const ClusterBackend = require('../backends/cluster');

const LayergroupAffectedTablesCache = require('../cache/layergroup-affected-tables');
const SurrogateKeysCache = require('../cache/surrogate-keys-cache');
const VarnishHttpCacheBackend = require('../cache/backend/varnish-http');
const FastlyCacheBackend = require('../cache/backend/fastly');
const NamedMapProviderCache = require('../cache/named-map-provider-cache');
const NamedMapsCacheEntry = require('../cache/model/named-maps-entry');
const NamedMapProviderReporter = require('../stats/reporter/named-map-provider');

const SqlWrapMapConfigAdapter = require('../models/mapconfig/adapter/sql-wrap-mapconfig-adapter');
const MapConfigNamedLayersAdapter = require('../models/mapconfig/adapter/mapconfig-named-layers-adapter');
const MapConfigBufferSizeAdapter = require('../models/mapconfig/adapter/mapconfig-buffer-size-adapter');
const AnalysisMapConfigAdapter = require('../models/mapconfig/adapter/analysis-mapconfig-adapter');
const MapConfigOverviewsAdapter = require('../models/mapconfig/adapter/mapconfig-overviews-adapter');
const TurboCartoAdapter = require('../models/mapconfig/adapter/turbo-carto-adapter');
const DataviewsWidgetsAdapter = require('../models/mapconfig/adapter/dataviews-widgets-adapter');
const AggregationMapConfigAdapter = require('../models/mapconfig/adapter/aggregation-mapconfig-adapter');
const MapConfigAdapter = require('../models/mapconfig/adapter');
const VectorMapConfigAdapter = require('../models/mapconfig/adapter/vector-mapconfig-adapter');

const ResourceLocator = require('../models/resource-locator');
const LayergroupMetadata = require('../utils/layergroup-metadata');
const RendererStatsReporter = require('../stats/reporter/renderer');

const initializeStatusCode = require('./middlewares/initialize-status-code');
const logger = require('./middlewares/logger');
const bodyParser = require('body-parser');
const servedByHostHeader = require('./middlewares/served-by-host-header');
const stats = require('./middlewares/stats');
const lzmaMiddleware = require('./middlewares/lzma');
const cors = require('./middlewares/cors');
const user = require('./middlewares/user');
const sendResponse = require('./middlewares/send-response');
const syntaxError = require('./middlewares/syntax-error');
const errorMiddleware = require('./middlewares/error-middleware');

const MapRouter = require('./map/map-router');
const TemplateRouter = require('./template/template-router');

module.exports = class ApiRouter {
    constructor ({ serverOptions, environmentOptions }) {
        this.serverOptions = serverOptions;

        const redisOptions = Object.assign({
            name: 'windshaft-server',
            unwatchOnRelease: false,
            noReadyCheck: true
        }, environmentOptions.redis);

        const redisPool = new RedisPool(redisOptions);

        redisPool.on('status', function (status) {
            var keyPrefix = 'windshaft.redis-pool.' + status.name + '.db' + status.db + '.';
            global.statsClient.gauge(keyPrefix + 'count', status.count);
            global.statsClient.gauge(keyPrefix + 'unused', status.unused);
            global.statsClient.gauge(keyPrefix + 'waiting', status.waiting);
        });

        const metadataBackend = cartodbRedis({ pool: redisPool });
        const pgConnection = new PgConnection(metadataBackend);

        const windshaftLogger = environmentOptions.log_windshaft && global.log4js ?
                                global.log4js.getLogger('[windshaft]') :
                                null;
        const mapStore = new windshaft.storage.MapStore({
            pool: redisPool,
            expire_time: serverOptions.grainstore.default_layergroup_ttl,
            logger: windshaftLogger
        });

        const rendererFactory = createRendererFactory({ redisPool, serverOptions, environmentOptions });

        const rendererCacheOpts = Object.assign({
            ttl: 60000, // 60 seconds TTL by default
            statsInterval: 60000 // reports stats every milliseconds defined here
        }, serverOptions.renderCache || {});

        const rendererCache = new windshaft.cache.RendererCache(rendererFactory, rendererCacheOpts);
        const rendererStatsReporter = new RendererStatsReporter(rendererCache, rendererCacheOpts.statsInterval);
        rendererStatsReporter.start();

        const tileBackend = new windshaft.backend.Tile(rendererCache);
        const attributesBackend = new windshaft.backend.Attributes();
        const concurrency = serverOptions.renderer.mapnik.poolSize +
                            serverOptions.renderer.mapnik.poolMaxWaitingClients;
        const previewBackend = new windshaft.backend.Preview(rendererCache, { concurrency });
        const mapValidatorBackend = new windshaft.backend.MapValidator(tileBackend, attributesBackend);
        const mapBackend = new windshaft.backend.Map(rendererCache, mapStore, mapValidatorBackend);

        const surrogateKeysCacheBackends = createSurrogateKeysCacheBackends(serverOptions);
        const surrogateKeysCache = new SurrogateKeysCache(surrogateKeysCacheBackends);
        const templateMaps = createTemplateMaps({ redisPool, surrogateKeysCache });

        const analysisStatusBackend = new AnalysisStatusBackend();
        const analysisBackend = new AnalysisBackend(metadataBackend, serverOptions.analysis);
        const dataviewBackend = new DataviewBackend(analysisBackend);
        const statsBackend = new StatsBackend();
        const clusterBackend = new ClusterBackend();

        const userLimitsBackend = new UserLimitsBackend(metadataBackend, {
            limits: {
                cacheOnTimeout: serverOptions.renderer.mapnik.limits.cacheOnTimeout || false,
                render: serverOptions.renderer.mapnik.limits.render || 0,
                rateLimitsEnabled: global.environment.enabledFeatures.rateLimitsEnabled
            }
        });
        const authBackend = new AuthBackend(pgConnection, metadataBackend, mapStore, templateMaps);

        const layergroupAffectedTablesCache = new LayergroupAffectedTablesCache();

        if (process.env.NODE_ENV === 'test') {
            this.layergroupAffectedTablesCache = layergroupAffectedTablesCache;
        }

        const pgQueryRunner = new PgQueryRunner(pgConnection);
        const overviewsMetadataBackend = new OverviewsMetadataBackend(pgQueryRunner);

        const filterStatsBackend = new FilterStatsApi(pgQueryRunner);
        const tablesExtentBackend = new TablesExtentBackend(pgQueryRunner);

        const mapConfigAdapter = new MapConfigAdapter(
            new MapConfigNamedLayersAdapter(templateMaps, pgConnection),
            new MapConfigBufferSizeAdapter(),
            new SqlWrapMapConfigAdapter(),
            new DataviewsWidgetsAdapter(),
            new AnalysisMapConfigAdapter(analysisBackend),
            new VectorMapConfigAdapter(pgConnection),
            new AggregationMapConfigAdapter(pgConnection),
            new MapConfigOverviewsAdapter(overviewsMetadataBackend, filterStatsBackend),
            new TurboCartoAdapter()
        );

        const resourceLocator = new ResourceLocator(global.environment);
        const layergroupMetadata = new LayergroupMetadata(resourceLocator);

        const namedMapProviderCache = new NamedMapProviderCache(
            templateMaps,
            pgConnection,
            metadataBackend,
            userLimitsBackend,
            mapConfigAdapter,
            layergroupAffectedTablesCache
        );

        const namedMapProviderReporter = new NamedMapProviderReporter({
            namedMapProviderCache,
            intervalInMilliseconds: rendererCacheOpts.statsInterval
        });

        namedMapProviderReporter.start();

        const collaborators = {
            analysisStatusBackend,
            attributesBackend,
            dataviewBackend,
            previewBackend,
            tileBackend,
            pgConnection,
            mapStore,
            userLimitsBackend,
            layergroupAffectedTablesCache,
            authBackend,
            surrogateKeysCache,
            templateMaps,
            mapBackend,
            metadataBackend,
            mapConfigAdapter,
            statsBackend,
            layergroupMetadata,
            namedMapProviderCache,
            tablesExtentBackend,
            clusterBackend
        };

        this.mapRouter = new MapRouter({ collaborators });
        this.templateRouter = new TemplateRouter({ collaborators });
    }

    route (app, routes) {
        // FIXME: we need a better way to reset cache while running tests
        if (process.env.NODE_ENV === 'test') {
            app.layergroupAffectedTablesCache = this.layergroupAffectedTablesCache;
        }

        routes.forEach(route => {
            const apiRouter = router({ mergeParams: true });
            const { paths, middlewares = [] } = route;

            middlewares.forEach(middleware => apiRouter.use(middleware()));

            apiRouter.use(logger(this.serverOptions));
            apiRouter.use(initializeStatusCode());
            apiRouter.use(bodyParser.json());
            apiRouter.use(servedByHostHeader());
            apiRouter.use(stats({
                enabled: this.serverOptions.useProfiler,
                statsClient: global.statsClient
            }));
            apiRouter.use(lzmaMiddleware());
            apiRouter.use(cors());
            apiRouter.use(user());

            this.templateRouter.route(apiRouter, route.template);
            this.mapRouter.route(apiRouter, route.map);

            apiRouter.use(sendResponse());
            apiRouter.use(syntaxError());
            apiRouter.use(errorMiddleware());

            paths.forEach(path => app.use(path, apiRouter));
        });
    }
};

function createTemplateMaps ({ redisPool, surrogateKeysCache }) {
    const templateMaps = new TemplateMaps(redisPool, {
        max_user_templates: global.environment.maxUserTemplates
    });

    function invalidateNamedMap (owner, templateName) {
        var startTime = Date.now();
        surrogateKeysCache.invalidate(new NamedMapsCacheEntry(owner, templateName), function(err) {
            var logMessage = JSON.stringify({
                username: owner,
                type: 'named_map_invalidation',
                elapsed: Date.now() - startTime,
                error: !!err ? JSON.stringify(err.message) : undefined
            });
            if (err) {
                global.logger.warn(logMessage);
            } else {
                global.logger.info(logMessage);
            }
        });
    }

    ['update', 'delete'].forEach(function(eventType) {
        templateMaps.on(eventType, invalidateNamedMap);
    });

    return templateMaps;
}

function createSurrogateKeysCacheBackends(serverOptions) {
    var cacheBackends = [];

    if (serverOptions.varnish_purge_enabled) {
        cacheBackends.push(
            new VarnishHttpCacheBackend(serverOptions.varnish_host, serverOptions.varnish_http_port)
        );
    }

    if (serverOptions.fastly &&
        !!serverOptions.fastly.enabled && !!serverOptions.fastly.apiKey && !!serverOptions.fastly.serviceId) {
        cacheBackends.push(
            new FastlyCacheBackend(serverOptions.fastly.apiKey, serverOptions.fastly.serviceId)
        );
    }

    return cacheBackends;
}

const timeoutErrorTilePath = __dirname + '/../../assets/render-timeout-fallback.png';
const timeoutErrorTile = require('fs').readFileSync(timeoutErrorTilePath, {encoding: null});

function createRendererFactory ({ redisPool, serverOptions, environmentOptions }) {
    var onTileErrorStrategy;
    if (environmentOptions.enabledFeatures.onTileErrorStrategy !== false) {
        onTileErrorStrategy = function onTileErrorStrategy$TimeoutTile(err, tile, headers, stats, format, callback) {

            function isRenderTimeoutError (err) {
                return err.message === 'Render timed out';
            }

            function isDatasourceTimeoutError (err) {
                return err.message && err.message.match(/canceling statement due to statement timeout/i);
            }

            function isTimeoutError (err) {
                return isRenderTimeoutError(err) || isDatasourceTimeoutError(err);
            }

            function isRasterFormat (format) {
                return format === 'png' || format === 'jpg';
            }

            if (isTimeoutError(err) && isRasterFormat(format)) {
                return callback(null, timeoutErrorTile, {
                    'Content-Type': 'image/png',
                }, {});
            } else {
                return callback(err, tile, headers, stats);
            }
        };
    }

    const rendererFactory = new windshaft.renderer.Factory({
        onTileErrorStrategy: onTileErrorStrategy,
        mapnik: {
            redisPool: redisPool,
            grainstore: serverOptions.grainstore,
            mapnik: serverOptions.renderer.mapnik
        },
        http: serverOptions.renderer.http,
        mvt: serverOptions.renderer.mvt,
        torque: serverOptions.renderer.torque
    });

    return rendererFactory;
}
<|endoftext|>


<commit_before>// Underscore-contrib (underscore.array.selectors.js 0.0.1)
// (c) 2013 Michael Fogus, DocumentCloud and Investigative Reporters & Editors
// Underscore-contrib may be freely distributed under the MIT license.

(function(root) {

  // Baseline setup
  // --------------

  // Establish the root object, `window` in the browser, or `global` on the server.
  var _ = root._ || require('underscore');

  // Helpers
  // -------

  // Create quick reference variables for speed access to core prototypes.
  var slice   = Array.prototype.slice,
      concat  = Array.prototype.concat;

  var existy = function(x) { return x != null; };
  var truthy = function(x) { return (x !== false) && existy(x); };
  var isSeq = function(x) { return (_.isArray(x)) || (_.isArguments(x)); };

  // Mixing in the array selectors
  // ----------------------------

  _.mixin({
    // Returns the second element of an array. Passing **n** will return all but
    // the first of the head N values in the array.  The **guard** check allows it
    // to work with `_.map`.
    second: function(array, n, guard) {
      if (array == null) return void 0;
      return (n != null) && !guard ? slice.call(array, 1, n) : array[1];
    },

    // Returns the third element of an array. Passing **n** will return all but
    // the first two of the head N values in the array.  The **guard** check allows it
    // to work with `_.map`.
    third: function(array, n, guard) {
      if (array == null) return void 0;
      return (n != null) && !guard ? slice.call(array, 2, n) : array[2];
    },

    // A function to get at an index into an array
    nth: function(array, index) {
      if ((index < 0) || (index > array.length - 1)) throw Error("Attempting to index outside the bounds of the array.");

      return array[index];
    },

    // Takes all items in an array while a given predicate returns truthy.
    takeWhile: function(array, pred) {
      if (!isSeq(array)) throw new TypeError;

      var sz = _.size(array);

      for (var index = 0; index < sz; index++) {
        if(!truthy(pred(array[index]))) {
          break;
        }
      }

      return _.take(array, index);
    },

    // Drops all items from an array while a given predicate returns truthy.
    dropWhile: function(array, pred) {
      if (!isSeq(array)) throw new TypeError;

      var sz = _.size(array);

      for (var index = 0; index < sz; index++) {
        if(!truthy(pred(array[index])))
          break;
      }

      return _.drop(array, index);
    },

    // Returns an array with two internal arrays built from
    // taking an original array and spliting it at the index
    // where a given function goes falsey.
    splitWith: function(array, pred) {
      return [_.takeWhile(pred, array), _.dropWhile(pred, array)];
    },

    // Takes an array and partitions it as the given predicate changes
    // truth sense.
    partitionBy: function(array, fun){
      if (_.isEmpty(array) || !existy(array)) return [];

      var fst    = _.first(array);
      var fstVal = fun(fst);
      var run    = concat.call([fst], _.takeWhile(_.rest(array), function(e) {
        return _.isEqual(fstVal, fun(e));
      }));

      return concat.call([run], _.partitionBy(_.drop(array, _.size(run)), fun));
    },

    // Returns the 'best' value in an array based on the result of a
    // given function.
    best: function(array, fun) {
      return _.reduce(array, function(x, y) {
        return fun(x, y) ? x : y;
      });
    },

    // Returns an array of existy results of a function over an source array.
    keep: function(array, fun) {
      if (!isSeq(array)) throw new TypeError("expected an array as the first argument");

      return _.filter(_.map(array, function(e) {
        return fun(e);
      }), existy);
    }
  });

})(this);
<commit_msg>Remove bounds check from nth based on feedback in https://github.com/jashkenas/underscore/pull/1252<commit_after>// Underscore-contrib (underscore.array.selectors.js 0.0.1)
// (c) 2013 Michael Fogus, DocumentCloud and Investigative Reporters & Editors
// Underscore-contrib may be freely distributed under the MIT license.

(function(root) {

  // Baseline setup
  // --------------

  // Establish the root object, `window` in the browser, or `global` on the server.
  var _ = root._ || require('underscore');

  // Helpers
  // -------

  // Create quick reference variables for speed access to core prototypes.
  var slice   = Array.prototype.slice,
      concat  = Array.prototype.concat;

  var existy = function(x) { return x != null; };
  var truthy = function(x) { return (x !== false) && existy(x); };
  var isSeq = function(x) { return (_.isArray(x)) || (_.isArguments(x)); };

  // Mixing in the array selectors
  // ----------------------------

  _.mixin({
    // Returns the second element of an array. Passing **n** will return all but
    // the first of the head N values in the array.  The **guard** check allows it
    // to work with `_.map`.
    second: function(array, n, guard) {
      if (array == null) return void 0;
      return (n != null) && !guard ? slice.call(array, 1, n) : array[1];
    },

    // Returns the third element of an array. Passing **n** will return all but
    // the first two of the head N values in the array.  The **guard** check allows it
    // to work with `_.map`.
    third: function(array, n, guard) {
      if (array == null) return void 0;
      return (n != null) && !guard ? slice.call(array, 2, n) : array[2];
    },

    // A function to get at an index into an array
    nth: function(array, index) {
      return array[index];
    },

    // Takes all items in an array while a given predicate returns truthy.
    takeWhile: function(array, pred) {
      if (!isSeq(array)) throw new TypeError;

      var sz = _.size(array);

      for (var index = 0; index < sz; index++) {
        if(!truthy(pred(array[index]))) {
          break;
        }
      }

      return _.take(array, index);
    },

    // Drops all items from an array while a given predicate returns truthy.
    dropWhile: function(array, pred) {
      if (!isSeq(array)) throw new TypeError;

      var sz = _.size(array);

      for (var index = 0; index < sz; index++) {
        if(!truthy(pred(array[index])))
          break;
      }

      return _.drop(array, index);
    },

    // Returns an array with two internal arrays built from
    // taking an original array and spliting it at the index
    // where a given function goes falsey.
    splitWith: function(array, pred) {
      return [_.takeWhile(pred, array), _.dropWhile(pred, array)];
    },

    // Takes an array and partitions it as the given predicate changes
    // truth sense.
    partitionBy: function(array, fun){
      if (_.isEmpty(array) || !existy(array)) return [];

      var fst    = _.first(array);
      var fstVal = fun(fst);
      var run    = concat.call([fst], _.takeWhile(_.rest(array), function(e) {
        return _.isEqual(fstVal, fun(e));
      }));

      return concat.call([run], _.partitionBy(_.drop(array, _.size(run)), fun));
    },

    // Returns the 'best' value in an array based on the result of a
    // given function.
    best: function(array, fun) {
      return _.reduce(array, function(x, y) {
        return fun(x, y) ? x : y;
      });
    },

    // Returns an array of existy results of a function over an source array.
    keep: function(array, fun) {
      if (!isSeq(array)) throw new TypeError("expected an array as the first argument");

      return _.filter(_.map(array, function(e) {
        return fun(e);
      }), existy);
    }
  });

})(this);
<|endoftext|>


<commit_before>import React, {Fragment} from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
import {CompositeDisposable} from 'event-kit';

import URIPattern, {nonURIMatch} from './uri-pattern';
import RefHolder from '../models/ref-holder';
import {createItem} from '../helpers';

/**
 * PaneItem registers an opener with the current Atom workspace as long as this component is mounted. The opener will
 * trigger on URIs that match a specified pattern and render a subtree returned by a render prop.
 *
 * The render prop can receive three arguments:
 *
 * * itemHolder: A RefHolder. If used as the target for a ref, the referenced component will be used as the "item" of
 *   the pane item - its `getTitle()`, `getIconName()`, and other methods will be used by the pane.
 * * params: An object containing the named parameters captured by the URI pattern.
 * * uri: The exact, matched URI used to launch this item.
 *
 * render() {
 *   return (
 *     <PaneItem workspace={this.props.workspace} uriPattern='atom-github://host/{id}'>
 *       {({itemHolder, params}) => (
 *         <ItemComponent ref={itemHolder.setter} id={params.id} />
 *       )}
 *     </PaneItem>
 *   );
 * }
 */
export default class PaneItem extends React.Component {
  static propTypes = {
    workspace: PropTypes.object.isRequired,
    children: PropTypes.func.isRequired,
    uriPattern: PropTypes.string.isRequired,
  }

  constructor(props) {
    super(props);

    const uriPattern = new URIPattern(this.props.uriPattern);
    const currentlyOpen = this.props.workspace.getPaneItems()
      .map(item => {
        const element = item.getElement ? item.getElement() : null;
        const match = item.getURI ? uriPattern.matches(item.getURI()) : nonURIMatch;
        const stub = item.setRealItem ? item : null;
        return {element, match, stub};
      })
      .filter(each => each.element && each.match.ok())
      .map(each => new OpenItem(each.match, each.element, each.stub));

    this.subs = new CompositeDisposable();
    this.state = {uriPattern, currentlyOpen};
  }

  static getDerivedStateFromProps(nextProps, prevState) {
    if (prevState.uriPattern.getOriginal() === nextProps.uriPattern) {
      return null;
    }

    return {
      uriPattern: new URIPattern(nextProps.uriPattern),
    };
  }

  componentDidMount() {
    for (const openItem of this.state.currentlyOpen) {
      this.subs.add(this.closeListener(openItem.stubItem, openItem));

      openItem.hydrateStub();
    }

    this.subs.add(this.props.workspace.addOpener(this.opener));
  }

  render() {
    return this.state.currentlyOpen.map(item => {
      return (
        <Fragment key={item.getURI()}>
          {item.renderPortal(this.props.children)}
        </Fragment>
      );
    });
  }

  componentWillUnmount() {
    this.subs.dispose();
  }

  opener = uri => {
    const m = this.state.uriPattern.matches(uri);
    if (!m.ok()) {
      return undefined;
    }

    const openItem = new OpenItem(m);

    return new Promise(resolve => {
      this.setState(prevState => ({
        currentlyOpen: [...prevState.currentlyOpen, openItem],
      }), () => {
        const paneItem = openItem.create();

        this.subs.add(this.closeListener(paneItem, openItem));

        resolve(paneItem);
      });
    });
  }

  closeListener(paneItem, openItem) {
    return this.props.workspace.onDidDestroyPaneItem(({item}) => {
      if (item === paneItem) {
        this.setState(prevState => ({
          currentlyOpen: prevState.currentlyOpen.filter(each => each !== openItem),
        }));
      }
    });
  }
}

/**
 * A subtree rendered through a portal onto a detached DOM node for use as the root as a PaneItem.
 */
class OpenItem {
  constructor(match, element = null, stub = null) {
    this.domNode = element || document.createElement('div');
    this.stubItem = stub;
    this.match = match;
    this.itemHolder = new RefHolder();
  }

  getURI() {
    return this.match.getURI();
  }

  create() {
    const h = this.itemHolder.isEmpty() ? null : this.itemHolder;
    return createItem(this.domNode, h, this.match.getURI());
  }

  hydrateStub() {
    if (this.stubItem) {
      this.stubItem.setRealItem(this.create());
      this.stubItem = null;
    }
  }

  renderPortal(renderProp) {
    return ReactDOM.createPortal(
      renderProp({
        itemHolder: this.itemHolder,
        params: this.match.getParams(),
        uri: this.match.getURI(),
      }),
      this.domNode,
    );
  }
}
<commit_msg>Key PaneItems on monotonic IDs, not URI<commit_after>import React, {Fragment} from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
import {CompositeDisposable} from 'event-kit';

import URIPattern, {nonURIMatch} from './uri-pattern';
import RefHolder from '../models/ref-holder';
import StubItem from '../atom-items/stub-item';
import {createItem, autobind} from '../helpers';

/**
 * PaneItem registers an opener with the current Atom workspace as long as this component is mounted. The opener will
 * trigger on URIs that match a specified pattern and render a subtree returned by a render prop.
 *
 * The render prop can receive three arguments:
 *
 * * itemHolder: A RefHolder. If used as the target for a ref, the referenced component will be used as the "item" of
 *   the pane item - its `getTitle()`, `getIconName()`, and other methods will be used by the pane.
 * * params: An object containing the named parameters captured by the URI pattern.
 * * uri: The exact, matched URI used to launch this item.
 *
 * render() {
 *   return (
 *     <PaneItem workspace={this.props.workspace} uriPattern='atom-github://host/{id}'>
 *       {({itemHolder, params}) => (
 *         <ItemComponent ref={itemHolder.setter} id={params.id} />
 *       )}
 *     </PaneItem>
 *   );
 * }
 */
export default class PaneItem extends React.Component {
  static propTypes = {
    workspace: PropTypes.object.isRequired,
    children: PropTypes.func.isRequired,
    uriPattern: PropTypes.string.isRequired,
  }

  constructor(props) {
    super(props);
    autobind(this, 'opener');

    const uriPattern = new URIPattern(this.props.uriPattern);
    const currentlyOpen = this.props.workspace.getPaneItems()
      .reduce((arr, item) => {
        const element = item.getElement ? item.getElement() : null;
        const match = item.getURI ? uriPattern.matches(item.getURI()) : nonURIMatch;
        const stub = item.setRealItem ? item : null;

        if (element && match.ok()) {
          const openItem = new OpenItem(match, element, stub);
          arr.push(openItem);
        }

        return arr;
      }, []);

    this.subs = new CompositeDisposable();
    this.state = {uriPattern, currentlyOpen};
  }

  static getDerivedStateFromProps(nextProps, prevState) {
    if (prevState.uriPattern.getOriginal() === nextProps.uriPattern) {
      return null;
    }

    return {
      uriPattern: new URIPattern(nextProps.uriPattern),
    };
  }

  componentDidMount() {
    for (const openItem of this.state.currentlyOpen) {
      this.registerCloseListener(openItem.stubItem, openItem);

      openItem.hydrateStub({
        copy: () => this.copyOpenItem(openItem),
      });
    }

    this.subs.add(this.props.workspace.addOpener(this.opener));
  }

  render() {
    return this.state.currentlyOpen.map(item => {
      return (
        <Fragment key={item.getKey()}>
          {item.renderPortal(this.props.children)}
        </Fragment>
      );
    });
  }

  componentWillUnmount() {
    this.subs.dispose();
  }

  async opener(uri) {
    const m = this.state.uriPattern.matches(uri);
    if (!m.ok()) {
      return undefined;
    }

    const openItem = new OpenItem(m);

    await new Promise(resolve => {
      this.setState(prevState => ({
        currentlyOpen: [...prevState.currentlyOpen, openItem],
      }), resolve);
    });

    const paneItem = openItem.create({
      copy: () => this.copyOpenItem(openItem),
    });
    this.registerCloseListener(paneItem, openItem);
    return paneItem;
  }

  copyOpenItem(openItem) {
    const m = this.state.uriPattern.matches(openItem.getURI());
    if (!m.ok()) {
      return null;
    }

    const stub = StubItem.create('generic', openItem.getStubProps(), openItem.getURI());

    const copiedItem = new OpenItem(m, stub.getElement(), stub);
    this.setState(prevState => ({
      currentlyOpen: [...prevState.currentlyOpen, copiedItem],
    }), () => {
      this.registerCloseListener(stub, copiedItem);
      copiedItem.hydrateStub({
        copy: () => this.copyOpenItem(copiedItem),
      });
    });

    return stub;
  }

  registerCloseListener(paneItem, openItem) {
    const sub = this.props.workspace.onDidDestroyPaneItem(({item}) => {
      if (item === paneItem) {
        sub.dispose();
        this.subs.remove(sub);
        this.setState(prevState => ({
          currentlyOpen: prevState.currentlyOpen.filter(each => each !== openItem),
        }));
      }
    });

    this.subs.add(sub);
  }
}

/**
 * A subtree rendered through a portal onto a detached DOM node for use as the root as a PaneItem.
 */
class OpenItem {
  static nextID = 0

  constructor(match, element = null, stub = null) {
    this.id = this.constructor.nextID;
    this.constructor.nextID++;

    this.domNode = element || document.createElement('div');
    this.stubItem = stub;
    this.match = match;
    this.itemHolder = new RefHolder();
  }

  getURI() {
    return this.match.getURI();
  }

  create(extra = {}) {
    const h = this.itemHolder.isEmpty() ? null : this.itemHolder;
    return createItem(this.domNode, h, this.match.getURI(), extra);
  }

  hydrateStub(extra = {}) {
    if (this.stubItem) {
      this.stubItem.setRealItem(this.create(extra));
      this.stubItem = null;
    }
  }

  getKey() {
    return this.id;
  }

  getStubProps() {
    if (!this.itemHolder.isEmpty()) {
      const item = this.itemHolder.get();
      return {
        title: item.getTitle ? item.getTitle() : null,
        iconName: item.getIconName ? item.getIconName() : null,
      };
    } else {
      return {};
    }
  }

  renderPortal(renderProp) {
    return ReactDOM.createPortal(
      renderProp({
        itemHolder: this.itemHolder,
        params: this.match.getParams(),
        uri: this.match.getURI(),
      }),
      this.domNode,
    );
  }
}
<|endoftext|>


<commit_before><commit_msg>Docs Version: 6.1.4<commit_after><|endoftext|>


<commit_before>/*global require, exports */
/*jslint devel: true */

(function () {
    'use strict';

    var atomize = require('atomize-client'),
        cereal = require('cereal'),
        sockjs = require('sockjs'),
        events = require('events'),
        sockjs_opts = {sockjs_url: "http://cdn.sockjs.org/sockjs-0.1.min.js",
                       log: function () {}},
        globalTVarCount = 0,
        globalTVars = {},
        globalLocal = {},
        create, util, rootTVar;

    util = (function () {
        return {
            isPrimitive: function (obj) {
                return obj !== Object(obj);
            },

            hasOwnProp: ({}).hasOwnProperty,

            shallowCopy: function (src, dest) {
                var keys, i;
                keys = Object.keys(src);
                for (i = 0; i < keys.length; i += 1) {
                    dest[keys[i]] = src[keys[i]];
                }
            },

            lift: function (src, dest, fields) {
                var i;
                for (i = 0; i < fields.length; i += 1) {
                    dest[fields[i]] = src[fields[i]].bind(src);
                }
            }
        };
    }());

    function TVar(id, isArray, raw, creator) {
        this.creator = creator;
        this.raw = isArray ? [] : {};
        util.shallowCopy(raw, this.raw);
        this.isArray = isArray;
        this.id = id;
        this.version = 0;
        this.observers = [];
    }
    TVar.prototype = {
        subscribe: function (fun) {
            this.observers.push(fun);
        },

        bump: function (future) {
            var i, observers;
            // create a fresh observers array otherwise, if on the
            // server side firing the observer creates a new
            // subscription, you can go into a loop!
            this.version += 1;
            observers = this.observers;
            this.observers = [];
            for (i = 0; i < observers.length; i += 1) {
                (observers[i])(this, future);
            }
        }
    };

    TVar.create = function (isArray, value, creator) {
        var globalTVar;
        globalTVarCount += 1;
        globalTVar = new TVar(globalTVarCount, isArray, value, creator);
        globalTVars[globalTVar.id] = globalTVar;
        return globalTVar;
    };

    function CoalescingObserver() {
        this.map = new cereal.Map();
        this.keys = [];
    }
    CoalescingObserver.prototype = {
        insert: function (key, value) {
            if (!this.map.has(key)) {
                this.keys.push(key);
            }
            this.map.set(key, value);
        },

        force: function () {
            var i, key;
            for (i = 0; i < this.keys.length; i += 1) {
                key = this.keys[i];
                (this.map.get(key))();
            }
            this.keys = [];
            this.map = new cereal.Map();
        }
    }

    function Client(connection, serverEventEmitter) {
        this.emitter = new events.EventEmitter();
        util.lift(this.emitter, this,
                  ['on', 'once', 'removeListener', 'removeAllListeners', 'emit']);

        this.connection = connection;
        this.localGlobal = {};
        this.localGlobal[rootTVar.id] = rootTVar.id;
        this.globalLocal = {};
        this.globalLocal[rootTVar.id] = rootTVar.id;
        globalLocal[connection.id] = this.globalLocal;
        this.minTVarId = 0;

        connection.on('data', this.data.bind(this));
        connection.on('close', this.close.bind(this));
        serverEventEmitter.emit('connection', this);
    }

    Client.prototype = {
        isAuthenticated: false,

        data: function (message) {
            if (this.isAuthenticated) {
                this.dispatch(message);
            } else {
                this.emit('data', message, this);
            }
        },

        close: function () {
            this.emit('close', this);
            delete globalLocal[this.connection.id];
        },

        write: function (msg) {
            this.connection.write(cereal.stringify(msg));
        },

        read: function (msg) {
            return cereal.parse(msg);
        },

        dispatch: function (msg) {
            var txnLog = this.read(msg);
            switch (txnLog.type) {
            case "commit":
                this.commit(txnLog);
                break;
            case "retry":
                this.retry(txnLog);
                break;
            default:
                console.log("Received unexpected message from client:");
                console.log(txnLog);
            }
        },

        create: function (txnLog, updates) {
            var keys, i, ok, localTVar, globalTVar;
            ok = true;
            keys = Object.keys(txnLog.created);

            for (i = 0; i < keys.length; i += 1) {
                localTVar = txnLog.created[keys[i]];
                localTVar.id = keys[i];

                globalTVar = TVar.create(localTVar.isArray, localTVar.value, this);
                this.localGlobal[localTVar.id] = globalTVar.id;
                this.globalLocal[globalTVar.id] = localTVar.id;
            }

            return ok;
        },

        checkThing: function (thing, updates) {
            var keys, i, ok, localTVar, globalTVar;
            ok = true;

            keys = Object.keys(thing);
            for (i = 0; i < keys.length; i += 1) {
                localTVar = thing[keys[i]];
                localTVar.id = keys[i];
                globalTVar = globalTVars[this.localGlobal[localTVar.id]];
                if (localTVar.version !== globalTVar.version) {
                    updates[globalTVar.id] = true;
                    ok = false;
                }
            }

            return ok;
        },

        checkReads: function (txnLog, updates) {
            return this.checkThing(txnLog.read, updates);
        },

        checkWrites: function (txnLog, updates) {
            return this.checkThing(txnLog.written, updates);
        },

        retry: function (txnLog) {
            var txnId, ok, updates, keys, i, localTVar, globalTVar, self, observer, fired;

            txnId = txnLog.txnId;
            updates = {};

            ok = this.checkReads(txnLog, updates);

            if (ok) {
                self = this;
                fired = false;
                observer = function (globalTVar, future) {
                    updates[globalTVar.id] = true;
                    future.insert(
                        updates,
                        function () {
                            if (!fired) {
                                fired = true;
                                self.write(self.createUpdates(updates, txnId));
                                self.write({type: "retry", txnId: txnId});
                            }
                        });
                };

                keys = Object.keys(txnLog.read);
                for (i = 0; i < keys.length; i += 1) {
                    localTVar = txnLog.read[keys[i]];
                    localTVar.id = keys[i];
                    globalTVar = globalTVars[this.localGlobal[localTVar.id]];
                    globalTVar.subscribe(observer);
                }
            } else {
                // oh good, need to do updates already and can then
                // immediately restart the txn.
                this.write(this.createUpdates(updates, txnId));
                this.write({type: "retry", txnId: txnId});
            }

        },

        commit: function (txnLog) {
            var txnId, ok, i, j, keys, localTVar, globalTVar, names, name, value, updates, key, future;

            txnId = txnLog.txnId;
            updates = {};

            // Prevent a short-cut impl of && from causing us problems
            ok = this.create(txnLog, updates);
            ok = this.checkReads(txnLog, updates) && ok;
            ok = this.checkWrites(txnLog, updates) && ok;

            // Regardless of success, we have grabbed the creates. We
            // should now bump their versions from 0 to 1.
            keys = Object.keys(txnLog.created);
            for (i = 0; i < keys.length; i += 1) {
                localTVar = txnLog.created[keys[i]];
                localTVar.id = keys[i];
                globalTVar = globalTVars[this.localGlobal[localTVar.id]];
                globalTVar.bump();
            }

            if (ok && this.onPreCommit(txnLog)) {
                // send out the success message first. This a)
                // improves performance; and more importantly b)
                // ensures that any updates that end up being sent to
                // the same client (due to pending retries) get
                // received after the success msg and thus we don't
                // fall out of step with vsns
                this.write({type: "commit", txnId: txnId, result: "success"});

                future = new CoalescingObserver();

                keys = Object.keys(txnLog.written);
                for (i = 0; i < keys.length; i += 1) {
                    localTVar = txnLog.written[keys[i]];
                    localTVar.id = keys[i];
                    globalTVar = globalTVars[this.localGlobal[localTVar.id]];
                    names = Object.keys(localTVar.children);
                    for (j = 0; j < names.length; j += 1) {
                        name = names[j];
                        value = localTVar.children[name];
                        if (undefined !== value.tvar) {
                            value.value = {tvar: this.localGlobal[value.tvar]};
                            delete value.tvar;
                            Object.defineProperty(globalTVar.raw, name, value);
                        } else if (undefined !== value.deleted) {
                            delete globalTVar.raw[name];
                        } else {
                            // mess for dealing with arrays, and some proxy stuff
                            if (globalTVar.isArray && 'length' === name) {
                                globalTVar.raw[name] = value.value;
                            } else {
                                Object.defineProperty(globalTVar.raw, name, value);
                            }
                        }
                    }
                    globalTVar.bump(future);
                }

                future.force();
            } else {
                this.write(this.createUpdates(updates, txnId));
                this.write({type: "commit", txnId: txnId, result: "failure"});
            }
        },

        onPreCommit: function (txnLog) {
            return true;
        },

        createUpdates: function (updates, txnId) {
            var keys, key, txnLog, localTVar, globalTVar, names, name, j, value, desc;
            keys = Object.keys(updates);
            txnLog = {type: "updates", updates: {}, txnId: txnId};
            while (0 < keys.length) {
                key = keys.shift();
                globalTVar = globalTVars[key];
                localTVar = {version: globalTVar.version};

                if (undefined === this.globalLocal[globalTVar.id]) {
                    this.minTVarId -= 1;
                    txnLog.updates[this.minTVarId] = localTVar;
                    this.localGlobal[this.minTVarId] = globalTVar.id;
                    this.globalLocal[globalTVar.id] = this.minTVarId;
                } else {
                    txnLog.updates[this.globalLocal[globalTVar.id]] = localTVar;
                }

                // TODO: check on array objects for additional
                // manually added properties.

                localTVar.value = {};
                localTVar.isArray = globalTVar.raw.constructor === Array;

                names = Object.getOwnPropertyNames(globalTVar.raw);
                for (j = 0; j < names.length; j += 1) {
                    name = names[j];
                    desc = Object.getOwnPropertyDescriptor(globalTVar.raw, name);
                    if (util.hasOwnProp.call(desc.value, 'tvar')) {
                        localTVar.value[name] = desc;
                        if (undefined === this.globalLocal[desc.value.tvar]) {
                            this.minTVarId -= 1;
                            this.localGlobal[this.minTVarId] = desc.value.tvar;
                            this.globalLocal[desc.value.tvar] = this.minTVarId;
                            keys.push(desc.value.tvar);
                            desc.tvar = this.minTVarId;
                        } else {
                            desc.tvar = this.globalLocal[desc.value.tvar];
                        }
                        delete desc.value;
                    } else {
                        localTVar.value[name] = desc;
                    }
                }
            }
            return txnLog;
        }
    };

    function ServerEventEmitter () {
        this.emitter = new events.EventEmitter();
        util.lift(this.emitter, this,
                  ['on', 'once', 'removeListener', 'removeAllListeners', 'emit']);
        this.on('connection', this.defaultAuthenticator.bind(this));
    }

    ServerEventEmitter.prototype = {
        connection: function (connection) {
            new Client(connection, this);
        },

        defaultAuthenticator: function (client) {
            if (1 === this.emitter.listeners('connection').length) {
                // nothing installed other than us, just let it straight through!
                client.isAuthenticated = true;
            }
        }
    };

    create = function (http, path, root) {
        var server = sockjs.createServer(sockjs_opts),
            emitter = new ServerEventEmitter();

        server.on('connection', emitter.connection.bind(emitter));
        http.addListener('upgrade', function (req, res) { res.end(); });
        server.installHandlers(http, {prefix: path});

        if (undefined === root || null === root) {
            root = {};
        }
        globalTVarCount = 1;
        rootTVar = new TVar(globalTVarCount, Array.isArray(root), root, undefined);
        globalTVars[rootTVar.id] = rootTVar;
        rootTVar.bump(); // Need 2 bumps to ensure we're ahead of any clients.
        rootTVar.bump();

        emitter.client = function () { return new atomize.Atomize(Client, emitter); };

        return emitter;
    };

    exports.create = create;
}());
<commit_msg>Cosmetics<commit_after>/*global require, exports */
/*jslint devel: true */

(function () {
    'use strict';

    var atomize = require('atomize-client'),
        cereal = require('cereal'),
        sockjs = require('sockjs'),
        events = require('events'),
        sockjs_opts = {sockjs_url: "http://cdn.sockjs.org/sockjs-0.1.min.js",
                       log: function () {}},
        globalTVarCount = 0,
        globalTVars = {},
        globalLocal = {},
        create, util, rootTVar;

    util = (function () {
        return {
            isPrimitive: function (obj) {
                return obj !== Object(obj);
            },

            hasOwnProp: ({}).hasOwnProperty,

            shallowCopy: function (src, dest) {
                var keys = Object.keys(src),
                    i;
                for (i = 0; i < keys.length; i += 1) {
                    dest[keys[i]] = src[keys[i]];
                }
            },

            lift: function (src, dest, fields) {
                var i;
                for (i = 0; i < fields.length; i += 1) {
                    dest[fields[i]] = src[fields[i]].bind(src);
                }
            }
        };
    }());

    function TVar(id, isArray, raw, creator) {
        this.creator = creator;
        this.raw = isArray ? [] : {};
        util.shallowCopy(raw, this.raw);
        this.isArray = isArray;
        this.id = id;
        this.version = 0;
        this.observers = [];
    }
    TVar.prototype = {
        subscribe: function (fun) {
            this.observers.push(fun);
        },

        bump: function (future) {
            // create a fresh observers array otherwise, if on the
            // server side firing the observer creates a new
            // subscription, you can go into a loop!
            var observers = this.observers,
                i;
            this.version += 1;
            this.observers = [];
            for (i = 0; i < observers.length; i += 1) {
                (observers[i])(this, future);
            }
        }
    };

    TVar.create = function (isArray, value, creator) {
        var globalTVar;
        globalTVarCount += 1;
        globalTVar = new TVar(globalTVarCount, isArray, value, creator);
        globalTVars[globalTVar.id] = globalTVar;
        return globalTVar;
    };

    function CoalescingObserver() {
        this.map = new cereal.Map();
        this.keys = [];
    }
    CoalescingObserver.prototype = {
        insert: function (key, value) {
            if (!this.map.has(key)) {
                this.keys.push(key);
            }
            this.map.set(key, value);
        },

        force: function () {
            var i;
            for (i = 0; i < this.keys.length; i += 1) {
                (this.map.get(this.keys[i]))();
            }
            this.keys = [];
            this.map = new cereal.Map();
        }
    }

    function Client(connection, serverEventEmitter) {
        this.emitter = new events.EventEmitter();
        util.lift(this.emitter, this,
                  ['on', 'once', 'removeListener', 'removeAllListeners', 'emit']);

        this.connection = connection;
        this.localGlobal = {};
        this.localGlobal[rootTVar.id] = rootTVar.id;
        this.globalLocal = {};
        this.globalLocal[rootTVar.id] = rootTVar.id;
        globalLocal[connection.id] = this.globalLocal;
        this.minTVarId = 0;

        connection.on('data', this.data.bind(this));
        connection.on('close', this.close.bind(this));
        serverEventEmitter.emit('connection', this);
    }

    Client.prototype = {
        isAuthenticated: false,

        data: function (message) {
            if (this.isAuthenticated) {
                this.dispatch(message);
            } else {
                this.emit('data', message, this);
            }
        },

        close: function () {
            this.emit('close', this);
            delete globalLocal[this.connection.id];
        },

        write: function (msg) {
            this.connection.write(cereal.stringify(msg));
        },

        read: function (msg) {
            return cereal.parse(msg);
        },

        dispatch: function (msg) {
            var txnLog = this.read(msg);
            switch (txnLog.type) {
            case "commit":
                this.commit(txnLog);
                break;
            case "retry":
                this.retry(txnLog);
                break;
            default:
                console.log("Received unexpected message from client:");
                console.log(txnLog);
            }
        },

        create: function (txnLog, updates) {
            var keys = Object.keys(txnLog.created),
                ok = true,
                i, localTVar, globalTVar;

            for (i = 0; i < keys.length; i += 1) {
                localTVar = txnLog.created[keys[i]];
                localTVar.id = keys[i];

                globalTVar = TVar.create(localTVar.isArray, localTVar.value, this);
                this.localGlobal[localTVar.id] = globalTVar.id;
                this.globalLocal[globalTVar.id] = localTVar.id;
            }

            return ok;
        },

        checkThing: function (thing, updates) {
            var keys = Object.keys(thing),
                ok = true,
                i, localTVar, globalTVar;

            for (i = 0; i < keys.length; i += 1) {
                localTVar = thing[keys[i]];
                localTVar.id = keys[i];
                globalTVar = globalTVars[this.localGlobal[localTVar.id]];
                if (localTVar.version !== globalTVar.version) {
                    updates[globalTVar.id] = true;
                    ok = false;
                }
            }

            return ok;
        },

        checkReads: function (txnLog, updates) {
            return this.checkThing(txnLog.read, updates);
        },

        checkWrites: function (txnLog, updates) {
            return this.checkThing(txnLog.written, updates);
        },

        retry: function (txnLog) {
            var txnId = txnLog.txnId,
                updates = {},
                ok, self, fired, observer, keys, i, localTVar, globalTVar;

            ok = this.checkReads(txnLog, updates);

            if (ok) {
                self = this;
                fired = false;
                observer = function (globalTVar, future) {
                    updates[globalTVar.id] = true;
                    future.insert(
                        updates,
                        function () {
                            if (!fired) {
                                fired = true;
                                self.write(self.createUpdates(updates, txnId));
                                self.write({type: "retry", txnId: txnId});
                            }
                        });
                };

                keys = Object.keys(txnLog.read);
                for (i = 0; i < keys.length; i += 1) {
                    localTVar = txnLog.read[keys[i]];
                    localTVar.id = keys[i];
                    globalTVar = globalTVars[this.localGlobal[localTVar.id]];
                    globalTVar.subscribe(observer);
                }
            } else {
                // oh good, need to do updates already and can then
                // immediately restart the txn.
                this.write(this.createUpdates(updates, txnId));
                this.write({type: "retry", txnId: txnId});
            }

        },

        commit: function (txnLog) {
            var txnId = txnLog.txnId,
                updates = {},
                ok, keys, i, j, localTVar, globalTVar, future, names, name, value;

            // Prevent a short-cut impl of && from causing us problems
            ok = this.create(txnLog, updates);
            ok = this.checkReads(txnLog, updates) && ok;
            ok = this.checkWrites(txnLog, updates) && ok;

            // Regardless of success, we have grabbed the creates. We
            // should now bump their versions from 0 to 1.
            keys = Object.keys(txnLog.created);
            for (i = 0; i < keys.length; i += 1) {
                localTVar = txnLog.created[keys[i]];
                localTVar.id = keys[i];
                globalTVar = globalTVars[this.localGlobal[localTVar.id]];
                globalTVar.bump();
            }

            if (ok && this.onPreCommit(txnLog)) {
                // send out the success message first. This a)
                // improves performance; and more importantly b)
                // ensures that any updates that end up being sent to
                // the same client (due to pending retries) get
                // received after the success msg and thus we don't
                // fall out of step with vsns
                this.write({type: "commit", txnId: txnId, result: "success"});

                future = new CoalescingObserver();

                keys = Object.keys(txnLog.written);
                for (i = 0; i < keys.length; i += 1) {
                    localTVar = txnLog.written[keys[i]];
                    localTVar.id = keys[i];
                    globalTVar = globalTVars[this.localGlobal[localTVar.id]];
                    names = Object.keys(localTVar.children);
                    for (j = 0; j < names.length; j += 1) {
                        name = names[j];
                        value = localTVar.children[name];
                        if (undefined !== value.tvar) {
                            value.value = {tvar: this.localGlobal[value.tvar]};
                            delete value.tvar;
                            Object.defineProperty(globalTVar.raw, name, value);
                        } else if (undefined !== value.deleted) {
                            delete globalTVar.raw[name];
                        } else {
                            // mess for dealing with arrays, and some proxy stuff
                            if (globalTVar.isArray && 'length' === name) {
                                globalTVar.raw[name] = value.value;
                            } else {
                                Object.defineProperty(globalTVar.raw, name, value);
                            }
                        }
                    }
                    globalTVar.bump(future);
                }

                future.force();
            } else {
                this.write(this.createUpdates(updates, txnId));
                this.write({type: "commit", txnId: txnId, result: "failure"});
            }
        },

        onPreCommit: function (txnLog) {
            return true;
        },

        createUpdates: function (updates, txnId) {
            var keys = Object.keys(updates),
                txnLog = {type: "updates", updates: {}, txnId: txnId},
                key, globalTVar, localTVar, names, name, i, desc;

            while (0 < keys.length) {
                key = keys.shift();
                globalTVar = globalTVars[key];
                localTVar = {version: globalTVar.version};

                if (undefined === this.globalLocal[globalTVar.id]) {
                    this.minTVarId -= 1;
                    txnLog.updates[this.minTVarId] = localTVar;
                    this.localGlobal[this.minTVarId] = globalTVar.id;
                    this.globalLocal[globalTVar.id] = this.minTVarId;
                } else {
                    txnLog.updates[this.globalLocal[globalTVar.id]] = localTVar;
                }

                // TODO: check on array objects for additional
                // manually added properties.

                localTVar.value = {};
                localTVar.isArray = globalTVar.raw.constructor === Array;

                names = Object.getOwnPropertyNames(globalTVar.raw);
                for (i = 0; i < names.length; i += 1) {
                    name = names[i];
                    desc = Object.getOwnPropertyDescriptor(globalTVar.raw, name);
                    if (util.hasOwnProp.call(desc.value, 'tvar')) {
                        localTVar.value[name] = desc;
                        if (undefined === this.globalLocal[desc.value.tvar]) {
                            this.minTVarId -= 1;
                            this.localGlobal[this.minTVarId] = desc.value.tvar;
                            this.globalLocal[desc.value.tvar] = this.minTVarId;
                            keys.push(desc.value.tvar);
                            desc.tvar = this.minTVarId;
                        } else {
                            desc.tvar = this.globalLocal[desc.value.tvar];
                        }
                        delete desc.value;
                    } else {
                        localTVar.value[name] = desc;
                    }
                }
            }
            return txnLog;
        }
    };

    function ServerEventEmitter () {
        this.emitter = new events.EventEmitter();
        util.lift(this.emitter, this,
                  ['on', 'once', 'removeListener', 'removeAllListeners', 'emit']);
        this.on('connection', this.defaultAuthenticator.bind(this));
    }

    ServerEventEmitter.prototype = {
        connection: function (connection) {
            new Client(connection, this);
        },

        defaultAuthenticator: function (client) {
            if (1 === this.emitter.listeners('connection').length) {
                // nothing installed other than us, just let it straight through!
                client.isAuthenticated = true;
            }
        }
    };

    create = function (http, path, root) {
        var server = sockjs.createServer(sockjs_opts),
            emitter = new ServerEventEmitter();

        server.on('connection', emitter.connection.bind(emitter));
        http.addListener('upgrade', function (req, res) { res.end(); });
        server.installHandlers(http, {prefix: path});

        if (undefined === root || null === root) {
            root = {};
        }
        globalTVarCount = 1;
        rootTVar = new TVar(globalTVarCount, Array.isArray(root), root, undefined);
        globalTVars[rootTVar.id] = rootTVar;
        rootTVar.bump(); // Need 2 bumps to ensure we're ahead of any clients.
        rootTVar.bump();

        emitter.client = function () { return new atomize.Atomize(Client, emitter); };

        return emitter;
    };

    exports.create = create;
}());
<|endoftext|>


<commit_before>'use strict';

(function (root, factory) {
  if (typeof define === 'function' && define.amd) {
    define([ 'underscore', 'jquery', 'backbone' ], factory);
  }
}(this, function (_, $, Backbone) {
  Backbone.Cache = (function () {
    var Cache = { lifetime = 30000 },
        ajax = Backbone.ajax,
        cache = new Backbone.Collection();

    Cache.ajax = function (url, options) {
      if (typeof url === 'object') options = url;

      var cachable = options.type === 'GET';

      if (!cachable) cache.reset();

      var cached = cache.findWhere({ url: options.url });

      // We need to fool Backbone into thinking that
      // the request has succeeded in a normal fashion
      var triggerSuccess = function (xhr) {
        options.success(xhr.responseJSON, 'success', xhr);
      };

      // We need the cached requests to remove themselves
      // after an alotted amount of time has passed
      var startCacheReaper = function () {
        _.delay(function () {
          cache.remove([ cache.findWhere({ url: options.url }) ]);
        }, Cache.lifetime);
      };

      // We need to add the request to the cache if it was
      // cacheable and it was successful
      var cacheRequest = function (response, status, xhr) {
        if (cachable && status === 'success') {
          cache.add({ url: options.url, xhr: xhr });
        }
      };

      if (cachable && options.success && cached) {
        var xhr = cached.get('xhr');

        // We need to defer our success to the end of the
        // current event loop stack due to Backbone.sync
        // firing a request event after calling this method
        _.defer(triggerSuccess, xhr);
      } else {
        var xhr = ajax.apply(this, arguments)
          .done(cacheRequest)
          .done(startCacheReaper);
      }

      return xhr;
    };

    // We need to replace the existing
    // ajax method with the caching one
    Backbone.ajax = Cache.ajax;

    return Cache;
  })(Backbone, $, _);
}));<commit_msg>Changed casing and naming to be more consistent<commit_after>'use strict';

(function (root, factory) {
  if (typeof define === 'function' && define.amd) {
    define([ 'underscore', 'jquery', 'backbone' ], factory);
  }
}(this, function (_, $, Backbone) {
  Backbone.cache = (function () {
    var cache = { lifetime = 30000 },
        ajax = Backbone.ajax,
        requests = new Backbone.Collection();

    cache.ajax = function (url, options) {
      if (typeof url === 'object') options = url;

      var cachable = options.type === 'GET';

      // If anything other than a GET request is
      // fired we reset our position to neutral
      if (!cachable) requests.reset();

      var cached = cache.findWhere({ url: options.url });

      // We need to fool Backbone into thinking that
      // the request has succeeded in a normal fashion
      var triggerSuccess = function (xhr) {
        options.success(xhr.responseJSON, 'success', xhr);
      };

      // We need the cached requests to remove themselves
      // after an alotted amount of time has passed
      var startCacheReaper = function () {
        _.delay(function () {
          requests.remove([ cache.findWhere({ url: options.url }) ]);
        }, Cache.lifetime);
      };

      // We need to add the request to the cache if it was
      // cacheable and it was successful
      var cacheRequest = function (response, status, xhr) {
        if (cachable && status === 'success') {
          requests.add({ url: options.url, xhr: xhr });
        }
      };

      if (cachable && options.success && cached) {
        var xhr = cached.get('xhr');

        // We need to defer our success to the end of the
        // current event loop stack due to Backbone.sync
        // firing a request event after calling this method
        _.defer(triggerSuccess, xhr);
      } else {
        var xhr = ajax.apply(this, arguments)
          .done(cacheRequest)
          .done(startCacheReaper);
      }

      return xhr;
    };

    // We need to replace the existing
    // ajax method with the caching one
    Backbone.ajax = Cache.ajax;

    return cache;
  })(Backbone, $, _);
}));<|endoftext|>


<commit_before>(function() {

  var Decode = (function() {

    var bencodedString
      , _object
      , decode
      , getType
      , getString
      , getNumber
      , getValue
      , counter
      , incrementCounter
      , setDecodedObject
    ;

    function Decode( string ) {
      bencodedString = string;
      counter = 0;

      var type = getType();

      if ( type === "string" || type === "integer" )
        setDecodedObject( getValue( type ) );
      else
        setDecodedObject( decode() );
    }

    decode = function() {
      var obj = getType() === "list" ? [] : {};

      incrementCounter( 1 );
      var type = getType();

      while( counter < bencodedString.length ) {
        if ( obj instanceof Array ) {
          var result = isDataStructure( type ) ? decode() : getValue( type );
          obj.push( result );
        }
        else if ( obj instanceof Object ) {
          var key = getValue( type );

          type = getType();
          var value = isDataStructure( type ) ? decode() : getValue( type );

          obj[ key ] = value;
        }
        else { throw new Error("Unkown object when iterating bencoded string"); }

        if ( bencodedString.charAt( counter ) === 'e' ) {
          incrementCounter( 1 );
          return obj;
        }

        type = getType();
      }
    }

    getType = function() {
      var char = bencodedString.charAt( counter );

      if ( char.match(/\d/) ) return "string";

      switch( char ) {
        case 'i':
          return "integer";
        case 'l':
          return "list";
        case 'd':
          return "dictionary";
        default:
          throw new Error("Format unreadable");
      }
    }

    setDecodedObject = function( obj ) {
      return _object = obj;
    }

    incrementCounter = function( amount ) {
      return counter += amount
    }

    getValue = function( type ) {
      return type === "string" ? getString() : getNumber();
    }

    isDataStructure = function( type ) {
      return ( type === "list" || type === "dictionary" ) ? true : false;
    }

    getString = function() {
      var str           = bencodedString.slice( counter )
        , strArray      = str.split(':')
        , len           = strArray[0]
      ;

      incrementCounter( len.length + 1 + parseInt(len) );

      str = strArray[1];
      return str.slice(0, len );
    }

    getNumber = function() {
      var str = bencodedString.slice( counter )
        , int = str.slice( 1, str.indexOf('e') )
      ;

      incrementCounter( int.length + 2 );
      return parseInt( int );
    }

    // Public Accessor
    Decode.prototype.getDecodedObject = function() {
      return _object;
    }

    return Decode;
  })();

  String.prototype.decode = function() {
    if ( this.length < 2 ) throw new Error("Not in Bencode Format");
    object = new Decode( this );
    return object.getDecodedObject();
  }

  var root = this;
  if ( root.Bencode == null ) root.Bencode = {};
  return root.Bencode.Decode = Decode;
}).call(this);
<commit_msg>global variable => private variable<commit_after>(function() {

  var Decode = (function() {

    var bencodedString
      , _object
      , decode
      , getType
      , getString
      , getNumber
      , getValue
      , counter
      , incrementCounter
      , setDecodedObject
      , isDataStructure
    ;

    function Decode( string ) {
      bencodedString = string;
      counter = 0;

      var type = getType();

      if ( type === "string" || type === "integer" )
        setDecodedObject( getValue( type ) );
      else
        setDecodedObject( decode() );
    }

    decode = function() {
      var obj = getType() === "list" ? [] : {};

      incrementCounter( 1 );
      var type = getType();

      while( counter < bencodedString.length ) {
        if ( obj instanceof Array ) {
          var result = isDataStructure( type ) ? decode() : getValue( type );
          obj.push( result );
        }
        else if ( obj instanceof Object ) {
          var key = getValue( type );

          type = getType();
          var value = isDataStructure( type ) ? decode() : getValue( type );

          obj[ key ] = value;
        }
        else { throw new Error("Unkown object when iterating bencoded string"); }

        if ( bencodedString.charAt( counter ) === 'e' ) {
          incrementCounter( 1 );
          return obj;
        }

        type = getType();
      }
    }

    getType = function() {
      var char = bencodedString.charAt( counter );

      if ( char.match(/\d/) ) return "string";

      switch( char ) {
        case 'i':
          return "integer";
        case 'l':
          return "list";
        case 'd':
          return "dictionary";
        default:
          throw new Error("Format unreadable");
      }
    }

    setDecodedObject = function( obj ) {
      return _object = obj;
    }

    incrementCounter = function( amount ) {
      return counter += amount
    }

    getValue = function( type ) {
      return type === "string" ? getString() : getNumber();
    }

    isDataStructure = function( type ) {
      return ( type === "list" || type === "dictionary" ) ? true : false;
    }

    getString = function() {
      var str           = bencodedString.slice( counter )
        , strArray      = str.split(':')
        , len           = strArray[0]
      ;

      incrementCounter( len.length + 1 + parseInt(len) );

      str = strArray[1];
      return str.slice(0, len );
    }

    getNumber = function() {
      var str = bencodedString.slice( counter )
        , int = str.slice( 1, str.indexOf('e') )
      ;

      incrementCounter( int.length + 2 );
      return parseInt( int );
    }

    // Public Accessor
    Decode.prototype.getDecodedObject = function() {
      return _object;
    }

    return Decode;
  })();

  String.prototype.decode = function() {
    if ( this.length < 2 ) throw new Error("Not in Bencode Format");
    object = new Decode( this );
    return object.getDecodedObject();
  }

  var root = this;
  if ( root.Bencode == null ) root.Bencode = {};
  return root.Bencode.Decode = Decode;
}).call(this);
<|endoftext|>


<commit_before>(function() {

  // Ensure compatibility with CommonJS modules or browser
  var root;
  root = typeof module !== "undefined" && module.exports ? module.exports : window;

  // if Bencode is not already Defined
  if ( !root.Bencode ) {
    root.Bencode = {};
  }

  var Decode = (function() {

    var bencodedString
      , _object
      , decode
      , getType
      , getString
      , getNumber
      , getArray
      , getObject
      , counter
      , incrementCounter
      , addToDecodedObject
    ;

    function Decode( string ) {
      bencodedString = string;
      counter = 0;
      decode();
    }

    decode = function() {
      var type = getType();

      switch( type ) {
        case "string":
          var string = getString();
          return addToDecodedObject( string );
        case "integer":
          var int = getNumber();
          return addToDecodedObject( int );
        case "list":
          var array = getArray();
          return addToDecodedObject( array );
        case "dictionary":
          var object = getObject();
          return addToDecodedObject( object );
      }
    }

    getType = function() {
      var char = bencodedString.charAt( counter );

      if ( char.match(/\d/) ) return "string";

      switch( char ) {
        case 'i':
          return "integer";
        case 'l':
          return "list";
        case 'd':
          return "dictionary";
      }
    }

    addToDecodedObject = function( obj ) {
      return _object = obj;
    }

    incrementCounter = function( lastIdx ) {
      console.log( lastIdx + 1);
      return counter = lastIdx + 1; // + 1 because we want to start at the next obj
    }

    getString = function() {
      var str           = bencodedString.slice( counter )
        , strArray      = str.split(':')
        , len           = strArray[0]
      ;

      // pass the index of where we cut the string to the incrementCounter function
      // + 1 because we need to include the length (1) of the colon (':')
      incrementCounter( len.length + 1 + parseInt(len) );

      str = strArray[1];
      return str.slice(0, len );
    }

    getNumber = function() {
      var str = bencodedString.slice( counter )
        , int = str.slice( 1, str.indexOf('e') )
      ;

      incrementCounter( int.length + 2 );
      return parseInt( int );
    }

    // Public Accessor
    Decode.prototype.getDecodedObject = function() {
      return _object;
    }

    return Decode;
  })();

  String.prototype.decode = function() {
    object = new Decode( this );
    return object.getDecodedObject();
  }

  return root.Bencode.Decode = Decode;
}).call(this);
<commit_msg>fix incrementCounter<commit_after>(function() {

  // Ensure compatibility with CommonJS modules or browser
  var root;
  root = typeof module !== "undefined" && module.exports ? module.exports : window;

  // if Bencode is not already Defined
  if ( !root.Bencode ) {
    root.Bencode = {};
  }

  var Decode = (function() {

    var bencodedString
      , _object
      , decode
      , getType
      , getString
      , getNumber
      , getArray
      , getObject
      , counter
      , incrementCounter
      , addToDecodedObject
    ;

    function Decode( string ) {
      bencodedString = string;
      counter = 0;
      decode();
    }

    decode = function() {
      var type = getType();

      switch( type ) {
        case "string":
          var string = getString();
          return addToDecodedObject( string );
        case "integer":
          var int = getNumber();
          return addToDecodedObject( int );
        case "list":
          var array = getArray();
          return addToDecodedObject( array );
        case "dictionary":
          var object = getObject();
          return addToDecodedObject( object );
      }
    }

    getType = function() {
      var char = bencodedString.charAt( counter );

      if ( char.match(/\d/) ) return "string";

      switch( char ) {
        case 'i':
          return "integer";
        case 'l':
          return "list";
        case 'd':
          return "dictionary";
      }
    }

    addToDecodedObject = function( obj ) {
      return _object = obj;
    }

    incrementCounter = function( amount ) {
      return counter += amount + 1;
    }

    getString = function() {
      var str           = bencodedString.slice( counter )
        , strArray      = str.split(':')
        , len           = strArray[0]
      ;

      incrementCounter( len.length + 1 + parseInt(len) );

      str = strArray[1];
      return str.slice(0, len );
    }

    getNumber = function() {
      var str = bencodedString.slice( counter )
        , int = str.slice( 1, str.indexOf('e') )
      ;

      incrementCounter( int.length + 2 );
      return parseInt( int );
    }

    // Public Accessor
    Decode.prototype.getDecodedObject = function() {
      return _object;
    }

    return Decode;
  })();

  String.prototype.decode = function() {
    object = new Decode( this );
    return object.getDecodedObject();
  }

  return root.Bencode.Decode = Decode;
}).call(this);
<|endoftext|>


<commit_before>if (typeof S3BL_IGNORE_PATH == 'undefined' || S3BL_IGNORE_PATH!=true) {
  var S3BL_IGNORE_PATH = false;
}
jQuery(function($) {
  if (typeof BUCKET_URL != 'undefined') {
    var s3_rest_url = BUCKET_URL;
  } else {
    var s3_rest_url = location.protocol + '//' + location.hostname;
  }

  s3_rest_url += '?delimiter=/';

  // handle pathes / prefixes - 2 options
  //
  // 1. Using the pathname
  // {bucket}/{path} => prefix = {path}
  //
  // 2. Using ?prefix={prefix}
  //
  // Why both? Because we want classic directory style listing in normal
  // buckets but also allow deploying to non-buckets
  //
  // Can explicitly disable using path (useful if *not* deploying to an s3
  // bucket) by setting
  //
  // S3BL_IGNORE_PATH = true
  var rx = /.*[?&]prefix=([^&]+)(&.*)?$/;
  var prefix = '';
  if (S3BL_IGNORE_PATH==false) {
    var prefix = location.pathname.replace(/^\//, '');
  }
  var match = location.search.match(rx);
  if (match) {
    prefix = match[1];
  }
  if (prefix) {
    // make sure we end in /
    var prefix = prefix.replace(/\/$/, '') + '/';
    s3_rest_url += '&prefix=' + prefix;
  }

  // set loading notice
  $('#listing').html('
Loading
');
  $.get(s3_rest_url)
    .done(function(data) {
      // clear loading notice
      $('#listing').html('');
      var xml = $(data);
      var files = $.map(xml.find('Contents'), function(item) {
        item = $(item);
        return {
          Key: item.find('Key').text(),
          LastModified: item.find('LastModified').text(),
          Size: item.find('Size').text(),
          Type: 'file'
        }
      });
      var directories = $.map(xml.find('CommonPrefixes'), function(item) {
        item = $(item);
        return {
          Key: item.find('Prefix').text(),
          LastModified: '',
          Size: '0',
          Type: 'directory'
        }
      });
      var outprefix = $(xml.find('Prefix')[0]).text();
      renderTable(files.concat(directories), outprefix);
    })
    .fail(function(error) {
      alert('There was an error');
      console.log(error);
    });
});

function renderTable(files, prefix) {
  var cols = [ 45, 30, 15 ];
  var content = [];
  content.push(padRight('Last Modified', cols[1]) + '  ' + padRight('Size', cols[2]) + 'Key \n');
  content.push(new Array(cols[0] + cols[1] + cols[2] + 4).join('-') + '\n');

  // add the ../ at the start of the directory listing
  // and remove first item (which will be that directory)
  if (prefix) {
    files.shift();
    var item = {
      // one directory up
      Key: prefix.replace(/\/$/, '').split('/').slice(0, -1).concat('').join('/'),
      LastModified: '',
      Size: '',
      keyText: '../',
      href: '../'
    };
    var row = renderRow(item, cols);
    content.push(row + '\n');
  }

  $.each(files, function(idx, item) {
    // strip off the prefix
    item.keyText = item.Key.substring(prefix.length);
    if (item.Type === 'directory') {
      if (S3BL_IGNORE_PATH) {
        item.href = location.protocol + '//' + location.hostname + location.pathname + '?prefix=' + item.Key;
      } else {
        item.href = item.keyText;
      }
    } else {
      // TODO: need to fix this up for cases where we are on site not bucket
      // in that case href for a file should point to s3 bucket
      item.href = '/' + item.Key;
    }
    var row = renderRow(item, cols);
    content.push(row + '\n');
  });

  document.getElementById('listing').innerHTML = '
' + content.join('') + '
';
}

function renderRow(item, cols) {
  var row = '';
  row += padRight(item.LastModified, cols[1]) + '  ';
  row += padRight(item.Size, cols[2]);
  row += '' + item.keyText + '';
  return row;
}

function padRight(padString, length) {
  var str = padString.slice(0, length-3);
  if (padString.length > str.length) {
    str += '...';
  }
  while (str.length < length) {
    str = str + ' ';
  }
  return str;
}

<commit_msg>Fixes going up a directory when S3BL_IGNORE_PATH is true<commit_after>if (typeof S3BL_IGNORE_PATH == 'undefined' || S3BL_IGNORE_PATH!=true) {
  var S3BL_IGNORE_PATH = false;
}
jQuery(function($) {
  if (typeof BUCKET_URL != 'undefined') {
    var s3_rest_url = BUCKET_URL;
  } else {
    var s3_rest_url = location.protocol + '//' + location.hostname;
  }

  s3_rest_url += '?delimiter=/';

  // handle pathes / prefixes - 2 options
  //
  // 1. Using the pathname
  // {bucket}/{path} => prefix = {path}
  //
  // 2. Using ?prefix={prefix}
  //
  // Why both? Because we want classic directory style listing in normal
  // buckets but also allow deploying to non-buckets
  //
  // Can explicitly disable using path (useful if *not* deploying to an s3
  // bucket) by setting
  //
  // S3BL_IGNORE_PATH = true
  var rx = /.*[?&]prefix=([^&]+)(&.*)?$/;
  var prefix = '';
  if (S3BL_IGNORE_PATH==false) {
    var prefix = location.pathname.replace(/^\//, '');
  }
  var match = location.search.match(rx);
  if (match) {
    prefix = match[1];
  }
  if (prefix) {
    // make sure we end in /
    var prefix = prefix.replace(/\/$/, '') + '/';
    s3_rest_url += '&prefix=' + prefix;
  }

  // set loading notice
  $('#listing').html('
Loading
');
  $.get(s3_rest_url)
    .done(function(data) {
      // clear loading notice
      $('#listing').html('');
      var xml = $(data);
      var files = $.map(xml.find('Contents'), function(item) {
        item = $(item);
        return {
          Key: item.find('Key').text(),
          LastModified: item.find('LastModified').text(),
          Size: item.find('Size').text(),
          Type: 'file'
        }
      });
      var directories = $.map(xml.find('CommonPrefixes'), function(item) {
        item = $(item);
        return {
          Key: item.find('Prefix').text(),
          LastModified: '',
          Size: '0',
          Type: 'directory'
        }
      });
      var outprefix = $(xml.find('Prefix')[0]).text();
      renderTable(files.concat(directories), outprefix);
    })
    .fail(function(error) {
      alert('There was an error');
      console.log(error);
    });
});

function renderTable(files, prefix) {
  var cols = [ 45, 30, 15 ];
  var content = [];
  content.push(padRight('Last Modified', cols[1]) + '  ' + padRight('Size', cols[2]) + 'Key \n');
  content.push(new Array(cols[0] + cols[1] + cols[2] + 4).join('-') + '\n');

  // add the ../ at the start of the directory listing
  // and remove first item (which will be that directory)
  if (prefix) {
    files.shift();

    var up = prefix.replace(/\/$/, '').split('/').slice(0, -1).concat('').join('/'), // one directory up
        item = {
          Key: up,
          LastModified: '',
          Size: '',
          keyText: '../',
          href: S3BL_IGNORE_PATH ? up : '../'
        },
        row = renderRow(item, cols);
    content.push(row + '\n');
  }

  $.each(files, function(idx, item) {
    // strip off the prefix
    item.keyText = item.Key.substring(prefix.length);
    if (item.Type === 'directory') {
      if (S3BL_IGNORE_PATH) {
        item.href = location.protocol + '//' + location.hostname + location.pathname + '?prefix=' + item.Key;
      } else {
        item.href = item.keyText;
      }
    } else {
      // TODO: need to fix this up for cases where we are on site not bucket
      // in that case href for a file should point to s3 bucket
      item.href = '/' + item.Key;
    }
    var row = renderRow(item, cols);
    content.push(row + '\n');
  });

  document.getElementById('listing').innerHTML = '
' + content.join('') + '
';
}

function renderRow(item, cols) {
  var row = '';
  row += padRight(item.LastModified, cols[1]) + '  ';
  row += padRight(item.Size, cols[2]);
  row += '' + item.keyText + '';
  return row;
}

function padRight(padString, length) {
  var str = padString.slice(0, length-3);
  if (padString.length > str.length) {
    str += '...';
  }
  while (str.length < length) {
    str = str + ' ';
  }
  return str;
}

<|endoftext|>