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:

Daniel said...

Thanks Simon! Very usefull!

Père Castor said...

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

Simon MacDonald said...

@Père

What line does the error come from?

Joe said...

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?

Simon MacDonald said...

@Joe

I will ask Becky, my iOS counterpart.

Joe said...

Thanks!

Joe said...

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

Joe said...

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

Unknown said...

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!

Joe said...

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.

Kim Rasmussen said...

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

Kim Rasmussen said...

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

THANSK

Irsha said...

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 .

Unknown said...

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

Simon MacDonald said...

@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.

monicka said...

@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.

Paulo Sergio D. said...

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,

Simon MacDonald said...

@kimbavng

Sorry man, what version of Cordova are you using?

jagadesh said...

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..

Simon MacDonald said...

@jagadesh

Here is the white list guide:

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

Unknown said...

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?

Simon MacDonald said...

@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.

jagadesh said...

thanks you it is working now

jagadesh said...

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);
}

Simon MacDonald said...

@jagadesh

This line:

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

is not valid JavaScript.

Unknown said...

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?

Simon MacDonald said...

@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}, ...);

Unknown said...

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?

Donovan said...

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.

Simon MacDonald said...

@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.

Simon MacDonald said...

@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

Donovan said...

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.

MuthuKumar said...

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

Simon MacDonald said...

@Donovan

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

https://gist.github.com/3835045

Simon MacDonald said...

@MuthuKumar

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

Unknown said...

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.

Simon MacDonald said...

@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

Donovan said...

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

Really appreciate the help.

MuthuKumar said...

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

Simon MacDonald said...

@MuthuKumar

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

MuthuKumar said...

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

Simon MacDonald said...

@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.

MuthuKumar said...

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...

Simon MacDonald said...

@MuthuKumar

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

Unknown said...

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

Simon MacDonald said...

@Satish Kumar

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

Nick said...

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.

Simon MacDonald said...

@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.

Unknown said...

@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

Simon MacDonald said...

@Satish Kumar

Sorry, is this Android or iOS?

Unknown said...

Simon ,

It is for both Android & IOS.

Thanks,
Satish

Simon MacDonald said...

@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.

oshevans said...

Just wanted to say thanks for the gist!

Arjun said...

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" ?.

Simon MacDonald said...

@Arjun

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

lamakun said...

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!!!

Simon MacDonald said...

@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

Billy said...

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

Simon MacDonald said...

@Billy

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

Unknown said...

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

Simon MacDonald said...

@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.

Unknown said...

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..

Simon MacDonald said...

@Ranjeet Kumar

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

Unknown said...

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..

Simon MacDonald said...

@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

Unknown said...

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

James said...

Hi Simon!

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

Simon MacDonald said...

@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.

RR said...

Excelente Simon MacDonald..funca maximo

Unknown said...

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

Simon MacDonald said...

@Thabo Letsoalo

Physical files only, no streaming.

Unknown said...

will it works for android

Simon MacDonald said...

@bijay kanshi

Yes