<commit_before>"use strict";

var
    _ = require('underscore'),
    async = require('async'),
    crypto = require('crypto'),
    fs = require('fs'),
    path = require('path'),
    moment = require('moment'),
    async = require('async'),
    utils = require('./utils'),
    Asset,

    eventHandler = function(variant, eventName, file) {
        var errorMessage = "Error during the " + eventName,
            emitArgs = [eventName, variant];

        file && (errorMessage += " for file " + file);
        return function(cb) {
            var count = 0,
                listenerCount = (variant.asset.listeners(eventName) || []).length,
                listenerCb = function(err) {
                    if (err) {
                        console.error(errorMessage);
                        console.error(err);
                        return cb(err);
                    }

                    if (++count >= listenerCount) cb();
                };

            file && emitArgs.push(file);
            emitArgs.push(listenerCb);

            if (listenerCount) {
                variant.asset.emit.apply(variant.asset, emitArgs);
            } else {
                cb();
            }
        };
    },

// should be able to create an asset variant
//   1. knowing the full path, and parsing the settings from the URL
//   2. knowing the settings, building the path and asset
AssetVariant = function(settings, asset) {
    settings = settings || {};

    utils.setupGetters(this);
    Asset = Asset || asset.constructor.super_;

    this.asset = asset;
    this.files = [].concat(asset.files);
    this.params = _.omit(settings.params || {}, 'cacheId');
    this.isBinary = null;
    this.expires = moment();

    _.bindAll(this);

    eventHandler(this, 'variantInit')(this.ensureURI);
};

AssetVariant.buildURI = function(pattern, params) {
    var uri = pattern.toString();

    _.each(params, function(v, k) {
        uri = uri.replace(':' + k, v);
    });

    return uri;
};

