Resumable downloads


#1

Hi all,

I’m trying to send large files (160MB - 1.5GB) using send_file(), it’s
working perfectly except that I can’t figure out how to make the
downloads resumable.
If a download fails, I have to re-download it from scratch.
As I’m dealing with large files I can’t set :stream => false

Here is how I call send_file from my controller :

send_file(@path, :type => (MIMETYPES[File.extname(@path)] ||
‘application/octet-stream’)) if File.file?(@path)

Any ideas ?
Should I use send_data() instead ?
Is there any Rails support for my problem or do I have to implement this
myself ?

Thanks

Mike


Michael BAUDINO
EPITA 2007
KameHouse Prod.


#2

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

On Nov 21, 2005, at 5:06 AM, Michael Baudino wrote:

Any ideas ?
Should I use send_data() instead ?
Is there any Rails support for my problem or do I have to implement
this myself ?

send_file and send_data do not handle requests with a Range header.
If you choose to add support, please contribute a patch + tests :slight_smile:

In the first response, use a 200 status and tell the browser that you
support Range requests:
headers[‘Status’] = ‘200 OK’ # unnecessary since it’s the default
headers[‘Accept-Ranges’] = ‘bytes’

In subsequent requests, the browser may send a Range header like:
Range: bytes=1234-
to indicate that it wants from byte 1234 to the end of the file.

In response:
headers[‘Status’] = ‘206 Partial Content’
headers[‘Accept-Ranges’] = ‘bytes’
headers[‘Content-Range’] = “bytes #{begin_range}-#{end_range}/#
{file.content_length}”
headers[‘Content-Length’] = (end_range - begin_range).to_s

You’ll also need to handle conditional GET (headers such as If-
Modified-Since, If-Range, etc.), send status 416 for unsatisfiable
ranges, and deal with ‘*’ ranges. You must also send ETag headers
for some browsers (Internet Explorer) to even try a Range request.

The easiest path is to serve the file directly from Apache or
lighttpd. If you’re using send_file in order to authorize downloads,
investigate modules such as lighttpd’s mod_secdownload.

Good luck!

jeremy
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.2 (Darwin)

iD8DBQFDgdA7AQHALep9HFYRAuBzAKCX8K0Rb/S2x6bOPprJCuxdNeE0SwCg2Tsq
DVofu4FfIxNGX24JuaEJSKE=
=Jr3e
-----END PGP SIGNATURE-----


#3

gornack wrote:

Any ideas ?
Should I use send_data() instead ?
Is there any Rails support for my problem or do I have to implement this
myself ?

Thanks

Mike


Michael BAUDINO
EPITA 2007
KameHouse Prod.

use sftp…http was not made to transfer big files…

Mikkel


#4

Jeremy K. wrote:

to indicate that it wants from byte 1234 to the end of the file.
ranges, and deal with ‘*’ ranges. You must also send ETag headers for
Version: GnuPG v1.4.2 (Darwin)

iD8DBQFDgdA7AQHALep9HFYRAuBzAKCX8K0Rb/S2x6bOPprJCuxdNeE0SwCg2Tsq
DVofu4FfIxNGX24JuaEJSKE=
=Jr3e
-----END PGP SIGNATURE-----

Thanks for your fast answer.
Finally, I chose to serve the file directly from the webserver, as you
suggested. that’s a lot easier, you were right :wink:

Thanks to all of you who answered.

Mike


Michael BAUDINO
EPITA 2007
KameHouse Prod


#5

Jeremy K. wrote:

to indicate that it wants from byte 1234 to the end of the file.
ranges, and deal with ‘*’ ranges. You must also send ETag headers for
some browsers (Internet Explorer) to even try a Range request.

The easiest path is to serve the file directly from Apache or
lighttpd. If you’re using send_file in order to authorize downloads,
investigate modules such as lighttpd’s mod_secdownload.

With PHP I use PEAR::HTTP_Download -
http://pear.php.net/package/HTTP_Download

It works really well and if somebody is planning to implement something
like this in Rails/Ruby then the PHP sourcecode might be a good place to
start.

HTH,

Jeroen


#6

Hi:

I would like to have one drop down list control another so that if
list one were companies and the second were employees, then once a
selection was made in the companies drop down list, only employees of
that company would show up in the second list. Pretty standard stuff
and a good use of Ajax (I assume).

I’m not asking for an answer (unless you’d truly like to give me an
answer) but where is the best place I can look for the clearest
explanation of how that can be done?

Thanks in advance,

bruce


#7

Hi:

I am re-posting this. Not only would the answer be useful to me, but
surely to a lot of other people also. As I say, I don’t need an
answer but I would like to know where to look for an answer. Trawling
has not yet brought me satisfaction.

thanks in advance,

bruce


#8

It is 2am so this example may not be 100% correct as I am doing it from
memory. :stuck_out_tongue:

The view will start off by populating the company select box with all
available companies. By default the employee select box will be disabled
until a company is selected. Once selected, the change to the company
select box will trigger a call to the lookup_employees method of the
controller to get the employees that are associated with the company_id
selected. On success, it will rewrite what is in the
‘employee_container’ DOM span object with the employee populated select
box.

There are some fancy things you can do like adding spinners to show
progress when the employee select box is being populated that I have not
included here. Hope this gets you off to the right start.

View

<%= select ‘client’, ‘company_id’, @companies, { :prompt => true } %>

<%= select_tag 'company', nil, { :disabled => 'disabled' } %>

<%= observe_field(
:client_company_id,
:update => :employee_container,
:url => { :controller => ‘client’, :action => :lookup_employees },
:with => “‘company_id=’+escape(value)”) %>

Controller

def new
@companies = Company.find(:all).collect {|co| [co.name, co.id]}
end

def lookup_employees
@employees =
Employee.find_all_by_company_id(@params[:company_id]).collect {|emp|
[emp.name, emp.id]}
render :inline => “<%= select ‘company’, ‘employee_id’, @employees, {
:prompt => true } %>”
end


#9

Bruce B. wrote:

Hi:

I am re-posting this. Not only would the answer be useful to me, but
surely to a lot of other people also. As I say, I don’t need an
answer but I would like to know where to look for an answer. Trawling
has not yet brought me satisfaction.

thanks in advance,

Ok from my understanding of your use-case, I’ve done something exactly
the same as this using DWR on a recent J2EE project, now translating
that into ruby…

You’d need to add the correct event handlers to each of your drop down
lists, so for example if you have companies / employees, you’d need some
kind of listener for companies that sent a link_to_remote onChange to
your controller, then the controller (say companies_controller) would
have a method similar to …

assume that each item in the companies list has a unique id that

matches said id in database
def update_employees_list
employees_for_x = Employee.find(‘where company = ?’, params[:id])
render :partial => ‘employees_list’, :object => @employees_for_x
#assuming a partial which contains the new drop down list
end

This method should replace the original employee_list with one
containing the correct data

I don’t know of any ‘here’s the code for exactly what you want to do’
web-sites out there, but this should get you going in the right
direction. http://script.aculo.us is always useful for seeing some AJAX
work (in rails). For the general strategy, you can look at DWR (google
for it) and they actually have an example of exactly what you want: in
Java.

Kev