| /* |
| * |
| * 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. |
| * |
| */ |
| |
| /*jshint -W030 */ |
| /*global Windows, WinJS*/ |
| /*global module, require*/ |
| |
| var FTErr = require('./FileTransferError'), |
| ProgressEvent = require('cordova-plugin-file.ProgressEvent'), |
| FileUploadResult = require('cordova-plugin-file.FileUploadResult'), |
| FileProxy = require('cordova-plugin-file.FileProxy'), |
| FileEntry = require('cordova-plugin-file.FileEntry'); |
| |
| var appData = Windows.Storage.ApplicationData.current; |
| |
| |
| // Some private helper functions, hidden by the module |
| function cordovaPathToNative(path) { |
| |
| var cleanPath = String(path); |
| // turn / into \\ |
| cleanPath = cleanPath.replace(/\//g, '\\'); |
| // turn \\ into \ |
| cleanPath = cleanPath.replace(/\\\\/g, '\\'); |
| // strip end \\ characters |
| cleanPath = cleanPath.replace(/\\+$/g, ''); |
| return cleanPath; |
| } |
| |
| function nativePathToCordova(path) { |
| return String(path).replace(/\\/g, '/'); |
| } |
| |
| var fileTransferOps = []; |
| |
| function FileTransferOperation(state, promise) { |
| this.state = state; |
| this.promise = promise; |
| } |
| |
| FileTransferOperation.PENDING = 0; |
| FileTransferOperation.DONE = 1; |
| FileTransferOperation.CANCELLED = 2; |
| |
| |
| module.exports = { |
| |
| /* |
| exec(win, fail, 'FileTransfer', 'upload', |
| [filePath, server, fileKey, fileName, mimeType, params, trustAllHosts, chunkedMode, headers, this._id, httpMethod]); |
| */ |
| upload:function(successCallback, errorCallback, options) { |
| var filePath = options[0]; |
| var server = options[1]; |
| var fileKey = options[2] || 'source'; |
| var fileName = options[3]; |
| var mimeType = options[4]; |
| var params = options[5]; |
| // var trustAllHosts = options[6]; // todo |
| // var chunkedMode = options[7]; // todo |
| var headers = options[8] || {}; |
| var uploadId = options[9]; |
| |
| if (!filePath || (typeof filePath !== 'string')) { |
| errorCallback(new FTErr(FTErr.FILE_NOT_FOUND_ERR,null,server)); |
| return; |
| } |
| |
| if (filePath.substr(0, 8) === "file:///") { |
| filePath = appData.localFolder.path + filePath.substr(8).split("/").join("\\"); |
| } else if (filePath.indexOf('ms-appdata:///') === 0) { |
| // Handle 'ms-appdata' scheme |
| filePath = filePath.replace('ms-appdata:///local', appData.localFolder.path) |
| .replace('ms-appdata:///temp', appData.temporaryFolder.path); |
| } else if (filePath.indexOf('cdvfile://') === 0) { |
| filePath = filePath.replace('cdvfile://localhost/persistent', appData.localFolder.path) |
| .replace('cdvfile://localhost/temporary', appData.temporaryFolder.path); |
| } |
| // normalize path separators |
| filePath = cordovaPathToNative(filePath); |
| |
| // Create internal download operation object |
| fileTransferOps[uploadId] = new FileTransferOperation(FileTransferOperation.PENDING, null); |
| |
| Windows.Storage.StorageFile.getFileFromPathAsync(filePath) |
| .then(function (storageFile) { |
| |
| if(!fileName) { |
| fileName = storageFile.name; |
| } |
| if(!mimeType) { |
| // use the actual content type of the file, probably this should be the default way. |
| // other platforms probably can't look this up. |
| 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 |
| errorCallback(new FTErr(FTErr.ABORT_ERR, nativePathToCordova(filePath), server)); |
| return; |
| } |
| |
| // setting request headers for uploader |
| var uploader = new Windows.Networking.BackgroundTransfer.BackgroundUploader(); |
| for (var header in headers) { |
| if (headers.hasOwnProperty(header)) { |
| uploader.setRequestHeader(header, headers[header]); |
| } |
| } |
| |
| // adding params supplied to request payload |
| var transferParts = []; |
| for (var key in params) { |
| if (params.hasOwnProperty(key)) { |
| var contentPart = new Windows.Networking.BackgroundTransfer.BackgroundTransferContentPart(); |
| contentPart.setHeader("Content-Disposition", "form-data; name=\"" + key + "\""); |
| contentPart.setText(params[key]); |
| transferParts.push(contentPart); |
| } |
| } |
| |
| // Adding file to upload to request payload |
| var fileToUploadPart = new Windows.Networking.BackgroundTransfer.BackgroundTransferContentPart(fileKey, fileName); |
| fileToUploadPart.setFile(storageFile); |
| transferParts.push(fileToUploadPart); |
| |
| // create download object. This will throw an exception if URL is malformed |
| var uri = new Windows.Foundation.Uri(server); |
| try { |
| uploader.createUploadAsync(uri, transferParts).then( |
| function (upload) { |
| // update internal TransferOperation object with newly created promise |
| var uploadOperation = upload.startAsync(); |
| fileTransferOps[uploadId].promise = uploadOperation; |
| |
| uploadOperation.then( |
| function (result) { |
| // Update TransferOperation object with new state, delete promise property |
| // since it is not actual anymore |
| var currentUploadOp = fileTransferOps[uploadId]; |
| if (currentUploadOp) { |
| currentUploadOp.state = FileTransferOperation.DONE; |
| currentUploadOp.promise = null; |
| } |
| |
| var response = result.getResponseInformation(); |
| var ftResult = new FileUploadResult(result.progress.bytesSent, response.statusCode, ''); |
| |
| // if server's response doesn't contain any data, then resolve operation now |
| if (result.progress.bytesReceived === 0) { |
| successCallback(ftResult); |
| return; |
| } |
| |
| // otherwise create a data reader, attached to response stream to get server's response |
| var reader = new Windows.Storage.Streams.DataReader(result.getResultStreamAt(0)); |
| reader.loadAsync(result.progress.bytesReceived).then(function (size) { |
| ftResult.response = reader.readString(size); |
| successCallback(ftResult); |
| reader.close(); |
| }); |
| }, |
| function (error) { |
| var source = nativePathToCordova(filePath); |
| |
| // Handle download error here. |
| // Wrap this routines into promise due to some async methods |
| var getTransferError = new WinJS.Promise(function(resolve) { |
| if (error.message === 'Canceled') { |
| // If download was cancelled, message property will be specified |
| resolve(new FTErr(FTErr.ABORT_ERR, source, server, null, null, error)); |
| } else { |
| // in the other way, try to get response property |
| var response = upload.getResponseInformation(); |
| if (!response) { |
| resolve(new FTErr(FTErr.CONNECTION_ERR, source, server)); |
| } else { |
| var reader = new Windows.Storage.Streams.DataReader(upload.getResultStreamAt(0)); |
| reader.loadAsync(upload.progress.bytesReceived).then(function (size) { |
| var responseText = reader.readString(size); |
| resolve(new FTErr(FTErr.FILE_NOT_FOUND_ERR, source, server, response.statusCode, responseText, error)); |
| reader.close(); |
| }); |
| } |
| } |
| }); |
| |
| // Update TransferOperation object with new state, delete promise property |
| // since it is not actual anymore |
| var currentUploadOp = fileTransferOps[uploadId]; |
| if (currentUploadOp) { |
| currentUploadOp.state = FileTransferOperation.CANCELLED; |
| currentUploadOp.promise = null; |
| } |
| |
| // Cleanup, remove incompleted file |
| getTransferError.then(function(transferError) { |
| storageFile.deleteAsync().then(function() { |
| errorCallback(transferError); |
| }); |
| }); |
| }, |
| function (evt) { |
| var progressEvent = new ProgressEvent('progress', { |
| loaded: evt.progress.bytesSent, |
| total: evt.progress.totalBytesToSend, |
| target: evt.resultFile |
| }); |
| progressEvent.lengthComputable = true; |
| successCallback(progressEvent, { keepCallback: true }); |
| } |
| ); |
| }, |
| function (err) { |
| var errorObj = new FTErr(FTErr.INVALID_URL_ERR); |
| errorObj.exception = err; |
| errorCallback(errorObj); |
| } |
| ); |
| } catch (e) { |
| errorCallback(new FTErr(FTErr.INVALID_URL_ERR)); |
| } |
| }, function(err) { |
| errorCallback(new FTErr(FTErr.FILE_NOT_FOUND_ERR, server, server, null, null, err)); |
| }); |
| }, |
| |
| // [source, target, trustAllHosts, id, headers] |
| download:function(successCallback, errorCallback, options) { |
| var source = options[0]; |
| var target = options[1]; |
| var downloadId = options[3]; |
| var headers = options[4] || {}; |
| |
| if (!target) { |
| errorCallback(new FTErr(FTErr.FILE_NOT_FOUND_ERR)); |
| return; |
| } |
| if (target.substr(0, 8) === "file:///") { |
| target = appData.localFolder.path + target.substr(8).split("/").join("\\"); |
| } else if (target.indexOf('ms-appdata:///') === 0) { |
| // Handle 'ms-appdata' scheme |
| target = target.replace('ms-appdata:///local', appData.localFolder.path) |
| .replace('ms-appdata:///temp', appData.temporaryFolder.path); |
| } else if (target.indexOf('cdvfile://') === 0) { |
| target = target.replace('cdvfile://localhost/persistent', appData.localFolder.path) |
| .replace('cdvfile://localhost/temporary', appData.temporaryFolder.path); |
| } |
| target = cordovaPathToNative(target); |
| |
| var path = target.substr(0, target.lastIndexOf("\\")); |
| var fileName = target.substr(target.lastIndexOf("\\") + 1); |
| if (path === null || fileName === null) { |
| errorCallback(new FTErr(FTErr.FILE_NOT_FOUND_ERR)); |
| return; |
| } |
| |
| var download = null; |
| |
| // Create internal download operation object |
| fileTransferOps[downloadId] = new FileTransferOperation(FileTransferOperation.PENDING, null); |
| |
| var downloadCallback = function(storageFolder) { |
| storageFolder.createFileAsync(fileName, 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 |
| errorCallback(new FTErr(FTErr.ABORT_ERR, source, target)); |
| return; |
| } |
| |
| // if download isn't cancelled, contunue with creating and preparing download operation |
| var downloader = new Windows.Networking.BackgroundTransfer.BackgroundDownloader(); |
| for (var header in headers) { |
| if (headers.hasOwnProperty(header)) { |
| downloader.setRequestHeader(header, headers[header]); |
| } |
| } |
| |
| // create download object. This will throw an exception if URL is malformed |
| try { |
| var uri = Windows.Foundation.Uri(source); |
| download = downloader.createDownload(uri, storageFile); |
| } catch (e) { |
| // so we handle this and call errorCallback |
| errorCallback(new FTErr(FTErr.INVALID_URL_ERR)); |
| return; |
| } |
| |
| var downloadOperation = download.startAsync(); |
| // update internal TransferOperation object with newly created promise |
| fileTransferOps[downloadId].promise = downloadOperation; |
| |
| downloadOperation.then(function () { |
| |
| // Update TransferOperation object with new state, delete promise property |
| // since it is not actual anymore |
| var currentDownloadOp = fileTransferOps[downloadId]; |
| if (currentDownloadOp) { |
| currentDownloadOp.state = FileTransferOperation.DONE; |
| currentDownloadOp.promise = null; |
| } |
| |
| var nativeURI = storageFile.path.replace(appData.localFolder.path, 'ms-appdata:///local') |
| .replace(appData.temporaryFolder.path, 'ms-appdata:///temp') |
| .replace(/\\/g, '/'); |
| |
| // Passing null as error callback here because downloaded file should exist in any case |
| // otherwise the error callback will be hit during file creation in another place |
| FileProxy.resolveLocalFileSystemURI(successCallback, null, [nativeURI]); |
| }, function(error) { |
| |
| var getTransferError = new WinJS.Promise(function (resolve) { |
| // Handle download error here. If download was cancelled, |
| // message property will be specified |
| if (error.message === 'Canceled') { |
| resolve(new FTErr(FTErr.ABORT_ERR, source, target, null, null, error)); |
| } else { |
| // in the other way, try to get response property |
| var response = download.getResponseInformation(); |
| if (!response) { |
| resolve(new FTErr(FTErr.CONNECTION_ERR, source, target)); |
| } else { |
| var reader = new Windows.Storage.Streams.DataReader(download.getResultStreamAt(0)); |
| reader.loadAsync(download.progress.bytesReceived).then(function (bytesLoaded) { |
| var payload = reader.readString(bytesLoaded); |
| resolve(new FTErr(FTErr.FILE_NOT_FOUND_ERR, source, target, response.statusCode, payload, error)); |
| }); |
| } |
| } |
| }); |
| getTransferError.then(function (fileTransferError) { |
| |
| // Update TransferOperation object with new state, delete promise property |
| // since it is not actual anymore |
| var currentDownloadOp = fileTransferOps[downloadId]; |
| if (currentDownloadOp) { |
| currentDownloadOp.state = FileTransferOperation.CANCELLED; |
| currentDownloadOp.promise = null; |
| } |
| |
| // Cleanup, remove incompleted file |
| storageFile.deleteAsync().then(function() { |
| errorCallback(fileTransferError); |
| }); |
| }); |
| |
| }, function(evt) { |
| |
| var progressEvent = new ProgressEvent('progress', { |
| loaded: evt.progress.bytesReceived, |
| total: evt.progress.totalBytesToReceive, |
| target: evt.resultFile |
| }); |
| // when bytesReceived == 0, BackgroundDownloader has not yet differentiated whether it could get file length or not, |
| // when totalBytesToReceive == 0, BackgroundDownloader is unable to get file length |
| progressEvent.lengthComputable = (evt.progress.bytesReceived > 0) && (evt.progress.totalBytesToReceive > 0); |
| |
| successCallback(progressEvent, { keepCallback: true }); |
| }); |
| }, function(error) { |
| errorCallback(new FTErr(FTErr.FILE_NOT_FOUND_ERR, source, target, null, null, error)); |
| }); |
| }; |
| |
| var fileNotFoundErrorCallback = function(error) { |
| errorCallback(new FTErr(FTErr.FILE_NOT_FOUND_ERR, source, target, null, null, error)); |
| }; |
| |
| Windows.Storage.StorageFolder.getFolderFromPathAsync(path).then(downloadCallback, function (error) { |
| // Handle non-existent directory |
| if (error.number === -2147024894) { |
| var parent = path.substr(0, path.lastIndexOf('\\')), |
| folderNameToCreate = path.substr(path.lastIndexOf('\\') + 1); |
| |
| Windows.Storage.StorageFolder.getFolderFromPathAsync(parent).then(function(parentFolder) { |
| parentFolder.createFolderAsync(folderNameToCreate).then(downloadCallback, fileNotFoundErrorCallback); |
| }, fileNotFoundErrorCallback); |
| } else { |
| fileNotFoundErrorCallback(); |
| } |
| }); |
| }, |
| |
| abort: function (successCallback, error, options) { |
| var fileTransferOpId = options[0]; |
| |
| // Try to find transferOperation with id specified, and cancel its' promise |
| var currentOp = fileTransferOps[fileTransferOpId]; |
| if (currentOp) { |
| currentOp.state = FileTransferOperation.CANCELLED; |
| currentOp.promise && currentOp.promise.cancel(); |
| } |
| } |
| |
| }; |
| |
| require("cordova/exec/proxy").add("FileTransfer",module.exports); |