Wednesday, April 18, 2012

FileTransfer.download on PhoneGap/Apache Cordova 1.5.0

Sorry for being gone so long, vacation put me off my posting stride but hopefully this week will get things back on track.

Today I'd like to talk about using the FileTransfer.download() command with PhoneGap. In PhoneGap 1.5.0 we introduced common JavaScript. This is a sub project which has each platform that PhoneGap supports pulling it's JS from a common source. One of the changes introduced because of this is that all FileEntry.fullPath's would start with the "file://" protocol.

Unfortunately the native FileTransfer.download() code was not updated to take care of the new input. I've raised CB-539: FileTransfer.download fails when target starts with "file://" to track this issue. I've already got a fix for this checked into the source repository and it'll be in the 1.7.0 release of PhoneGap.

In the meantime you'll need to strip the "file://' from the target path you pass into FileTransfer.download().  Something like this:
var localPath = fileEntry.fullPath;
if (device.platform === "Android" &&
  localPath.indexOf("file://") === 0) {
    localPath = localPath.substring(7);
}
Here is a full example and don't forget to whitelist "http://i3.kym-cdn.com":

73 comments:

  1. I try your code but i have one mistake .
    When i click on "Download and display image" the browser says me :
    << Uncaught TypeError: string is not a function >>
    in the body html

    ReplyDelete
  2. @Père

    What line does the error come from?

    ReplyDelete
  3. Hi Simon,

    Thanks for the article, it's really helpful. I'm seeing an issue, though. According to my console, all the files are downloading correctly, but when I try to set the source of an image to the entry.fullPath, I just get a broken image?

    I'm testing with a 4s and the path I'm getting from entry.fullPath looks like this:

    /var/mobile/Applications/94B5774C-F4B4-4295-B0F2-9C779CDB048E/Documents/138.png

    Ideas?

    ReplyDelete
  4. @Joe

    I will ask Becky, my iOS counterpart.

    ReplyDelete
  5. FWIW, I also tried to use .toURL() and I got the URL as:

    http://localhost/var/mobile/Applications/94B5774C-F4B4-4295-B0F2-9C779CDB048E/Documents/138.png

    BUT, it still displays a broken image.

    Joe

    ReplyDelete
  6. Also of note is that I'm using 1.5, not 1.6.1. Do you think there was something in there that changed?

    ReplyDelete
  7. Thanks for this Simon, been struggling with the download method for a while (html & php are my game, js not so much), looks like I'm finally making some headway thanks to you!

    To add to @Joe's issue, I'm writing for iPad and struck the same problem. I'm not sure why this works, but I was having no joy pulling data from my db into the app a while back, turned out to be the syntax in the whitelisting.

    Did the same thing with your example and it's all good - whitelist '*.i3.kym-cdn.com' rather than 'http://i3.kym-cdn.com'.

    If anyone far more knowledgable than I (i.e. most everyone) can shed any light on why this works it would be appreciated!

    ReplyDelete
  8. Found the problem - our backend is serving up the image as a stream and the FileTransfer.download is expecting a file, so I was getting images that were size 0. I'm writing out the image to a physical file now and downloading it and everything's working.

    ReplyDelete
  9. Hi,
    First of all thanks for the HUGE job you are dooing.. It is highly appriciated..

    I am building a App (On Android at the moment) that should download all the images locally..
    I am struggeling with a Connect error and it looks like the whitelisnting problem..

    Could you give me a quic explaination of implementing this or a link to a doc.. I have been googling for some time and I might have gone blind...

    Thanks in advance
    Kim

    ReplyDelete
  10. Awsome.. did mug a littel around with it but now it work..

    THANSK

    ReplyDelete
  11. Hi,
    I am using FileTranfer.download to download images from both Android and Iphone .But i am getting FileTransferError.INVALID_URL_ERR on Iphone .On Android its works fine for that urls.I am using Phonegap 1.4.1 version. Please help .

    ReplyDelete
  12. i am working on android 2.3 sdk and when i run this code it says 06-08 13:23:21.873: E/Web Console(352): TypeError: Result of expression 'window.requestFileSystem' [undefined] is not a function. at file:///android_asset/www/libs/modules/contacts/contacts.js:180

    ReplyDelete
  13. @megha rathore

    It sounds like the PhoneGap/Cordova library is not being loaded properly. Are you seeing the "Thunderbirds are go!" log in your console? If not then PG is not loaded.

    ReplyDelete
  14. @joe , hi joe where u r writing the image to a physical file. whether in client side or server side. i am using the java as a backend file. doing exactly as u r doing like sending image as stream and in client side its downloading as empty file. what to do. please help me joe. hi simon pl post this comment. its very urgent.

    ReplyDelete
  15. hi dear, great article you write.
    At first, sorry my poor english.

    Man, i´ve tryed download a file with FileTransfer in iOS 5.1 but a try/catch in "var ft = FileTransfer();"
    Showed me a error like this: "ReferenceError: Can´t findi variable: FileTransfer"
    So, my app don´t work.

    I´ve checked my permissions on my plist file of my project in xCode.
    But, the little sample don´t work.

    In my project, the another functions of cordova/phonegap are working fine. Only FileTransfer return me this error.

    Do you have any info about this error? In google, and cordova site, i don´t found any info about that.

    My best regards,

    ReplyDelete
  16. @kimbavng

    Sorry man, what version of Cordova are you using?

    ReplyDelete
  17. Hi,

    I have tried to download the image into my device and display it. I have tried your code but i am not able to download the image i have been getting the following issue could you please help on this

    Source URL is not in white list: 'http://i3.kym-cdn.com/entries/icons/original/000/000/080/doubt.jpg'

    Could you explain me how to white list the url.

    Thank in advance..

    ReplyDelete
  18. @jagadesh

    Here is the white list guide:

    http://docs.phonegap.com/en/2.0.0/guide_whitelist_index.md.html#Domain%20Whitelist%20Guide

    ReplyDelete
  19. This is very useful, however, I haven't been able to get my download to work. I have been successful with the upload command, but not download. Here is my code:

    var fileTransfer = new FileTransfer(),
    uri = encodeURI('myURI');

    fileTransfer.download(uri, LocalFileSystem.PERSISTENT,
    function(entry) {
    console.log("download complete: " + entry.fullPath);
    },
    function(error) {
    console.log("download error source " + error.source);
    console.log("download error target " + error.target);
    console.log("upload error code" + error.code);
    }
    );

    What do you think I am doing wrong?

    ReplyDelete
  20. @Spencer Quistberg

    Well LocalFileSystem.PERSISTENT is a constant and not a file path. You'd need to pass in something like "/sdcard" to get this to work.

    ReplyDelete
  21. Hi,

    I was successfully in downloading the single image to the sdcard by following the code instruction mentioned above. But when i am trying to download the multiple images i have been getting the images but with the same image

    here i will mention the code could you help me in this issue

    function onDeviceReady()
    {
    console.log("on the ondevice ready function");
    // do your thing!

    window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, function(fileSystem) {
    for(var i=0;i"+additems[i]);
    var remoteFile = "http://www.raymondcamden.com/demos/2012/jan/17/"+additems[i];
    console.log("remoteFile------------>"+remoteFile);
    var localFileName = remoteFile.substring(remoteFile.lastIndexOf('/')+1);
    fileSystem.root.getFile(localFileName, {create: true, exclusive: false}, function(fileEntry) {
    var localPath = fileEntry.fullPath;

    if (device.platform === "Android" && localPath.indexOf("file://") === 0) {
    localPath = localPath.substring(7);
    console.log("localPath------------>"+localPath);
    }
    var ft = new FileTransfer();
    console.log("in the file transfer ");
    ft.download(remoteFile,
    localPath, function(entry) {
    console.log("in the file download method");
    }, fail);
    }, fail);
    }
    }, fail);
    }

    ReplyDelete
  22. @jagadesh

    This line:

    for(var i=0;i"+additems[i]);

    is not valid JavaScript.

    ReplyDelete
  23. I was successful in downloading my file to file:///mnt/sdcard/myfile.jpg. How can I download the file to my downloads on my phone? would it be something like file:///mnt/sdcard/downloads or is there a constant for this as well?

    ReplyDelete
  24. @Spencer Quistberg

    Well in the download method you just need to specify you want it to go in the downloads directory. Modify the getFile input to be:

    fileSystem.root.getFile("downloads/" + localFileName, {create: true, exclusive: false}, ...);

    ReplyDelete
  25. Thanks. I successfully downloaded the file to file:///mnt/sdcard/Download/file.jpg, but I can find it in my "Downloads" on my phone. Basically, I would like this to work the way an attchment download in an email works on your phone. When you download the email attachment, your phone notifies you that you have an attachment and then you can go to your "Downloads" from there. Does phonegap has this capability?

    ReplyDelete
  26. I really dont know if you can help. I am using this code to download multiple PDF documents. How do I wait for the first PDF to complete it's download then move onto the second then third etc.

    I was using $.each but this may be the wrong way round.

    var local2User = JSON.parse( localStorage["locallessons"] ) ;
    $.each(local2User, function(key) {
    var remoteFile = optionsJSON + local2User[key].idcountries + '/' + local2User[key].idcurriculum + '/' + local2User[key].idoptions + '/pdf/' + local2User[key].pdfname;
    var localFileName = remoteFile.substring(remoteFile.lastIndexOf('/')+1);

    window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, function(fileSystem) {
    fileSystem.root.getFile(localFileName, {create: true, exclusive: false}, function(fileEntry) {
    var localPath = fileEntry.fullPath;
    if (device.platform === "Android" && localPath.indexOf("file://") === 0) {
    localPath = localPath.substring(7);
    }
    var ft = new FileTransfer();
    ft.download(remoteFile,
    localPath, function(entry) {
    //var dwnldImg = document.getElementById("dwnldImg");
    //dwnldImg.src = entry.fullPath;
    //dwnldImg.style.visibility = "visible";
    //dwnldImg.style.display = "block";
    }, fail);
    }, fail);
    }, fail);
    });

    I just seem to be stuck and no matter what I try just cant get it right.

    ReplyDelete
  27. @Donovan

    Don't use a "each" loop. Push all the files you want to download into an array. Start the download as normal but in your success callback you pop the next file off the array and call file transfer download again. Repeat until no files are left in the array.

    ReplyDelete
  28. @Spencer Quistberg

    Once the file is downloaded you could fire off a local notification use a plugin:

    https://github.com/phonegap/phonegap-plugins/tree/master/Android/StatusBarNotification

    ReplyDelete
  29. Hi Simon thanks for the response. I have always used each loops. Could you give me a quick example of what you were sugesting for array and call back. A bit confussed at the moment. Thanks again for the advise.

    ReplyDelete
  30. When i try your code, I am getting an error of error code 12. what may be fault..

    ReplyDelete
  31. @Donovan

    I have a cold today but here is some pseudo code that should get you going:

    https://gist.github.com/3835045

    ReplyDelete
  32. @MuthuKumar

    That is a PATH_EXISTS_ERR so you are trying to create a file that already exists.

    ReplyDelete
  33. Hi Simon, I want to download a couple of files in a folder stored in my server, is there a way to download the folder itself, or how can I download all the files in that folder.

    I was also considering ziping the folder, so the app downloads the zipped file and unzips it within the device filesystem.

    What do you think can solve this challenge, thanks, I appreciate your great work.

    ReplyDelete
  34. @Dalton Whyte

    You can't download a folder but you can zip up the contents of the folder download it and unzip it on your device using this plugin:

    ExtractZipFile Plugin

    ReplyDelete
  35. Thanks Simon I was able to implement your code on https://gist.github.com/3835045.

    Really appreciate the help.

    ReplyDelete
  36. I am able to download single pdf file by directly giving that url. suppose if i want to downlaod all pdf files(dont know the no.) from the folder, how to do that simon..,,, pls guide in doing this

    ReplyDelete
  37. @MuthuKumar

    I just covered that in my comments to @Donovan. If you read back a bit you'll see the 2 options.

    ReplyDelete
  38. I downloaded the list of pdf files from my server using file download, and able to view in my device during online mode. But I want to view the file during offline and also need to synchronize the files. Is there a way to do file synchronisation from the server using file download

    ReplyDelete
  39. @MuthuKumar

    The way I would do it is to have a service that I could query to get the last updated times of your PDF files. If it is different than what you have stored from the last request you made to the service. If the files are newer then use the FileTransfer.download method.

    ReplyDelete
  40. In the phonegap filestorage, i want to insert data in my localdatabase, when data is already not there. byt when I tried to do by using select query, if exists i am updating it, if not i am inserting the data. but i am getting error. how to procede in this...

    ReplyDelete
  41. @MuthuKumar

    You need to show some code, errors, logs, etc. Bring this discussion over to my FormSpring page or PhoneGap Google Group.

    ReplyDelete
  42. I am struggling to Save the downloaded Image from server to Save it to Device Photo ALbum ,Please guide me.

    ReplyDelete
  43. @Satish Kumar

    So you are able to download the file but it is not showing up in the Photo Album?

    ReplyDelete
  44. Just popped in to say thanks for explaining this. It works fine for me with a simple image and my next step will be to download multiple pdf files, images and videos.

    I cam here trying to figure out how to get the local folder because I could not find a clear explanation on phonegap's website.

    ReplyDelete
  45. @Nick

    The success callback for FileTransfer.download is called with a FileEntry object that you can use to manipulate the file. If you call FileEntry.getParent it's success callback will be called with a DirectoryEntry which is the parent of the file you just downloaded.

    ReplyDelete
  46. @Simon ,


    Yes , I am able to download the file . But unable to save or move to the Photo Album.

    I tried with downloading the file to the Application's/ Document folder , Wrote a plugin to move this File to the Photo Album , It was not successful .

    My intention is to achieve two things ,

    1) Save the Downloaded Image to Photo Album of the device.

    2) Save the Downloaded Image to a specific Album in the Device .

    Please let me know if this is possible.

    Thanks,
    satish

    ReplyDelete
  47. @Satish Kumar

    Sorry, is this Android or iOS?

    ReplyDelete
  48. Simon ,

    It is for both Android & IOS.

    Thanks,
    Satish

    ReplyDelete
  49. @Satish Kumar

    It is certainly possible on Android. If you download the file to the "Pictures" directory the media scanner process should recognize this and add the picture to the "Gallery" app. Not 100% sure on iOS.

    ReplyDelete
  50. Just wanted to say thanks for the gist!

    ReplyDelete
  51. Hi
    I am using cordova for iPhone. I was able to download videos using this code to the Documents directory. Can you please guide "How to move the video from Documents directory to the photo album" ?.

    ReplyDelete
  52. @Arjun

    Sorry, I don't know how to do that.

    ReplyDelete
  53. Hi Simon,

    I am having a problem when downloading a batch of files (potentially hundreds).

    At the beginning I was using the most basic code to download, but there was always a few (random) files which could not be downloaded. When using alerts to keep track of the execution they all downloaded properly so I assumed it might be a sync problem, I mean, some files did not have enought time to be downloaded (so when using alerts they had enough time).

    I don't know if this idea makes sense, but I tried a code with flags to make sure the code did not continue until each file was downloaded.

    The code is as follows:

    function download_img(imgToDownload, imgToRemove){
    var url = remote_url+imgToDownload; // image url
    root_path = get_root_path();
    var flag = "working";
    var flag_delete = false;
    var imageToDownloadPath = root_path + "/" + imgToDownload; // full file path
    var imageToRemovePath = root_path + "/" + imgToRemove; // full file path
    try{
    var fileTransfer = new FileTransfer();
    fileTransfer.download(url, imageToDownloadPath,
    function () {
    if(imgToRemove != "" && imgToRemove != null){
    var entry = new FileEntry("foo", imageToRemovePath);
    entry.remove(function (){alert("fine");flag_delete = true;}, function (){alert("marron");flag_delete = true;});
    }
    else{
    flag_delete = true;
    }
    flag = "done";
    },
    function (error) {
    flag = "done";
    flag_delete = true;
    }
    );
    }catch(error){
    alert("Error capturado: "+error.message);
    }
    while(flag=="working" && !flag_delete){
    try{
    setTimeout(
    function() {
    /* Código */
    },
    300
    );
    }
    catch(error){
    alert("Error en el bucle: " + error.message);
    }

    }
    }

    The result is:

    * Only the first file is downloaded.
    * However, neither succes function nor error function is executed.
    * The loop keeps on as flags are never changed.

    I really need a stable method to download remote files. Would you have any advice on this? Should I use a different approach or there is an error here to be solved.

    Manny thanks!!!

    ReplyDelete
  54. @lamakun

    Most probably the reason why things work when you use an alert is that an alert is a blocking call. It'll give time for the file to be downloaded.

    What you want to do is to keep a list of files you want to download and on the success callback of each download you need to look at the list to see if there are more files to be downloaded so you can do them all in sequence.

    Check out this gist for example:

    https://gist.github.com/3835045

    ReplyDelete
  55. i want to download the images in the specific folder. how to do that.

    ReplyDelete
  56. @Billy

    Just provide the full path in the second parameter to download.

    ReplyDelete
  57. Hi simon Thanks for the article.
    Actually, i have tried this code for downloading a file from local server, but i m getting JSCallbackError : Request Failed at Line 3723 in Cordova.android.js file.

    Thanks Ranjeet

    ReplyDelete
  58. @Ranjeet Kumar

    It is exactly what is sounds like. There is a coding error in your error callback to FileTransfer. You'll need to fix that first.

    ReplyDelete
  59. Thanks simon i fixed it now. But how to display all types of file in android emulator. i m able to display only image files, other files like text,doc... are not display..

    ReplyDelete
  60. @Ranjeet Kumar

    I'm not even sure what you are trying to do. Can you provide more info?

    ReplyDelete
  61. Hi Simon, Thanks for response. Actually i m trying to open Text, Doc. etc files.in android emulator. but i think these files required corresponding software to open in emulator and one more thing i want to ask that i m trying to make one vertical sliding page for my mobile app. using phonegap, like curtain type with our touch it should go up-down. but i didn't get any clue anywhere. plz help me..

    ReplyDelete
  62. @Ranjeet Kumar

    If you want to open a file set its file path to document.location. That should cause the default file type viewer to start.

    Regarding a curtain then you'd need to do some CSS to do it. Check out this link for instance:

    http://www.thecssninja.com/css/reveal-effect

    ReplyDelete
  63. Thanks Simon, It's really good working fine with android emulator..

    ReplyDelete
  64. Hi Simon!

    Great! I can download multiple images but how can I save them into my phonegap directory www/img?

    ReplyDelete
  65. @James

    You can't save into the www directory of your app. That would break the checksum of your app causing all sorts of security issues. You can reference the downloaded images via their file:// URL.

    ReplyDelete
  66. Excelente Simon MacDonald..funca maximo

    ReplyDelete
  67. Does the download only work with physical files or can I stream the file from my server in byte array or even base64 string?

    ReplyDelete
  68. @Thabo Letsoalo

    Physical files only, no streaming.

    ReplyDelete