From b1c58587ed886d420b731734d94273445fdf34f8 Mon Sep 17 00:00:00 2001 From: Pawel Andruszkiewicz Date: Fri, 30 Oct 2015 15:13:30 +0100 Subject: [PATCH] [FileTransfer] Adapted to cordova plugin model. [Verification] Plugin loads fine, not tested as tests require file system. Change-Id: I6694eeb5908d3f17bd48da88a72cc19a089f1034 Signed-off-by: Pawel Andruszkiewicz --- src/filetransfer/cordova_filetransfer_api.js | 424 ++++++++------------- src/lib/cordova_plugins.js | 17 +- .../www/FileTransfer.js | 237 ++++++++++++ .../www/FileTransferError.js | 43 +++ 4 files changed, 456 insertions(+), 265 deletions(-) create mode 100644 src/lib/plugins/cordova-plugin-file-transfer/www/FileTransfer.js create mode 100644 src/lib/plugins/cordova-plugin-file-transfer/www/FileTransferError.js diff --git a/src/filetransfer/cordova_filetransfer_api.js b/src/filetransfer/cordova_filetransfer_api.js index 662d215..39858ee 100755 --- a/src/filetransfer/cordova_filetransfer_api.js +++ b/src/filetransfer/cordova_filetransfer_api.js @@ -14,28 +14,11 @@ * limitations under the License. */ -var _global = window || global || {}; - - -/** - * FileTransferError - * @constructor - */ -var FileTransferError = function(code, source, target, status, body, exception) { - this.code = code || null; - this.source = source || null; - this.target = target || null; - this.http_status = status || null; - this.body = body || null; - this.exception = exception || null; -}; - -FileTransferError.FILE_NOT_FOUND_ERR = 1; -FileTransferError.INVALID_URL_ERR = 2; -FileTransferError.CONNECTION_ERR = 3; -FileTransferError.ABORT_ERR = 4; -FileTransferError.NOT_MODIFIED_ERR = 5; +// TODO: remove when added to public cordova repository -> begin +var plugin_name = 'cordova-plugin-file.tizen.FileTransfer'; +cordova.define(plugin_name, function(require, exports, module) { +// TODO: remove -> end function TizenErrCodeToErrCode(err_code) { switch (err_code) { @@ -56,271 +39,184 @@ function TizenErrCodeToErrCode(err_code) { } } +var uploads = {}; +var downloads = {}; + +exports = { + upload: function(successCallback, errorCallback, args) { + var filePath = args[0], + server = args[1], + fileKey = args[2], + fileName = args[3], + mimeType = args[4], + params = args[5], + trustAllHosts = args[6], // not used + chunkedMode = args[7], + headers = args[8], + id = args[9], + httpMethod = args[10]; + + var fail = function(code, status, response) { + uploads[id] && delete uploads[id]; + var error = new FileTransferError(code, filePath, server, status, response); + errorCallback && errorCallback(error); + }; + + function successCB(entry) { + if (entry.isFile) { + entry.file(function(file) { + function uploadFile(blobFile) { + var fd = new FormData(); + + fd.append(fileKey, blobFile, fileName); + + for (var prop in params) { + if(params.hasOwnProperty(prop)) { + fd.append(prop, params[prop]); + } + } + var xhr = uploads[id] = new XMLHttpRequest(); -var FileUploadResult = function() { - this.bytesSent = 0; - this.responseCode = null; - this.response = null; -}; - - -/** - * Options to customize the HTTP request used to upload files. - * @constructor - * @param fileKey {String} Name of file request parameter. - * @param fileName {String} Filename to be used by the server. Defaults to image.jpg. - * @param mimeType {String} Mimetype of the uploaded file. Defaults to image/jpeg. - * @param params {Object} Object with key: value params to send to the server. - * @param headers {Object} Keys are header names, values are header values. Multiple - * headers of the same name are not supported. - */ -var FileUploadOptions = function(fileKey, fileName, mimeType, params, headers, httpMethod) { - this.fileKey = fileKey || null; - this.fileName = fileName || null; - this.mimeType = mimeType || 'image/jpeg'; - this.params = params || null; - this.headers = headers || null; - this.httpMethod = httpMethod || null; -}; - - -function getUrlCredentials(urlString) { - var credentialsPattern = /^https?\:\/\/(?:(?:(([^:@\/]*)(?::([^@\/]*))?)?@)?([^:\/?#]*)(?::(\d*))?).*$/, - credentials = credentialsPattern.exec(urlString); - - return credentials && credentials[1]; -} - - -function getBasicAuthHeader(urlString) { - var header = null; - - // This is changed due to MS Windows doesn't support credentials in http uris - // so we detect them by regexp and strip off from result url - // Proof: http://social.msdn.microsoft.com/Forums/windowsapps/en-US/a327cf3c-f033-4a54-8b7f-03c56ba3203f/windows-foundation-uri-security-problem - - if (window.btoa) { - var credentials = getUrlCredentials(urlString); - if (credentials) { - var authHeader = "Authorization"; - var authHeaderValue = "Basic " + window.btoa(credentials); - - header = { - name : authHeader, - value : authHeaderValue - }; - } - } - return header; -} - + xhr.open(httpMethod, server); -var FileTransfer = function() { - this._downloadId = 0; - this.onprogress = null; // optional callback -}; + // 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); + } + }; -/** -* Given an absolute file path, uploads a file on the device to a remote server -* using a multipart HTTP request. -* @param filePath {String} Full path of the file on the device -* @param server {String} URL of the server to receive the file -* @param successCallback (Function} Callback to be invoked when upload has completed -* @param errorCallback {Function} Callback to be invoked upon error -* @param options {FileUploadOptions} Optional parameters such as file name and mimetype -*/ -FileTransfer.prototype.upload = function(filePath, server, successCallback, errorCallback, options, trustAllHosts) { - // TODO - check arguments - - var fileKey = null; - var fileName = null; - var httpMethod = null; - var mimeType = null; - var params = null; - var chunkedMode = true; - var headers = null; - - var basicAuthHeader = getBasicAuthHeader(server); - if (basicAuthHeader) { - options = options || {}; - options.headers = options.headers || {}; - options.headers[basicAuthHeader.name] = basicAuthHeader.value; - } + xhr.ontimeout = function(evt) { + fail(FileTransferError.CONNECTION_ERR, this.status, this.response); + }; - if (options) { - fileKey = options.fileKey; - fileName = options.fileName; - mimeType = options.mimeType; - headers = options.headers; - if (httpMethod.toUpperCase() === "PUT"){ - httpMethod = "PUT"; - } else { - httpMethod = "POST"; - } - if (options.chunkedMode !== null || typeof options.chunkedMode !== "undefined") { - chunkedMode = options.chunkedMode; - } - params = options.params || {}; - } + xhr.onerror = function() { + fail(FileTransferError.CONNECTION_ERR, this.status, this.response); + }; - function successCB(entry) { - if (entry.isFile) { - entry.file( function(file) { - function uploadFile(blobFile) { - var fd = new FormData(); + xhr.onabort = function () { + fail(FileTransferError.ABORT_ERR, this.status, this.response); + }; - fd.append(fileKey, blobFile, fileName); + xhr.upload.onprogress = function (e) { + successCallback(e); + }; - for (var prop in params) { - if(params.hasOwnProperty(prop)) { - fd.append(prop, params[prop]); - } + xhr.send(fd); } - var xhr = new XMLHttpRequest(); - - xhr.open("POST", server); - - xhr.onload = function(evt) { - if (xhr.status == 200) { - var result = new FileUploadResult(); - result.bytesSent = file.size; - result.responseCode = xhr.status; - result.response = xhr.response; - successCallback(result); - } else if (xhr.status == 404) { - if (errorCallback) { - errorCallback(new FileTransferError(FileTransferError.INVALID_URL_ERR)); - } - } else { - if (errorCallback) { - errorCallback(new FileTransferError(FileTransferError.CONNECTION_ERR)); - } - } - }; - xhr.ontimeout = function(evt) { - if (errorCallback) { - errorCallback(new FileTransferError(FileTransferError.CONNECTION_ERR)); - } - }; - - xhr.send(fd); - } + var bytesPerChunk; - var bytesPerChunk; - - if (options.chunkedMode === true) { - bytesPerChunk = 1024 * 1024; // 1MB chunk sizes. - } else { - bytesPerChunk = file.size; - } - var start = 0; - var end = bytesPerChunk; - while (start < file.size) { - var chunk = file.webkitSlice(start, end, mimeType); - uploadFile(chunk); - start = end; - end = start + bytesPerChunk; - } - }, function(error) { - if (errorCallback) { - errorCallback(new FileTransferError(FileTransferError.CONNECTION_ERR)); - } - }); + if (options.chunkedMode === true) { + bytesPerChunk = 1024 * 1024; // 1MB chunk sizes. + } else { + bytesPerChunk = file.size; + } + var start = 0; + var end = bytesPerChunk; + while (start < file.size) { + var chunk = file.webkitSlice(start, end, mimeType); + uploadFile(chunk); + start = end; + end = start + bytesPerChunk; + } + }, function(error) { + fail(FileTransferError.CONNECTION_ERR); + }); + } } - } - function errorCB() { - if (errorCallback) { - errorCallback(new FileTransferError(FileTransferError.CONNECTION_ERR)); + function errorCB() { + fail(FileTransferError.CONNECTION_ERR); } - } - window.webkitResolveLocalFilSystemURL(filePath, successCB, errorCB); -}; - - -/** - * Downloads a file form a given URL and saves it to the specified directory. - * @param source {String} URL of the server to receive the file - * @param target {String} Full path of the file on the device - * @param successCallback (Function} Callback to be invoked when upload has completed - * @param errorCallback {Function} Callback to be invoked upon error - * @param trustAllHosts not used - * @param options {FileDownloadOptions} Optional parameters such as headers - */ -FileTransfer.prototype.download = function(source, target, successCallback, errorCallback, trustAllHosts, options) { - // TODO - check arguments - var self = this; - - var basicAuthHeader = getBasicAuthHeader(source); - if (basicAuthHeader) { - source = source.replace(getUrlCredentials(source) + '@', ''); - - options = options || {}; - options.headers = options.headers || {}; - options.headers[basicAuthHeader.name] = basicAuthHeader.value; - } - - var headers = null; - if (options) { - headers = options.headers || null; - } + window.webkitResolveLocalFilSystemURL(filePath, successCB, errorCB); + }, + download: function(successCallback, errorCallback, args) { + var source = args[0], + target = args[1], + trustAllHosts = args[2], // not used + id = args[3], + headers = args[4]; + + var fail = function(code) { + downloads[id] && delete downloads[id]; + var error = new FileTransferError(code, source, target); + errorCallback && errorCallback(error); + } - var listener = { - onprogress: function(id, receivedSize, totalSize) { - if (self.onprogress) { - return self.onprogress(new ProgressEvent()); - } - }, - onpaused: function(id) { - // TODO not needed in filetransfer cordova plugin - }, - oncanceled: function(id) { - if (errorCallback) { - errorCallback(new FileTransferError(FileTransferError.ABORT_ERR)); - } - }, - oncompleted: function(id, fullPath) { - if (successCallback) { - // TODO filesystem plugin should be implemented in order to pass the argument - // of successCallback. Temporary null used instead - successCallback(null); - } - }, - onfailed: function(id, error) { - if (errorCallback) { - errorCallback(new FileTransferError(TizenErrCodeToErrCode(error.code))); + var listener = { + onprogress: function(id, receivedSize, totalSize) { + successCallback({ + lengthComputable: true, + loaded: receivedSize, + total: totalSize + }); + }, + onpaused: function(id) { + // not needed in file-transfer plugin + }, + oncanceled: function(id) { + fail(FileTransferError.ABORT_ERR); + }, + oncompleted: function(id, fullPath) { + if (successCallback) { + // TODO: success callback should receive FileEntry Object + successCallback(null); + } + }, + onfailed: function(id, error) { + fail(TizenErrCodeToErrCode(error.code)); } + }; + + var idx = target.lastIndexOf('/'); + var targetPath = target.substr(0, idx); + var targetFilename = target.substr(idx + 1); + + var downloadRequest = new tizen.DownloadRequest(source, targetPath, targetFilename, 'ALL', headers); + downloads[id] = tizen.download.start(downloadRequest, listener); + }, + abort: function(successCallback, errorCallback, args) { + var id = args[0]; + if (uploads[id]) { + uploads[id].abort(); + delete uploads[id]; + } else if (downloads[id]) { + tizen.download.cancel(downloads[id]); + delete downloads[id]; + } else { + console.warn('Unknown file transfer ID: ' + id); } - }; - - var idx = target.lastIndexOf('/'); - var targetPath = target.substr(0, idx); - var targetFilename = target.substr(idx + 1); - - var downloadRequest = new tizen.DownloadRequest(source, targetPath, targetFilename, 'ALL', headers); - self._downloadId = tizen.download.start(downloadRequest, listener); + }, }; +require("cordova/exec/proxy").add("FileTransfer", exports); -/** - * Aborts the ongoing file transfer on this object. The original error - * callback for the file transfer will be called if necessary. - */ -FileTransfer.prototype.abort = function() { - if (this._downloadId) { - tizen.download.cancel(this._downloadId); - } -}; - -_global.FileUploadResult = FileUploadResult; -_global.FileTransfer = FileTransfer; -_global.FileTransferError = FileTransferError; -_global.FileUploadOptions = FileUploadOptions; +console.log('Loaded cordova.file-transfer API'); -console.log('Loaded FileTransfer API'); +//TODO: remove when added to public cordova repository -> begin +}); exports = function(require) { + // this plugin is not loaded via cordova_plugins.js, we need to manually add + // it to module mapper + var mm = require('cordova/modulemapper'); + mm.runs(plugin_name); }; +//TODO: remove -> end diff --git a/src/lib/cordova_plugins.js b/src/lib/cordova_plugins.js index 3ef753a..3192c61 100644 --- a/src/lib/cordova_plugins.js +++ b/src/lib/cordova_plugins.js @@ -151,7 +151,21 @@ module.exports = [ "cordova" ], "runs": true - } + }, + { + "file": "plugins/cordova-plugin-file-transfer/www/FileTransferError.js", + "id": "cordova-plugin-file-transfer.FileTransferError", + "clobbers": [ + "window.FileTransferError" + ] + }, + { + "file": "plugins/cordova-plugin-file-transfer/www/FileTransfer.js", + "id": "cordova-plugin-file-transfer.FileTransfer", + "clobbers": [ + "window.FileTransfer" + ] + }, ]; module.exports.metadata = // TOP OF METADATA @@ -159,6 +173,7 @@ module.exports.metadata = "cordova-plugin-device": "1.0.1", "cordova-plugin-dialogs": "1.1.1", "cordova-plugin-file": "3.0.0", + "cordova-plugin-file-transfer": "1.3.0", } // BOTTOM OF METADATA }); diff --git a/src/lib/plugins/cordova-plugin-file-transfer/www/FileTransfer.js b/src/lib/plugins/cordova-plugin-file-transfer/www/FileTransfer.js new file mode 100644 index 0000000..ff0e6c9 --- /dev/null +++ b/src/lib/plugins/cordova-plugin-file-transfer/www/FileTransfer.js @@ -0,0 +1,237 @@ +cordova.define("cordova-plugin-file-transfer.FileTransfer", function(require, exports, module) { /* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * +*/ + +var argscheck = require('cordova/argscheck'), + exec = require('cordova/exec'), + FileTransferError = require('./FileTransferError'), + ProgressEvent = require('cordova-plugin-file.ProgressEvent'); + +function newProgressEvent(result) { + var pe = new ProgressEvent(); + pe.lengthComputable = result.lengthComputable; + pe.loaded = result.loaded; + pe.total = result.total; + return pe; +} + +function getUrlCredentials(urlString) { + var credentialsPattern = /^https?\:\/\/(?:(?:(([^:@\/]*)(?::([^@\/]*))?)?@)?([^:\/?#]*)(?::(\d*))?).*$/, + credentials = credentialsPattern.exec(urlString); + + return credentials && credentials[1]; +} + +function getBasicAuthHeader(urlString) { + var header = null; + + + // This is changed due to MS Windows doesn't support credentials in http uris + // so we detect them by regexp and strip off from result url + // Proof: http://social.msdn.microsoft.com/Forums/windowsapps/en-US/a327cf3c-f033-4a54-8b7f-03c56ba3203f/windows-foundation-uri-security-problem + + if (window.btoa) { + var credentials = getUrlCredentials(urlString); + if (credentials) { + var authHeader = "Authorization"; + var authHeaderValue = "Basic " + window.btoa(credentials); + + header = { + name : authHeader, + value : authHeaderValue + }; + } + } + + return header; +} + +function convertHeadersToArray(headers) { + var result = []; + for (var header in headers) { + if (headers.hasOwnProperty(header)) { + var headerValue = headers[header]; + result.push({ + name: header, + value: headerValue.toString() + }); + } + } + return result; +} + +var idCounter = 0; + +/** + * FileTransfer uploads a file to a remote server. + * @constructor + */ +var FileTransfer = function() { + this._id = ++idCounter; + this.onprogress = null; // optional callback +}; + +/** +* Given an absolute file path, uploads a file on the device to a remote server +* using a multipart HTTP request. +* @param filePath {String} Full path of the file on the device +* @param server {String} URL of the server to receive the file +* @param successCallback (Function} Callback to be invoked when upload has completed +* @param errorCallback {Function} Callback to be invoked upon error +* @param options {FileUploadOptions} Optional parameters such as file name and mimetype +* @param trustAllHosts {Boolean} Optional trust all hosts (e.g. for self-signed certs), defaults to false +*/ +FileTransfer.prototype.upload = function(filePath, server, successCallback, errorCallback, options, trustAllHosts) { + argscheck.checkArgs('ssFFO*', 'FileTransfer.upload', arguments); + // check for options + var fileKey = null; + var fileName = null; + var mimeType = null; + var params = null; + var chunkedMode = true; + var headers = null; + var httpMethod = null; + var basicAuthHeader = getBasicAuthHeader(server); + if (basicAuthHeader) { + server = server.replace(getUrlCredentials(server) + '@', ''); + + options = options || {}; + options.headers = options.headers || {}; + options.headers[basicAuthHeader.name] = basicAuthHeader.value; + } + + if (options) { + fileKey = options.fileKey; + fileName = options.fileName; + mimeType = options.mimeType; + headers = options.headers; + httpMethod = options.httpMethod || "POST"; + if (httpMethod.toUpperCase() == "PUT"){ + httpMethod = "PUT"; + } else { + httpMethod = "POST"; + } + if (options.chunkedMode !== null || typeof options.chunkedMode != "undefined") { + chunkedMode = options.chunkedMode; + } + if (options.params) { + params = options.params; + } + else { + params = {}; + } + } + + if (cordova.platformId === "windowsphone") { + headers = headers && convertHeadersToArray(headers); + params = params && convertHeadersToArray(params); + } + + var fail = errorCallback && function(e) { + var error = new FileTransferError(e.code, e.source, e.target, e.http_status, e.body, e.exception); + errorCallback(error); + }; + + var self = this; + var win = function(result) { + if (typeof result.lengthComputable != "undefined") { + if (self.onprogress) { + self.onprogress(newProgressEvent(result)); + } + } else { + successCallback && successCallback(result); + } + }; + exec(win, fail, 'FileTransfer', 'upload', [filePath, server, fileKey, fileName, mimeType, params, trustAllHosts, chunkedMode, headers, this._id, httpMethod]); +}; + +/** + * Downloads a file form a given URL and saves it to the specified directory. + * @param source {String} URL of the server to receive the file + * @param target {String} Full path of the file on the device + * @param successCallback (Function} Callback to be invoked when upload has completed + * @param errorCallback {Function} Callback to be invoked upon error + * @param trustAllHosts {Boolean} Optional trust all hosts (e.g. for self-signed certs), defaults to false + * @param options {FileDownloadOptions} Optional parameters such as headers + */ +FileTransfer.prototype.download = function(source, target, successCallback, errorCallback, trustAllHosts, options) { + argscheck.checkArgs('ssFF*', 'FileTransfer.download', arguments); + var self = this; + + var basicAuthHeader = getBasicAuthHeader(source); + if (basicAuthHeader) { + source = source.replace(getUrlCredentials(source) + '@', ''); + + options = options || {}; + options.headers = options.headers || {}; + options.headers[basicAuthHeader.name] = basicAuthHeader.value; + } + + var headers = null; + if (options) { + headers = options.headers || null; + } + + if (cordova.platformId === "windowsphone" && headers) { + headers = convertHeadersToArray(headers); + } + + var win = function(result) { + if (typeof result.lengthComputable != "undefined") { + if (self.onprogress) { + return self.onprogress(newProgressEvent(result)); + } + } else if (successCallback) { + var entry = null; + if (result.isDirectory) { + entry = new (require('cordova-plugin-file.DirectoryEntry'))(); + } + else if (result.isFile) { + entry = new (require('cordova-plugin-file.FileEntry'))(); + } + entry.isDirectory = result.isDirectory; + entry.isFile = result.isFile; + entry.name = result.name; + entry.fullPath = result.fullPath; + entry.filesystem = new FileSystem(result.filesystemName || (result.filesystem == window.PERSISTENT ? 'persistent' : 'temporary')); + entry.nativeURL = result.nativeURL; + successCallback(entry); + } + }; + + var fail = errorCallback && function(e) { + var error = new FileTransferError(e.code, e.source, e.target, e.http_status, e.body, e.exception); + errorCallback(error); + }; + + exec(win, fail, 'FileTransfer', 'download', [source, target, trustAllHosts, this._id, headers]); +}; + +/** + * Aborts the ongoing file transfer on this object. The original error + * callback for the file transfer will be called if necessary. + */ +FileTransfer.prototype.abort = function() { + exec(null, null, 'FileTransfer', 'abort', [this._id]); +}; + +module.exports = FileTransfer; + +}); diff --git a/src/lib/plugins/cordova-plugin-file-transfer/www/FileTransferError.js b/src/lib/plugins/cordova-plugin-file-transfer/www/FileTransferError.js new file mode 100644 index 0000000..8339c2b --- /dev/null +++ b/src/lib/plugins/cordova-plugin-file-transfer/www/FileTransferError.js @@ -0,0 +1,43 @@ +cordova.define("cordova-plugin-file-transfer.FileTransferError", function(require, exports, module) { /* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * +*/ + +/** + * FileTransferError + * @constructor + */ +var FileTransferError = function(code, source, target, status, body, exception) { + this.code = code || null; + this.source = source || null; + this.target = target || null; + this.http_status = status || null; + this.body = body || null; + this.exception = exception || null; +}; + +FileTransferError.FILE_NOT_FOUND_ERR = 1; +FileTransferError.INVALID_URL_ERR = 2; +FileTransferError.CONNECTION_ERR = 3; +FileTransferError.ABORT_ERR = 4; +FileTransferError.NOT_MODIFIED_ERR = 5; + +module.exports = FileTransferError; + +}); -- 2.7.4