| /** |
| * node-archiver |
| * |
| * Copyright (c) 2012-2014 Chris Talkington, contributors. |
| * Licensed under the MIT license. |
| * https://github.com/ctalkington/node-archiver/blob/master/LICENSE-MIT |
| */ |
| var fs = require('fs'); |
| var inherits = require('util').inherits; |
| var Transform = require('readable-stream').Transform; |
| |
| var async = require('async'); |
| |
| var util = require('../../util'); |
| |
| var Archiver = module.exports = function(options) { |
| options = this.options = util.defaults(options, { |
| highWaterMark: 1024 * 1024 |
| }); |
| |
| Transform.call(this, options); |
| |
| this._entries = []; |
| this._module = false; |
| this._pointer = 0; |
| |
| this._queue = async.queue(this._onQueueTask.bind(this), 1); |
| this._queue.drain = this._onQueueDrain.bind(this); |
| |
| this._state = { |
| finalize: false, |
| finalized: false, |
| modulePiped: false |
| }; |
| }; |
| |
| inherits(Archiver, Transform); |
| |
| Archiver.prototype._append = function(filepath, data) { |
| data = data || {}; |
| |
| if (!data.name) { |
| data.name = filepath; |
| } |
| |
| data.sourcePath = filepath; |
| |
| this._queue.push({ |
| data: data, |
| source: null, |
| deferredStat: true, |
| filepath: filepath |
| }); |
| }; |
| |
| Archiver.prototype._moduleAppend = function(source, data, callback) { |
| this._module.append(source, data, callback); |
| }; |
| |
| Archiver.prototype._moduleFinalize = function() { |
| this._state.finalized = true; |
| |
| if (typeof this._module.finalize === 'function') { |
| this._module.finalize(); |
| } else if (typeof this._module.end === 'function') { |
| this._module.end(); |
| } else { |
| this.emit('error', new Error('format module missing finalize and end method')); |
| } |
| }; |
| |
| Archiver.prototype._moduleSupports = function(key) { |
| this._module.supports = util.defaults(this._module.supports, { |
| directory: false |
| }); |
| |
| return this._module.supports[key]; |
| }; |
| |
| Archiver.prototype._normalizeEntryData = function(data, stats) { |
| stats = stats || false; |
| data = util.defaults(data, { |
| type: 'file', |
| name: null, |
| date: null, |
| mode: null, |
| sourcePath: null |
| }); |
| |
| var isDir = data.type === 'directory'; |
| |
| if (data.name) { |
| data.name = util.sanitizePath(data.name); |
| |
| if (data.name.slice(-1) === '/') { |
| isDir = true; |
| data.type = 'directory'; |
| } else if (isDir) { |
| data.name += '/'; |
| } |
| } |
| |
| if (typeof data.mode === 'number') { |
| data.mode &= 0777; |
| } else if (stats) { |
| data.mode = stats.mode & 0777; |
| } else { |
| data.mode = isDir ? 0755 : 0644; |
| } |
| |
| if (stats && data.date === null) { |
| data.date = stats.mtime; |
| } |
| |
| data.date = util.dateify(data.date); |
| |
| data._stats = stats; |
| |
| return data; |
| }; |
| |
| Archiver.prototype._onModuleError = function(err) { |
| this.emit('error', err); |
| }; |
| |
| Archiver.prototype._onQueueDrain = function() { |
| if (this._state.finalize && !this._state.finalized && this._queue.idle()) { |
| this._moduleFinalize(); |
| } |
| }; |
| |
| Archiver.prototype._onQueueTask = function(task, callback) { |
| var afterAppend = function(err, entry) { |
| if (err) { |
| this.emit('error', err); |
| callback(); |
| return; |
| } |
| |
| entry = entry || task.data; |
| |
| this.emit('entry', entry); |
| this._entries.push(entry); |
| |
| callback(); |
| }.bind(this); |
| |
| var afterStat = function(err, stats) { |
| if (err) { |
| this.emit('error', err); |
| callback(); |
| return; |
| } |
| |
| task = this._updateQueueTaskWithStats(task, stats); |
| |
| if (task.source !== null) { |
| this._moduleAppend(task.source, task.data, afterAppend); |
| } else { |
| this.emit('error', new Error('unsupported entry: ' + task.filepath)); |
| callback(); |
| return; |
| } |
| }.bind(this); |
| |
| if (task.deferredStat) { |
| fs.stat(task.filepath, afterStat); |
| } else { |
| this._moduleAppend(task.source, task.data, afterAppend); |
| } |
| }; |
| |
| Archiver.prototype._updateQueueTaskWithStats = function(task, stats) { |
| if (stats.isFile()) { |
| task.data.type = 'file'; |
| task.data.sourceType = 'stream'; |
| task.source = util.lazyReadStream(task.filepath); |
| } else if (stats.isDirectory() && this._moduleSupports('directory')) { |
| task.data.name = util.trailingSlashIt(task.data.name); |
| task.data.type = 'directory'; |
| task.data.sourcePath = util.trailingSlashIt(task.filepath); |
| task.data.sourceType = 'buffer'; |
| task.source = new Buffer(0); |
| } else { |
| return task; |
| } |
| |
| task.data = this._normalizeEntryData(task.data, stats); |
| return task; |
| }; |
| |
| Archiver.prototype._pipeModuleOutput = function() { |
| this._module.on('error', this._onModuleError.bind(this)); |
| this._module.pipe(this); |
| |
| this._state.modulePiped = true; |
| }; |
| |
| Archiver.prototype._processFile = function(source, data, callback) { |
| this.emit('error', new Error('method not implemented')); |
| }; |
| |
| Archiver.prototype._transform = function(chunk, encoding, callback) { |
| if (chunk) { |
| this._pointer += chunk.length; |
| } |
| |
| callback(null, chunk); |
| }; |
| |
| Archiver.prototype.append = function(source, data) { |
| if (this._state.finalize) { |
| this.emit('error', new Error('unable to append after calling finalize.')); |
| return this; |
| } |
| |
| data = this._normalizeEntryData(data); |
| |
| if (typeof data.name !== 'string' || data.name.length === 0) { |
| this.emit('error', new Error('entry name must be a non-empty string value')); |
| return this; |
| } |
| |
| if (data.type === 'directory' && !this._moduleSupports('directory')) { |
| this.emit('error', new Error('entries of "directory" type not currently supported by this module')); |
| return this; |
| } |
| |
| source = util.normalizeInputSource(source); |
| |
| if (Buffer.isBuffer(source)) { |
| data.sourceType = 'buffer'; |
| } else if (util.isStream(source)) { |
| data.sourceType = 'stream'; |
| } else { |
| this.emit('error', new Error('input source must be valid Stream or Buffer instance')); |
| return this; |
| } |
| |
| this._queue.push({ |
| data: data, |
| source: source |
| }); |
| |
| return this; |
| }; |
| |
| Archiver.prototype.bulk = function(mappings) { |
| if (this._state.finalize) { |
| this.emit('error', new Error('unable to append after calling finalize.')); |
| return this; |
| } |
| |
| if (!Array.isArray(mappings)) { |
| mappings = [mappings]; |
| } |
| |
| var self = this; |
| var files = util.file.normalizeFilesArray(mappings); |
| |
| files.forEach(function(file){ |
| var isExpandedPair = file.orig.expand || false; |
| var fileData = file.data || {}; |
| |
| file.src.forEach(function(filepath) { |
| var data = util._.extend({}, fileData); |
| var name = isExpandedPair ? file.dest : util.unixifyPath(file.dest || '', filepath); |
| |
| if (name === '.') { |
| return; |
| } |
| |
| data.name = name; |
| self._append(filepath, data); |
| }); |
| }); |
| |
| return this; |
| }; |
| |
| Archiver.prototype.file = function(filepath, data) { |
| if (this._state.finalize) { |
| this.emit('error', new Error('unable to append after calling finalize.')); |
| return this; |
| } |
| |
| if (typeof filepath !== 'string' || filepath.length === 0) { |
| this.emit('error', new Error('filepath must be a non-empty string value')); |
| return this; |
| } |
| |
| this._append(filepath, data); |
| |
| return this; |
| }; |
| |
| Archiver.prototype.finalize = function() { |
| this._state.finalize = true; |
| |
| if (this._queue.idle()) { |
| this._moduleFinalize(); |
| } |
| |
| return this; |
| }; |
| |
| Archiver.prototype.setModule = function(module) { |
| if (this._state.modulePiped) { |
| this.emit('error', new Error('format module already set')); |
| return; |
| } |
| |
| this._module = module; |
| this._pipeModuleOutput(); |
| }; |
| |
| Archiver.prototype.pointer = function() { |
| return this._pointer; |
| }; |