arnaucube
arnaucube/openEventsPlatformAppPublic Self-hosted repository
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})();