[Filetransfer] Fixed performance of strict Blob object passing 78/247778/3
authorPiotr Kosko <p.kosko@samsung.com>
Mon, 16 Nov 2020 08:33:10 +0000 (09:33 +0100)
committerPiotr Kosko <p.kosko@samsung.com>
Mon, 16 Nov 2020 11:14:23 +0000 (12:14 +0100)
[Bug] Because of need to pass strict Blob object to FormData since
Chromium M76, there is a need to increase a performance to pass
cordova tests - finish test in timeout.

Because of that, xml request creation was moved before reading a file
to make possible to trigger abort callback, even when the file is not
fully loaded to memory yet.

[Verification] Below code (based on failing test) produces times below 600ms.
E.g. TEST FINISHED: Time taken: 555

/// <testing precondition>, you need to have running http server accepting POST
/// requests running on SERVER ip address

var SERVER = "http://192.168.0.220:5000";

var persistentRoot;
var GRACE_TIME_DELTA = 600; // in milliseconds
var DEFAULT_FILESYSTEM_SIZE = 1024 * 50; //filesystem size in bytes
var ABORT_DELAY = 100; // for abort() tests

window.requestFileSystem(LocalFileSystem.PERSISTENT, DEFAULT_FILESYSTEM_SIZE,
    function (fileSystem) {
        persistentRoot = fileSystem.root;
        runTest();
    },
    function () {
        throw new Error('Failed to initialize persistent file system.');
    }
);

var unexpectedCallbacks = {
    httpFail: function () { },
    httpWin: function () { },
    fileSystemFail: function () { },
    fileSystemWin: function () { },
    fileOperationFail: function () { },
    fileOperationWin: function () { },
};

var writeFile = function (fileSystem, name, content, success) {
    fileSystem.getFile(name, { create: true },
        function (fileEntry) {
            fileEntry.createWriter(function (writer) {

                writer.onwrite = function () {
                    success(fileEntry);
                };

                writer.onabort = function (evt) {
                    throw new Error('aborted creating test file \'' + name + '\': ' + evt);
                };

                writer.error = function (evt) {
                    throw new Error('aborted creating test file \'' + name + '\': ' + evt);
                };

                if (cordova.platformId === 'browser') {
                    // var builder = new BlobBuilder();
                    // builder.append(content + '\n');
                    var blob = new Blob([content + '\n'], { type: 'text/plain' });
                    writer.write(blob);
                } else {
                    writer.write(content + "\n");
                }

            }, unexpectedCallbacks.fileOperationFail);
        },
        function () {
            throw new Error('could not create test file \'' + name + '\'');
        }
    );
};

var runTest = function () {
    transfer = new FileTransfer();

    // assign onprogress handler
    var defaultOnProgressHandler = function (event) {
        if (event.lengthComputable) {
            expect(event.loaded).toBeGreaterThan(1);
            expect(event.total).toBeGreaterThan(0);
            expect(event.total).not.toBeLessThan(event.loaded);
            expect(event.lengthComputable).toBe(true, 'lengthComputable');
        } else {
            // In IE, when lengthComputable === false, event.total somehow is equal to 2^64
            if (isIE) {
                expect(event.total).toBe(Math.pow(2, 64));
            } else {
                expect(event.total).toBe(0);
            }
        }
    };

    transfer.onprogress = defaultOnProgressHandler;

    root = persistentRoot;
    fileName = 'testFile.txt';
    localFilePath = root.toURL() + fileName;

    uploadParams = {};
    uploadParams.value1 = "test";
    uploadParams.value2 = "param";

    uploadOptions = new FileUploadOptions();
    uploadOptions.fileKey = "file";
    uploadOptions.fileName = fileName;
    uploadOptions.mimeType = "text/plain";
    uploadOptions.params = uploadParams;

    var fileURL = SERVER + '';
    var startTime;

    var uploadFail = function (e) {
        console.log('uploadFail - ' + JSON.stringify(e));
        console.log('TEST FINISHED: Time taken: ' + (new Date() - startTime));
    };

    var fileWin = function () {
        console.log('fileWin');
        startTime = +new Date();

        transfer.onprogress = (s) => {
            console.log('upload on progress: ' + JSON.stringify(s) +
            'time taken: ' + (new Date() - startTime)) };
        console.log('Call upload, time taken: ' + (new Date() - startTime))
        transfer.upload(localFilePath, fileURL, (s) => {
            console.log('uploadWin!!! ' + JSON.stringify(s)); }, uploadFail, uploadOptions, true);
        console.log('After upload call, time taken: ' + (new Date() - startTime))
        setTimeout(function () {
            console.log('Call abort, time taken: ' + (new Date() - startTime))
            transfer.abort();
        }, ABORT_DELAY);
    };

    writeFile(root, fileName, new Array(100000).join('aborttest!'), fileWin);
}

Change-Id: I29717417009dc309456158529cebc85b904e31d3

src/lib/plugins/cordova-plugin-file-transfer/tizen/FileTransfer.js

