1

I have custom download function. When user clicks on download icon, first file is decrypted and then downloaded. This download icon is present in load component.

If decryption is successful, file is returned. If decryption fails, I want to show flash message that 'Download failed'.

Here is my custom download function:

def custom_download():      
    download_row = db(db.documents.id == request.args(0)).select().first()
    download_file = download_row.file

    # Name of file is table_name.field.XXXXX.ext, so retrieve original file name
    org_file_name = db.documents.file.retrieve(download_file)[0]
    file_header = "attachment; filename=" + org_file_name

    response.headers['ContentType'] = "application/octet-stream"
    response.headers['Content-Disposition'] = file_header

    file_full_path = os.path.join(request.folder, 'uploads', download_file)
    decrypted_file = decrypt_file(file_full_path)

    if decrypted_file:
        fh = open(decrypted_file, 'rb')
        return response.stream(fh)
    else:
        return "Download Failed"

How can I trigger flash message in view from controller? Or any other way to tell user that download failed.

Gaurav Vichare
  • 1,143
  • 2
  • 11
  • 26

2 Answers2

1

The easiest way I can think of to accomplish this is to do an ajax request for the file whereby if the result is 'Download Failed' then you alter the DOM to display the error message in a container somewhere on the page. You could alternatively have the action generate an HTTP error code so that you do not have to parse the returned data.

There is an example of what you want: Download a file by jQuery.Ajax

$.fileDownload('some/file.pdf')
    .done(function () { alert('File download a success!'); })
    .fail(function () { alert('File download failed!'); });
Community
  • 1
  • 1
CzechErface
  • 326
  • 2
  • 12
  • custom_download() is not called from any controller. When user clicks on download icon, custom_download() is called and returns file. – Gaurav Vichare Feb 19 '16 at 10:44
1

The problem is if the download link is a regular link (i.e., does not trigger an Ajax request), then the whole page will reload if you return something other than a file. Alternatively, if you make the link trigger an Ajax request, you will not be able to return a file. So, one approach is to redirect back to the original page in case the decryption fails:

    if decrypted_file:
        fh = open(decrypted_file, 'rb')
        return response.stream(fh)
    else:
        session.flash = 'Download failed'
        redirect(URL('original_controller', 'original_function', extension=False))

session.flash is used because there is a redirect. Also, note that extension=False ensures that the extension of the current request does not propagate to the redirect URL (by default, the URL() helper propagates the extension of the current request).

The only downside is that in case of failure, the parent page must be completely re-loaded, but this should have minimal impact assuming failures are relatively rare.

Another approach would be to create one function to generate the decrypted file and return a success/failure message, and a second function to serve the file. You would make an Ajax request to the first function, and depending on the result, either show a failure message or set window.location to the URL of the second function to download the file. The first approach is simpler and a bit more efficient in the case of successful downloads (only one request instead of two).

Anthony
  • 25,466
  • 3
  • 28
  • 57