Twitter: raymondcamden


Address: Lafayette, LA, USA

Cordova Sample: Check for a file and download if it isn't there

07-01-2014 9,671 views Mobile, HTML5 34 Comments

I've begun work on trying to answer the questions I gathered concerning Cordova's FileSystem support. As I work through the questions I'm trying to build "real" samples to go along with the text. My first sample is a simple one, but I think it is pretty relevant for the types of things folks may do with Cordova and the file system - checking to see if a file exists locally and if not - fetching it.

I'll begin by sharing the code and then explaining the parts. Here is the entire JavaScript file for the application. (Earlier today, Andrew Grieve shared a way my code could be simplified by a good 1/3rd. The code below reflects his update and has been changed since my original writing of the blog post.)

document.addEventListener("deviceready", init, false);

//The directory to store data
var store;

//Used for status updates
var $status;

//URL of our asset
var assetURL = "https://raw.githubusercontent.com/cfjedimaster/Cordova-Examples/master/readme.md";

//File name of our important data file we didn't ship with the app
var fileName = "mydatafile.txt";

function init() {
	
	$status = document.querySelector("#status");

	$status.innerHTML = "Checking for data file.";

	store = cordova.file.dataDirectory;

	//Check for the file. 
	window.resolveLocalFileSystemURL(store + fileName, appStart, downloadAsset);

}

function downloadAsset() {
	var fileTransfer = new FileTransfer();
	console.log("About to start transfer");
	fileTransfer.download(assetURL, store + fileName, 
		function(entry) {
			console.log("Success!");
			appStart();
		}, 
		function(err) {
			console.log("Error");
			console.dir(err);
		});
}

//I'm only called when the file exists or has been downloaded.
function appStart() {
	$status.innerHTML = "App ready!";
}

Ok, let's break it down. The first step is to check to see if our file exists already. The question is - where should we store the file? If you look at the docs for the FileSystem, you will see that the latest version of the plugin adds some useful aliases for common folders. Unfortunately, the docs are not exactly clear about how some of these aliases work. I asked for help (both on the PhoneGap Google group and the Cordova development list) and got some good responses from Kerri Shotts and Julio Sanchez.

The directory that I thought made sense, cordova.file.applicationStorageDirectory, is incorrectly documented as being writeable in iOS. A pull request has already been filed to fix this mistake. For my application, the most appropriate directory is the next one, cordova.file.dataDirectory. Once I have my directory alias, I can make use of resolveLocalFileSystem on the directory plus desired file name to see if it exists. The third argument, downloadAsset, will only be run on an error, in this case a file not existing.

If the file does not exist, we then have to download it. For this we use a second plugin, FileTransfer. This is where one more point of confusion comes in. We need to convert that earlier DirectoryEntry object, the one we used to get an API for files and directories, back to a URL so we can give a path to the Download API.

So to recap - we've got a few moving parts here. We've got a directory alias, built into the plugin for easily finding common folders for our application. Again, the docs here are currently a bit wrong but they should be corrected soon. From that we can quickly see if our desired file exists, and if not, use the FileTransfer plugin to download it.

Simple... but even a simple application caused me a bit of trouble, so hopefully this helps others. You can get the full source code here: https://github.com/cfjedimaster/Cordova-Examples/tree/master/checkanddownload

