PageSourceSearch

https://beta.jackimaging.com/static/third_party/idb.filesystem.js/src/idb.filesystem.js

js jackimaging.com collected 2026-10-02 14:31:42 UTC 30,057 bytes, 999 lines download raw bytes

1/**
2 * Copyright 2013 - Eric Bidelman
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15
16 * @fileoverview
17 * A polyfill implementation of the HTML5 Filesystem API which sits on top of
18 * IndexedDB as storage layer. Files and folders are stored as FileEntry and
19 * FolderEntry objects in a single object store. IDBKeyRanges are used to query
20 * into a folder. A single object store is sufficient because we can utilize the
21 * properties of ASCII. Namely, ASCII / is followed by ASCII 0. Thus,
22 * "/one/two/" comes before "/one/two/ANYTHING" comes before "/one/two/0".
23 *
24 * @author Eric Bidelman ([email protected])
25 */
26
27'use strict';
28
29(function(exports) {
30
31// Bomb out if the Filesystem API is available natively.
32if (exports.requestFileSystem || exports.webkitRequestFileSystem) {
33  return;
34}
35
36// Bomb out if no indexedDB available
37const indexedDB = exports.indexedDB || exports.mozIndexedDB ||
38                  exports.msIndexedDB;
39if (!indexedDB) {
40  return;
41}
42
43let IDB_SUPPORTS_BLOB = true;
44
45// Check to see if IndexedDB support blobs
46const support = function() {
47  var dbName = "blob-support";
48  indexedDB.deleteDatabase(dbName).onsuccess = function() {
49    var request = indexedDB.open(dbName, 1);
50    request.onerror = function() {
51      IDB_SUPPORTS_BLOB = false;
52    };
53    request.onsuccess = function() {
54      var db = request.result;
55      try {
56        var blob = new Blob(["test"], {type: "text/plain"});
57        var transaction = db.transaction("store", "readwrite");
58        transaction.objectStore("store").put(blob, "key");
59        IDB_SUPPORTS_BLOB = true;
60      } catch (err) {
61        IDB_SUPPORTS_BLOB = false;
62      } finally {
63        db.close();
64        indexedDB.deleteDatabase(dbName);
65      }
66    };
67    request.onupgradeneeded = function() {
68      request.result.createObjectStore("store");
69    };
70  };
71};
72
73const Base64ToBlob = function(dataURL) {
74  var BASE64_MARKER = ';base64,';
75  if (dataURL.indexOf(BASE64_MARKER) == -1) {
76    var parts = dataURL.split(',');
77    var contentType = parts[0].split(':')[1];
78    var raw = decodeURIComponent(parts[1]);
79
80    return new Blob([raw], {type: contentType});
81  }
82
83  var parts = dataURL.split(BASE64_MARKER);
84  var contentType = parts[0].split(':')[1];
85  var raw = window.atob(parts[1]);
86  var rawLength = raw.length;
87
88  var uInt8Array = new Uint8Array(rawLength);
89
90  for (var i = 0; i < rawLength; ++i) {
91    uInt8Array[i] = raw.charCodeAt(i);
92  }
93
94  return new Blob([uInt8Array], {type: contentType});
95};
96
97const BlobToBase64 = function(blob, onload) {
98  var reader = new FileReader();
99  reader.readAsDataURL(blob);
100  reader.onloadend = function() {
101    onload(reader.result);
102  };
103};
104
105if (!exports.PERSISTENT) {
106  exports.TEMPORARY = 0;
107  exports.PERSISTENT = 1;
108}
109
110// Prevent errors in browsers that don't support FileError.
111// TODO: FF 13+ supports DOM4 Events (DOMError). Use them instead?
112if (exports.FileError === undefined) {
113  window.FileError = function() {};
114  FileError.prototype.prototype = Error.prototype;
115}
116
117if (!FileError.INVALID_MODIFICATION_ERR) {
118  FileError.INVALID_MODIFICATION_ERR = 9;
119  FileError.NOT_FOUND_ERR  = 1;
120}
121
122function MyFileError(obj) {
123  var code_ = obj.code;
124  var name_ = obj.name;
125
126    // Required for FF 11.
127  Object.defineProperty(this, 'code', {
128    set: function(code) {
129      code_ = code;
130    },
131    get: function() {
132      return code_;
133    }
134  });
135
136  Object.defineProperty(this, 'name', {
137    set: function(name) {
138      name_ = name;
139    },
140    get: function() {
141      return name_;
142    }
143  });
144}
145
146MyFileError.prototype = FileError.prototype;
147MyFileError.prototype.toString = Error.prototype.toString;
148
149const INVALID_MODIFICATION_ERR = new MyFileError({
150      code: FileError.INVALID_MODIFICATION_ERR,
151      name: 'INVALID_MODIFICATION_ERR'});
152const NOT_IMPLEMENTED_ERR = new MyFileError({code: 1000,
153                                             name: 'Not implemented'});
154const NOT_FOUND_ERR = new MyFileError({code: FileError.NOT_FOUND_ERR,
155                                       name: 'Not found'});
156
157let fs_ = null;
158
159// Browsers other than Chrome don't implement persistent vs. temporary storage.
160// but default to temporary anyway.
161let storageType_ = 'temporary';
162const idb_ = {db: null};
163const FILE_STORE_ = 'entries';
164
165const DIR_SEPARATOR = '/';
166const DIR_OPEN_BOUND = String.fromCharCode(DIR_SEPARATOR.charCodeAt(0) + 1);
167
168// When saving an entry, the fullPath should always lead with a slash and never
169// end with one (e.g. a directory). Also, resolve '.' and '..' to an absolute
170// one. This method ensures path is legit!
171function resolveToFullPath_(cwdFullPath, path) {
172  var fullPath = path;
173
174  var relativePath = path[0] != DIR_SEPARATOR;
175  if (relativePath) {
176    fullPath = cwdFullPath + DIR_SEPARATOR + path;
177  }
178
179  // Normalize '.'s,  '..'s and '//'s.
180  var parts = fullPath.split(DIR_SEPARATOR);
181  var finalParts = [];
182  for (var i = 0; i < parts.length; ++i) {
183    var part = parts[i];
184    if (part === '..') {
185      // Go up one level.
186      if (!finalParts.length) {
187        throw Error('Invalid path');
188      }
189      finalParts.pop();
190    } else if (part === '.') {
191      // Skip over the current directory.
192    } else if (part !== '') {
193      // Eliminate sequences of '/'s as well as possible leading/trailing '/'s.
194      finalParts.push(part);
195    }
196  }
197
198  fullPath = DIR_SEPARATOR + finalParts.join(DIR_SEPARATOR);
199
200  // fullPath is guaranteed to be normalized by construction at this point:
201  // '.'s, '..'s, '//'s will never appear in it.
202
203  return fullPath;
204}
205
206// // Path can be relative or absolute. If relative, it's taken from the cwd_.
207// // If a filesystem URL is passed it, it is simple returned
208// function pathToFsURL_(path) {
209//   path = resolveToFullPath_(cwdFullPath, path);
210//   path = fs_.root.toURL() + path.substring(1);
211//   return path;
212// };
213
214/**
215 * Interface to wrap the native File interface.
216 *
217 * This interface is necessary for creating zero-length (empty) files,
218 * something the Filesystem API allows you to do. Unfortunately, File's
219 * constructor cannot be called directly, making it impossible to instantiate
220 * an empty File in JS.
221 *
222 * @param {Object} opts Initial values.
223 * @constructor
224 */
225function MyFile(opts) {
226  var blob_ = null;
227
228  this.size = opts.size || 0;
229  this.name = opts.name || '';
230  this.type = opts.type || '';
231  this.lastModifiedDate = opts.lastModifiedDate || null;
232  //this.slice = Blob.prototype.slice; // Doesn't work with structured clones.
233
234  // Need some black magic to correct the object's size/name/type based on the
235  // blob that is saved.
236  Object.defineProperty(this, 'blob_', {
237    enumerable: true,
238    get: function() {
239      return blob_;
240    },
241    set: function (val) {
242      blob_ = val;
243      this.size = blob_.size;
244      this.name = blob_.name;
245      this.type = blob_.type;
246      this.lastModifiedDate = blob_.lastModifiedDate;
247    }.bind(this)
248  });
249}
250MyFile.prototype.constructor = MyFile;
251//MyFile.prototype.slice = Blob.prototype.slice;
252
253/**
254 * Interface to writing a Blob/File.
255 *
256 * Modeled from:
257 * dev.w3.org/2009/dap/file-system/file-writer.html#the-filewriter-interface
258 *
259 * @param {FileEntry} fileEntry The FileEntry associated with this writer.
260 * @constructor
261 */
262function FileWriter(fileEntry) {
263  if (!fileEntry) {
264    throw Error('Expected fileEntry argument to write.');
265  }
266
267  var position_ = 0;
268  var blob_ = fileEntry.file_ ? fileEntry.file_.blob_ : null;
269
270  Object.defineProperty(this, 'position', {
271    get: function() {
272      return position_;
273    }
274  });
275
276  Object.defineProperty(this, 'length', {
277    get: function() {
278      return blob_ ? blob_.size : 0;
279    }
280  });
281
282  this.seek = function(offset) {
283    position_ = offset;
284
285    if (position_ > this.length) {
286      position_ = this.length;
287    }
288    if (position_ < 0) {
289      position_ += this.length;
290    }
291    if (position_ < 0) {
292      position_ = 0;
293    }
294  };
295
296  this.truncate = function(size) {
297    if (blob_) {
298      if (size < this.length) {
299        blob_ = blob_.slice(0, size);
300      } else {
301        blob_ = new Blob([blob_, new Uint8Array(size - this.length)],
302                         {type: blob_.type});
303      }
304    } else {
305      blob_ = new Blob([]);
306    }
307
308    position_ = 0; // truncate from beginning of file.
309
310    this.write(blob_); // calls onwritestart and onwriteend.
311  };
312
313  this.write = function(data) {
314    if (!data) {
315      throw Error('Expected blob argument to write.');
316    }
317
318    // Call onwritestart if it was defined.
319    if (this.onwritestart) {
320      this.onwritestart();
321    }
322
323    // TODO: not handling onprogress, onwrite, onabort. Throw an error if
324    // they're defined.
325
326    if (blob_) {
327      // Calc the head and tail fragments
328      var head = blob_.slice(0, position_);
329      var tail = blob_.slice(position_ + data.size);
330
331      // Calc the padding
332      var padding = position_ - head.size;
333      if (padding < 0) {
334        padding = 0;
335      }
336
337      // Do the "write". In fact, a full overwrite of the Blob.
338      // TODO: figure out if data.type should overwrite the exist blob's type.
339      blob_ = new Blob([head, new Uint8Array(padding), data, tail],
340                       {type: blob_.type});
341    } else {
342      blob_ = new Blob([data], {type: data.type});
343    }
344
345    const writeFile = function(blob) {
346      // Blob might be a DataURI depending on browser support.
347      fileEntry.file_.blob_ = blob;
348      fileEntry.file_.lastModifiedDate = data.lastModifiedDate || new Date();
349      idb_.put(fileEntry, function(entry) {
350        if (!IDB_SUPPORTS_BLOB) {
351          // Set the blob we're writing on this file entry so we can recall it later.
352          fileEntry.file_.blob_ = blob_;
353          fileEntry.file_.lastModifiedDate = data.lastModifiedDate || null;
354        }
355
356        // Add size of data written to writer.position.
357        position_ += data.size;
358
359        if (this.onwriteend) {
360          this.onwriteend();
361        }
362      }.bind(this), this.onerror);
363    }.bind(this);
364
365    if (IDB_SUPPORTS_BLOB) {
366      writeFile(blob_);
367    } else {
368      BlobToBase64(blob_, writeFile);
369    }
370  };
371}
372
373
374/**
375 * Interface for listing a directory's contents (files and folders).
376 *
377 * Modeled from:
378 * dev.w3.org/2009/dap/file-system/pub/FileSystem/#idl-def-DirectoryReader
379 *
380 * @constructor
381 */
382function DirectoryReader(dirEntry) {
383  var dirEntry_ = dirEntry;
384  var used_ = false;
385
386  this.readEntries = function(successCallback, opt_errorCallback) {
387    if (!successCallback) {
388      throw Error('Expected successCallback argument.');
389    }
390
391    // This is necessary to mimic the way DirectoryReader.readEntries() should
392    // normally behavior.  According to spec, readEntries() needs to be called
393    // until the length of result array is 0. To handle someone implementing
394    // a recursive call to readEntries(), get everything from indexedDB on the
395    // first shot. Then (DirectoryReader has been used), return an empty
396    // result array.
397    if (!used_) {
398      idb_.getAllEntries(dirEntry_.fullPath, function(entries) {
399        used_= true;
400        successCallback(entries);
401      }, opt_errorCallback);
402    } else {
403      successCallback([]);
404    }
405  };
406};
407
408/**
409 * Interface supplies information about the state of a file or directory.
410 *
411 * Modeled from:
412 * dev.w3.org/2009/dap/file-system/file-dir-sys.html#idl-def-Metadata
413 *
414 * @constructor
415 */
416function Metadata(modificationTime, size) {
417  this.modificationTime_ = modificationTime || null;
418  this.size_ = size || 0;
419}
420
421Metadata.prototype = {
422  get modificationTime() {
423    return this.modificationTime_;
424  },
425  get size() {
426    return this.size_;
427  }
428}
429
430/**
431 * Interface representing entries in a filesystem, each of which may be a File
432 * or DirectoryEntry.
433 *
434 * Modeled from:
435 * dev.w3.org/2009/dap/file-system/pub/FileSystem/#idl-def-Entry
436 *
437 * @constructor
438 */
439function Entry() {}
440
441Entry.prototype = {
442  name: null,
443  fullPath: null,
444  filesystem: null,
445  copyTo: function() {
446    throw NOT_IMPLEMENTED_ERR;
447  },
448  getMetadata: function(successCallback, opt_errorCallback) {
449    if (!successCallback) {
450      throw Error('Expected successCallback argument.');
451    }
452
453    try {
454      if (this.isFile) {
455        successCallback(
456            new Metadata(this.file_.lastModifiedDate, this.file_.size));
457      } else {
458        opt_errorCallback(new MyFileError({code: 1001,
459            name: 'getMetadata() not implemented for DirectoryEntry'}));
460      }
461    } catch(e) {
462      opt_errorCallback && opt_errorCallback(e);
463    }
464  },
465  getParent: function() {
466    throw NOT_IMPLEMENTED_ERR;
467  },
468  moveTo: function() {
469    throw NOT_IMPLEMENTED_ERR;
470  },
471  remove: function(successCallback, opt_errorCallback) {
472    if (!successCallback) {
473      throw Error('Expected successCallback argument.');
474    }
475    // TODO: This doesn't protect against directories that have content in it.
476    // Should throw an error instead if the dirEntry is not empty.
477    idb_['delete'](this.fullPath, function() {
478      successCallback();
479    }, opt_errorCallback);
480  },
481  toURL: function() {
482    var origin = location.protocol + '//' + location.host;
483    return 'filesystem:' + origin + DIR_SEPARATOR + storageType_.toLowerCase() +
484           this.fullPath;
485  },
486};
487
488/**
489 * Interface representing a file in the filesystem.
490 *
491 * Modeled from:
492 * dev.w3.org/2009/dap/file-system/pub/FileSystem/#the-fileentry-interface
493 *
494 * @param {FileEntry} opt_fileEntry Optional FileEntry to initialize this
495 *     object from.
496 * @constructor
497 * @extends {Entry}
498 */
499function FileEntry(opt_fileEntry) {
500  this.file_ = null;
501
502  Object.defineProperty(this, 'isFile', {
503    enumerable: true,
504    get: function() {
505      return true;
506    }
507  });
508  Object.defineProperty(this, 'isDirectory', {
509    enumerable: true,
510    get: function() {
511      return false;
512    }
513  });
514
515  // Create this entry from properties from an existing FileEntry.
516  if (opt_fileEntry) {
517    this.file_ = opt_fileEntry.file_;
518    this.name = opt_fileEntry.name;
519    this.fullPath = opt_fileEntry.fullPath;
520    this.filesystem = opt_fileEntry.filesystem;
521    if (typeof(this.file_.blob_) === "string") {
522      this.file_.blob_ = Base64ToBlob(this.file_.blob_);
523    }
524  }
525}
526FileEntry.prototype = new Entry();
527FileEntry.prototype.constructor = FileEntry;
528FileEntry.prototype.createWriter = function(callback) {
529  // TODO: figure out if there's a way to dispatch onwrite event as we're writing
530  // data to IDB. Right now, we're only calling onwritend/onerror
531  // FileEntry.write().
532  callback(new FileWriter(this));
533};
534FileEntry.prototype.file = function(successCallback, opt_errorCallback) {
535  if (!successCallback) {
536    throw Error('Expected successCallback argument.');
537  }
538
539  if (this.file_ == null) {
540    if (opt_errorCallback) {
541      opt_errorCallback(NOT_FOUND_ERR);
542    } else {
543      throw NOT_FOUND_ERR;
544    }
545    return;
546  }
547
548  // If we're returning a zero-length (empty) file, return the fake file obj.
549  // Otherwise, return the native File object that we've stashed.
550  var file = this.file_.blob_ == null ? this.file_ : this.file_.blob_;
551  file.lastModifiedDate = this.file_.lastModifiedDate;
552
553  // Add Blob.slice() to this wrapped object. Currently won't work :(
554  /*if (!val.slice) {
555    val.slice = Blob.prototype.slice; // Hack to add back in .slice().
556  }*/
557  successCallback(file);
558};
559
560/**
561 * Interface representing a directory in the filesystem.
562 *
563 * Modeled from:
564 * dev.w3.org/2009/dap/file-system/pub/FileSystem/#the-directoryentry-interface
565 *
566 * @param {DirectoryEntry} opt_folderEntry Optional DirectoryEntry to
567 *     initialize this object from.
568 * @constructor
569 * @extends {Entry}
570 */
571function DirectoryEntry(opt_folderEntry) {
572  Object.defineProperty(this, 'isFile', {
573    enumerable: true,
574    get: function() {
575      return false;
576    }
577  });
578  Object.defineProperty(this, 'isDirectory', {
579    enumerable: true,
580    get: function() {
581      return true;
582    }
583  });
584
585  // Create this entry from properties from an existing DirectoryEntry.
586  if (opt_folderEntry) {
587    this.name = opt_folderEntry.name;
588    this.fullPath = opt_folderEntry.fullPath;
589    this.filesystem = opt_folderEntry.filesystem;
590  }
591}
592DirectoryEntry.prototype = new Entry();
593DirectoryEntry.prototype.constructor = DirectoryEntry;
594DirectoryEntry.prototype.createReader = function() {
595  return new DirectoryReader(this);
596};
597DirectoryEntry.prototype.getDirectory = function(path, options, successCallback,
598                                                 opt_errorCallback) {
599
600  // Create an absolute path if we were handed a relative one.
601  path = resolveToFullPath_(this.fullPath, path);
602
603  idb_.get(path, function(folderEntry) {
604    if (!options) {
605      options = {};
606    }
607
608    if (options.create === true && options.exclusive === true && folderEntry) {
609      // If create and exclusive are both true, and the path already exists,
610      // getDirectory must fail.
611      if (opt_errorCallback) {
612        opt_errorCallback(INVALID_MODIFICATION_ERR);
613        return;
614      }
615    } else if (options.create === true && !folderEntry) {
616      // If create is true, the path doesn't exist, and no other error occurs,
617      // getDirectory must create it as a zero-length file and return a corresponding
618      // DirectoryEntry.
619      var dirEntry = new DirectoryEntry();
620      dirEntry.name = path.split(DIR_SEPARATOR).pop(); // Just need filename.
621      dirEntry.fullPath = path;
622      dirEntry.filesystem = fs_;
623
624      idb_.put(dirEntry, successCallback, opt_errorCallback);
625    } else if (options.create === true && folderEntry) {
626
627      if (folderEntry.isDirectory) {
628        // IDB won't save methods, so we need re-create the DirectoryEntry.
629        successCallback(new DirectoryEntry(folderEntry));
630      } else {
631        if (opt_errorCallback) {
632          opt_errorCallback(INVALID_MODIFICATION_ERR);
633          return;
634        }
635      }
636    } else if ((!options.create || options.create === false) && !folderEntry) {
637      // Handle root special. It should always exist.
638      if (path == DIR_SEPARATOR) {
639        folderEntry = new DirectoryEntry();
640        folderEntry.name = '';
641        folderEntry.fullPath = DIR_SEPARATOR;
642        folderEntry.filesystem = fs_;
643        successCallback(folderEntry);
644        return;
645      }
646
647      // If create is not true and the path doesn't exist, getDirectory must fail.
648      if (opt_errorCallback) {
649        opt_errorCallback(NOT_FOUND_ERR);
650        return;
651      }
652    } else if ((!options.create || options.create === false) && folderEntry &&
653               folderEntry.isFile) {
654      // If create is not true and the path exists, but is a file, getDirectory
655      // must fail.
656      if (opt_errorCallback) {
657        opt_errorCallback(INVALID_MODIFICATION_ERR);
658        return;
659      }
660    } else {
661      // Otherwise, if no other error occurs, getDirectory must return a
662      // DirectoryEntry corresponding to path.
663
664      // IDB won't' save methods, so we need re-create DirectoryEntry.
665      successCallback(new DirectoryEntry(folderEntry));
666    }
667  }, opt_errorCallback);
668};
669
670DirectoryEntry.prototype.getFile = function(path, options, successCallback,
671                                            opt_errorCallback) {
672
673  // Create an absolute path if we were handed a relative one.
674  path = resolveToFullPath_(this.fullPath, path);
675
676  idb_.get(path, function(fileEntry) {
677    if (!options) {
678      options = {};
679    }
680
681    if (options.create === true && options.exclusive === true && fileEntry) {
682      // If create and exclusive are both true, and the path already exists,
683      // getFile must fail.
684
685      if (opt_errorCallback) {
686        opt_errorCallback(INVALID_MODIFICATION_ERR);
687        return;
688      }
689    } else if (options.create === true && !fileEntry) {
690      // If create is true, the path doesn't exist, and no other error occurs,
691      // getFile must create it as a zero-length file and return a corresponding
692      // FileEntry.
693      var fileEntry = new FileEntry();
694      fileEntry.name = path.split(DIR_SEPARATOR).pop(); // Just need filename.
695      fileEntry.fullPath = path;
696      fileEntry.filesystem = fs_;
697      fileEntry.file_ = new MyFile({size: 0, name: fileEntry.name,
698                                    lastModifiedDate: new Date()});
699
700      idb_.put(fileEntry, successCallback, opt_errorCallback);
701
702    } else if (options.create === true && fileEntry) {
703      if (fileEntry.isFile) {
704        // IDB won't save methods, so we need re-create the FileEntry.
705        successCallback(new FileEntry(fileEntry));
706      } else {
707        if (opt_errorCallback) {
708          opt_errorCallback(INVALID_MODIFICATION_ERR);
709          return;
710        }
711      }
712    } else if ((!options.create || options.create === false) && !fileEntry) {
713      // If create is not true and the path doesn't exist, getFile must fail.
714      if (opt_errorCallback) {
715        opt_errorCallback(NOT_FOUND_ERR);
716        return;
717      }
718    } else if ((!options.create || options.create === false) && fileEntry &&
719               fileEntry.isDirectory) {
720      // If create is not true and the path exists, but is a directory, getFile
721      // must fail.
722      if (opt_errorCallback) {
723        opt_errorCallback(INVALID_MODIFICATION_ERR);
724        return;
725      }
726    } else {
727      // Otherwise, if no other error occurs, getFile must return a FileEntry
728      // corresponding to path.
729
730      // IDB won't' save methods, so we need re-create the FileEntry.
731      successCallback(new FileEntry(fileEntry));
732    }
733  }, opt_errorCallback);
734};
735
736DirectoryEntry.prototype.removeRecursively = function(successCallback,
737                                                      opt_errorCallback) {
738  if (!successCallback) {
739    throw Error('Expected successCallback argument.');
740  }
741
742  this.remove(successCallback, opt_errorCallback);
743};
744
745/**
746 * Interface representing a filesystem.
747 *
748 * Modeled from:
749 * dev.w3.org/2009/dap/file-system/pub/FileSystem/#idl-def-LocalFileSystem
750 *
751 * @param {number} type Kind of storage to use, either TEMPORARY or PERSISTENT.
752 * @param {number} size Storage space (bytes) the application expects to need.
753 * @constructor
754 */
755function DOMFileSystem(type, size) {
756  storageType_ = type == exports.TEMPORARY ? 'Temporary' : 'Persistent';
757  this.name = (location.protocol + location.host).replace(/:/g, '_') +
758              ':' + storageType_;
759  this.root = new DirectoryEntry();
760  this.root.fullPath = DIR_SEPARATOR;
761  this.root.filesystem = this;
762  this.root.name = '';
763}
764
765function requestFileSystem(type, size, successCallback, opt_errorCallback) {
766  if (type != exports.TEMPORARY && type != exports.PERSISTENT) {
767    if (opt_errorCallback) {
768      opt_errorCallback(INVALID_MODIFICATION_ERR);
769      return;
770    }
771  }
772
773  fs_ = new DOMFileSystem(type, size);
774  idb_.open(fs_.name, function(e) {
775    successCallback(fs_);
776  }, opt_errorCallback);
777}
778
779function resolveLocalFileSystemURL(url, successCallback, opt_errorCallback) {
780  var origin = location.protocol + '//' + location.host;
781  var base = 'filesystem:' + origin + DIR_SEPARATOR + storageType_.toLowerCase();
782  url = url.replace(base, '');
783  if (url.substr(-1) === '/') {
784    url = url.slice(0, -1);
785  }
786  if (url) {
787    idb_.get(url, function(entry) {
788      if (entry) {
789        if (entry.isFile) {
790          return successCallback(new FileEntry(entry));
791        } else if (entry.isDirectory) {
792          return successCallback(new DirectoryEntry(entry));
793        }
794      } else {
795        opt_errorCallback && opt_errorCallback(NOT_FOUND_ERR);
796      }
797    }, opt_errorCallback);
798  } else {
799    successCallback(fs_.root);
800  }
801}
802
803// Core logic to handle IDB operations =========================================
804
805idb_.open = function(dbName, successCallback, opt_errorCallback) {
806  var self = this;
807
808  // TODO: FF 12.0a1 isn't liking a db name with : in it.
809  var request = indexedDB.open(dbName.replace(':', '_')/*, 1 /*version*/);
810
811  request.onerror = opt_errorCallback || onError;
812
813  request.onupgradeneeded = function(e) {
814    // First open was called or higher db version was used.
815
816   // console.log('onupgradeneeded: oldVersion:' + e.oldVersion,
817   //           'newVersion:' + e.newVersion);
818
819    self.db = e.target.result;
820    self.db.onerror = onError;
821
822    if (!self.db.objectStoreNames.contains(FILE_STORE_)) {
823      var store = self.db.createObjectStore(FILE_STORE_/*,{keyPath: 'id', autoIncrement: true}*/);
824    }
825  };
826
827  request.onsuccess = function(e) {
828    self.db = e.target.result;
829    self.db.onerror = onError;
830    successCallback(e);
831  };
832
833  request.onblocked = opt_errorCallback || onError;
834};
835
836idb_.close = function() {
837  this.db.close();
838  this.db = null;
839};
840
841// TODO: figure out if we should ever call this method. The filesystem API
842// doesn't allow you to delete a filesystem once it is 'created'. Users should
843// use the public remove/removeRecursively API instead.
844idb_.drop = function(successCallback, opt_errorCallback) {
845  if (!this.db) {
846    return;
847  }
848
849  var dbName = this.db.name;
850
851  var request = indexedDB.deleteDatabase(dbName);
852  request.onsuccess = function(e) {
853    successCallback(e);
854  };
855  request.onerror = opt_errorCallback || onError;
856
857  idb_.close();
858};
859
860idb_.get = function(fullPath, successCallback, opt_errorCallback) {
861  if (!this.db) {
862    return;
863  }
864
865  var tx = this.db.transaction([FILE_STORE_], 'readonly');
866
867  //var request = tx.objectStore(FILE_STORE_).get(fullPath);
868  var range = IDBKeyRange.bound(fullPath, fullPath + DIR_OPEN_BOUND,
869                                false, true);
870  var request = tx.objectStore(FILE_STORE_).get(range);
871
872  tx.onabort = opt_errorCallback || onError;
873  tx.oncomplete = function(e) {
874    successCallback(request.result);
875  };
876};
877
878idb_.getAllEntries = function(fullPath, successCallback, opt_errorCallback) {
879  if (!this.db) {
880    return;
881  }
882
883  var results = [];
884
885  //var range = IDBKeyRange.lowerBound(fullPath, true);
886  //var range = IDBKeyRange.upperBound(fullPath, true);
887
888  // Treat the root entry special. Querying it returns all entries because
889  // they match '/'.
890  var range = null;
891  if (fullPath != DIR_SEPARATOR) {
892    //console.log(fullPath + '/', fullPath + DIR_OPEN_BOUND)
893    range = IDBKeyRange.bound(
894        fullPath + DIR_SEPARATOR, fullPath + DIR_OPEN_BOUND, false, true);
895  }
896
897  var tx = this.db.transaction([FILE_STORE_], 'readonly');
898  tx.onabort = opt_errorCallback || onError;
899  tx.oncomplete = function(e) {
900    // TODO: figure out how to do be range queries instead of filtering result
901    // in memory :(
902    results = results.filter(function(val) {
903      var valPartsLen = val.fullPath.split(DIR_SEPARATOR).length;
904      var fullPathPartsLen = fullPath.split(DIR_SEPARATOR).length;
905
906      if (fullPath == DIR_SEPARATOR && valPartsLen < fullPathPartsLen + 1) {
907        // Hack to filter out entries in the root folder. This is inefficient
908        // because reading the entires of fs.root (e.g. '/') returns ALL
909        // results in the database, then filters out the entries not in '/'.
910        return val;
911      } else if (fullPath != DIR_SEPARATOR &&
912                 valPartsLen == fullPathPartsLen + 1) {
913        // If this a subfolder and entry is a direct child, include it in
914        // the results. Otherwise, it's not an entry of this folder.
915        return val;
916      }
917    });
918
919    successCallback(results);
920  };
921
922  var request = tx.objectStore(FILE_STORE_).openCursor(range);
923
924  request.onsuccess = function(e) {
925    var cursor = e.target.result;
926    if (cursor) {
927      var val = cursor.value;
928
929      results.push(val.isFile ? new FileEntry(val) : new DirectoryEntry(val));
930      cursor['continue']();
931    }
932  };
933};
934
935idb_['delete'] = function(fullPath, successCallback, opt_errorCallback) {
936  if (!this.db) {
937    return;
938  }
939
940  var tx = this.db.transaction([FILE_STORE_], 'readwrite');
941  tx.oncomplete = successCallback;
942  tx.onabort = opt_errorCallback || onError;
943
944  //var request = tx.objectStore(FILE_STORE_).delete(fullPath);
945  var range = IDBKeyRange.bound(
946      fullPath, fullPath + DIR_OPEN_BOUND, false, true);
947  var request = tx.objectStore(FILE_STORE_)['delete'](range);
948};
949
950idb_.put = function(entry, successCallback, opt_errorCallback) {
951  if (!this.db) {
952    return;
953  }
954
955  var tx = this.db.transaction([FILE_STORE_], 'readwrite');
956  tx.onabort = opt_errorCallback || onError;
957  tx.oncomplete = function(e) {
958    // TODO: Error is thrown if we pass the request event back instead.
959    successCallback(entry);
960  };
961
962  var request = tx.objectStore(FILE_STORE_).put(entry, entry.fullPath);
963};
964
965// Global error handler. Errors bubble from request, to transaction, to db.
966function onError(e) {
967  switch (e.target.errorCode) {
968    case 12:
969      console.log('Error - Attempt to open db with a lower version than the ' +
970                  'current one.');
971      break;
972    default:
973      console.log('errorCode: ' + e.target.errorCode);
974  }
975
976  console.log(e, e.code, e.message);
977}
978
979// Clean up.
980// TODO: decide if this is the best place for this.
981exports.addEventListener('beforeunload', function(e) {
982  idb_.db && idb_.db.close();
983}, false);
984
985//exports.idb = idb_;
986exports.requestFileSystem = requestFileSystem;
987exports.resolveLocalFileSystemURL = resolveLocalFileSystemURL;
988
989// Export more stuff (to window) for unit tests to do their thing.
990if (exports === window && exports.RUNNING_TESTS) {
991  exports['Entry'] = Entry;
992  exports['FileEntry'] = FileEntry;
993  exports['DirectoryEntry'] = DirectoryEntry;
994  exports['resolveToFullPath_'] = resolveToFullPath_;
995  exports['Metadata'] = Metadata;
996  exports['Base64ToBlob'] = Base64ToBlob;
997}
998
999})(self); // Don't use window because we want to run in workers.

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.