Cordova – file-transfer, unzip and present adventures
For a hybrid project I have a very simple requirement: download zip file, unzip it, serve content on a page from artifacts (html text, images).
I started with Intel XDK, an environment I did some other work with. A great integrated development environment, but some of its big problems: lagging behind in version of Cordova, fixed set of selected plugins, no possibility to include other plugins. Because I wanted to unzip, I tried it with a Javascript only solution zip.js. The problem I had was that it worked if I downloaded the zip file over http using importHttpContent(), but I could’t get it working with loading from the local file system. Another problem I had was with where files ended up on different devices: in / on wp8, on /storage/sdcard0 on Android, and on iOS in even another location.
I decided to go with plain Cordova, with the command-line commands. That was a good decision. It was now possible to use the latest version (3.4 at time of writing), and use the new file and file-transfer plugins that now support a huge improvement to hybrid development: the cdvfile protocol. Files can now be addressed with respect to one multi-platform root: cdvfile://, rather than through device-specific paths. There is also a great plugin available to unzip that understands the new cdvfile:// notation as well. I ended up with a simple example to showcase the download, unzip and present case. To get it working execute the following commands with a Cordova 3.4 installation:
- cordova create Cordova-TransferUnzipPresent com.svdoever.tranferunzippresent TransferUnzipPresent
- cd Cordova-TransferUnzipPresent
- cordova platform add android
- cordova plugin add org.apache.cordova.file
- cordova plugin add org.apache.cordova.file-transfer
- cordova plugin add org.chromium.zip
- Replace code in www\index.html with the code below
- cordova emulate android
Works with iOS platform as well. The zip plugin does not work on wp8 (yet).
The code:
<!-- Code by Serge van den Oever, http://weblogs.asp.net/soever --> <!-- Using Cordova 3.4 with the following plugins: --> <!-- 'org.apache.cordova.file' (version 1.0.1), 'org.apache.cordova.file-transfer' (0.4.2), 'org.chromium.zip' (2.0.0) --> <!DOCTYPE html> <html> <head> <title>Cordova Download Unzip Display Sample</title> <meta http-equiv="Content-type" content="text/html; charset=utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=0"> <script src="cordova.js"></script> </head><
body> <button id="btnLoad">Load</button> <button id="btnUnzip">Unzip</button> <hr/> <div id="statusPlace"></div> <hr/> <img id="imgPlace" src="http://lorempixel.com/320/200"> <br/> <div id="txtPlace">TEXT COMES HERE</div><
script type="application/javascript"> document.addEventListener("deviceready", onDeviceReady, false);</script> </body> </html>function registerHandlers() { document.getElementById("btnLoad").onclick = function() { var that = this, App = new DownloadApp(), fileName = "ft-p.zip", uri = encodeURI("https://dl.dropboxusercontent.com/u/7197720/ftpack.zip"), folderName = "content"; console.log("load button clicked"); document.getElementById("statusPlace").innerHTML += "<br/>Loading: " + uri; App.load(uri, folderName, fileName, /*progress*/function(percentage) { document.getElementById("statusPlace").innerHTML += "<br/>" + percentage + "%"; }, /*success*/function(entry) { document.getElementById("statusPlace").innerHTML += "<br/>Zip saved to: " + entry.toURL(); }, /*fail*/function() { document.getElementById("statusPlace").innerHTML += "<br/>Failed load zip: " + that.uri; } ); }; document.getElementById("btnUnzip").onclick = function() { var that = this, App = new DownloadApp(), fileName = "ft-p.zip", folderName = "content"; console.log("zip button clicked"); App.unzip(folderName, fileName, /*success*/function() { alert("Unzipped and assigned"); }, /*fail*/function(error) { alert("Unzip failed: " + error.code); } ); }; } function onDeviceReady() { // navigator.splashscreen.hide(); document.getElementById("statusPlace").innerHTML += "<br/>deviceready event received"; registerHandlers(); } var DownloadApp = function() { } DownloadApp.prototype = { load: function(uri, folderName, fileName, progress, success, fail) { var that = this; that.progress = progress; that.success = success; that.fail = fail; filePath = ""; that.getFilesystem( function(fileSystem) { console.log("GotFS"); that.getFolder(fileSystem, folderName, function(folder) { filePath = folder.toURL() + "/" + fileName; that.transferFile(uri, filePath, progress, success, fail); }, function(error) { console.log("Failed to get folder: " + error.code); typeof that.fail === 'function' && that.fail(error); }); }, function(error) { console.log("Failed to get filesystem: " + error.code); typeof that.fail === 'function' && that.fail(error); } ); }, getFilesystem:function (success, fail) { window.requestFileSystem = window.requestFileSystem || window.webkitRequestFileSystem; window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, success, fail); }, getFolder: function (fileSystem, folderName, success, fail) { fileSystem.root.getDirectory(folderName, {create: true, exclusive: false}, success, fail) }, transferFile: function (uri, filePath, progress, success, fail) { var that = this; that.progress = progress; that.success = success; that.fail = fail; var transfer = new FileTransfer(); transfer.onprogress = function(progressEvent) { if (progressEvent.lengthComputable) { var perc = Math.floor(progressEvent.loaded / progressEvent.total * 100); typeof that.progress === 'function' && that.progress(perc); // progression on scale 0..100 (percentage) as number } else { } }; transfer.download( uri, filePath, function(entry) { console.log("File saved to: " + entry.toURL()); typeof that.success === 'function' && that.success(entry); }, function(error) { console.log("An error has occurred: Code = " + error.code); console.log("download error source " + error.source); console.log("download error target " + error.target); console.log("download error code " + error.code); typeof that.fail === 'function' && that.fail(error); } ); }, unzip: function(folderName, fileName, success, fail) { var that = this; that.success = success; that.fail = fail; zip.unzip("cdvfile://localhost/persistent/" + folderName + "/" + fileName, "cdvfile://localhost/persistent/" + folderName, function(code) { console.log("result: " + code); that.getFilesystem( function(fileSystem) { console.log("gotFS"); that.getFolder(fileSystem, folderName + "/ftpack", function (folder) { document.getElementById("imgPlace").src = folder.nativeURL + "/img.jpg"; folder.getFile("text.html", {create: false}, function (fileEntry) { fileEntry.file(function(file) { var reader = new FileReader(); reader.onloadend = function (evt) { console.log("Read as text"); console.log(evt.target.result); document.getElementById("txtPlace").innerHTML = evt.target.result; typeof that.success === ' function && that.success();' }; reader.readAsText(file); }, function(error) { console.log("Failed to get file"); typeof that.fail === 'function' && that.fail(error); }); }, function (error) { console.log("failed to get file: " + error.code); typeof that.fail === 'function' && that.fail(error); }); }, function (error) { console.log("failed to get folder: " + error.code); typeof that.fail === 'function' && that.fail(error); }); }, function(error) { console.log("failed to get filesystem: " + error.code); typeof that.fail === 'function' && that.fail(error); }); } ); } }