_.extend(AssetVariant.prototype, {
    _getCacheId: function() {
        this._cacheId = this._cacheId || this.hashFiles();

        return this._cacheId;
    },
    _getParamsWithCacheId: function() {
        if (!this._paramsWithCacheId) {
            this._paramsWithCacheId = _.extend({
                cacheId: this.cacheId.substr(0, Asset.CACHE_ID_LENGTH)
            }, this.params);
        }

        return this._paramsWithCacheId;
    },
    _getCacheHeaders: function() {
        var now = moment(),
            d = moment();

        if (this.cachedFilePath && now < this.expires) {
            var mtime = fs.statSync(this.cachedFilePath).mtime;
            d = moment(mtime);
        }

        if (!this._cacheHeaders || (now >= this.expires)) {
            var headers = {},
                cacheControl = [],
                maxAge = this.asset.maxAge,
                httpFormat = 'ddd, DD MMM YYYY HH:mm:ss \\G\\M\\T';

            //use utc for http headers
            d.utc();

            //Last-Modified
            headers['Last-Modified'] = d.format(httpFormat);

            //Expires
            this.expires = d.add('seconds', maxAge);
            headers['Expires'] = d.format(httpFormat);

            //Cache-Control
            cacheControl.push('max-age=' + maxAge);
            cacheControl.push('public');
            headers['Cache-Control'] = cacheControl.join(', ');

            //ETag
            headers['Etag'] = this.cacheId;

            //X-Powered-By
            headers['X-Powered-By'] = 'Express/exstatic';

            this._cacheHeaders = headers;

            //ensure the file is re-rendered when rebuilding the
            //cache headers by nulling the cachedFilePath
            this.cachedFilePath = null;
        }

        return this._cacheHeaders;
    },
    ensureURI: function() {
        this.uri = this.uri || AssetVariant.buildURI(this.asset.pattern, this.paramsWithCacheId);
    },
    hashFiles: function() {
        var md5 = crypto.createHash('md5');

        _.each(this.files, function(file) {
            var stat = fs.statSync(file);
            stat.isFile() && md5.update(fs.readFileSync(file));
        });

        return md5.digest('hex');
    },
    getRenderCache: function(file) {
        return this._renderCache && this._renderCache[file];
    },
    setRenderCache: function(file, data) {
        this._renderCache = this._renderCache || {};
        this._renderCache[file] = data;
    },
    concatRenderCache: function() {
        // TODO: add different handler (or error) for binary files
        var values = _.values(this._renderCache);<commit_msg>Make checking of expiration a method<commit_after>"use strict";

var
    _ = require('underscore'),
    async = require('async'),
    crypto = require('crypto'),
    fs = require('fs'),
    path = require('path'),
    moment = require('moment'),
    async = require('async'),
    utils = require('./utils'),
    Asset,

    eventHandler = function(variant, eventName, file) {
        var errorMessage = "Error during the " + eventName,
            emitArgs = [eventName, variant];

        file && (errorMessage += " for file " + file);
        return function(cb) {
            var count = 0,
                listenerCount = (variant.asset.listeners(eventName) || []).length,
                listenerCb = function(err) {
                    if (err) {
                        console.error(errorMessage);
                        console.error(err);
                        return cb(err);
                    }

                    if (++count >= listenerCount) cb();
                };

            file && emitArgs.push(file);
            emitArgs.push(listenerCb);

            if (listenerCount) {
                variant.asset.emit.apply(variant.asset, emitArgs);
            } else {
                cb();
            }
        };
    },

// should be able to create an asset variant
//   1. knowing the full path, and parsing the settings from the URL
//   2. knowing the settings, building the path and asset
AssetVariant = function(settings, asset) {
    settings = settings || {};

    utils.setupGetters(this);
    Asset = Asset || asset.constructor.super_;

    this.asset = asset;
    this.files = [].concat(asset.files);
    this.params = _.omit(settings.params || {}, 'cacheId');
    this.isBinary = null;
    this.expires = moment();

    _.bindAll(this);

    eventHandler(this, 'variantInit')(this.ensureURI);
};

AssetVariant.buildURI = function(pattern, params) {
    var uri = pattern.toString();

    _.each(params, function(v, k) {
        uri = uri.replace(':' + k, v);
    });

    return uri;
};

_.extend(AssetVariant.prototype, {
    _getCacheId: function() {
        this._cacheId = this._cacheId || this.hashFiles();

        return this._cacheId;
    },
    _getParamsWithCacheId: function() {
        if (!this._paramsWithCacheId) {
            this._paramsWithCacheId = _.extend({
                cacheId: this.cacheId.substr(0, Asset.CACHE_ID_LENGTH)
            }, this.params);
        }

        return this._paramsWithCacheId;
    },
    _getCacheHeaders: function() {
        var now = moment(),
            d = moment();

        if ( this.cachedFilePath && !this.isExpired() ) {
            var mtime = fs.statSync(this.cachedFilePath).mtime;
            d = moment(mtime);
        }

        if ( !this._cacheHeaders || this.isExpired() ) {
            var headers = {},
                cacheControl = [],
                maxAge = this.asset.maxAge,
                httpFormat = 'ddd, DD MMM YYYY HH:mm:ss \\G\\M\\T';

            //use utc for http headers
            d.utc();

            //Last-Modified
            headers['Last-Modified'] = d.format(httpFormat);

            //Expires
            this.expires = d.add('seconds', maxAge);
            headers['Expires'] = d.format(httpFormat);

            //Cache-Control
            cacheControl.push('max-age=' + maxAge);
            cacheControl.push('public');
            headers['Cache-Control'] = cacheControl.join(', ');

            //ETag
            headers['Etag'] = this.cacheId;

            //X-Powered-By
            headers['X-Powered-By'] = 'Express/exstatic';

            this._cacheHeaders = headers;

            //ensure the file is re-rendered when rebuilding the
            //cache headers by nulling the cachedFilePath
            this.cachedFilePath = null;
        }

        return this._cacheHeaders;
    },
    ensureURI: function() {
        this.uri = this.uri || AssetVariant.buildURI(this.asset.pattern, this.paramsWithCacheId);
    },
    isExpired: function() {
        var now = moment();

        return now >= this.expires;
    },
    hashFiles: function() {
        var md5 = crypto.createHash('md5');

        _.each(this.files, function(file) {
            var stat = fs.statSync(file);
            stat.isFile() && md5.update(fs.readFileSync(file));
        });

        return md5.digest('hex');
    },
    getRenderCache: function(file) {
        return this._renderCache && this._renderCache[file];
    },
    setRenderCache: function(file, data) {
        this._renderCache = this._renderCache || {};
        this._renderCache[file] = data;
    },
    concatRenderCache: function() {
        // TODO: add different handler (or error) for binary files
        var values = _.values(this._renderCache);
{'fraction_non_alphanumeric': 0.13394475845153034, 'fraction_numerical': 0.0017063026554335074, 'mean_word_length': 1.8556638246041413, 'pattern_counts': {'":': 0, '<': 4, '<?xml version=': 0, '>': 7, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5858195', 'n_tokens_mistral': 2664, 'n_tokens_neox': 2541, 'n_words': 566}