arnaucube
arnaucube/openEventsPlatformAppPublic Self-hosted repository
openEventsPlatformApp/plugins/cordova-plugin-file/src/firefoxos/FileProxy.js
plugins/cordova-plugin-file/src/firefoxos/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
 22/* global IDBKeyRange */
 23
 24var LocalFileSystem = require('./LocalFileSystem'),
 25    FileSystem = require('./FileSystem'),
 26    FileEntry = require('./FileEntry'),
 27    FileError = require('./FileError'),
 28    DirectoryEntry = require('./DirectoryEntry'),
 29    File = require('./File');
 30
 31/*
 32QUIRKS:
 33    Does not fail when removing non-empty directories
 34    Does not support metadata for directories
 35    Does not support requestAllFileSystems
 36    Does not support resolveLocalFileSystemURI
 37    Methods copyTo and moveTo do not support directories
 38
 39    Heavily based on https://github.com/ebidel/idb.filesystem.js
 40 */
 41
 42
 43(function(exports, global) {
 44    var indexedDB = global.indexedDB || global.mozIndexedDB;
 45    if (!indexedDB) {
 46        throw "Firefox OS File plugin: indexedDB not supported";
 47    }
 48
 49    var fs_ = null;
 50
 51    var idb_ = {};
 52    idb_.db = null;
 53    var FILE_STORE_ = 'entries';
 54
 55    var DIR_SEPARATOR = '/';
 56    var DIR_OPEN_BOUND = String.fromCharCode(DIR_SEPARATOR.charCodeAt(0) + 1);
 57
 58    var pathsPrefix = {
 59        // Read-only directory where the application is installed.
 60        applicationDirectory: location.origin + "/",
 61        // Where to put app-specific data files.
 62        dataDirectory: 'file:///persistent/',
 63        // Cached files that should survive app restarts.
 64        // Apps should not rely on the OS to delete files in here.
 65        cacheDirectory: 'file:///temporary/',
 66    };
 67
 68/*** Exported functionality ***/
 69
 70    exports.requestFileSystem = function(successCallback, errorCallback, args) {
 71        var type = args[0];
 72        //var size = args[1];
 73
 74        if (type !== LocalFileSystem.TEMPORARY && type !== LocalFileSystem.PERSISTENT) {
 75            if (errorCallback) {
 76                errorCallback(FileError.INVALID_MODIFICATION_ERR);
 77            }
 78            return;
 79        }
 80
 81        var name = type === LocalFileSystem.TEMPORARY ? 'temporary' : 'persistent';
 82        var storageName = (location.protocol + location.host).replace(/:/g, '_');
 83
 84        var root = new DirectoryEntry('', DIR_SEPARATOR);
 85        fs_ = new FileSystem(name, root);
 86
 87        idb_.open(storageName, function() {
 88            successCallback(fs_);
 89        }, errorCallback);
 90    };
 91
 92    require('./fileSystems').getFs = function(name, callback) {
 93        callback(new FileSystem(name, fs_.root));
 94    };
 95
 96    // list a directory's contents (files and folders).
 97    exports.readEntries = function(successCallback, errorCallback, args) {
 98        var fullPath = args[0];
 99
100        if (!successCallback) {
101            throw Error('Expected successCallback argument.');
102        }
103
104        var path = resolveToFullPath_(fullPath);
105
106        idb_.getAllEntries(path.fullPath, path.storagePath, function(entries) {
107            successCallback(entries);
108        }, errorCallback);
109    };
110
111    exports.getFile = function(successCallback, errorCallback, args) {
112        var fullPath = args[0];
113        var path = args[1];
114        var options = args[2] || {};
115
116        // Create an absolute path if we were handed a relative one.
117        path = resolveToFullPath_(fullPath, path);
118
119        idb_.get(path.storagePath, function(fileEntry) {
120            if (options.create === true && options.exclusive === true && fileEntry) {
121                // If create and exclusive are both true, and the path already exists,
122                // getFile must fail.
123
124                if (errorCallback) {
125                    errorCallback(FileError.PATH_EXISTS_ERR);
126                }
127            } else if (options.create === true && !fileEntry) {
128                // If create is true, the path doesn't exist, and no other error occurs,
129                // getFile must create it as a zero-length file and return a corresponding
130                // FileEntry.
131                var newFileEntry = new FileEntry(path.fileName, path.fullPath, new FileSystem(path.fsName, fs_.root));
132
133                newFileEntry.file_ = new MyFile({
134                    size: 0,
135                    name: newFileEntry.name,
136                    lastModifiedDate: new Date(),
137                    storagePath: path.storagePath
138                });
139
140                idb_.put(newFileEntry, path.storagePath, successCallback, errorCallback);
141            } else if (options.create === true && fileEntry) {
142                if (fileEntry.isFile) {
143                    // Overwrite file, delete then create new.
144                    idb_['delete'](path.storagePath, function() {
145                        var newFileEntry = new FileEntry(path.fileName, path.fullPath, new FileSystem(path.fsName, fs_.root));
146
147                        newFileEntry.file_ = new MyFile({
148                            size: 0,
149                            name: newFileEntry.name,
150                            lastModifiedDate: new Date(),
151                            storagePath: path.storagePath
152                        });
153
154                        idb_.put(newFileEntry, path.storagePath, successCallback, errorCallback);
155                    }, errorCallback);
156                } else {
157                    if (errorCallback) {
158                        errorCallback(FileError.INVALID_MODIFICATION_ERR);
159                    }
160                }
161            } else if ((!options.create || options.create === false) && !fileEntry) {
162                // If create is not true and the path doesn't exist, getFile must fail.
163                if (errorCallback) {
164                    errorCallback(FileError.NOT_FOUND_ERR);
165                }
166            } else if ((!options.create || options.create === false) && fileEntry &&
167                fileEntry.isDirectory) {
168                // If create is not true and the path exists, but is a directory, getFile
169                // must fail.
170                if (errorCallback) {
171                    errorCallback(FileError.INVALID_MODIFICATION_ERR);
172                }
173            } else {
174                // Otherwise, if no other error occurs, getFile must return a FileEntry
175                // corresponding to path.
176
177                successCallback(fileEntryFromIdbEntry(fileEntry));
178            }
179        }, errorCallback);
180    };
181
182    exports.getFileMetadata = function(successCallback, errorCallback, args) {
183        var fullPath = args[0];
184
185        exports.getFile(function(fileEntry) {
186            successCallback(new File(fileEntry.file_.name, fileEntry.fullPath, '', fileEntry.file_.lastModifiedDate,
187                fileEntry.file_.size));
188        }, errorCallback, [fullPath, null]);
189    };
190
191    exports.getMetadata = function(successCallback, errorCallback, args) {
192        exports.getFile(function (fileEntry) {
193            successCallback(
194                {
195                    modificationTime: fileEntry.file_.lastModifiedDate,
196                    size: fileEntry.file_.lastModifiedDate
197                });
198        }, errorCallback, args);
199    };
200
201    exports.setMetadata = function(successCallback, errorCallback, args) {
202        var fullPath = args[0];
203        var metadataObject = args[1];
204
205        exports.getFile(function (fileEntry) {
206              fileEntry.file_.lastModifiedDate = metadataObject.modificationTime;
207        }, errorCallback, [fullPath, null]);
208    };
209
210    exports.write = function(successCallback, errorCallback, args) {
211        var fileName = args[0],
212            data = args[1],
213            position = args[2];
214            //isBinary = args[3];
215
216        if (!data) {
217            if (errorCallback) {
218                errorCallback(FileError.INVALID_MODIFICATION_ERR);
219            }
220            return;
221        }
222
223        exports.getFile(function(fileEntry) {
224            var blob_ = fileEntry.file_.blob_;
225
226            if (!blob_) {
227                blob_ = new Blob([data], {type: data.type});
228            } else {
229                // Calc the head and tail fragments
230                var head = blob_.slice(0, position);
231                var tail = blob_.slice(position + data.byteLength);
232
233                // Calc the padding
234                var padding = position - head.size;
235                if (padding < 0) {
236                    padding = 0;
237                }
238
239                // Do the "write". In fact, a full overwrite of the Blob.
240                blob_ = new Blob([head, new Uint8Array(padding), data, tail],
241                    {type: data.type});
242            }
243
244            // Set the blob we're writing on this file entry so we can recall it later.
245            fileEntry.file_.blob_ = blob_;
246            fileEntry.file_.lastModifiedDate = data.lastModifiedDate || null;
247            fileEntry.file_.size = blob_.size;
248            fileEntry.file_.name = blob_.name;
249            fileEntry.file_.type = blob_.type;
250
251            idb_.put(fileEntry, fileEntry.file_.storagePath, function() {
252                successCallback(data.byteLength);
253            }, errorCallback);
254        }, errorCallback, [fileName, null]);
255    };
256
257    exports.readAsText = function(successCallback, errorCallback, args) {
258        var fileName = args[0],
259            enc = args[1],
260            startPos = args[2],
261            endPos = args[3];
262
263        readAs('text', fileName, enc, startPos, endPos, successCallback, errorCallback);
264    };
265
266    exports.readAsDataURL = function(successCallback, errorCallback, args) {
267        var fileName = args[0],
268            startPos = args[1],
269            endPos = args[2];
270
271        readAs('dataURL', fileName, null, startPos, endPos, successCallback, errorCallback);
272    };
273
274    exports.readAsBinaryString = function(successCallback, errorCallback, args) {
275        var fileName = args[0],
276            startPos = args[1],
277            endPos = args[2];
278
279        readAs('binaryString', fileName, null, startPos, endPos, successCallback, errorCallback);
280    };
281
282    exports.readAsArrayBuffer = function(successCallback, errorCallback, args) {
283        var fileName = args[0],
284            startPos = args[1],
285            endPos = args[2];
286
287        readAs('arrayBuffer', fileName, null, startPos, endPos, successCallback, errorCallback);
288    };
289
290    exports.removeRecursively = exports.remove = function(successCallback, errorCallback, args) {
291        var fullPath = args[0];
292
293        // TODO: This doesn't protect against directories that have content in it.
294        // Should throw an error instead if the dirEntry is not empty.
295        idb_['delete'](fullPath, function() {
296            successCallback();
297        }, errorCallback);
298    };
299
300    exports.getDirectory = function(successCallback, errorCallback, args) {
301        var fullPath = args[0];
302        var path = args[1];
303        var options = args[2];
304
305        // Create an absolute path if we were handed a relative one.
306        path = resolveToFullPath_(fullPath, path);
307
308        idb_.get(path.storagePath, function(folderEntry) {
309            if (!options) {
310                options = {};
311            }
312
313            if (options.create === true && options.exclusive === true && folderEntry) {
314                // If create and exclusive are both true, and the path already exists,
315                // getDirectory must fail.
316                if (errorCallback) {
317                    errorCallback(FileError.INVALID_MODIFICATION_ERR);
318                }
319            } else if (options.create === true && !folderEntry) {
320                // If create is true, the path doesn't exist, and no other error occurs,
321                // getDirectory must create it as a zero-length file and return a corresponding
322                // MyDirectoryEntry.
323                var dirEntry = new DirectoryEntry(path.fileName, path.fullPath, new FileSystem(path.fsName, fs_.root));
324
325                idb_.put(dirEntry, path.storagePath, successCallback, errorCallback);
326            } else if (options.create === true && folderEntry) {
327
328                if (folderEntry.isDirectory) {
329                    // IDB won't save methods, so we need re-create the MyDirectoryEntry.
330                    successCallback(new DirectoryEntry(folderEntry.name, folderEntry.fullPath, folderEntry.fileSystem));
331                } else {
332                    if (errorCallback) {
333                        errorCallback(FileError.INVALID_MODIFICATION_ERR);
334                    }
335                }
336            } else if ((!options.create || options.create === false) && !folderEntry) {
337                // Handle root special. It should always exist.
338                if (path.fullPath === DIR_SEPARATOR) {
339                    successCallback(fs_.root);
340                    return;
341                }
342
343                // If create is not true and the path doesn't exist, getDirectory must fail.
344                if (errorCallback) {
345                    errorCallback(FileError.NOT_FOUND_ERR);
346                }
347            } else if ((!options.create || options.create === false) && folderEntry &&
348                folderEntry.isFile) {
349                // If create is not true and the path exists, but is a file, getDirectory
350                // must fail.
351                if (errorCallback) {
352                    errorCallback(FileError.INVALID_MODIFICATION_ERR);
353                }
354            } else {
355                // Otherwise, if no other error occurs, getDirectory must return a
356                // MyDirectoryEntry corresponding to path.
357
358                // IDB won't' save methods, so we need re-create MyDirectoryEntry.
359                successCallback(new DirectoryEntry(folderEntry.name, folderEntry.fullPath, folderEntry.fileSystem));
360            }
361        }, errorCallback);
362    };
363
364    exports.getParent = function(successCallback, errorCallback, args) {
365        var fullPath = args[0];
366
367        if (fullPath === DIR_SEPARATOR) {
368            successCallback(fs_.root);
369            return;
370        }
371
372        var pathArr = fullPath.split(DIR_SEPARATOR);
373        pathArr.pop();
374        var namesa = pathArr.pop();
375        var path = pathArr.join(DIR_SEPARATOR);
376
377        exports.getDirectory(successCallback, errorCallback, [path, namesa, {create: false}]);
378    };
379
380    exports.copyTo = function(successCallback, errorCallback, args) {
381        var srcPath = args[0];
382        var parentFullPath = args[1];
383        var name = args[2];
384
385        // Read src file
386        exports.getFile(function(srcFileEntry) {
387
388            // Create dest file
389            exports.getFile(function(dstFileEntry) {
390
391                exports.write(function() {
392                    successCallback(dstFileEntry);
393                }, errorCallback, [dstFileEntry.file_.storagePath, srcFileEntry.file_.blob_, 0]);
394
395            }, errorCallback, [parentFullPath, name, {create: true}]);
396
397        }, errorCallback, [srcPath, null]);
398    };
399
400    exports.moveTo = function(successCallback, errorCallback, args) {
401        var srcPath = args[0];
402        //var parentFullPath = args[1];
403        //var name = args[2];
404
405        exports.copyTo(function (fileEntry) {
406
407            exports.remove(function () {
408                successCallback(fileEntry);
409            }, errorCallback, [srcPath]);
410
411        }, errorCallback, args);
412    };
413
414    exports.resolveLocalFileSystemURI = function(successCallback, errorCallback, args) {
415        var path = args[0];
416
417        // Ignore parameters
418        if (path.indexOf('?') !== -1) {
419            path = String(path).split("?")[0];
420        }
421
422        // support for encodeURI
423        if (/\%5/g.test(path)) {
424            path = decodeURI(path);
425        }
426
427        if (path.indexOf(pathsPrefix.dataDirectory) === 0) {
428            path = path.substring(pathsPrefix.dataDirectory.length - 1);
429
430            exports.requestFileSystem(function(fs) {
431                fs.root.getFile(path, {create: false}, successCallback, function() {
432                    fs.root.getDirectory(path, {create: false}, successCallback, errorCallback);
433                });
434            }, errorCallback, [LocalFileSystem.PERSISTENT]);
435        } else if (path.indexOf(pathsPrefix.cacheDirectory) === 0) {
436            path = path.substring(pathsPrefix.cacheDirectory.length - 1);
437
438            exports.requestFileSystem(function(fs) {
439                fs.root.getFile(path, {create: false}, successCallback, function() {
440                    fs.root.getDirectory(path, {create: false}, successCallback, errorCallback);
441                });
442            }, errorCallback, [LocalFileSystem.TEMPORARY]);
443        } else if (path.indexOf(pathsPrefix.applicationDirectory) === 0) {
444            path = path.substring(pathsPrefix.applicationDirectory.length);
445
446            var xhr = new XMLHttpRequest();
447            xhr.open("GET", path, true);
448            xhr.onreadystatechange = function () {
449                if (xhr.status === 200 && xhr.readyState === 4) {
450                    exports.requestFileSystem(function(fs) {
451                        fs.name = location.hostname;
452                        fs.root.getFile(path, {create: true}, writeFile, errorCallback);
453                    }, errorCallback, [LocalFileSystem.PERSISTENT]);
454                }
455            };
456
457            xhr.onerror = function () {
458                if (errorCallback) {
459                    errorCallback(FileError.NOT_READABLE_ERR);
460                }
461            };
462
463            xhr.send();
464        } else {
465            if (errorCallback) {
466                errorCallback(FileError.NOT_FOUND_ERR);
467            }
468        }
469
470        function writeFile(entry) {
471            entry.createWriter(function (fileWriter) {
472                fileWriter.onwriteend = function (evt) {
473                    if (!evt.target.error) {
474                        entry.filesystemName = location.hostname;
475                        successCallback(entry);
476                    }
477                };
478                fileWriter.onerror = function () {
479                    if (errorCallback) {
480                        errorCallback(FileError.NOT_READABLE_ERR);
481                    }
482                };
483                fileWriter.write(new Blob([xhr.response]));
484            }, errorCallback);
485        }
486    };
487
488    exports.requestAllPaths = function(successCallback) {
489        successCallback(pathsPrefix);
490    };
491
492/*** Helpers ***/
493
494    /**
495     * Interface to wrap the native File interface.
496     *
497     * This interface is necessary for creating zero-length (empty) files,
498     * something the Filesystem API allows you to do. Unfortunately, File's
499     * constructor cannot be called directly, making it impossible to instantiate
500     * an empty File in JS.
501     *
502     * @param {Object} opts Initial values.
503     * @constructor
504     */
505    function MyFile(opts) {
506        var blob_ = new Blob();
507
508        this.size = opts.size || 0;
509        this.name = opts.name || '';
510        this.type = opts.type || '';
511        this.lastModifiedDate = opts.lastModifiedDate || null;
512        this.storagePath = opts.storagePath || '';
513
514        // Need some black magic to correct the object's size/name/type based on the
515        // blob that is saved.
516        Object.defineProperty(this, 'blob_', {
517            enumerable: true,
518            get: function() {
519                return blob_;
520            },
521            set: function(val) {
522                blob_ = val;
523                this.size = blob_.size;
524                this.name = blob_.name;
525                this.type = blob_.type;
526                this.lastModifiedDate = blob_.lastModifiedDate;
527            }.bind(this)
528        });
529    }
530
531    MyFile.prototype.constructor = MyFile;
532
533    // When saving an entry, the fullPath should always lead with a slash and never
534    // end with one (e.g. a directory). Also, resolve '.' and '..' to an absolute
535    // one. This method ensures path is legit!
536    function resolveToFullPath_(cwdFullPath, path) {
537        path = path || '';
538        var fullPath = path;
539        var prefix = '';
540
541        cwdFullPath = cwdFullPath || DIR_SEPARATOR;
542        if (cwdFullPath.indexOf(FILESYSTEM_PREFIX) === 0) {
543            prefix = cwdFullPath.substring(0, cwdFullPath.indexOf(DIR_SEPARATOR, FILESYSTEM_PREFIX.length));
544            cwdFullPath = cwdFullPath.substring(cwdFullPath.indexOf(DIR_SEPARATOR, FILESYSTEM_PREFIX.length));
545        }
546
547        var relativePath = path[0] !== DIR_SEPARATOR;
548        if (relativePath) {
549            fullPath = cwdFullPath;
550            if (cwdFullPath != DIR_SEPARATOR) {
551                fullPath += DIR_SEPARATOR + path;
552            } else {
553                fullPath += path;
554            }
555        }
556
557        // Adjust '..'s by removing parent directories when '..' flows in path.
558        var parts = fullPath.split(DIR_SEPARATOR);
559        for (var i = 0; i < parts.length; ++i) {
560            var part = parts[i];
561            if (part == '..') {
562                parts[i - 1] = '';
563                parts[i] = '';
564            }
565        }
566        fullPath = parts.filter(function(el) {
567            return el;
568        }).join(DIR_SEPARATOR);
569
570        // Add back in leading slash.
571        if (fullPath[0] !== DIR_SEPARATOR) {
572            fullPath = DIR_SEPARATOR + fullPath;
573        }
574
575        // Replace './' by current dir. ('./one/./two' -> one/two)
576        fullPath = fullPath.replace(/\.\//g, DIR_SEPARATOR);
577
578        // Replace '//' with '/'.
579        fullPath = fullPath.replace(/\/\//g, DIR_SEPARATOR);
580
581        // Replace '/.' with '/'.
582        fullPath = fullPath.replace(/\/\./g, DIR_SEPARATOR);
583
584        // Remove '/' if it appears on the end.
585        if (fullPath[fullPath.length - 1] == DIR_SEPARATOR &&
586            fullPath != DIR_SEPARATOR) {
587            fullPath = fullPath.substring(0, fullPath.length - 1);
588        }
589
590        return {
591            storagePath: prefix + fullPath,
592            fullPath: fullPath,
593            fileName: fullPath.split(DIR_SEPARATOR).pop(),
594            fsName: prefix.split(DIR_SEPARATOR).pop()
595        };
596    }
597
598    function fileEntryFromIdbEntry(fileEntry) {
599        // IDB won't save methods, so we need re-create the FileEntry.
600        var clonedFileEntry = new FileEntry(fileEntry.name, fileEntry.fullPath, fileEntry.fileSystem);
601        clonedFileEntry.file_ = fileEntry.file_;
602
603        return clonedFileEntry;
604    }
605
606    function readAs(what, fullPath, encoding, startPos, endPos, successCallback, errorCallback) {
607        exports.getFile(function(fileEntry) {
608            var fileReader = new FileReader(),
609                blob = fileEntry.file_.blob_.slice(startPos, endPos);
610
611            fileReader.onload = function(e) {
612                successCallback(e.target.result);
613            };
614
615            fileReader.onerror = errorCallback;
616
617            switch (what) {
618                case 'text':
619                    fileReader.readAsText(blob, encoding);
620                    break;
621                case 'dataURL':
622                    fileReader.readAsDataURL(blob);
623                    break;
624                case 'arrayBuffer':
625                    fileReader.readAsArrayBuffer(blob);
626                    break;
627                case 'binaryString':
628                    fileReader.readAsBinaryString(blob);
629                    break;
630            }
631
632        }, errorCallback, [fullPath, null]);
633    }
634
635/*** Core logic to handle IDB operations ***/
636
637    idb_.open = function(dbName, successCallback, errorCallback) {
638        var self = this;
639
640        // TODO: FF 12.0a1 isn't liking a db name with : in it.
641        var request = indexedDB.open(dbName.replace(':', '_')/*, 1 /*version*/);
642
643        request.onerror = errorCallback || onError;
644
645        request.onupgradeneeded = function(e) {
646            // First open was called or higher db version was used.
647
648            // console.log('onupgradeneeded: oldVersion:' + e.oldVersion,
649            //           'newVersion:' + e.newVersion);
650
651            self.db = e.target.result;
652            self.db.onerror = onError;
653
654            if (!self.db.objectStoreNames.contains(FILE_STORE_)) {
655                self.db.createObjectStore(FILE_STORE_/*,{keyPath: 'id', autoIncrement: true}*/);
656            }
657        };
658
659        request.onsuccess = function(e) {
660            self.db = e.target.result;
661            self.db.onerror = onError;
662            successCallback(e);
663        };
664
665        request.onblocked = errorCallback || onError;
666    };
667
668    idb_.close = function() {
669        this.db.close();
670        this.db = null;
671    };
672
673    idb_.get = function(fullPath, successCallback, errorCallback) {
674        if (!this.db) {
675            if (errorCallback) {
676                errorCallback(FileError.INVALID_MODIFICATION_ERR);
677            }
678            return;
679        }
680
681        var tx = this.db.transaction([FILE_STORE_], 'readonly');
682
683        //var request = tx.objectStore(FILE_STORE_).get(fullPath);
684        var range = IDBKeyRange.bound(fullPath, fullPath + DIR_OPEN_BOUND,
685            false, true);
686        var request = tx.objectStore(FILE_STORE_).get(range);
687
688        tx.onabort = errorCallback || onError;
689        tx.oncomplete = function(e) {
690            successCallback(request.result);
691        };
692    };
693
694    idb_.getAllEntries = function(fullPath, storagePath, successCallback, errorCallback) {
695        if (!this.db) {
696            if (errorCallback) {
697                errorCallback(FileError.INVALID_MODIFICATION_ERR);
698            }
699            return;
700        }
701
702        var results = [];
703
704        if (storagePath[storagePath.length - 1] === DIR_SEPARATOR) {
705            storagePath = storagePath.substring(0, storagePath.length - 1);
706        }
707
708        var range = IDBKeyRange.bound(
709                storagePath + DIR_SEPARATOR, storagePath + DIR_OPEN_BOUND, false, true);
710
711        var tx = this.db.transaction([FILE_STORE_], 'readonly');
712        tx.onabort = errorCallback || onError;
713        tx.oncomplete = function(e) {
714            results = results.filter(function(val) {
715                var valPartsLen = val.fullPath.split(DIR_SEPARATOR).length;
716                var fullPathPartsLen = fullPath.split(DIR_SEPARATOR).length;
717
718                if (fullPath === DIR_SEPARATOR && valPartsLen < fullPathPartsLen + 1) {
719                    // Hack to filter out entries in the root folder. This is inefficient
720                    // because reading the entires of fs.root (e.g. '/') returns ALL
721                    // results in the database, then filters out the entries not in '/'.
722                    return val;
723                } else if (fullPath !== DIR_SEPARATOR &&
724                    valPartsLen === fullPathPartsLen + 1) {
725                    // If this a subfolder and entry is a direct child, include it in
726                    // the results. Otherwise, it's not an entry of this folder.
727                    return val;
728                }
729            });
730
731            successCallback(results);
732        };
733
734        var request = tx.objectStore(FILE_STORE_).openCursor(range);
735
736        request.onsuccess = function(e) {
737            var cursor = e.target.result;
738            if (cursor) {
739                var val = cursor.value;
740
741                results.push(val.isFile ? fileEntryFromIdbEntry(val) : new DirectoryEntry(val.name, val.fullPath, val.fileSystem));
742                cursor['continue']();
743            }
744        };
745    };
746
747    idb_['delete'] = function(fullPath, successCallback, errorCallback) {
748        if (!this.db) {
749            if (errorCallback) {
750                errorCallback(FileError.INVALID_MODIFICATION_ERR);
751            }
752            return;
753        }
754
755        var tx = this.db.transaction([FILE_STORE_], 'readwrite');
756        tx.oncomplete = successCallback;
757        tx.onabort = errorCallback || onError;
758
759        //var request = tx.objectStore(FILE_STORE_).delete(fullPath);
760        var range = IDBKeyRange.bound(
761            fullPath, fullPath + DIR_OPEN_BOUND, false, true);
762        tx.objectStore(FILE_STORE_)['delete'](range);
763    };
764
765    idb_.put = function(entry, storagePath, successCallback, errorCallback) {
766        if (!this.db) {
767            if (errorCallback) {
768                errorCallback(FileError.INVALID_MODIFICATION_ERR);
769            }
770            return;
771        }
772
773        var tx = this.db.transaction([FILE_STORE_], 'readwrite');
774        tx.onabort = errorCallback || onError;
775        tx.oncomplete = function(e) {
776            // TODO: Error is thrown if we pass the request event back instead.
777            successCallback(entry);
778        };
779
780        tx.objectStore(FILE_STORE_).put(entry, storagePath);
781    };
782
783    // Global error handler. Errors bubble from request, to transaction, to db.
784    function onError(e) {
785        switch (e.target.errorCode) {
786            case 12:
787                console.log('Error - Attempt to open db with a lower version than the ' +
788                    'current one.');
789                break;
790            default:
791                console.log('errorCode: ' + e.target.errorCode);
792        }
793
794        console.log(e, e.code, e.message);
795    }
796
797// Clean up.
798// TODO: Is there a place for this?
799//    global.addEventListener('beforeunload', function(e) {
800//        idb_.db && idb_.db.close();
801//    }, false);
802
803})(module.exports, window);
804
805require("cordova/exec/proxy").add("File", module.exports);