index 2042474..bcf20cf 100755 (executable)
@@ -98,86 +98,95 @@ exports = {
 
     function successCB(entry) {
       if (entry.isFile) {
-        var fullPath = entry.toURL();
-        entry.file(function(file) {
-          function uploadFile(blobFile) {
-            var fd = new FormData();
+        // initialize XMLHTTPRequest (without starting it)
+        var xhr = uploads[id] = new XMLHttpRequest();
 
-            fd.append(fileKey, blobFile, fileName);
+        xhr.open(httpMethod, server);
 
-            for (var prop in params) {
-              if(params.hasOwnProperty(prop)) {
-                 fd.append(prop, params[prop]);
-              }
-            }
-            var xhr = uploads[id] = new XMLHttpRequest();
+        // Fill XHR headers
+        for (var header in headers) {
+          if (headers.hasOwnProperty(header)) {
+            xhr.setRequestHeader(header, headers[header]);
+          }
+        }
+
+        xhr.onload = function(evt) {
+
+          if (xhr.status === 200) {
+            uploads[id] && delete uploads[id];
+            successCallback({
+              bytesSent: file.size,
+              responseCode: xhr.status,
+              response: xhr.response
+            });
+          } else if (xhr.status === 404) {
+            fail(FileTransferError.INVALID_URL_ERR, this.status, this.response);
+          } else {
+            fail(FileTransferError.CONNECTION_ERR, this.status, this.response);
+          }
+        };
 
-            xhr.open(httpMethod, server);
+        xhr.ontimeout = function(evt) {
+          fail(FileTransferError.CONNECTION_ERR, this.status, this.response);
+        };
 
-            // Fill XHR headers
-            for (var header in headers) {
-              if (headers.hasOwnProperty(header)) {
-                xhr.setRequestHeader(header, headers[header]);
-              }
-            }
+        xhr.onerror = function() {
+          fail(FileTransferError.CONNECTION_ERR, this.status, this.response);
+        };
 
-            xhr.onload = function(evt) {
-              if (xhr.status === 200) {
-                uploads[id] && delete uploads[id];
-                successCallback({
-                  bytesSent: file.size,
-                  responseCode: xhr.status,
-                  response: xhr.response
-                });
-              } else if (xhr.status === 404) {
-                fail(FileTransferError.INVALID_URL_ERR, this.status, this.response);
-              } else {
-                fail(FileTransferError.CONNECTION_ERR, this.status, this.response);
-              }
-            };
-
-            xhr.ontimeout = function(evt) {
-              fail(FileTransferError.CONNECTION_ERR, this.status, this.response);
-            };
-
-            xhr.onerror = function() {
-              fail(FileTransferError.CONNECTION_ERR, this.status, this.response);
-            };
-
-            xhr.onabort = function () {
-              fail(FileTransferError.ABORT_ERR, this.status, this.response);
-            };
-
-            xhr.upload.onprogress = function (e) {
-              successCallback(e);
-            };
-
-            xhr.send(fd);
-
-            // Special case when transfer already aborted, but XHR isn't sent.
-            // In this case XHR won't fire an abort event, so we need to check if transfers record
-            // isn't deleted by filetransfer.abort and if so, call XHR's abort method again
-            if (!uploads[id]) {
-              xhr.abort();
-            }
+        xhr.onabort = function () {
+          fail(FileTransferError.ABORT_ERR, this.status, this.response);
+        };
+
+        xhr.upload.onprogress = function (e) {
+          successCallback(e);
+        };
+        // end of XMLHTTPRequest initialization
+
+        var fullPath = entry.toURL();
+        var fileHandle;
+
+        function closeHandle() {
+          if (fileHandle) {
+            fileHandle.close();
           }
+        }
 
-          var fileHandle;
-          try {
-            fileHandle = tizen.filesystem.openFile(fullPath, 'r');
-            var fileBlob = fileHandle.readBlob();
-            uploadFile(fileBlob);
-          } catch (e) {
-            fail(FileTransferError.ABORT_ERR, 'Could not read file');
-          } finally {
-            if (fileHandle) {
-              fileHandle.close();
+        function uploadFile(blobFile) {
+          closeHandle();
+          // create FormData
+          var fd = new FormData();
+          fd.append(fileKey, blobFile, fileName);
+          for (var prop in params) {
+            if(params.hasOwnProperty(prop)) {
+              fd.append(prop, params[prop]);
             }
           }
 
-        }, function(error) {
-          fail(FileTransferError.CONNECTION_ERR);
-        });
+          // sending already initialized request
+          xhr.send(fd);
+
+          // Special case when transfer already aborted, but XHR isn't sent.
+          // In this case XHR won't fire an abort event, so we need to check if transfers record
+          // isn't deleted by filetransfer.abort and if so, call XHR's abort method again
+          if (!uploads[id]) {
+            xhr.abort();
+          }
+        }
+
+        try {
+          fileHandle = tizen.filesystem.openFile(fullPath, 'r');
+          var fileBlob = fileHandle.readBlobNonBlocking(
+            uploadFile,
+            function(error) {
+              closeHandle();
+              fail(FileTransferError.ABORT_ERR, 'Could not read file ' + e);
+            }
+          );
+        } catch (e) {
+          closeHandle();
+          fail(FileTransferError.ABORT_ERR, 'Could not read file ' + e);
+        }
       }
     }