Skip to content

Instantly share code, notes, and snippets.

@unixpickle
Created May 31, 2014 23:20
Show Gist options
  • Save unixpickle/b104a4f9f5734f365ab5 to your computer and use it in GitHub Desktop.
Save unixpickle/b104a4f9f5734f365ab5 to your computer and use it in GitHub Desktop.
Download Streaming Rough Code
Page = require './page'
Session = require '../session'
Transfer = require '../transfer'
tar = require 'tar-stream'
url = require 'url'
getTarName = (path) ->
result = path.match /\/([^\/]*)$/
str = if result? then result[1] else path
if str[str.length - 1] is '/'
str = str[0...str.length - 1]
return str + '.tar'
class Download extends Page
constructor: -> super()
path: -> '/download'
get: (req, res) ->
if not req.session.userInfo?
return res.redirect 'login'
if typeof req.query.hash isnt 'string'
return @error res, 'invalid hash parameter'
session = new Session req.session.userInfo
session.lookupDownload req.query.hash, (err, info) =>
if err?
session.end()
return @error res, err.toString()
session.getFTP (err, ftp) =>
throw err if err?
@doTransfer res, session, ftp, info
doTransfer: (res, session, ftp, info) ->
tarClient = tar.pack()
transfer = new Transfer info.path, ftp, tarClient, info.path
transfer.on 'error', =>
session.end()
tarClient.finalize()
transfer.on 'end', =>
session.end()
tarClient.finalize()
fileName = getTarName info.path
res.setHeader 'content-type', 'application/octet-stream'
res.setHeader 'content-disposition', 'attachment; filename=' + fileName
tarClient.pipe res
transfer.begin()
module.exports = Download
{EventEmitter} = require 'events'
class Transfer extends EventEmitter
constructor: (@root, @ftp, @tar, @path) ->
@dirs = []
@files = []
begin: ->
@ftp.list @path, (args...) => @_gotListing args...
_gotListing: (err, list) ->
return @emit 'error', err if err?
for item in list
if item.type is 'd'
@dirs.push item
else if item.type is '-'
@files.push item
@_getNext()
_fullPath: (item) ->
if item.name[0] is '/'
return item.name
if @path[@path.length - 1] == '/'
return @path + item.name
else
return @path + '/' + item.name
_getRelative: (path) ->
res = path[@root.length..]
if res[0] == '/'
return res[1..]
return res
_getNext: ->
if @files.length
console.log 'getNext -> files'
file = @files.pop()
@_getFile file
else if @dirs.length
console.log 'getNext -> dirs'
dir = @dirs.pop()
@_getDir dir
else
console.log 'getNext -> end'
@emit 'end'
_getFile: (file) ->
path = @_fullPath file
@ftp.get path, (err, stream) =>
return @emit error if err?
entryInfo =
name: @_getRelative path
size: parseInt file.size
entry = @tar.entry entryInfo, (err) =>
console.log 'result', err
return @emit err if err?
@_getNext()
@tar.on 'end', -> console.log 'he'
stream.pipe entry
_getDir: (item) ->
path = @_fullPath item
sender = new Transfer @root, @ftp, @tar, path
sender.on 'error', (err) => @emit err
sender.on 'end', => @_getNext()
sender.begin()
module.exports = Transfer
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment