[FileTransfer] Fixed file upload for server
[platform/core/api/cordova-plugins.git] / src / lib / plugins / cordova-plugin-file-transfer / tizen / FileTransfer.js
1 /*
2  * Copyright (c) 2015 Samsung Electronics Co., Ltd All Rights Reserved
3  *
4  *    Licensed under the Apache License, Version 2.0 (the "License");
5  *    you may not use this file except in compliance with the License.
6  *    You may obtain a copy of the License at
7  *
8  *        http://www.apache.org/licenses/LICENSE-2.0
9  *
10  *    Unless required by applicable law or agreed to in writing, software
11  *    distributed under the License is distributed on an "AS IS" BASIS,
12  *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  *    See the License for the specific language governing permissions and
14  *    limitations under the License.
15  */
16
17 // TODO: remove when added to public cordova repository -> begin
18 var plugin_name = 'cordova-plugin-file-transfer.tizen.FileTransfer';
19
20 cordova.define(plugin_name, function(require, exports, module) {
21 // TODO: remove -> end
22
23 function getParentPath(filePath) {
24   var pos = filePath.lastIndexOf('/');
25   return filePath.substring(0, pos + 1);
26 }
27
28 function getFileName(filePath) {
29   var pos = filePath.lastIndexOf('/');
30   return filePath.substring(pos + 1);
31 }
32
33 function TizenErrCodeToErrCode(err_code) {
34   switch (err_code) {
35     case WebAPIException.NOT_FOUND_ERR:
36       return FileTransferError.FILE_NOT_FOUND_ERR;
37
38     case WebAPIException.URL_MISMATCH_ERR:
39       return FileTransferError.INVALID_URL_ERR;
40
41     case WebAPIException.NETWORK_ERR:
42       return FileTransferError.CONNECTION_ERR;
43
44     case WebAPIException.ABORT_ERR:
45       return FileTransferError.ABORT_ERR;
46
47     default:
48       return FileTransferError.NOT_MODIFIED_ERR;
49   }
50 }
51
52 function FileErrorCodeToErrCode(err_code) {
53   switch(err_code) {
54     case FileError.SECURITY_ERR:
55       return FileTransferError.ABORT_ERR;
56     default:
57       return FileTransferError.FILE_NOT_FOUND_ERR;
58   }
59 }
60
61 function checkURL(url) {
62   return url.indexOf(' ') === -1;
63 }
64
65 var uploads = {};
66 var downloads = {};
67
68 var filePrefix = 'file://';
69
70 exports = {
71   upload: function(successCallback, errorCallback, args) {
72     var filePath = args[0],
73         server = args[1],
74         fileKey = args[2] || 'file',
75         fileName = args[3] || 'image.jpg',
76         mimeType = args[4] || 'image/jpeg',
77         params = args[5],
78         trustAllHosts = args[6], // not used
79         chunkedMode = args[7],
80         headers = args[8],
81         id = args[9],
82         httpMethod = args[10] || 'POST';
83
84     if (0 !== filePath.indexOf(filePrefix)) {
85       filePath = filePrefix + filePath;
86     }
87
88     var fail = function(code, status, response) {
89       uploads[id] && delete uploads[id];
90       var error = new FileTransferError(code, filePath, server, status, response);
91       errorCallback && errorCallback(error);
92     };
93
94     if (!checkURL(server)) {
95       fail(FileTransferError.INVALID_URL_ERR);
96       return;
97     }
98
99     function successCB(entry) {
100       if (entry.isFile) {
101         // initialize XMLHTTPRequest (without starting it)
102         var xhr = uploads[id] = new XMLHttpRequest();
103
104         xhr.open(httpMethod, server);
105
106         // Fill XHR headers
107         for (var header in headers) {
108           if (headers.hasOwnProperty(header)) {
109             xhr.setRequestHeader(header, headers[header]);
110           }
111         }
112
113         xhr.ontimeout = function(evt) {
114           fail(FileTransferError.CONNECTION_ERR, this.status, this.response);
115         };
116
117         xhr.onerror = function() {
118           fail(FileTransferError.CONNECTION_ERR, this.status, this.response);
119         };
120
121         xhr.onabort = function () {
122           fail(FileTransferError.ABORT_ERR, this.status, this.response);
123         };
124
125         xhr.upload.onprogress = function (e) {
126           successCallback(e);
127         };
128         // end of XMLHTTPRequest initialization
129
130         var fullPath = entry.toURL();
131         var fileHandle;
132
133         function closeHandle() {
134           if (fileHandle) {
135             fileHandle.close();
136           }
137         }
138
139         function uploadFile(blobFile) {
140           closeHandle();
141           // create FormData
142           var fd = new FormData();
143           fd.append(fileKey, blobFile, fileName);
144           for (var prop in params) {
145             if(params.hasOwnProperty(prop)) {
146               fd.append(prop, params[prop]);
147             }
148           }
149
150           // sending already initialized request
151           // 'onload' needs to be defined here because it needs blobFile.size
152           xhr.onload = function(evt) {
153             // 2xx codes are valid
154             if (xhr.status >= 200 && xhr.status < 300) {
155               uploads[id] && delete uploads[id];
156               successCallback({
157                 bytesSent: blobFile.size,
158                 responseCode: xhr.status,
159                 response: xhr.response
160               });
161             } else if (xhr.status === 404) {
162               fail(FileTransferError.INVALID_URL_ERR, this.status, this.response);
163             } else {
164               fail(FileTransferError.CONNECTION_ERR, this.status, this.response);
165             }
166           };
167           xhr.send(fd);
168
169           // Special case when transfer already aborted, but XHR isn't sent.
170           // In this case XHR won't fire an abort event, so we need to check if transfers record
171           // isn't deleted by filetransfer.abort and if so, call XHR's abort method again
172           if (!uploads[id]) {
173             xhr.abort();
174           }
175         }
176
177         try {
178           fileHandle = tizen.filesystem.openFile(fullPath, 'r');
179           var fileBlob = fileHandle.readBlobNonBlocking(
180             uploadFile,
181             function(error) {
182               closeHandle();
183               fail(FileTransferError.ABORT_ERR, 'Could not read file ' + e);
184             }
185           );
186         } catch (e) {
187           closeHandle();
188           fail(FileTransferError.ABORT_ERR, 'Could not read file ' + e);
189         }
190       }
191     }
192
193     function errorCB(error) {
194       fail(FileErrorCodeToErrCode(error.code));
195     }
196
197     resolveLocalFileSystemURL(filePath, successCB, errorCB);
198   },
199   download: function(successCallback, errorCallback, args) {
200     var url = args[0],
201         filePath = args[1],
202         trustAllHosts = args[2],  // not used
203         id = args[3],
204         headers = args[4];
205
206     if (!checkURL(url)) {
207       errorCallback(new FileTransferError(FileTransferError.INVALID_URL_ERR, url, filePath));
208       return;
209     }
210
211     var dirPath = getParentPath(filePath);
212     var fileName = getFileName(filePath);
213
214     var xhr = downloads[id] = new XMLHttpRequest();
215
216     function fail(code, body) {
217       delete downloads[id];
218       errorCallback(new FileTransferError(code,
219                                           url,
220                                           filePath,
221                                           xhr.status,
222                                           body,
223                                           null));
224     }
225
226     xhr.addEventListener('progress', function (evt) {
227       successCallback(evt);
228     });
229
230     xhr.addEventListener('abort', function (evt) {
231       fail(FileTransferError.ABORT_ERR, xhr.response);
232     });
233
234     xhr.addEventListener('error', function (evt) {
235       fail(FileTransferError.CONNECTION_ERR, xhr.response);
236     });
237
238     xhr.addEventListener('load', function (evt) {
239       if ((xhr.status === 200 || xhr.status === 0) && xhr.response) {
240         try {
241           tizen.filesystem.resolve(dirPath, function (dir) {
242             if (dir.isFile) {
243               fail(FileTransferError.FILE_NOT_FOUND_ERR);
244               return;
245             }
246
247             function writeFile(dir) {
248               var file = dir.createFile(fileName);
249
250               file.openStream(
251                 'rw',
252                 function (stream) {
253                   stream.writeBytes(Array.prototype.slice.call(new Uint8Array(xhr.response)));
254
255                   delete downloads[id];
256
257                   resolveLocalFileSystemURL(
258                     filePath,
259                     function (fileEntry) {
260                       fileEntry.filesystemName = fileEntry.filesystem.name;
261                       successCallback(fileEntry);
262                     }, function (err) {
263                       fail(TizenErrCodeToErrCode(err.code));
264                     });
265                 }, function (err) {
266                   fail(TizenErrCodeToErrCode(err.code));
267                 }
268               );
269             }
270
271             dir.deleteFile(
272               filePath,
273               function() {
274                 writeFile(dir);
275               }, function (err) {
276                 writeFile(dir);
277               });
278
279           }, function (err) {
280             fail(TizenErrCodeToErrCode(err.code));
281           },
282           'rw');
283         } catch(e) {
284           fail(FileTransferError.ABORT_ERR);
285         }
286       } else if (xhr.status === 404) {
287         fail(FileTransferError.INVALID_URL_ERR,
288              String.fromCharCode.apply(null, new Uint8Array(xhr.response)));
289       } else {
290         fail(FileTransferError.CONNECTION_ERR,
291              String.fromCharCode.apply(null, new Uint8Array(xhr.response)));
292       }
293     });
294
295     xhr.open('GET', url, true);
296     xhr.responseType = 'arraybuffer';
297     // Fill XHR headers
298     for (var header in headers) {
299       if (headers.hasOwnProperty(header)) {
300         xhr.setRequestHeader(header, headers[header]);
301       }
302     }
303     xhr.send();
304   },
305   abort: function(successCallback, errorCallback, args) {
306     var id = args[0];
307     if (uploads[id]) {
308       uploads[id].abort();
309       delete uploads[id];
310     } else if (downloads[id]) {
311       downloads[id].abort();
312       delete downloads[id];
313     } else {
314       console.warn('Unknown file transfer ID: ' + id);
315     }
316   },
317 };
318
319 require("cordova/exec/proxy").add("FileTransfer", exports);
320
321 console.log('Loaded cordova.file-transfer API');
322
323 // TODO: remove when added to public cordova repository -> begin
324 });
325 // TODO: remove -> end