openEventsPlatformApp/plugins/cordova-plugin-file/src/browser/FileProxy.js
plugins/cordova-plugin-file/src/browser/FileProxy.jsRaw
1/*
2 *
3 * Licensed to the Apache Software Foundation (ASF) under one
4 * or more contributor license agreements. See the NOTICE file
5 * distributed with this work for additional information
6 * regarding copyright ownership. The ASF licenses this file
7 * to you under the Apache License, Version 2.0 (the
8 * "License"); you may not use this file except in compliance
9 * with the License. You may obtain a copy of the License at
10 *
11 * http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing,
14 * software distributed under the License is distributed on an
15 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16 * KIND, either express or implied. See the License for the
17 * specific language governing permissions and limitations
18 * under the License.
19 *
20 */
21(function() {
22 /*global require, exports, module*/
23 /*global FILESYSTEM_PREFIX*/
24 /*global IDBKeyRange*/
25
26 /* Heavily based on https://github.com/ebidel/idb.filesystem.js */
27
28 // For chrome we don't need to implement proxy methods
29 // All functionality can be accessed natively.
30 if (require('./isChrome')()) {
31 var pathsPrefix = {
32 // Read-only directory where the application is installed.
33 applicationDirectory: location.origin + "/",
34 // Where to put app-specific data files.
35 dataDirectory: 'filesystem:file:///persistent/',
36 // Cached files that should survive app restarts.
37 // Apps should not rely on the OS to delete files in here.
38 cacheDirectory: 'filesystem:file:///temporary/',
39 };
40
41 exports.requestAllPaths = function(successCallback) {
42 successCallback(pathsPrefix);
43 };
44
45 require("cordova/exec/proxy").add("File", module.exports);
46 return;
47 }
48
49 var LocalFileSystem = require('./LocalFileSystem'),
50 FileSystem = require('./FileSystem'),
51 FileEntry = require('./FileEntry'),
52 FileError = require('./FileError'),
53 DirectoryEntry = require('./DirectoryEntry'),
54 File = require('./File');
55
56 (function(exports, global) {
57 var indexedDB = global.indexedDB || global.mozIndexedDB;
58 if (!indexedDB) {
59 throw "Firefox OS File plugin: indexedDB not supported";
60 }
61
62 var fs_ = null;
63
64 var idb_ = {};
65 idb_.db = null;
66 var FILE_STORE_ = 'entries';
67
68 var DIR_SEPARATOR = '/';
69
70 var pathsPrefix = {
71 // Read-only directory where the application is installed.
72 applicationDirectory: location.origin + "/",
73 // Where to put app-specific data files.
74 dataDirectory: 'file:///persistent/',
75 // Cached files that should survive app restarts.
76 // Apps should not rely on the OS to delete files in here.
77 cacheDirectory: 'file:///temporary/',
78 };
79
80 var unicodeLastChar = 65535;
81
82 /*** Exported functionality ***/
83
84 exports.requestFileSystem = function(successCallback, errorCallback, args) {
85 var type = args[0];
86 // Size is ignored since IDB filesystem size depends
87 // on browser implementation and can't be set up by user
88 var size = args[1]; // jshint ignore: line
89
90 if (type !== LocalFileSystem.TEMPORARY && type !== LocalFileSystem.PERSISTENT) {
91 if (errorCallback) {
92 errorCallback(FileError.INVALID_MODIFICATION_ERR);
93 }
94 return;
95 }
96
97 var name = type === LocalFileSystem.TEMPORARY ? 'temporary' : 'persistent';
98 var storageName = (location.protocol + location.host).replace(/:/g, '_');
99
100 var root = new DirectoryEntry('', DIR_SEPARATOR);
101 fs_ = new FileSystem(name, root);
102
103 idb_.open(storageName, function() {
104 successCallback(fs_);
105 }, errorCallback);
106 };
107
108 // Overridden by Android, BlackBerry 10 and iOS to populate fsMap
109 require('./fileSystems').getFs = function(name, callback) {
110 callback(new FileSystem(name, fs_.root));
111 };
112
113 // list a directory's contents (files and folders).
114 exports.readEntries = function(successCallback, errorCallback, args) {
115 var fullPath = args[0];
116
117 if (typeof successCallback !== 'function') {
118 throw Error('Expected successCallback argument.');
119 }
120
121 var path = resolveToFullPath_(fullPath);
122
123 exports.getDirectory(function() {
124 idb_.getAllEntries(path.fullPath + DIR_SEPARATOR, path.storagePath, function(entries) {
125 successCallback(entries);
126 }, errorCallback);
127 }, function() {
128 if (errorCallback) {
129 errorCallback(FileError.NOT_FOUND_ERR);
130 }
131 }, [path.storagePath, path.fullPath, {create: false}]);
132 };
133
134 exports.getFile = function(successCallback, errorCallback, args) {
135 var fullPath = args[0];
136 var path = args[1];
137 var options = args[2] || {};
138
139 // Create an absolute path if we were handed a relative one.
140 path = resolveToFullPath_(fullPath, path);
141
142 idb_.get(path.storagePath, function(fileEntry) {
143 if (options.create === true && options.exclusive === true && fileEntry) {
144 // If create and exclusive are both true, and the path already exists,
145 // getFile must fail.
146
147 if (errorCallback) {
148 errorCallback(FileError.PATH_EXISTS_ERR);
149 }
150 } else if (options.create === true && !fileEntry) {
151 // If create is true, the path doesn't exist, and no other error occurs,
152 // getFile must create it as a zero-length file and return a corresponding
153 // FileEntry.
154 var newFileEntry = new FileEntry(path.fileName, path.fullPath, new FileSystem(path.fsName, fs_.root));
155
156 newFileEntry.file_ = new MyFile({
157 size: 0,
158 name: newFileEntry.name,
159 lastModifiedDate: new Date(),
160 storagePath: path.storagePath
161 });
162
163 idb_.put(newFileEntry, path.storagePath, successCallback, errorCallback);
164 } else if (options.create === true && fileEntry) {
165 if (fileEntry.isFile) {
166 // Overwrite file, delete then create new.
167 idb_['delete'](path.storagePath, function() {
168 var newFileEntry = new FileEntry(path.fileName, path.fullPath, new FileSystem(path.fsName, fs_.root));
169
170 newFileEntry.file_ = new MyFile({
171 size: 0,
172 name: newFileEntry.name,
173 lastModifiedDate: new Date(),
174 storagePath: path.storagePath
175 });
176
177 idb_.put(newFileEntry, path.storagePath, successCallback, errorCallback);
178 }, errorCallback);
179 } else {
180 if (errorCallback) {
181 errorCallback(FileError.INVALID_MODIFICATION_ERR);
182 }
183 }
184 } else if ((!options.create || options.create === false) && !fileEntry) {
185 // If create is not true and the path doesn't exist, getFile must fail.
186 if (errorCallback) {
187 errorCallback(FileError.NOT_FOUND_ERR);
188 }
189 } else if ((!options.create || options.create === false) && fileEntry &&
190 fileEntry.isDirectory) {
191 // If create is not true and the path exists, but is a directory, getFile
192 // must fail.
193 if (errorCallback) {
194 errorCallback(FileError.TYPE_MISMATCH_ERR);
195 }
196 } else {
197 // Otherwise, if no other error occurs, getFile must return a FileEntry
198 // corresponding to path.
199
200 successCallback(fileEntryFromIdbEntry(fileEntry));
201 }
202 }, errorCallback);
203 };
204
205 exports.getFileMetadata = function(successCallback, errorCallback, args) {
206 var fullPath = args[0];
207
208 exports.getFile(function(fileEntry) {
209 successCallback(new File(fileEntry.file_.name, fileEntry.fullPath, '', fileEntry.file_.lastModifiedDate,
210 fileEntry.file_.size));
211 }, errorCallback, [fullPath, null]);
212 };
213
214 exports.getMetadata = function(successCallback, errorCallback, args) {
215 exports.getFile(function (fileEntry) {
216 successCallback(
217 {
218 modificationTime: fileEntry.file_.lastModifiedDate,
219 size: fileEntry.file_.lastModifiedDate
220 });
221 }, errorCallback, args);
222 };
223
224 exports.setMetadata = function(successCallback, errorCallback, args) {
225 var fullPath = args[0];
226 var metadataObject = args[1];
227
228 exports.getFile(function (fileEntry) {
229 fileEntry.file_.lastModifiedDate = metadataObject.modificationTime;
230 idb_.put(fileEntry, fileEntry.file_.storagePath, successCallback, errorCallback);
231 }, errorCallback, [fullPath, null]);
232 };
233
234 exports.write = function(successCallback, errorCallback, args) {
235 var fileName = args[0],
236 data = args[1],
237 position = args[2],
238 isBinary = args[3]; // jshint ignore: line
239
240 if (!data) {
241 if (errorCallback) {
242 errorCallback(FileError.INVALID_MODIFICATION_ERR);
243 }
244 return;
245 }
246
247 if (typeof data === 'string' || data instanceof String) {
248 data = new Blob([data]);
249 }
250
251 exports.getFile(function(fileEntry) {
252 var blob_ = fileEntry.file_.blob_;
253
254 if (!blob_) {
255 blob_ = new Blob([data], {type: data.type});
256 } else {
257 // Calc the head and tail fragments
258 var head = blob_.slice(0, position);
259 var tail = blob_.slice(position + (data.size || data.byteLength));
260
261 // Calc the padding
262 var padding = position - head.size;
263 if (padding < 0) {
264 padding = 0;
265 }
266
267 // Do the "write". In fact, a full overwrite of the Blob.
268 blob_ = new Blob([head, new Uint8Array(padding), data, tail],
269 {type: data.type});
270 }
271
272 // Set the blob we're writing on this file entry so we can recall it later.
273 fileEntry.file_.blob_ = blob_;
274 fileEntry.file_.lastModifiedDate = new Date() || null;
275 fileEntry.file_.size = blob_.size;
276 fileEntry.file_.name = blob_.name;
277 fileEntry.file_.type = blob_.type;
278
279 idb_.put(fileEntry, fileEntry.file_.storagePath, function() {
280 successCallback(data.size || data.byteLength);
281 }, errorCallback);
282 }, errorCallback, [fileName, null]);
283 };
284
285 exports.readAsText = function(successCallback, errorCallback, args) {
286 var fileName = args[0],
287 enc = args[1],
288 startPos = args[2],
289 endPos = args[3];
290
291 readAs('text', fileName, enc, startPos, endPos, successCallback, errorCallback);
292 };
293
294 exports.readAsDataURL = function(successCallback, errorCallback, args) {
295 var fileName = args[0],
296 startPos = args[1],
297 endPos = args[2];
298
299 readAs('dataURL', fileName, null, startPos, endPos, successCallback, errorCallback);
300 };
301
302 exports.readAsBinaryString = function(successCallback, errorCallback, args) {
303 var fileName = args[0],
304 startPos = args[1],
305 endPos = args[2];
306
307 readAs('binaryString', fileName, null, startPos, endPos, successCallback, errorCallback);
308 };
309
310 exports.readAsArrayBuffer = function(successCallback, errorCallback, args) {
311 var fileName = args[0],
312 startPos = args[1],
313 endPos = args[2];
314
315 readAs('arrayBuffer', fileName, null, startPos, endPos, successCallback, errorCallback);
316 };
317
318 exports.removeRecursively = exports.remove = function(successCallback, errorCallback, args) {
319 if (typeof successCallback !== 'function') {
320 throw Error('Expected successCallback argument.');
321 }
322
323 var fullPath = resolveToFullPath_(args[0]).storagePath;
324 if (fullPath === pathsPrefix.cacheDirectory || fullPath === pathsPrefix.dataDirectory) {
325 errorCallback(FileError.NO_MODIFICATION_ALLOWED_ERR);
326 return;
327 }
328
329 function deleteEntry(isDirectory) {
330 // TODO: This doesn't protect against directories that have content in it.
331 // Should throw an error instead if the dirEntry is not empty.
332 idb_['delete'](fullPath, function() {
333 successCallback();
334 }, function() {
335 if (errorCallback) { errorCallback(); }
336 }, isDirectory);
337 }
338
339 // We need to to understand what we are deleting:
340 exports.getDirectory(function(entry) {
341 deleteEntry(entry.isDirectory);
342 }, function(){
343 //DirectoryEntry was already deleted or entry is FileEntry
344 deleteEntry(false);
345 }, [fullPath, null, {create: false}]);
346 };
347
348 exports.getDirectory = function(successCallback, errorCallback, args) {
349 var fullPath = args[0];
350 var path = args[1];
351 var options = args[2];
352
353 // Create an absolute path if we were handed a relative one.
354 path = resolveToFullPath_(fullPath, path);
355
356 idb_.get(path.storagePath, function(folderEntry) {
357 if (!options) {
358 options = {};
359 }
360
361 if (options.create === true && options.exclusive === true && folderEntry) {
362 // If create and exclusive are both true, and the path already exists,
363 // getDirectory must fail.
364 if (errorCallback) {
365 errorCallback(FileError.PATH_EXISTS_ERR);
366 }
367 // There is a strange bug in mobilespec + FF, which results in coming to multiple else-if's
368 // so we are shielding from it with returns.
369 return;
370 }
371
372 if (options.create === true && !folderEntry) {
373 // If create is true, the path doesn't exist, and no other error occurs,
374 // getDirectory must create it as a zero-length file and return a corresponding
375 // MyDirectoryEntry.
376 var dirEntry = new DirectoryEntry(path.fileName, path.fullPath, new FileSystem(path.fsName, fs_.root));
377
378 idb_.put(dirEntry, path.storagePath, successCallback, errorCallback);
379 return;
380 }
381
382 if (options.create === true && folderEntry) {
383
384 if (folderEntry.isDirectory) {
385 // IDB won't save methods, so we need re-create the MyDirectoryEntry.
386 successCallback(new DirectoryEntry(folderEntry.name, folderEntry.fullPath, folderEntry.filesystem));
387 } else {
388 if (errorCallback) {
389 errorCallback(FileError.INVALID_MODIFICATION_ERR);
390 }
391 }
392 return;
393 }
394
395 if ((!options.create || options.create === false) && !folderEntry) {
396 // Handle root special. It should always exist.
397 if (path.fullPath === DIR_SEPARATOR) {
398 successCallback(fs_.root);
399 return;
400 }
401
402 // If create is not true and the path doesn't exist, getDirectory must fail.
403 if (errorCallback) {
404 errorCallback(FileError.NOT_FOUND_ERR);
405 }
406
407 return;
408 }
409 if ((!options.create || options.create === false) && folderEntry && folderEntry.isFile) {
410 // If create is not true and the path exists, but is a file, getDirectory
411 // must fail.
412 if (errorCallback) {
413 errorCallback(FileError.TYPE_MISMATCH_ERR);
414 }
415 return;
416 }
417
418 // Otherwise, if no other error occurs, getDirectory must return a
419 // MyDirectoryEntry corresponding to path.
420
421 // IDB won't' save methods, so we need re-create MyDirectoryEntry.
422 successCallback(new DirectoryEntry(folderEntry.name, folderEntry.fullPath, folderEntry.filesystem));
423 }, errorCallback);
424 };
425
426 exports.getParent = function(successCallback, errorCallback, args) {
427 if (typeof successCallback !== 'function') {
428 throw Error('Expected successCallback argument.');
429 }
430
431 var fullPath = args[0];
432 //fullPath is like this:
433 //file:///persistent/path/to/file or
434 //file:///persistent/path/to/directory/
435
436 if (fullPath === DIR_SEPARATOR || fullPath === pathsPrefix.cacheDirectory ||
437 fullPath === pathsPrefix.dataDirectory) {
438 successCallback(fs_.root);
439 return;
440 }
441
442 //To delete all slashes at the end
443 while (fullPath[fullPath.length - 1] === '/') {
444 fullPath = fullPath.substr(0, fullPath.length - 1);
445 }
446
447 var pathArr = fullPath.split(DIR_SEPARATOR);
448 pathArr.pop();
449 var parentName = pathArr.pop();
450 var path = pathArr.join(DIR_SEPARATOR) + DIR_SEPARATOR;
451
452 //To get parent of root files
453 var joined = path + parentName + DIR_SEPARATOR;//is like this: file:///persistent/
454 if (joined === pathsPrefix.cacheDirectory || joined === pathsPrefix.dataDirectory) {
455 exports.getDirectory(successCallback, errorCallback, [joined, DIR_SEPARATOR, {create: false}]);
456 return;
457 }
458
459 exports.getDirectory(successCallback, errorCallback, [path, parentName, {create: false}]);
460 };
461
462 exports.copyTo = function(successCallback, errorCallback, args) {
463 var srcPath = args[0];
464 var parentFullPath = args[1];
465 var name = args[2];
466
467 if (name.indexOf('/') !== -1 || srcPath === parentFullPath + name) {
468 if (errorCallback) {
469 errorCallback(FileError.INVALID_MODIFICATION_ERR);
470 }
471
472 return;
473 }
474
475 // Read src file
476 exports.getFile(function(srcFileEntry) {
477
478 var path = resolveToFullPath_(parentFullPath);
479 //Check directory
480 exports.getDirectory(function() {
481
482 // Create dest file
483 exports.getFile(function(dstFileEntry) {
484
485 exports.write(function() {
486 successCallback(dstFileEntry);
487 }, errorCallback, [dstFileEntry.file_.storagePath, srcFileEntry.file_.blob_, 0]);
488
489 }, errorCallback, [parentFullPath, name, {create: true}]);
490
491 }, function() { if (errorCallback) { errorCallback(FileError.NOT_FOUND_ERR); }},
492 [path.storagePath, null, {create:false}]);
493
494 }, errorCallback, [srcPath, null]);
495 };
496
497 exports.moveTo = function(successCallback, errorCallback, args) {
498 var srcPath = args[0];
499 // parentFullPath and name parameters is ignored because
500 // args is being passed downstream to exports.copyTo method
501 var parentFullPath = args[1]; // jshint ignore: line
502 var name = args[2]; // jshint ignore: line
503
504 exports.copyTo(function (fileEntry) {
505
506 exports.remove(function () {
507 successCallback(fileEntry);
508 }, errorCallback, [srcPath]);
509
510 }, errorCallback, args);
511 };
512
513 exports.resolveLocalFileSystemURI = function(successCallback, errorCallback, args) {
514 var path = args[0];
515
516 // Ignore parameters
517 if (path.indexOf('?') !== -1) {
518 path = String(path).split("?")[0];
519 }
520
521 // support for encodeURI
522 if (/\%5/g.test(path) || /\%20/g.test(path)) {
523 path = decodeURI(path);
524 }
525
526 if (path.trim()[0] === '/') {
527 if (errorCallback) {
528 errorCallback(FileError.ENCODING_ERR);
529 }
530 return;
531 }
532
533 //support for cdvfile
534 if (path.trim().substr(0,7) === "cdvfile") {
535 if (path.indexOf("cdvfile://localhost") === -1) {
536 if (errorCallback) {
537 errorCallback(FileError.ENCODING_ERR);
538 }
539 return;
540 }
541
542 var indexPersistent = path.indexOf("persistent");
543 var indexTemporary = path.indexOf("temporary");
544
545 //cdvfile://localhost/persistent/path/to/file
546 if (indexPersistent !== -1) {
547 path = "file:///persistent" + path.substr(indexPersistent + 10);
548 } else if (indexTemporary !== -1) {
549 path = "file:///temporary" + path.substr(indexTemporary + 9);
550 } else {
551 if (errorCallback) {
552 errorCallback(FileError.ENCODING_ERR);
553 }
554 return;
555 }
556 }
557
558 // to avoid path form of '///path/to/file'
559 function handlePathSlashes(path) {
560 var cutIndex = 0;
561 for (var i = 0; i < path.length - 1; i++) {
562 if (path[i] === DIR_SEPARATOR && path[i + 1] === DIR_SEPARATOR) {
563 cutIndex = i + 1;
564 } else break;
565 }
566
567 return path.substr(cutIndex);
568 }
569
570 // Handle localhost containing paths (see specs )
571 if (path.indexOf('file://localhost/') === 0) {
572 path = path.replace('file://localhost/', 'file:///');
573 }
574
575 if (path.indexOf(pathsPrefix.dataDirectory) === 0) {
576 path = path.substring(pathsPrefix.dataDirectory.length - 1);
577 path = handlePathSlashes(path);
578
579 exports.requestFileSystem(function() {
580 exports.getFile(successCallback, function() {
581 exports.getDirectory(successCallback, errorCallback, [pathsPrefix.dataDirectory, path,
582 {create: false}]);
583 }, [pathsPrefix.dataDirectory, path, {create: false}]);
584 }, errorCallback, [LocalFileSystem.PERSISTENT]);
585 } else if (path.indexOf(pathsPrefix.cacheDirectory) === 0) {
586 path = path.substring(pathsPrefix.cacheDirectory.length - 1);
587 path = handlePathSlashes(path);
588
589 exports.requestFileSystem(function() {
590 exports.getFile(successCallback, function() {
591 exports.getDirectory(successCallback, errorCallback, [pathsPrefix.cacheDirectory, path,
592 {create: false}]);
593 }, [pathsPrefix.cacheDirectory, path, {create: false}]);
594 }, errorCallback, [LocalFileSystem.TEMPORARY]);
595 } else if (path.indexOf(pathsPrefix.applicationDirectory) === 0) {
596 path = path.substring(pathsPrefix.applicationDirectory.length);
597 //TODO: need to cut out redundant slashes?
598
599 var xhr = new XMLHttpRequest();
600 xhr.open("GET", path, true);
601 xhr.onreadystatechange = function () {
602 if (xhr.status === 200 && xhr.readyState === 4) {
603 exports.requestFileSystem(function(fs) {
604 fs.name = location.hostname;
605
606 //TODO: need to call exports.getFile(...) to handle errors correct
607 fs.root.getFile(path, {create: true}, writeFile, errorCallback);
608 }, errorCallback, [LocalFileSystem.PERSISTENT]);
609 }
610 };
611
612 xhr.onerror = function () {
613 if(errorCallback) {
614 errorCallback(FileError.NOT_READABLE_ERR);
615 }
616 };
617
618 xhr.send();
619 } else {
620 if(errorCallback) {
621 errorCallback(FileError.NOT_FOUND_ERR);
622 }
623 }
624
625 function writeFile(entry) {
626 entry.createWriter(function (fileWriter) {
627 fileWriter.onwriteend = function (evt) {
628 if (!evt.target.error) {
629 entry.filesystemName = location.hostname;
630 successCallback(entry);
631 }
632 };
633 fileWriter.onerror = function () {
634 if (errorCallback) {
635 errorCallback(FileError.NOT_READABLE_ERR);
636 }
637 };
638 fileWriter.write(new Blob([xhr.response]));
639 }, errorCallback);
640 }
641 };
642
643 exports.requestAllPaths = function(successCallback) {
644 successCallback(pathsPrefix);
645 };
646
647 /*** Helpers ***/
648
649 /**
650 * Interface to wrap the native File interface.
651 *
652 * This interface is necessary for creating zero-length (empty) files,
653 * something the Filesystem API allows you to do. Unfortunately, File's
654 * constructor cannot be called directly, making it impossible to instantiate
655 * an empty File in JS.
656 *
657 * @param {Object} opts Initial values.
658 * @constructor
659 */
660 function MyFile(opts) {
661 var blob_ = new Blob();
662
663 this.size = opts.size || 0;
664 this.name = opts.name || '';
665 this.type = opts.type || '';
666 this.lastModifiedDate = opts.lastModifiedDate || null;
667 this.storagePath = opts.storagePath || '';
668
669 // Need some black magic to correct the object's size/name/type based on the
670 // blob that is saved.
671 Object.defineProperty(this, 'blob_', {
672 enumerable: true,
673 get: function() {
674 return blob_;
675 },
676 set: function(val) {
677 blob_ = val;
678 this.size = blob_.size;
679 this.name = blob_.name;
680 this.type = blob_.type;
681 this.lastModifiedDate = blob_.lastModifiedDate;
682 }.bind(this)
683 });
684 }
685
686 MyFile.prototype.constructor = MyFile;
687
688 // When saving an entry, the fullPath should always lead with a slash and never
689 // end with one (e.g. a directory). Also, resolve '.' and '..' to an absolute
690 // one. This method ensures path is legit!
691 function resolveToFullPath_(cwdFullPath, path) {
692 path = path || '';
693 var fullPath = path;
694 var prefix = '';
695
696 cwdFullPath = cwdFullPath || DIR_SEPARATOR;
697 if (cwdFullPath.indexOf(FILESYSTEM_PREFIX) === 0) {
698 prefix = cwdFullPath.substring(0, cwdFullPath.indexOf(DIR_SEPARATOR, FILESYSTEM_PREFIX.length));
699 cwdFullPath = cwdFullPath.substring(cwdFullPath.indexOf(DIR_SEPARATOR, FILESYSTEM_PREFIX.length));
700 }
701
702 var relativePath = path[0] !== DIR_SEPARATOR;
703 if (relativePath) {
704 fullPath = cwdFullPath;
705 if (cwdFullPath !== DIR_SEPARATOR) {
706 fullPath += DIR_SEPARATOR + path;
707 } else {
708 fullPath += path;
709 }
710 }
711
712 // Remove doubled separator substrings
713 var re = new RegExp(DIR_SEPARATOR + DIR_SEPARATOR, 'g');
714 fullPath = fullPath.replace(re, DIR_SEPARATOR);
715
716 // Adjust '..'s by removing parent directories when '..' flows in path.
717 var parts = fullPath.split(DIR_SEPARATOR);
718 for (var i = 0; i < parts.length; ++i) {
719 var part = parts[i];
720 if (part === '..') {
721 parts[i - 1] = '';
722 parts[i] = '';
723 }
724 }
725 fullPath = parts.filter(function(el) {
726 return el;
727 }).join(DIR_SEPARATOR);
728
729 // Add back in leading slash.
730 if (fullPath[0] !== DIR_SEPARATOR) {
731 fullPath = DIR_SEPARATOR + fullPath;
732 }
733
734 // Replace './' by current dir. ('./one/./two' -> one/two)
735 fullPath = fullPath.replace(/\.\//g, DIR_SEPARATOR);
736
737 // Replace '//' with '/'.
738 fullPath = fullPath.replace(/\/\//g, DIR_SEPARATOR);
739
740 // Replace '/.' with '/'.
741 fullPath = fullPath.replace(/\/\./g, DIR_SEPARATOR);
742
743 // Remove '/' if it appears on the end.
744 if (fullPath[fullPath.length - 1] === DIR_SEPARATOR &&
745 fullPath !== DIR_SEPARATOR) {
746 fullPath = fullPath.substring(0, fullPath.length - 1);
747 }
748
749 var storagePath = prefix + fullPath;
750 storagePath = decodeURI(storagePath);
751 fullPath = decodeURI(fullPath);
752
753 return {
754 storagePath: storagePath,
755 fullPath: fullPath,
756 fileName: fullPath.split(DIR_SEPARATOR).pop(),
757 fsName: prefix.split(DIR_SEPARATOR).pop()
758 };
759 }
760
761 function fileEntryFromIdbEntry(fileEntry) {
762 // IDB won't save methods, so we need re-create the FileEntry.
763 var clonedFileEntry = new FileEntry(fileEntry.name, fileEntry.fullPath, fileEntry.filesystem);
764 clonedFileEntry.file_ = fileEntry.file_;
765
766 return clonedFileEntry;
767 }
768
769 function readAs(what, fullPath, encoding, startPos, endPos, successCallback, errorCallback) {
770 exports.getFile(function(fileEntry) {
771 var fileReader = new FileReader(),
772 blob = fileEntry.file_.blob_.slice(startPos, endPos);
773
774 fileReader.onload = function(e) {
775 successCallback(e.target.result);
776 };
777
778 fileReader.onerror = errorCallback;
779
780 switch (what) {
781 case 'text':
782 fileReader.readAsText(blob, encoding);
783 break;
784 case 'dataURL':
785 fileReader.readAsDataURL(blob);
786 break;
787 case 'arrayBuffer':
788 fileReader.readAsArrayBuffer(blob);
789 break;
790 case 'binaryString':
791 fileReader.readAsBinaryString(blob);
792 break;
793 }
794
795 }, errorCallback, [fullPath, null]);
796 }
797
798 /*** Core logic to handle IDB operations ***/
799
800 idb_.open = function(dbName, successCallback, errorCallback) {
801 var self = this;
802
803 // TODO: FF 12.0a1 isn't liking a db name with : in it.
804 var request = indexedDB.open(dbName.replace(':', '_')/*, 1 /*version*/);
805
806 request.onerror = errorCallback || onError;
807
808 request.onupgradeneeded = function(e) {
809 // First open was called or higher db version was used.
810
811 // console.log('onupgradeneeded: oldVersion:' + e.oldVersion,
812 // 'newVersion:' + e.newVersion);
813
814 self.db = e.target.result;
815 self.db.onerror = onError;
816
817 if (!self.db.objectStoreNames.contains(FILE_STORE_)) {
818 self.db.createObjectStore(FILE_STORE_/*,{keyPath: 'id', autoIncrement: true}*/);
819 }
820 };
821
822 request.onsuccess = function(e) {
823 self.db = e.target.result;
824 self.db.onerror = onError;
825 successCallback(e);
826 };
827
828 request.onblocked = errorCallback || onError;
829 };
830
831 idb_.close = function() {
832 this.db.close();
833 this.db = null;
834 };
835
836 idb_.get = function(fullPath, successCallback, errorCallback) {
837 if (!this.db) {
838 if (errorCallback) {
839 errorCallback(FileError.INVALID_MODIFICATION_ERR);
840 }
841 return;
842 }
843
844 var tx = this.db.transaction([FILE_STORE_], 'readonly');
845
846 var request = tx.objectStore(FILE_STORE_).get(fullPath);
847
848 tx.onabort = errorCallback || onError;
849 tx.oncomplete = function() {
850 successCallback(request.result);
851 };
852 };
853
854 idb_.getAllEntries = function(fullPath, storagePath, successCallback, errorCallback) {
855 if (!this.db) {
856 if (errorCallback) {
857 errorCallback(FileError.INVALID_MODIFICATION_ERR);
858 }
859 return;
860 }
861
862 var results = [];
863
864 if (storagePath[storagePath.length - 1] === DIR_SEPARATOR) {
865 storagePath = storagePath.substring(0, storagePath.length - 1);
866 }
867
868 var range = IDBKeyRange.bound(storagePath + DIR_SEPARATOR + ' ',
869 storagePath + DIR_SEPARATOR + String.fromCharCode(unicodeLastChar));
870
871 var tx = this.db.transaction([FILE_STORE_], 'readonly');
872 tx.onabort = errorCallback || onError;
873 tx.oncomplete = function() {
874 results = results.filter(function(val) {
875 var pathWithoutSlash = val.fullPath;
876
877 if (val.fullPath[val.fullPath.length - 1] === DIR_SEPARATOR) {
878 pathWithoutSlash = pathWithoutSlash.substr(0, pathWithoutSlash.length - 1);
879 }
880
881 var valPartsLen = pathWithoutSlash.split(DIR_SEPARATOR).length;
882 var fullPathPartsLen = fullPath.split(DIR_SEPARATOR).length;
883
884 /* Input fullPath parameter equals '//' for root folder */
885 /* Entries in root folder has valPartsLen equals 2 (see below) */
886 if (fullPath[fullPath.length -1] === DIR_SEPARATOR && fullPath.trim().length === 2) {
887 fullPathPartsLen = 1;
888 } else if (fullPath[fullPath.length -1] === DIR_SEPARATOR) {
889 fullPathPartsLen = fullPath.substr(0, fullPath.length - 1).split(DIR_SEPARATOR).length;
890 } else {
891 fullPathPartsLen = fullPath.split(DIR_SEPARATOR).length;
892 }
893
894 if (valPartsLen === fullPathPartsLen + 1) {
895 // If this a subfolder and entry is a direct child, include it in
896 // the results. Otherwise, it's not an entry of this folder.
897 return val;
898 } else return false;
899 });
900
901 successCallback(results);
902 };
903
904 var request = tx.objectStore(FILE_STORE_).openCursor(range);
905
906 request.onsuccess = function(e) {
907 var cursor = e.target.result;
908 if (cursor) {
909 var val = cursor.value;
910
911 results.push(val.isFile ? fileEntryFromIdbEntry(val) : new DirectoryEntry(val.name, val.fullPath, val.filesystem));
912 cursor['continue']();
913 }
914 };
915 };
916
917 idb_['delete'] = function(fullPath, successCallback, errorCallback, isDirectory) {
918 if (!idb_.db) {
919 if (errorCallback) {
920 errorCallback(FileError.INVALID_MODIFICATION_ERR);
921 }
922 return;
923 }
924
925 var tx = this.db.transaction([FILE_STORE_], 'readwrite');
926 tx.oncomplete = successCallback;
927 tx.onabort = errorCallback || onError;
928 tx.oncomplete = function() {
929 if (isDirectory) {
930 //We delete nested files and folders after deleting parent folder
931 //We use ranges: https://developer.mozilla.org/en-US/docs/Web/API/IDBKeyRange
932 fullPath = fullPath + DIR_SEPARATOR;
933
934 //Range contains all entries in the form fullPath<symbol> where
935 //symbol in the range from ' ' to symbol which has code `unicodeLastChar`
936 var range = IDBKeyRange.bound(fullPath + ' ', fullPath + String.fromCharCode(unicodeLastChar));
937
938 var newTx = this.db.transaction([FILE_STORE_], 'readwrite');
939 newTx.oncomplete = successCallback;
940 newTx.onabort = errorCallback || onError;
941 newTx.objectStore(FILE_STORE_)['delete'](range);
942 } else {
943 successCallback();
944 }
945 };
946 tx.objectStore(FILE_STORE_)['delete'](fullPath);
947 };
948
949 idb_.put = function(entry, storagePath, successCallback, errorCallback) {
950 if (!this.db) {
951 if (errorCallback) {
952 errorCallback(FileError.INVALID_MODIFICATION_ERR);
953 }
954 return;
955 }
956
957 var tx = this.db.transaction([FILE_STORE_], 'readwrite');
958 tx.onabort = errorCallback || onError;
959 tx.oncomplete = function() {
960 // TODO: Error is thrown if we pass the request event back instead.
961 successCallback(entry);
962 };
963
964 tx.objectStore(FILE_STORE_).put(entry, storagePath);
965 };
966
967 // Global error handler. Errors bubble from request, to transaction, to db.
968 function onError(e) {
969 switch (e.target.errorCode) {
970 case 12:
971 console.log('Error - Attempt to open db with a lower version than the ' +
972 'current one.');
973 break;
974 default:
975 console.log('errorCode: ' + e.target.errorCode);
976 }
977
978 console.log(e, e.code, e.message);
979 }
980
981 })(module.exports, window);
982
983 require("cordova/exec/proxy").add("File", module.exports);
984})();