Download binary data into the app sandbox using XH

2019-03-02 00:14发布

问题:

Cordova is "sunsetting" (going to deprecate) cordovan-plugin-file, see their blogpost.

No more work will be done on the file-transfer plugin by the Cordova development community. You can continue to use the file-transfer plugin if you wish - it should work fine as-is for the foreseeable future. We highly suggest Cordova users transition to using the standards-compliant way of sending and receiving binary data.

They are encouraging a transition to use XHR2 requests (XHR Requests where the responseType is set to Blob or ArrayBuffer.

The blog post wants to provide an example how binary data can be fetched using XHR2:

window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, function (fs) {
console.log('file system open: ' + fs.name);
fs.root.getFile('bot.png', { create: true, exclusive: false }, function (fileEntry) {
    console.log('fileEntry is file? ' + fileEntry.isFile.toString());
    var oReq = new XMLHttpRequest();
    // Make sure you add the domain name to the Content-Security-Policy <meta> element.
    oReq.open("GET", "http://cordova.apache.org/static/img/cordova_bot.png", true);
    // Define how you want the XHR data to come back
    oReq.responseType = "blob";
    oReq.onload = function (oEvent) {
        var blob = oReq.response; // Note: not oReq.responseText
        if (blob) {
            // Create a URL based on the blob, and set an <img> tag's src to it.
            var url = window.URL.createObjectURL(blob);
            document.getElementById('bot-img').src = url;
            // Or read the data with a FileReader
            var reader = new FileReader();
            reader.addEventListener("loadend", function() {
               // reader.result contains the contents of blob as text
            });
            reader.readAsText(blob);
        } else console.error('we didnt get an XHR response!');
    };
    oReq.send(null);
}, function (err) { console.error('error getting file! ' + err); });}, function (err) { console.error('error getting persistent fs! ' + err); });


I have some issues understanding the code above and the intention of cordova to drop the file-tranfer plugin in favour of directly fetching the Blobs via Ajax.

Am I seeing this right: fs.root.getFile creates a file. The download success handler (oReq.onload) does not attempt to write the fetched blob to the created file. There is no clear reason why the fileEntry is created. If I would want to save the fetched blob to the created FileEntry, within oReq.onload I could go on using a FileWriter, but only for small (I read up to 5 MB) files (since the Blob is handled in-memory). The blog post is more about how a blob can be fetched in general and not about it can be downloaded into the filesystem. If I would want to download bigger files (like a couple of 100 MB), moving away from cordova-plugin-filetransfer is not an option at the moment.

回答1:

With this code you can download big images as they are written by blocks of 1MB instead of doing the whole write at once. Without the 1MB writting I wasn't able to write files bigger than 4MB, but with this I've tested with files up to 40MB without problems

 window.resolveLocalFileSystemURL(cordova.file.externalDataDirectory, 
    function (dirEntry) {
        console.log('file system open: ' + dirEntry.name);
        var isAppend = true;
        createFile(dirEntry, "downloadedImage.jpg", isAppend);
    }, onFSError);
    function onFSError(error) {
        alert(JSON.stringify(error));
    }
    function createFile(dirEntry, fileName, isAppend) {
        // Creates a new file or returns the file if it already exists.
        dirEntry.getFile(fileName, {create: true, exclusive: false}, function(fileEntry) {

            var xhr = new XMLHttpRequest();
            xhr.open('GET', 'https://static.vix.com/es/sites/default/files/styles/large/public/imj/3/30-cosas-de-los-gatos-que-no-sabias-3.jpg', true);
            xhr.responseType = 'blob';

            xhr.onload = function() {
                if (this.status == 200) {
                    var blob = new Blob([this.response], { type: 'image/jpeg' });
                    writeFile(fileEntry, blob);
                }
            };
            xhr.send();
        }, onFSError);
    }

    function writeFile(fileEntry, data) {
       // Create a FileWriter object for our FileEntry (log.txt).
       fileEntry.createWriter(function (fileWriter) {

            fileWriter.onerror = function(e) {
                console.log("Failed file write: " + e.toString());
            };

            function writeFinish() {
                function success(file) {
                    alert("Wrote file with size: " + file.size);
                }
                function fail(error) {
                    alert("Unable to retrieve file properties: " + error.code);
                }
                fileEntry.file(success, fail);
            }
            var written = 0;
            var BLOCK_SIZE = 1*1024*1024; // write 1M every time of write
            function writeNext(cbFinish) {
                fileWriter.onwrite = function(evt) {
                    if (written < data.size)
                        writeNext(cbFinish);
                    else
                        cbFinish();
                };
                if (written) fileWriter.seek(fileWriter.length);
                fileWriter.write(data.slice(written, written + Math.min(BLOCK_SIZE, data.size - written)));
                written += Math.min(BLOCK_SIZE, data.size - written);
            }
            writeNext(writeFinish);
        });
    }