34 Comments

  • Krishna #
    Commented on 07-01-2014 at 9:28 PM
    I tried cordova.file.dataDirectory as a way to store files in a cross platform way and it seems to be undefined. I installed the file and file-transfer plugins. From what version is this available in the file plugin?
  • Commented on 07-01-2014 at 9:34 PM
    1.2 and higher. Did you wait for deviceready to fire?
  • Krishna #
    Commented on 07-01-2014 at 9:56 PM
    Yes the call was inside deviceready. The reason it probably didn't work was because I used Phonegap Build. I can now see in the plugins page (https://build.phonegap.com/plugins/617) that the latest version is 1.0.1.

    Will I get version 1.2 of the file plugin if I use cordova CLI?
  • Commented on 07-01-2014 at 10:46 PM
    Yep.
  • ManuelPaulo #
    Commented on 07-02-2014 at 12:04 AM
    Thanks for this. I am testing your code to update a file regularly.
    Using Network Link Conditioner, I simulated a 100% loss network.
    Now the problem is, when the file transfer fails, the stored local life gets deleted. Any idea on how to prevent fileTransfer from deleting it?
  • Commented on 07-02-2014 at 8:13 AM
    Oh now that is interesting. I guess it would be expected as it needs to stream the data to the system and if the stream fails, it wipes the file, but I can see how this may be a surprise to people.

    So I see two things:
    a) We should document it in the FileTransfer download method.
    b) For your specific use case, the logic should be modified to download to another location and on success, copy to the proper location.
  • Commented on 07-02-2014 at 10:47 PM
    Note - code updated.
  • ManuelPaulo #
    Commented on 07-03-2014 at 1:06 AM
    Thanks for your reply.

    I think FileTransfer should have a cache mechanism like the OS's. Start downloading to .tmp file; if successful move it to the final file name, if not, delete the .tmp.

    Yes, I will have to implement this. Will use fileEntry.copyTo. When not update your code to do this also? :)
  • Commented on 07-03-2014 at 9:13 AM
    I'd suggest filing a request for this on the issue tracker: https://issues.apache.org/jira/browse/CB/?selected...
  • Néstor RP #
    Commented on 07-14-2014 at 8:08 AM
    Hi Raymond

    Nice post, this is exactly what I was trying to do. However, I have a question about it: You don't seem to call requestFileSystem() anywhere.
    In every documentation or tutorial I've seen so far, requestFileSystem() must be called in order to access any file in the file system. I presume that resolveLocalFileSystemURL() has been introduced in version 1.2 and it's intended to be used when the full path for the file bein accessed is already known, while requestFileSystem() allows navigating through the file system and look for it, but since I can't find any reason on the net why resolveLocalFileSystemURL() was introduced, I'm not sure. Could you please confirm or refuse it?

    Thanks!
  • Commented on 07-14-2014 at 8:46 AM
    My understanding is that requestFileSystem is mainly giving you an alias to the root of either the persistent or temp directory. The new aliases in the latest FS plugin makes this unnecessary (assuming you want to use one of those aliases of course).
  • Jason U #
    Commented on 07-16-2014 at 12:36 PM
    This post was great and helped me to download a file to my app. How do I set the download location to be in my \www\data directory that I have in my app?

    Thanks
  • Commented on 07-16-2014 at 3:06 PM
    I believe that may be read only. I think the docs say as much here:

    cordova.file.applicationDirectory - Read-only directory where the application is installed. (iOS, Android)
  • Jason U #
    Commented on 07-16-2014 at 3:11 PM
    Here's the actual goal. I ship a json file with my app. It is stored in the \www\data directory. I would like to be able to replace that file from time to time with new data. If that directory is read only then where can I store the file during development? I don't see any of the app storage directories when developing in cordova.

    Thanks for all of the help, it is greatly appreciated.
  • Commented on 07-16-2014 at 3:12 PM
    I'd just store the new version in localStorage. Then your logic can be:

    if I have it in localstorage, see if too old, and if so, update
    otherwise if not too old, use it
    otherwise if not there, default to one I ship with the app in file storage
  • David Blanchard #
    Commented on 07-27-2014 at 4:14 PM
    This code worked great for Android devices. But when I ran the same code on a wp8 device, the cordova.file namespace appears to be undefined as I receive an error of 'unable to get property 'dataDirectory' of undefined or null reference'. When I reference the cordova-plugin-file at git://git.apache.org/cordova-plugin-file.git, it seems the plugin is supported by wp8, but that is the only reference to it. None of the cordova.file.* have wp8 listed.

    Do you know what I should be using instead for wp8? And where I can reference the 'where to store files' table for wp8 that is an excellent summary for each other platform?
  • Commented on 07-27-2014 at 9:16 PM
    The docs mention that dataDirectory is not supported in WP8.

    Since these are just aliases to help simplify your code, in theory, it shouldn't stop you per se, you would just need to figure out the right folder by hand. I'd google around and see what the typical directory would be for that platform.
  • Commented on 08-07-2014 at 11:07 AM
    First thanks. This post helped me get up to speed quickly.
    On IOS I can download and open the file in Adobe smoothly. But it never finds the downloaded file. It always downloads another copy(#). I have tried Documents, Library, Temp directories.
    My console logs show the right directory all the way through.
    Adobe will open those very files offline, but my code doesn't find it or at least cant open it.
    resolveLocalFileSystemURL is never true, but it should be.
    Is adobe moving my files?
    Do i need to requestFileSystem before opening a file that doesn't need to be downloaded?

    I will appreciate any help and regardless thank you again for your time. I am certainly downloading and opening files in the sandbox when I wasn't before and that is useful.
  • Commented on 08-07-2014 at 1:03 PM
    First off... what do you mean by Adobe? Adobe what?
  • Commented on 08-07-2014 at 1:26 PM
    I can download the file and open it in Adobe Reader. I use a different plugin to just read PDF offline no prob.
    But Forms PDFs we need to open in reader so client can email print etc the form. Console shows adobe "copying" the file. Which is actually great for us.
    And it seems to still show the file in the application sandbox listing in xcode.
    But it always downloads another copy it never finds the file that is there and makes the copy.
  • Commented on 08-07-2014 at 1:27 PM
    I honestly can't tell what you are saying here. It sounds like you modified my code to work with PDFs, and you are trying to open the PDF, is that so? If so, it does not apply to this blog post, does it?
  • AJ Davidson #
    Commented on 08-07-2014 at 2:42 PM
    the pdf opens with the fileopener plugin just fine.
    it is just one call line added to your code to open the file. I could have used inAppBrower too. Its not top of mind relevant because
    resolveLocalFileSystemURL is never true with or without even trying to open the file, except if I am still connected to the mac and hit run again then

    your code does work great to download and open pdf in adobe reader btw, when used with the fileopener plugin.

    I just cant open the downloaded file online or offline.
    resolveLocalFileSystemURL cant find it or it isnt still there.
  • Commented on 08-07-2014 at 3:26 PM
    I'm still not quite I understand you. That being said - I'm currently investigating a bug with this on Android. Please see: https://github.com/cfjedimaster/Cordova-Examples/i...
  • AJ Davidson #
    Commented on 08-07-2014 at 3:59 PM
    ty already watching that. I am teamdenver
  • AJ Davidson #
    Commented on 08-08-2014 at 12:50 PM
    I have a bit more info. I watched my apps sandboxed directory from the xcode organiser window.
    I can see the file in the Documents folder after successful transfer code.
    Then console says it is "copy" file to Adobe Reader Sandbox.
    But as soon as I choose "Open With" Adobe Reader, and refresh organiser window, it is gone.
    It is a "move" not a "copy".
    resolveLocalFileSystemURL isnt true because the file is no longer there.
  • Dave #
    Commented on 09-10-2014 at 6:38 AM
    Great article! Helped me a lot. Do you also know how to delete a file? Will I have to loop through the entire dir to get the filehandle, or is it possible to delete a file straight away if you know the filename? Thank you
  • Dave #
    Commented on 09-10-2014 at 7:16 AM
    Figured that out, the success routine will have the filehandle.
  • Nehul Agrawal #
    Commented on 09-10-2014 at 4:11 PM
    Hello,

    I want to use your this file checking method in loop.

    I have write this something like below.

    window.resolveLocalFileSystemURL(DATADIR.toURL() +"/"+ filename, appStart, function(){
                                     ft.download(uri, dlPath, function(e){
                                        //renderPicture(e.toURL());
                                        alert("Successful download of "+e.toURL());
                                     }, onError, true);
                                  
                                  
                                  });

    But it seems like i am doing some mistake here. Can you please tell me the right way?
  • Commented on 09-10-2014 at 5:03 PM
    Remember that these calls are async, so if you have it in a loop, there is no guarantee for which order they will finish.
  • Nehul Agrawal #
    Commented on 09-11-2014 at 1:20 AM
    Hello, thank you for your reply.
    No problem with the order. But the same image is keep downloading. And even though it dont download the file. It just shows that, it has been downloaded successfully.

    I generated one query here at http://stackoverflow.com/questions/25780407/same-f....

    Please if there is anyway i can resolve this problem let?
  • Commented on 09-11-2014 at 8:55 PM
    Posted an answer there.
  • Nehul Agrawal #
    Commented on 09-12-2014 at 4:36 AM
    Thank you, you are a life saver....This one worked perfectly fine.

    But i dont understand. What the problem from my method?

    Thank you! Thank you so much!
  • Commented on 09-12-2014 at 5:55 AM
    I explained - kinda - on SO. It has to do with the nature of callbacks. If it is any consolation, I make the same mistake myself all the time.
  • Mohammed #
    Commented on 09-25-2014 at 9:47 AM
    Your effort really appreciated Raymond. Everybody loves Raymond :)

Post Reply

Please refrain from posting large blocks of code as a comment. Use Pastebin or Gists instead. Text wrapped in asterisks (*) will be bold and text wrapped in underscores (_) will be italicized.

Leave this field empty