4

I need to create a way for a user to open a web page, choose from a list of checkboxes in a form, and once the form is submitted, download all of those files together.

Here are the restrictions imposed on me by the client:

  • Platform is primarily mobile devices
  • No zip files (since we cannot assume that the mobile device can handle zips)
  • Files MUST be downloaded, not streamed

So I created a web app using XHTML/CSS w/ jQuery Mobile 1.0a3 on the front-end and Apache w/ Python 2.6 on the back-end. The target files that will be downloaded are .mp3 files.

I have successfully managed to perform the desired effect on the desktop using hidden iframes passed from the server and loaded by jQuery via AJAX... but it doesn't work on the default Android browser or Dolphin browser.

I made sure my Apache config will force download behavior:

<Files *.mp3>
    ForceType application/octet-stream
    Header set Content-Disposition attachment
</Files>

Also, Apache's "headers" module is enabled (needed for the "Header set" config parameter), so that's not an issue.

I make an AJAX call to the server with all of the selected items as parameters, and when the server reads the array of items, it will query the database for information about each item (such as the URL of each mp3 file). Then the iframe code is created on the backend for each mp3 file and then sent back to jQuery's $.load() function to load the new iframes (which downloads the mp3s).

Without pasting too much code, here's a very short test case of what I'm doing:

Server Side

def download(req):
    resultDiv = """<div id="downloads">"""
    queryIds = []
    for element in req.form:
        # "element" contains the id number that matches database record
        trackId = re.match('^track(\d+)', element).group(1)
        queryIds.append(trackId)

    conn = MySQLdb.connect(host='localhost', user='fake', passwd='fake', db='fake')
    cursor = conn.cursor()
    
    buildQuery = """\
        SELECT filePath FROM tracks
        WHERE trackNum in ("""

    buildQuery += ','.join(queryIds)
    buildQuery += ')'

    cursor.execute(buildQuery)
    downloadRows = cursor.fetchall()

    for track in downloadRows:
        resultDiv += """
            <iframe src="%s"></iframe>
        """ % track[0]

    return resultDiv

Client Side

<!DOCTYPE html>
<html>
    <head>
        <!-- INCLUDES FOR JQUERY MOBILE AND JQUERY -->
        <style type="text/css">
            .invisible {
                display: none;
            }
        </style>
        <script type="text/javascript">
            $(document).ready(function() {
                $('#albumForm').submit(function(e) {
                    e.preventDefault();

                    // this will hold the selected items on the form
                    selTracks = {};

                    $('#trackList').find(':checked').each(function() {
                        selTracks[this.id] = 'on';
                    });

                    // load the iframes into a 'div' set aside for that purpose
                    $('#results').load('control.py/download #tracks', selTracks);
                });
            });
        </script>
    </head>
    <body>
        <div data-role='page' id='page'>
            <div data-role='header' id='header'>
            </div>
            <div data-role='content' id='content'>
                <div id='container'>
                    <form id='albumForm'>
                        <div data-role='controlgroup' data-role='fieldcontain'>
                            <input type='checkbox' name='track1' id='track1' />
                            <label for='track1' id='track1label'>Track 1</label>
                            <input type='checkbox' name='track2' id='track2' />
                            <label for='track2' id='track2label'>Track 2</label>
                            <input type='checkbox' name='track3' id='track3' />
                            <label for='track3' id='track3label'>Track 3</label>
                            
                            <input type='submit' id='downloadButton' value='Download' />
                        </div>
                    </form>
                </div>
                <div id='results' class='invisible'>
                </div>
            </div>
            <div data-role='footer' id='footer'>
            </div>
        </div>
    </body>
</html>

Sorry the code is so generic (and significantly shortened), but I'm not authorized to post the actual code (you know how it is). But this is basically the gist of it; I believe the problem lies somewhere either in the mobile browser's interpretation, or maybe in the HTTP headers somewhere? This WORKS in Chrome and Firefox on the desktop, and it actually works exactly as expected in Fennec for Android (it downloads all the files with no further interaction and just displays them in the notification bar). I just can't assume everyone's using Fennec (which they're not, lol).

In addition to the above, I've tried the following (which have all worked on desktop but not on mobile):

  • JSON returned from the server, and iframes created on the client by jQuery
  • JSON returned from the server, and a for-loop to call window.open() for each URL
  • JSON returned from the server, and <a> tags created by jQuery and a click() triggered
  • Use different DOCTYPEs

Here's what I tried that didn't work on either desktop or mobile:

  • Modifying location.href or window.location (can only do it once, obviously)
  • Calling req.sendfile() on the server (maybe I'm doing it wrong?)
  • Returning multipart/form-data and dumping binary data with a set boundary from the server (VERY messy, and maybe I'm also doing this one wrong?)

Still no joy; What could I be missing?

P.S. Please don't flame me for using hidden iframes...

EDIT: I'll even be okay going with another native browser protocol that I can setup on the server, such as FTP. All ideas are welcome.

UPDATE: Am trying to initiate an FTP connection from the client to the server and run a "mget". I know net2ftp can do this... now to figure it out ;) Still up for new ideas.

peterh
  • 11,875
  • 18
  • 85
  • 108
JoeLinux
  • 4,198
  • 1
  • 29
  • 31
  • Since you don't actually need the `` to be populated with anything, have you tried using ` – Pointy Mar 30 '11 at 19:33
  • That didn't work... it loaded the ` – JoeLinux Mar 30 '11 at 19:56
  • Hmm. Oh well, sorry to waste your time (but if it were me I'd have tried it too :-) – Pointy Mar 30 '11 at 20:44
  • @Pointy: Don't worry, no time wasted! Took me about 5 seconds to try ;) – JoeLinux Mar 30 '11 at 20:45

1 Answers1

0

It is a client limit. There is a per domain parallel downloads limit. Try serving the files from different domains, say, d1.example.com and d2.example.com. They can all be served from the same virtual host:

<VirtualHost *:80>
    ServerName example.com
    ServerAlias d1.example.com d2.example.com
    ...
</VirtualHost>
Clodoaldo Neto
  • 118,695
  • 26
  • 233
  • 260
  • Is this a client limit specifically for the Android browser? Because it works fine on the desktop, as well as Firefox for Mobile on Android (Fennec). It just isn't working on the default Android browser, or Dolphin Browser. – JoeLinux Apr 06 '11 at 18:27
  • @JoeLinux Each browser will have its own limits. – Clodoaldo Neto Apr 06 '11 at 18:40
  • Ok, I added the code to download each track from a differing but sequential subdomain... still didn't work on the mobile browser. It continued to work on the desktop. – JoeLinux Apr 10 '11 at 04:36