'use strict'; var u = require('underscore'); var events = require('events'); var fetch = require('fetch'); var fs = require('fs-extra'); var factory = require('./factory'); var config = require('./asset-assembler-config'); function buildEnd(assembler, targetFile, resultUriList, callback) { var footer = ''; if (assembler.config.assetFooter !== null) { footer = assembler.config.assetFooter; } fs.appendFile(targetFile, footer, function (err) { if (err) { callback(err); } else { var buildTime = new Date().getTime() - assembler.startTime, buildResultSize = resultUriList.length, targetStats = fs.statSync(targetFile), targetSize = targetStats.size; if (assembler.config.linkTargetSingleResult) { resultUriList = [assembler.config.linkTarget]; } assembler.emit('file-write-post',targetFile); assembler.emit('log','debug','target file: '+targetFile); assembler.emit('log','debug','target size: '+targetSize); assembler.emit('log','info','build result size: '+resultUriList.length+' from: '+buildResultSize); assembler.emit('log','info','build done in: '+buildTime+' ms.'); assembler.emit('result',resultUriList); assembler.emit('end'); callback(); } }); } function downloadFile(assembler, remoteUrl, localFile, callback) { assembler.emit('file-download-pre',remoteUrl); assembler.emit('log','debug','downloadFile: '+remoteUrl); fs.ensureFile(localFile, function(err) { if (err) { callback(err); } else { var stream = new fetch.FetchStream(remoteUrl); var streamCallbackErrorOnEnd = null; var streamCallback = function(err) { if (err) { streamCallbackErrorOnEnd = err; return; } else { callback(streamCallbackErrorOnEnd); } }; stream.on('error', function(err) { callback(err); // check !! //streamCallback(err); }); stream.on('meta', function(meta) { if (meta.status !== 200) { streamCallback(new Error('Got status: '+meta.status)); } }); stream.on('end', function() { assembler.emit('file-download-post',remoteUrl); streamCallback(); }); stream.pipe(fs.createWriteStream(localFile)); } }); } function downloadFileList(assembler, downloadList, callback) { if (downloadList.length === 0) { callback(); return; } var download = downloadList.pop(); downloadFile(assembler, download.remoteUrl, download.localFile, function(err) { if (err) { callback(err); } else { downloadFileList(assembler, downloadList, callback); } }); } function aggregateFileList(assembler, targetFile, aggregateList, readFile, callback) { if (aggregateList.length === 0) { callback(); return; } var aggregateFile = aggregateList.pop(); assembler.emit('file-read-pre',aggregateFile); readFile(aggregateFile, function(err, data) { if (err) { callback(err); } else { assembler.emit('file-read-post',aggregateFile); assembler.emit('log','debug','readFile: '+aggregateFile+' size: '+data.length); if (assembler.config.assetSeperator !== null && assembler.config.assetSeperator.length > 0) { var seperatorTemplate = u.template(assembler.config.assetSeperator); data = seperatorTemplate({file: aggregateFile}) + data; } fs.appendFile(targetFile, data + '\n', function (err) { if (err) { callback(err); } else { aggregateFileList(assembler, targetFile, aggregateList, readFile, callback); } }); } }); } function mapLocalFileSync(assembler, linkSource) { var uriMapping = assembler.config.linkMapping; if (uriMapping) { var uriMappingKeys = Object.keys(uriMapping); uriMappingKeys.sort(function(a, b) { return a.length < b.length; // longest first as we break on first hit }); for (var ii = 0; ii < uriMappingKeys.length; ii++) { var uriKey = uriMappingKeys[ii]; var localPath = uriMapping[uriKey]; var mapIndex = linkSource.indexOf(uriKey); if (mapIndex === 0) { return localPath+linkSource.substring(uriKey.length); } } } return linkSource; } function buildAsset(assembler, targetFile, readFile, callback) { var uriList = assembler.config.linkSources; var readFileList = []; var resultUriList = []; var downloadList = []; for (var i = 0; i < uriList.length; i++) { var assetItem = uriList[i]; var remoteUrl = null; var remoteForce = null; var remoteIndex = assetItem.indexOf('@'); if (remoteIndex > 0) { remoteUrl = assetItem.substring(remoteIndex + 1); if (remoteUrl.indexOf('@') === 0) { remoteUrl = assetItem.substring(remoteIndex + 2); remoteForce = true; } assetItem = assetItem.substring(0,remoteIndex); } var localFile = mapLocalFileSync(assembler, assetItem); // override force on all files if (remoteForce===undefined && assembler.config.downloadForce !== undefined) { remoteForce = assembler.config.downloadForce; } if (remoteUrl && (remoteForce || !fs.existsSync(localFile))) { downloadList.push({ remoteUrl: remoteUrl, localFile: localFile }); } readFileList.push(localFile); resultUriList.push(assetItem); } downloadList.reverse(); readFileList.reverse(); assembler.emit('log','debug','downloadList size: '+downloadList.length); assembler.emit('log','debug','readFileList size: '+readFileList.length); downloadFileList(assembler, downloadList, function(err) { if (err) { callback(err); } else { //for(.... //if (!fs.existsSync(localFile)) { // Log.warn("illegal entry: "+localFile); // continue; //} aggregateFileList(assembler, targetFile, readFileList, readFile, function(err) { if (err) { callback(err); } else { buildEnd(assembler, targetFile, resultUriList, callback); } }); } }); } function buildStart(assembler, readFile, callback) { assembler.startTime = new Date().getTime(); assembler.emit('begin'); assembler.emit('log','info','build begin for: '+assembler.config.linkTarget); if (assembler.config.downloadForce) { assembler.emit('log','info','build using forced downloads.'); } var targetFile = mapLocalFileSync(assembler, assembler.config.linkTarget); fs.ensureFile(targetFile, function (err) { if (err) { callback(err); } else { assembler.emit('file-write-pre',targetFile); var header = ''; if (assembler.config.assetHeader !== null) { header = assembler.config.assetHeader; } fs.writeFile(targetFile, header, function(err) { if (err) { callback(err); } else { buildAsset(assembler, targetFile, readFile, callback); } }); } }); } function runAssembler(assembler, readFile, callback) { if (assembler.config.downloadStartDelay) { setTimeout(function() { buildStart(assembler, readFile, callback); }, assembler.config.downloadStartDelay); } else { buildStart(assembler, readFile, callback); } } function AssetAssembler(config, readFile) { if (config === undefined) { throw new Error('no config'); } this.config = config; this.startTime = 0; this.readFile = readFile || factory.assembler.constructor.readFile(); events.EventEmitter.call(this); } AssetAssembler.prototype.__proto__ = events.EventEmitter.prototype; AssetAssembler.prototype.run = function(callback) { if (callback === undefined) { throw new Error('no callback'); } var self = this; config.checkConfig(self.config,function(err) { if (err) { callback(err); } else { config.fillDefaults(self.config, function (err) { if (err) { callback(err); } else { runAssembler(self, self.readFile, callback); } }); } }); }; module.exports = AssetAssembler;