diff --git a/README.md b/README.md index 76df55e..d280b2a 100644 --- a/README.md +++ b/README.md @@ -290,6 +290,10 @@ A `FileTransferError` object is passed to an error callback when an error occurs - 4 = `FileTransferError.ABORT_ERR` - 5 = `FileTransferError.NOT_MODIFIED_ERR` +## Windows Quirks + +- The plugin implementation is based on [BackgroundDownloader](https://msdn.microsoft.com/en-us/library/windows/apps/windows.networking.backgroundtransfer.backgrounddownloader.aspx)/[BackgroundUploader](https://msdn.microsoft.com/en-us/library/windows/apps/windows.networking.backgroundtransfer.backgrounduploader.aspx), which entails the latency issues on Windows devices (creation/starting of an operation can take up to a few seconds). You can use XHR or [HttpClient](https://msdn.microsoft.com/en-us/library/windows/apps/windows.web.http.httpclient.aspx) as a quicker alternative for small downloads. + ## Backwards Compatibility Notes Previous versions of this plugin would only accept device-absolute-file-paths as the source for uploads, or as the target for downloads. These paths would typically be of the form diff --git a/src/windows/FileTransferProxy.js b/src/windows/FileTransferProxy.js index 2f2d8ed..f26502d 100644 --- a/src/windows/FileTransferProxy.js +++ b/src/windows/FileTransferProxy.js @@ -49,6 +49,11 @@ function nativePathToCordova(path) { return String(path).replace(/\\/g, '/'); } +function alreadyCancelled(opId) { + var op = fileTransferOps[opId]; + return op && op.state === FileTransferOperation.CANCELLED; +} + var fileTransferOps = []; function FileTransferOperation(state, promise) { @@ -116,10 +121,7 @@ exec(win, fail, 'FileTransfer', 'upload', mimeType = storageFile.contentType; } - // check if download isn't already cancelled - var uploadOp = fileTransferOps[uploadId]; - if (uploadOp && uploadOp.state === FileTransferOperation.CANCELLED) { - // Here we should call errorCB with ABORT_ERR error + if (alreadyCancelled(uploadId)) { errorCallback(new FTErr(FTErr.ABORT_ERR, nativePathToCordova(filePath), server)); return; } @@ -166,6 +168,11 @@ exec(win, fail, 'FileTransfer', 'upload', createUploadOperation.then( function (upload) { + if (alreadyCancelled(uploadId)) { + errorCallback(new FTErr(FTErr.ABORT_ERR, nativePathToCordova(filePath), server)); + return; + } + // update internal TransferOperation object with newly created promise var uploadOperation = upload.startAsync(); fileTransferOps[uploadId].promise = uploadOperation; @@ -298,10 +305,7 @@ exec(win, fail, 'FileTransfer', 'upload', var downloadCallback = function(storageFolder) { storageFolder.createFileAsync(tempFileName, Windows.Storage.CreationCollisionOption.replaceExisting).then(function (storageFile) { - // check if download isn't already cancelled - var downloadOp = fileTransferOps[downloadId]; - if (downloadOp && downloadOp.state === FileTransferOperation.CANCELLED) { - // Here we should call errorCB with ABORT_ERR error + if (alreadyCancelled(downloadId)) { errorCallback(new FTErr(FTErr.ABORT_ERR, source, target)); return; } @@ -431,6 +435,9 @@ exec(win, fail, 'FileTransfer', 'upload', if (currentOp) { currentOp.state = FileTransferOperation.CANCELLED; currentOp.promise && currentOp.promise.cancel(); + } else if (typeof fileTransferOpId !== 'undefined') { + // Create the operation in cancelled state to be aborted right away + fileTransferOps[fileTransferOpId] = new FileTransferOperation(FileTransferOperation.CANCELLED, null); } } diff --git a/tests/tests.js b/tests/tests.js index dd28bf9..83c5723 100644 --- a/tests/tests.js +++ b/tests/tests.js @@ -31,9 +31,11 @@ exports.defineAutoTests = function () { var ONE_SECOND = 1000; // in milliseconds var GRACE_TIME_DELTA = 600; // in milliseconds var DEFAULT_FILESYSTEM_SIZE = 1024 * 50; // filesystem size in bytes + var WINDOWS_GRACE_TIME_DELTA = 5 * ONE_SECOND; // Some Windows devices need a few seconds to create an upload/download operation. var UNKNOWN_HOST = "http://foobar.apache.org"; var HEADERS_ECHO = "http://whatheaders.com"; // NOTE: this site is very useful! var DOWNLOAD_TIMEOUT = 7 * ONE_SECOND; + var WINDOWS_UNKNOWN_HOST_TIMEOUT = 35 * ONE_SECOND; var UPLOAD_TIMEOUT = 7 * ONE_SECOND; var ABORT_DELAY = 100; // for abort() tests @@ -479,7 +481,7 @@ exports.defineAutoTests = function () { var downloadFail = function (error) { expect(error.code).toBe(FileTransferError.ABORT_ERR); - expect(new Date() - startTime).toBeLessThan(GRACE_TIME_DELTA); + expect(new Date() - startTime).toBeLessThan(isWindows ? WINDOWS_GRACE_TIME_DELTA : GRACE_TIME_DELTA); // delay calling done() to wait for the bogus abort() setTimeout(done, GRACE_TIME_DELTA * 2); @@ -566,7 +568,7 @@ exports.defineAutoTests = function () { transfer.onprogress = function () {}; transfer.download(fileURL, localFilePath, unexpectedCallbacks.httpWin, downloadFail); - }, DOWNLOAD_TIMEOUT); + }, isWindows ? WINDOWS_UNKNOWN_HOST_TIMEOUT : DOWNLOAD_TIMEOUT); it("filetransfer.spec.16 should handle bad file path", function (done) { var fileURL = SERVER; @@ -830,7 +832,7 @@ exports.defineAutoTests = function () { var uploadFail = function (e) { expect(e.code).toBe(FileTransferError.ABORT_ERR); - expect(new Date() - startTime).toBeLessThan(GRACE_TIME_DELTA); + expect(new Date() - startTime).toBeLessThan(isWindows ? WINDOWS_GRACE_TIME_DELTA : GRACE_TIME_DELTA); // delay calling done() to wait for the bogus abort() setTimeout(done, GRACE_TIME_DELTA * 2); @@ -853,7 +855,7 @@ exports.defineAutoTests = function () { }, GRACE_TIME_DELTA); }; - writeFile(root, fileName, new Array(100000).join("aborttest!"), fileWin); + writeFile(root, fileName, new Array(200000).join("aborttest!"), fileWin); }, UPLOAD_TIMEOUT); it("filetransfer.spec.22 should get http status and body on failure", function (done) {