stream-length.coffee 2.37 KB
Promise = require "bluebird"
fs = Promise.promisifyAll(require "fs")

nodeifyWrapper = (callback, func) ->
	func().nodeify(callback)

createRetrieverPromise = (stream, retriever) ->
	new Promise (resolve, reject) ->
		retriever stream, (result) ->
			if result?
				if result instanceof Error
					reject result
				else
					resolve result
			else
				reject new Error("Could not find a length using this lengthRetriever.")

retrieveBuffer = (stream, callback) ->
	if stream instanceof Buffer
		callback stream.length
	else
		callback null

retrieveFilesystemStream = (stream, callback) ->
	if stream.hasOwnProperty "fd"
		# FIXME: https://github.com/joyent/node/issues/7819
		if stream.end != undefined and stream.end != Infinity and stream.start != undefined
			# A stream start and end were defined, we can calculate the size just from that information.
			callback(stream.end + 1 - (stream.start ? 0))
		else
			# We have the start offset at most, stat the file and work off the filesize.
			Promise.try ->
				fs.statAsync stream.path
			.then (stat) ->
				callback(stat.size - (stream.start ? 0))
			.catch (err) ->
				callback err
	else
		callback null

retrieveCoreHttpStream = (stream, callback) ->
	if stream.hasOwnProperty("httpVersion") and stream.headers["content-length"]?
		callback parseInt(stream.headers["content-length"])
	else
		callback null

retrieveRequestHttpStream = (stream, callback) ->
	if stream.hasOwnProperty "httpModule"
		stream.on "response", (response) ->
			if response.headers["content-length"]?
				callback parseInt(response.headers["content-length"])
			else
				callback null
	else
		callback null

retrieveCombinedStream = (stream, callback) ->
	if stream.getCombinedStreamLength?
		stream.getCombinedStreamLength()
			.then (length) -> callback(length)
			.catch (err) -> callback(err)
	else
		callback null


module.exports = (stream, options = {}, callback) ->
	nodeifyWrapper callback, ->
		retrieverPromises = []

		if options.lengthRetrievers?
			# First, the custom length retrievers, if any.
			for retriever in options.lengthRetrievers
				retrieverPromises.push createRetrieverPromise(stream, retriever)

		# Then, the standard ones.
		for retriever in [retrieveBuffer, retrieveFilesystemStream, retrieveCoreHttpStream, retrieveRequestHttpStream, retrieveCombinedStream]
			retrieverPromises.push createRetrieverPromise(stream, retriever)

		Promise.any retrieverPromises